From 135c37fbf71c8c03f28e61eaae8238d99c4f7470 Mon Sep 17 00:00:00 2001 From: jaberjaber23 Date: Sun, 15 Mar 2026 01:25:15 +0300 Subject: [PATCH] community fixes --- Cargo.lock | 28 +++++++++---------- Cargo.toml | 5 +++- crates/openfang-api/src/openai_compat.rs | 4 +-- crates/openfang-api/src/routes.rs | 20 ++++--------- crates/openfang-api/src/ws.rs | 2 +- crates/openfang-api/static/js/app.js | 2 ++ .../openfang-api/static/js/pages/settings.js | 8 ++++-- crates/openfang-cli/src/tui/event.rs | 2 +- crates/openfang-kernel/src/kernel.rs | 27 ++++++++++++------ .../tests/wasm_agent_integration_test.rs | 2 +- crates/openfang-runtime/src/model_catalog.rs | 4 +++ crates/openfang-types/src/model_catalog.rs | 3 +- packages/whatsapp-gateway/index.js | 13 +++++++-- 13 files changed, 72 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1fd6c3d8..9a945ed7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3792,7 +3792,7 @@ dependencies = [ [[package]] name = "openfang-api" -version = "0.4.1" +version = "0.4.2" dependencies = [ "async-trait", "axum", @@ -3832,7 +3832,7 @@ dependencies = [ [[package]] name = "openfang-channels" -version = "0.4.1" +version = "0.4.2" dependencies = [ "async-trait", "axum", @@ -3864,7 +3864,7 @@ dependencies = [ [[package]] name = "openfang-cli" -version = "0.4.1" +version = "0.4.2" dependencies = [ "clap", "clap_complete", @@ -3891,7 +3891,7 @@ dependencies = [ [[package]] name = "openfang-desktop" -version = "0.4.1" +version = "0.4.2" dependencies = [ "axum", "open", @@ -3917,7 +3917,7 @@ dependencies = [ [[package]] name = "openfang-extensions" -version = "0.4.1" +version = "0.4.2" dependencies = [ "aes-gcm", "argon2", @@ -3945,7 +3945,7 @@ dependencies = [ [[package]] name = "openfang-hands" -version = "0.4.1" +version = "0.4.2" dependencies = [ "chrono", "dashmap", @@ -3962,7 +3962,7 @@ dependencies = [ [[package]] name = "openfang-kernel" -version = "0.4.1" +version = "0.4.2" dependencies = [ "async-trait", "chrono", @@ -3999,7 +3999,7 @@ dependencies = [ [[package]] name = "openfang-memory" -version = "0.4.1" +version = "0.4.2" dependencies = [ "async-trait", "chrono", @@ -4018,7 +4018,7 @@ dependencies = [ [[package]] name = "openfang-migrate" -version = "0.4.1" +version = "0.4.2" dependencies = [ "chrono", "dirs 6.0.0", @@ -4037,7 +4037,7 @@ dependencies = [ [[package]] name = "openfang-runtime" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "async-trait", @@ -4071,7 +4071,7 @@ dependencies = [ [[package]] name = "openfang-skills" -version = "0.4.1" +version = "0.4.2" dependencies = [ "chrono", "hex", @@ -4094,7 +4094,7 @@ dependencies = [ [[package]] name = "openfang-types" -version = "0.4.1" +version = "0.4.2" dependencies = [ "async-trait", "chrono", @@ -4113,7 +4113,7 @@ dependencies = [ [[package]] name = "openfang-wire" -version = "0.4.1" +version = "0.4.2" dependencies = [ "async-trait", "chrono", @@ -8776,7 +8776,7 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "xtask" -version = "0.4.1" +version = "0.4.2" [[package]] name = "yoke" diff --git a/Cargo.toml b/Cargo.toml index b48e120d..35978f16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ members = [ ] [workspace.package] -version = "0.4.2" +version = "0.4.3" edition = "2021" license = "Apache-2.0 OR MIT" repository = "https://github.com/RightNow-AI/openfang" @@ -138,6 +138,9 @@ imap = "2" native-tls = "0.2" mailparse = "0.15" +# OpenSSL (vendored = statically compiled, no runtime libssl dependency on Linux) +openssl = { version = "0.10", features = ["vendored"] } + # Testing tokio-test = "0.4" tempfile = "3" diff --git a/crates/openfang-api/src/openai_compat.rs b/crates/openfang-api/src/openai_compat.rs index 1d95076e..723763cc 100644 --- a/crates/openfang-api/src/openai_compat.rs +++ b/crates/openfang-api/src/openai_compat.rs @@ -322,7 +322,7 @@ pub async fn chat_completions( let kernel_handle: Arc = state.kernel.clone() as Arc; match state .kernel - .send_message_with_handle(agent_id, &last_user_msg, Some(kernel_handle)) + .send_message_with_handle(agent_id, &last_user_msg, Some(kernel_handle), None, None) .await { Ok(result) => { @@ -378,7 +378,7 @@ async fn stream_response( let (mut rx, _handle) = state .kernel - .send_message_streaming(agent_id, message, Some(kernel_handle)) + .send_message_streaming(agent_id, message, Some(kernel_handle), None, None) .map_err(|e| format!("Streaming setup failed: {e}"))?; let (tx, stream_rx) = tokio::sync::mpsc::channel::>(64); diff --git a/crates/openfang-api/src/routes.rs b/crates/openfang-api/src/routes.rs index 4a300293..5b873175 100644 --- a/crates/openfang-api/src/routes.rs +++ b/crates/openfang-api/src/routes.rs @@ -364,16 +364,10 @@ pub async fn send_message( } } - // TODO(#597): Thread req.sender_id / req.sender_name into PromptContext - // so the agent loop can see who sent the message. Requires extending - // send_message_with_handle to accept optional sender metadata. - let _sender_id = req.sender_id; - let _sender_name = req.sender_name; - let kernel_handle: Arc = state.kernel.clone() as Arc; match state .kernel - .send_message_with_handle(agent_id, &req.message, Some(kernel_handle)) + .send_message_with_handle(agent_id, &req.message, Some(kernel_handle), req.sender_id, req.sender_name) .await { Ok(result) => { @@ -1349,15 +1343,11 @@ pub async fn send_message_stream( .into_response(); } - // TODO(#597): Thread req.sender_id / req.sender_name into PromptContext - let _sender_id = req.sender_id; - let _sender_name = req.sender_name; - let kernel_handle: Arc = state.kernel.clone() as Arc; let (rx, _handle) = match state .kernel - .send_message_streaming(agent_id, &req.message, Some(kernel_handle)) + .send_message_streaming(agent_id, &req.message, Some(kernel_handle), req.sender_id, req.sender_name) { Ok(pair) => pair, Err(e) => { @@ -3990,7 +3980,9 @@ pub async fn install_hand_deps( let combined = format!("{stdout}{stderr}"); let likely_ok = combined.contains("already installed") || combined.contains("No applicable update") - || combined.contains("No available upgrade"); + || combined.contains("No available upgrade") + || combined.contains("already an App at") + || combined.contains("is already installed"); results.push(serde_json::json!({ "key": req.key, "status": if likely_ok { "installed" } else { "error" }, @@ -8189,7 +8181,7 @@ pub async fn run_schedule( ); let kernel_handle: Arc = state.kernel.clone() as Arc; - match state.kernel.send_message_with_handle(target_agent, &run_message, Some(kernel_handle)).await { + match state.kernel.send_message_with_handle(target_agent, &run_message, Some(kernel_handle), None, None).await { Ok(result) => ( StatusCode::OK, Json(serde_json::json!({ diff --git a/crates/openfang-api/src/ws.rs b/crates/openfang-api/src/ws.rs index bb3752c6..436fec0d 100644 --- a/crates/openfang-api/src/ws.rs +++ b/crates/openfang-api/src/ws.rs @@ -504,7 +504,7 @@ async fn handle_text_message( state.kernel.clone() as Arc; match state .kernel - .send_message_streaming(agent_id, &content, Some(kernel_handle)) + .send_message_streaming(agent_id, &content, Some(kernel_handle), None, None) { Ok((mut rx, handle)) => { // Forward stream events to WebSocket with debouncing diff --git a/crates/openfang-api/static/js/app.js b/crates/openfang-api/static/js/app.js index 4a9f373b..9ec6bd75 100644 --- a/crates/openfang-api/static/js/app.js +++ b/crates/openfang-api/static/js/app.js @@ -27,6 +27,8 @@ function renderMarkdown(text) { var html = marked.parse(text); // Add copy buttons to code blocks html = html.replace(/
]*target=)([^>]*)>/gi, '');
     return html;
   }
   return escapeHtml(text);
diff --git a/crates/openfang-api/static/js/pages/settings.js b/crates/openfang-api/static/js/pages/settings.js
index 2df7f318..662004c6 100644
--- a/crates/openfang-api/static/js/pages/settings.js
+++ b/crates/openfang-api/static/js/pages/settings.js
@@ -401,8 +401,12 @@ function settingsPage() {
       var key = this.providerKeyInputs[provider.id];
       if (!key || !key.trim()) { OpenFangToast.error('Please enter an API key'); return; }
       try {
-        await OpenFangAPI.post('/api/providers/' + encodeURIComponent(provider.id) + '/key', { key: key.trim() });
-        OpenFangToast.success('API key saved for ' + provider.display_name);
+        var resp = await OpenFangAPI.post('/api/providers/' + encodeURIComponent(provider.id) + '/key', { key: key.trim() });
+        if (resp && resp.switched_default) {
+          OpenFangToast.warning(resp.message || 'Default provider was switched to ' + provider.display_name);
+        } else {
+          OpenFangToast.success('API key saved for ' + provider.display_name);
+        }
         this.providerKeyInputs[provider.id] = '';
         await this.loadProviders();
         await this.loadModels();
diff --git a/crates/openfang-cli/src/tui/event.rs b/crates/openfang-cli/src/tui/event.rs
index 7e24e3a5..ce5fe841 100644
--- a/crates/openfang-cli/src/tui/event.rs
+++ b/crates/openfang-cli/src/tui/event.rs
@@ -303,7 +303,7 @@ pub fn spawn_inprocess_stream(
         // send_message_streaming() finds the reactor.
         let _guard = rt.enter();
 
-        match kernel.send_message_streaming(agent_id, &message, None) {
+        match kernel.send_message_streaming(agent_id, &message, None, None, None) {
             Ok((mut rx, handle)) => {
                 rt.block_on(async {
                     while let Some(ev) = rx.recv().await {
diff --git a/crates/openfang-kernel/src/kernel.rs b/crates/openfang-kernel/src/kernel.rs
index c22bb332..83231dc7 100644
--- a/crates/openfang-kernel/src/kernel.rs
+++ b/crates/openfang-kernel/src/kernel.rs
@@ -1389,7 +1389,7 @@ impl OpenFangKernel {
             .get()
             .and_then(|w| w.upgrade())
             .map(|arc| arc as Arc);
-        self.send_message_with_handle(agent_id, message, handle)
+        self.send_message_with_handle(agent_id, message, handle, None, None)
             .await
     }
 
@@ -1408,7 +1408,7 @@ impl OpenFangKernel {
             .get()
             .and_then(|w| w.upgrade())
             .map(|arc| arc as Arc);
-        self.send_message_with_handle_and_blocks(agent_id, message, handle, Some(blocks))
+        self.send_message_with_handle_and_blocks(agent_id, message, handle, Some(blocks), None, None)
             .await
     }
 
@@ -1418,8 +1418,10 @@ impl OpenFangKernel {
         agent_id: AgentId,
         message: &str,
         kernel_handle: Option>,
+        sender_id: Option,
+        sender_name: Option,
     ) -> KernelResult {
-        self.send_message_with_handle_and_blocks(agent_id, message, kernel_handle, None)
+        self.send_message_with_handle_and_blocks(agent_id, message, kernel_handle, None, sender_id, sender_name)
             .await
     }
 
@@ -1438,6 +1440,8 @@ impl OpenFangKernel {
         message: &str,
         kernel_handle: Option>,
         content_blocks: Option>,
+        sender_id: Option,
+        sender_name: Option,
     ) -> KernelResult {
         // Acquire per-agent lock to serialize concurrent messages for the same agent.
         // This prevents session corruption when multiple messages arrive in quick
@@ -1467,7 +1471,7 @@ impl OpenFangKernel {
             self.execute_python_agent(&entry, agent_id, message).await
         } else {
             // Default: LLM agent loop (builtin:chat or any unrecognized module)
-            self.execute_llm_agent(&entry, agent_id, message, kernel_handle, content_blocks)
+            self.execute_llm_agent(&entry, agent_id, message, kernel_handle, content_blocks, sender_id, sender_name)
                 .await
         };
 
@@ -1522,6 +1526,8 @@ impl OpenFangKernel {
         agent_id: AgentId,
         message: &str,
         kernel_handle: Option>,
+        sender_id: Option,
+        sender_name: Option,
     ) -> KernelResult<(
         tokio::sync::mpsc::Receiver,
         tokio::task::JoinHandle>,
@@ -1759,8 +1765,8 @@ impl OpenFangKernel {
                 },
                 peer_agents,
                 current_date: Some(chrono::Local::now().format("%A, %B %d, %Y (%Y-%m-%d %H:%M %Z)").to_string()),
-                sender_id: None,
-                sender_name: None,
+                sender_id,
+                sender_name,
             };
             manifest.model.system_prompt =
                 openfang_runtime::prompt_builder::build_system_prompt(&prompt_ctx);
@@ -2087,6 +2093,7 @@ impl OpenFangKernel {
     }
 
     /// Execute the default LLM-based agent loop.
+    #[allow(clippy::too_many_arguments)]
     async fn execute_llm_agent(
         &self,
         entry: &AgentEntry,
@@ -2094,6 +2101,8 @@ impl OpenFangKernel {
         message: &str,
         kernel_handle: Option>,
         content_blocks: Option>,
+        sender_id: Option,
+        sender_name: Option,
     ) -> KernelResult {
         // Check metering quota before starting
         self.metering
@@ -2268,8 +2277,8 @@ impl OpenFangKernel {
                 },
                 peer_agents,
                 current_date: Some(chrono::Local::now().format("%A, %B %d, %Y (%Y-%m-%d %H:%M %Z)").to_string()),
-                sender_id: None,
-                sender_name: None,
+                sender_id,
+                sender_name,
             };
             manifest.model.system_prompt =
                 openfang_runtime::prompt_builder::build_system_prompt(&prompt_ctx);
@@ -3924,7 +3933,7 @@ impl OpenFangKernel {
                                 let kh: std::sync::Arc = kernel.clone();
                                 match tokio::time::timeout(
                                     timeout,
-                                    kernel.send_message_with_handle(agent_id, message, Some(kh)),
+                                    kernel.send_message_with_handle(agent_id, message, Some(kh), None, None),
                                 )
                                 .await
                                 {
diff --git a/crates/openfang-kernel/tests/wasm_agent_integration_test.rs b/crates/openfang-kernel/tests/wasm_agent_integration_test.rs
index 26ec5212..2d659c2e 100644
--- a/crates/openfang-kernel/tests/wasm_agent_integration_test.rs
+++ b/crates/openfang-kernel/tests/wasm_agent_integration_test.rs
@@ -303,7 +303,7 @@ async fn test_wasm_agent_streaming_fallback() {
     let agent_id = kernel.spawn_agent(manifest).unwrap();
 
     let (mut rx, handle) = kernel
-        .send_message_streaming(agent_id, "Hi!", None)
+        .send_message_streaming(agent_id, "Hi!", None, None, None)
         .expect("Streaming should start");
 
     // Collect all stream events
diff --git a/crates/openfang-runtime/src/model_catalog.rs b/crates/openfang-runtime/src/model_catalog.rs
index cce9d4b7..15094537 100644
--- a/crates/openfang-runtime/src/model_catalog.rs
+++ b/crates/openfang-runtime/src/model_catalog.rs
@@ -865,6 +865,10 @@ fn builtin_aliases() -> HashMap {
         ("qwen-coder", "qwen-code/qwen3-coder"),
         ("qwen-coder-plus", "qwen-code/qwen-coder-plus"),
         ("qwq", "qwen-code/qwq-32b"),
+        // OpenRouter free-tier aliases
+        ("openrouter/free", "openrouter/meta-llama/llama-3.1-8b-instruct:free"),
+        ("free", "openrouter/meta-llama/llama-3.1-8b-instruct:free"),
+        ("free-reasoning", "openrouter/deepseek/deepseek-r1:free"),
     ];
     pairs
         .into_iter()
diff --git a/crates/openfang-types/src/model_catalog.rs b/crates/openfang-types/src/model_catalog.rs
index 58590e55..3fce8e7b 100644
--- a/crates/openfang-types/src/model_catalog.rs
+++ b/crates/openfang-types/src/model_catalog.rs
@@ -37,7 +37,8 @@ pub const GITHUB_COPILOT_BASE_URL: &str = "https://api.githubcopilot.com";
 
 // ── Chinese providers ─────────────────────────────────────────────
 pub const QWEN_BASE_URL: &str = "https://dashscope.aliyuncs.com/compatible-mode/v1";
-pub const MINIMAX_BASE_URL: &str = "https://api.minimaxi.chat/v1";
+/// Global endpoint. For China mainland, override via `[provider_urls] minimax = "https://api.minimaxi.com/v1"`.
+pub const MINIMAX_BASE_URL: &str = "https://api.minimax.io/v1";
 pub const ZHIPU_BASE_URL: &str = "https://open.bigmodel.cn/api/paas/v4";
 pub const ZHIPU_CODING_BASE_URL: &str = "https://open.bigmodel.cn/api/coding/paas/v4";
 /// Z.AI domain aliases (same API, different domain).
diff --git a/packages/whatsapp-gateway/index.js b/packages/whatsapp-gateway/index.js
index 18e84573..8da94bf4 100644
--- a/packages/whatsapp-gateway/index.js
+++ b/packages/whatsapp-gateway/index.js
@@ -125,12 +125,21 @@ async function startConnection() {
       if (msg.key.remoteJid === 'status@broadcast') continue;
 
       const sender = msg.key.remoteJid || '';
-      const text = msg.message?.conversation
+      let text = msg.message?.conversation
         || msg.message?.extendedTextMessage?.text
         || msg.message?.imageMessage?.caption
         || '';
 
-      if (!text) continue;
+      // Detect media type if no text
+      if (!text) {
+        const m = msg.message;
+        if (m?.imageMessage) text = '[Image received]' + (m.imageMessage.caption ? ': ' + m.imageMessage.caption : '');
+        else if (m?.audioMessage) text = '[Voice note received]';
+        else if (m?.videoMessage) text = '[Video received]' + (m.videoMessage.caption ? ': ' + m.videoMessage.caption : '');
+        else if (m?.documentMessage) text = '[Document received: ' + (m.documentMessage.fileName || 'file') + ']';
+        else if (m?.stickerMessage) text = '[Sticker received]';
+        else continue; // Only skip truly empty messages
+      }
 
       // Extract phone number from JID (e.g. "1234567890@s.whatsapp.net" → "+1234567890")
       const phone = '+' + sender.replace(/@.*$/, '');