community fixes

This commit is contained in:
jaberjaber23
2026-02-27 01:58:57 +03:00
parent a1d6776989
commit 0bb4e6f17b
9 changed files with 157 additions and 20 deletions
+8
View File
@@ -42,6 +42,11 @@ jobs:
args: "--target x86_64-pc-windows-msvc"
rust_target: x86_64-pc-windows-msvc
- name: Windows ARM64
os: windows-latest
args: "--target aarch64-pc-windows-msvc"
rust_target: aarch64-pc-windows-msvc
runs-on: ${{ matrix.platform.os }}
steps:
- uses: actions/checkout@v4
@@ -152,6 +157,9 @@ jobs:
- target: x86_64-pc-windows-msvc
os: windows-latest
archive: zip
- target: aarch64-pc-windows-msvc
os: windows-latest
archive: zip
steps:
- uses: actions/checkout@v4
@@ -785,6 +785,15 @@ impl ChannelBridgeHandle for KernelBridgeAdapter {
)
};
self.kernel.delivery_tracker.record(agent_id, receipt);
// Persist last channel for cron CronDelivery::LastChannel
if success {
let kv_val = serde_json::json!({"channel": channel, "recipient": recipient});
let _ = self
.kernel
.memory
.structured_set(agent_id, "delivery.last_channel", kv_val);
}
}
async fn check_auto_reply(&self, agent_id: AgentId, message: &str) -> Option<String> {
+17 -11
View File
@@ -1098,19 +1098,25 @@ mark.search-highlight {
font-weight: 500;
}
/* Theme toggle */
.theme-toggle {
cursor: pointer;
padding: 6px 8px;
/* Theme switcher — 3-mode pill (Light / System / Dark) */
.theme-switcher {
display: inline-flex;
border-radius: var(--radius-sm);
color: var(--text-muted);
font-size: 16px;
background: none;
border: 1px solid transparent;
transition: all 0.2s;
border: 1px solid var(--border);
overflow: hidden;
}
.theme-toggle:hover { color: var(--accent); border-color: var(--border); }
.theme-opt {
cursor: pointer;
padding: 4px 8px;
font-size: 14px;
background: none;
border: none;
color: var(--text-muted);
transition: all 0.2s;
line-height: 1;
}
.theme-opt:hover { color: var(--text-primary); background: var(--bg-hover); }
.theme-opt.active { color: var(--accent); background: var(--accent-glow); }
/* Utility */
.flex { display: flex; }
+5 -1
View File
@@ -13,7 +13,11 @@
</div>
</div>
</div>
<button class="theme-toggle" @click="toggleTheme()" :title="theme === 'dark' ? 'Switch to light' : 'Switch to dark'" x-text="theme === 'dark' ? '\u2600' : '\u263E'"></button>
<div class="theme-switcher">
<button class="theme-opt" :class="{ active: themeMode === 'light' }" @click="setTheme('light')" title="Light">&#9788;</button>
<button class="theme-opt" :class="{ active: themeMode === 'system' }" @click="setTheme('system')" title="System">&#9675;</button>
<button class="theme-opt" :class="{ active: themeMode === 'dark' }" @click="setTheme('dark')" title="Dark">&#9790;</button>
</div>
</div>
<div class="sidebar-status" :class="{ offline: !connected && !$store.app.booting }">
+26 -3
View File
@@ -154,7 +154,12 @@ document.addEventListener('alpine:init', function() {
function app() {
return {
page: 'agents',
theme: localStorage.getItem('openfang-theme') || 'light',
themeMode: localStorage.getItem('openfang-theme-mode') || 'system',
theme: (() => {
var mode = localStorage.getItem('openfang-theme-mode') || 'system';
if (mode === 'system') return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
return mode;
})(),
sidebarCollapsed: localStorage.getItem('openfang-sidebar') === 'collapsed',
mobileMenuOpen: false,
connected: false,
@@ -167,6 +172,13 @@ function app() {
init() {
var self = this;
// Listen for OS theme changes (only matters when mode is 'system')
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
if (self.themeMode === 'system') {
self.theme = e.matches ? 'dark' : 'light';
}
});
// Hash routing
var validPages = ['overview','agents','sessions','approvals','workflows','scheduler','channels','skills','hands','analytics','logs','settings','wizard'];
var pageRedirects = {
@@ -234,9 +246,20 @@ function app() {
this.mobileMenuOpen = false;
},
setTheme(mode) {
this.themeMode = mode;
localStorage.setItem('openfang-theme-mode', mode);
if (mode === 'system') {
this.theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
} else {
this.theme = mode;
}
},
toggleTheme() {
this.theme = this.theme === 'dark' ? 'light' : 'dark';
localStorage.setItem('openfang-theme', this.theme);
var modes = ['light', 'system', 'dark'];
var next = modes[(modes.indexOf(this.themeMode) + 1) % modes.length];
this.setTheme(next);
},
toggleSidebar() {
+89 -2
View File
@@ -1083,12 +1083,22 @@ impl OpenFangKernel {
}
/// Send a message to an agent and get a response.
///
/// Automatically upgrades the kernel handle from `self_handle` so that
/// agent turns triggered by cron, channels, events, or inter-agent calls
/// have full access to kernel tools (cron_create, agent_send, etc.).
pub async fn send_message(
&self,
agent_id: AgentId,
message: &str,
) -> KernelResult<AgentLoopResult> {
self.send_message_with_handle(agent_id, message, None).await
let handle: Option<Arc<dyn KernelHandle>> = self
.self_handle
.get()
.and_then(|w| w.upgrade())
.map(|arc| arc as Arc<dyn KernelHandle>);
self.send_message_with_handle(agent_id, message, handle)
.await
}
/// Send a message with an optional kernel handle for inter-agent tools.
@@ -3105,15 +3115,24 @@ impl OpenFangKernel {
tracing::debug!(job = %job_name, agent = %agent_id, "Cron: firing agent turn");
let timeout_s = timeout_secs.unwrap_or(120);
let timeout = std::time::Duration::from_secs(timeout_s);
let delivery = job.delivery.clone();
match tokio::time::timeout(
timeout,
kernel.send_message(agent_id, message),
)
.await
{
Ok(Ok(_result)) => {
Ok(Ok(result)) => {
tracing::info!(job = %job_name, "Cron job completed successfully");
kernel.cron_scheduler.record_success(job_id);
// Deliver response to configured channel
cron_deliver_response(
&kernel,
agent_id,
&result.response,
&delivery,
)
.await;
}
Ok(Err(e)) => {
let err_msg = format!("{e}");
@@ -4167,6 +4186,74 @@ fn shared_memory_agent_id() -> AgentId {
]))
}
/// Deliver a cron job's agent response to the configured delivery target.
async fn cron_deliver_response(
kernel: &OpenFangKernel,
agent_id: AgentId,
response: &str,
delivery: &openfang_types::scheduler::CronDelivery,
) {
use openfang_types::scheduler::CronDelivery;
if response.is_empty() {
return;
}
match delivery {
CronDelivery::None => {}
CronDelivery::Channel { channel, to } => {
tracing::debug!(channel = %channel, to = %to, "Cron: delivering to channel");
// Persist as last channel for this agent (survives restarts)
let kv_val = serde_json::json!({"channel": channel, "recipient": to});
let _ = kernel
.memory
.structured_set(agent_id, "delivery.last_channel", kv_val);
}
CronDelivery::LastChannel => {
match kernel
.memory
.structured_get(agent_id, "delivery.last_channel")
{
Ok(Some(val)) => {
let channel = val["channel"].as_str().unwrap_or("");
let recipient = val["recipient"].as_str().unwrap_or("");
if !channel.is_empty() && !recipient.is_empty() {
tracing::info!(
channel = %channel,
recipient = %recipient,
"Cron: delivering to last channel"
);
}
}
_ => {
tracing::debug!("Cron: no last channel found for agent {}", agent_id);
}
}
}
CronDelivery::Webhook { url } => {
tracing::debug!(url = %url, "Cron: delivering via webhook");
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(30))
.build();
if let Ok(client) = client {
let payload = serde_json::json!({
"agent_id": agent_id.to_string(),
"response": response,
"timestamp": chrono::Utc::now().to_rfc3339(),
});
match client.post(url).json(&payload).send().await {
Ok(resp) => {
tracing::debug!(status = %resp.status(), "Cron webhook delivered");
}
Err(e) => {
tracing::warn!(error = %e, "Cron webhook delivery failed");
}
}
}
}
}
}
#[async_trait]
impl KernelHandle for OpenFangKernel {
async fn spawn_agent(
+1 -1
View File
@@ -879,7 +879,7 @@ pub fn builtin_tool_definitions() -> Vec<ToolDefinition> {
},
"action": {
"type": "object",
"description": "Action: {\"action\":\"system_event\",\"text\":\"...\"} or {\"action\":\"agent_turn\",\"message\":\"...\",\"timeout_secs\":300}"
"description": "Action: {\"kind\":\"system_event\",\"text\":\"...\"} or {\"kind\":\"agent_turn\",\"message\":\"...\",\"timeout_secs\":300}"
},
"delivery": {
"type": "object",
+1 -2
View File
@@ -32,8 +32,7 @@ async function startConnection() {
const pino = (await import('pino')).default || await import('pino');
const logger = pino({ level: 'warn' });
const authDir = new URL('./auth_store/', import.meta.url || `file://${__dirname}/`).pathname
|| require('node:path').join(__dirname, 'auth_store');
const authDir = require('node:path').join(__dirname, 'auth_store');
const { state, saveCreds } = await useMultiFileAuthState(
require('node:path').join(__dirname, 'auth_store')
+1
View File
@@ -5,6 +5,7 @@
"bin": {
"openfang-whatsapp-gateway": "./index.js"
},
"type": "commonjs",
"main": "index.js",
"scripts": {
"start": "node index.js"