From 62e6e0f08893955faab092de42daae08caf8ee87 Mon Sep 17 00:00:00 2001 From: jaberjaber23 Date: Mon, 2 Mar 2026 23:12:19 +0300 Subject: [PATCH] batch fixes --- Cargo.lock | 28 +++---- crates/openfang-api/src/routes.rs | 7 ++ crates/openfang-channels/src/bridge.rs | 7 +- crates/openfang-channels/src/telegram.rs | 1 + crates/openfang-kernel/src/kernel.rs | 77 +++++++++++++++++-- crates/openfang-runtime/src/mcp.rs | 61 ++++++++++++++- crates/openfang-runtime/src/prompt_builder.rs | 3 + 7 files changed, 161 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cf77fe7d..227f2e8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3866,7 +3866,7 @@ dependencies = [ [[package]] name = "openfang-api" -version = "0.2.9" +version = "0.3.0" dependencies = [ "async-trait", "axum", @@ -3902,7 +3902,7 @@ dependencies = [ [[package]] name = "openfang-channels" -version = "0.2.9" +version = "0.3.0" dependencies = [ "async-trait", "axum", @@ -3933,7 +3933,7 @@ dependencies = [ [[package]] name = "openfang-cli" -version = "0.2.9" +version = "0.3.0" dependencies = [ "clap", "clap_complete", @@ -3960,7 +3960,7 @@ dependencies = [ [[package]] name = "openfang-desktop" -version = "0.2.9" +version = "0.3.0" dependencies = [ "axum", "open", @@ -3986,7 +3986,7 @@ dependencies = [ [[package]] name = "openfang-extensions" -version = "0.2.9" +version = "0.3.0" dependencies = [ "aes-gcm", "argon2", @@ -4014,7 +4014,7 @@ dependencies = [ [[package]] name = "openfang-hands" -version = "0.2.9" +version = "0.3.0" dependencies = [ "chrono", "dashmap", @@ -4031,7 +4031,7 @@ dependencies = [ [[package]] name = "openfang-kernel" -version = "0.2.9" +version = "0.3.0" dependencies = [ "async-trait", "chrono", @@ -4067,7 +4067,7 @@ dependencies = [ [[package]] name = "openfang-memory" -version = "0.2.9" +version = "0.3.0" dependencies = [ "async-trait", "chrono", @@ -4086,7 +4086,7 @@ dependencies = [ [[package]] name = "openfang-migrate" -version = "0.2.9" +version = "0.3.0" dependencies = [ "chrono", "dirs 6.0.0", @@ -4105,7 +4105,7 @@ dependencies = [ [[package]] name = "openfang-runtime" -version = "0.2.9" +version = "0.3.0" dependencies = [ "anyhow", "async-trait", @@ -4137,7 +4137,7 @@ dependencies = [ [[package]] name = "openfang-skills" -version = "0.2.9" +version = "0.3.0" dependencies = [ "chrono", "hex", @@ -4159,7 +4159,7 @@ dependencies = [ [[package]] name = "openfang-types" -version = "0.2.9" +version = "0.3.0" dependencies = [ "async-trait", "chrono", @@ -4178,7 +4178,7 @@ dependencies = [ [[package]] name = "openfang-wire" -version = "0.2.9" +version = "0.3.0" dependencies = [ "async-trait", "chrono", @@ -8790,7 +8790,7 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "xtask" -version = "0.2.9" +version = "0.3.0" [[package]] name = "yoke" diff --git a/crates/openfang-api/src/routes.rs b/crates/openfang-api/src/routes.rs index bb6edb4b..4c9ec368 100644 --- a/crates/openfang-api/src/routes.rs +++ b/crates/openfang-api/src/routes.rs @@ -7568,6 +7568,13 @@ pub async fn patch_agent_config( } } + // Persist updated manifest to database so changes survive restart + if let Some(entry) = state.kernel.registry.get(agent_id) { + if let Err(e) = state.kernel.memory.save_agent(&entry) { + tracing::warn!("Failed to persist agent config update: {e}"); + } + } + ( StatusCode::OK, Json(serde_json::json!({"status": "ok", "agent_id": id})), diff --git a/crates/openfang-channels/src/bridge.rs b/crates/openfang-channels/src/bridge.rs index cc719e8f..85dae6a5 100644 --- a/crates/openfang-channels/src/bridge.rs +++ b/crates/openfang-channels/src/bridge.rs @@ -373,10 +373,15 @@ async fn dispatch_message( // Fetch per-channel overrides (if configured) let overrides = handle.channel_overrides(ct_str).await; + let channel_default_format = match ct_str { + "telegram" => OutputFormat::TelegramHtml, + "slack" => OutputFormat::SlackMrkdwn, + _ => OutputFormat::Markdown, + }; let output_format = overrides .as_ref() .and_then(|o| o.output_format) - .unwrap_or(OutputFormat::Markdown); + .unwrap_or(channel_default_format); let threading_enabled = overrides.as_ref().map(|o| o.threading).unwrap_or(false); let thread_id = if threading_enabled { message.thread_id.as_deref() diff --git a/crates/openfang-channels/src/telegram.rs b/crates/openfang-channels/src/telegram.rs index 21ab3ea3..ad21a0a2 100644 --- a/crates/openfang-channels/src/telegram.rs +++ b/crates/openfang-channels/src/telegram.rs @@ -85,6 +85,7 @@ impl TelegramAdapter { let body = serde_json::json!({ "chat_id": chat_id, "text": chunk, + "parse_mode": "HTML", }); let resp = self.client.post(&url).json(&body).send().await?; diff --git a/crates/openfang-kernel/src/kernel.rs b/crates/openfang-kernel/src/kernel.rs index ce49b4f2..b077bc08 100644 --- a/crates/openfang-kernel/src/kernel.rs +++ b/crates/openfang-kernel/src/kernel.rs @@ -904,6 +904,61 @@ impl OpenFangKernel { let agent_id = entry.id; let name = entry.name.clone(); + // Check if TOML on disk is newer/different — if so, update from file + let mut entry = entry; + let toml_path = kernel + .config + .home_dir + .join("agents") + .join(&name) + .join("agent.toml"); + if toml_path.exists() { + match std::fs::read_to_string(&toml_path) { + Ok(toml_str) => { + match toml::from_str::( + &toml_str, + ) { + Ok(disk_manifest) => { + // Compare key fields to detect changes + let changed = disk_manifest.name != entry.manifest.name + || disk_manifest.description != entry.manifest.description + || disk_manifest.model.system_prompt != entry.manifest.model.system_prompt + || disk_manifest.model.provider != entry.manifest.model.provider + || disk_manifest.model.model != entry.manifest.model.model + || disk_manifest.capabilities.tools != entry.manifest.capabilities.tools; + if changed { + info!( + agent = %name, + "Agent TOML on disk differs from DB, updating" + ); + entry.manifest = disk_manifest; + // Persist the update back to DB + if let Err(e) = kernel.memory.save_agent(&entry) { + warn!( + agent = %name, + "Failed to persist TOML update: {e}" + ); + } + } + } + Err(e) => { + warn!( + agent = %name, + path = %toml_path.display(), + "Invalid agent TOML on disk, using DB version: {e}" + ); + } + } + } + Err(e) => { + warn!( + agent = %name, + "Failed to read agent TOML: {e}" + ); + } + } + } + // Re-grant capabilities let caps = manifest_to_capabilities(&entry.manifest); kernel.capabilities.grant(agent_id, caps); @@ -3132,18 +3187,30 @@ impl OpenFangKernel { /// `Continuous`, `Periodic`, or `Proactive` schedules. pub fn start_background_agents(self: &Arc) { let agents = self.registry.list(); - let mut started = 0u32; + let mut bg_agents: Vec<(openfang_types::agent::AgentId, String, ScheduleMode)> = + Vec::new(); for entry in &agents { if matches!(entry.manifest.schedule, ScheduleMode::Reactive) { continue; } - self.start_background_for_agent(entry.id, &entry.name, &entry.manifest.schedule); - started += 1; + bg_agents.push((entry.id, entry.name.clone(), entry.manifest.schedule.clone())); } - if started > 0 { - info!("Started {started} background agent loop(s)"); + if !bg_agents.is_empty() { + let count = bg_agents.len(); + let kernel = Arc::clone(self); + // Stagger agent startup to prevent rate-limit storm on shared providers. + // Each agent gets a 500ms delay before the next one starts. + tokio::spawn(async move { + for (i, (id, name, schedule)) in bg_agents.into_iter().enumerate() { + kernel.start_background_for_agent(id, &name, &schedule); + if i > 0 { + tokio::time::sleep(std::time::Duration::from_millis(500)).await; + } + } + info!("Started {count} background agent loop(s) (staggered)"); + }); } // Start heartbeat monitor for agent health checking diff --git a/crates/openfang-runtime/src/mcp.rs b/crates/openfang-runtime/src/mcp.rs index 292417ab..82ebf33b 100644 --- a/crates/openfang-runtime/src/mcp.rs +++ b/crates/openfang-runtime/src/mcp.rs @@ -32,7 +32,7 @@ pub struct McpServerConfig { } fn default_timeout() -> u64 { - 30 + 60 } /// Transport type for MCP server connections. @@ -393,7 +393,31 @@ impl McpConnection { return Err("MCP command path contains '..': rejected".to_string()); } - let mut cmd = tokio::process::Command::new(command); + // On Windows, npm/npx install as .cmd batch wrappers. Detect and adapt. + let resolved_command: String = if cfg!(windows) { + // If the user already specified .cmd/.bat, use as-is + if command.ends_with(".cmd") || command.ends_with(".bat") { + command.to_string() + } else { + // Check if the .cmd variant exists on PATH + let cmd_variant = format!("{command}.cmd"); + let has_cmd = std::env::var("PATH") + .unwrap_or_default() + .split(';') + .any(|dir| { + std::path::Path::new(dir).join(&cmd_variant).exists() + }); + if has_cmd { + cmd_variant + } else { + command.to_string() + } + } + } else { + command.to_string() + }; + + let mut cmd = tokio::process::Command::new(&resolved_command); cmd.args(args); cmd.stdin(Stdio::piped()); cmd.stdout(Stdio::piped()); @@ -410,10 +434,41 @@ impl McpConnection { if let Ok(path) = std::env::var("PATH") { cmd.env("PATH", path); } + // On Windows, npm/node need APPDATA, USERPROFILE, LOCALAPPDATA, and SystemRoot + if cfg!(windows) { + for var in &[ + "APPDATA", + "LOCALAPPDATA", + "USERPROFILE", + "SystemRoot", + "TEMP", + "TMP", + "HOME", + "HOMEDRIVE", + "HOMEPATH", + ] { + if let Ok(val) = std::env::var(var) { + cmd.env(var, val); + } + } + } let mut child = cmd .spawn() - .map_err(|e| format!("Failed to spawn MCP server '{command}': {e}"))?; + .map_err(|e| format!("Failed to spawn MCP server '{resolved_command}': {e}"))?; + + // Log stderr in background for debugging MCP server issues + if let Some(stderr) = child.stderr.take() { + let cmd_name = resolved_command.clone(); + tokio::spawn(async move { + use tokio::io::AsyncBufReadExt; + let reader = tokio::io::BufReader::new(stderr); + let mut lines = reader.lines(); + while let Ok(Some(line)) = lines.next_line().await { + tracing::debug!(mcp_server = %cmd_name, "stderr: {line}"); + } + }); + } let stdin = child .stdin diff --git a/crates/openfang-runtime/src/prompt_builder.rs b/crates/openfang-runtime/src/prompt_builder.rs index a2cae0df..e0a8bd2a 100644 --- a/crates/openfang-runtime/src/prompt_builder.rs +++ b/crates/openfang-runtime/src/prompt_builder.rs @@ -215,6 +215,9 @@ const TOOL_CALL_BEHAVIOR: &str = "\ - Prefer action over narration. If you can answer by using a tool, do it. - When executing multiple sequential tool calls, batch them — don't output reasoning between each call. - If a tool returns useful results, present the KEY information, not the raw output. +- When web_fetch or web_search returns content, you MUST include the relevant data in your response. \ +Quote specific facts, numbers, or passages from the fetched content. Never say you fetched something \ +without sharing what you found. - Start with the answer, not meta-commentary about how you'll help. - IMPORTANT: If your instructions or persona mention a shell command, script path, or code snippet, \ execute it via the appropriate tool call (shell_exec, file_write, etc.). Never output commands as \