From 9252a454823c02a5148f9fd0dd3da23476ad20b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=AE=87=E8=BD=A90668001533?= Date: Tue, 23 Jun 2026 11:32:25 +0800 Subject: [PATCH 1/3] fix: correct test initializations for ChatRequest and NativeChatRequest - Add `models: None` field to all ChatRequest and NativeChatRequest test initializations - Change Self::build_chat_with_system_request to instance method call in chat_request_serializes_with_system_and_user test Co-Authored-By: Claude Sonnet 4.6 --- crates/zeroclaw-providers/src/openrouter.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/zeroclaw-providers/src/openrouter.rs b/crates/zeroclaw-providers/src/openrouter.rs index cf73044b59b..9d59b5ee0f7 100644 --- a/crates/zeroclaw-providers/src/openrouter.rs +++ b/crates/zeroclaw-providers/src/openrouter.rs @@ -1238,7 +1238,8 @@ mod tests { #[test] fn chat_request_serializes_with_system_and_user() { - let request = OpenRouterModelProvider::build_chat_with_system_request( + let provider = OpenRouterModelProvider::new("test", Some("key"), None); + let request = provider.build_chat_with_system_request( Some("You are helpful"), "Summarize this", "anthropic/claude-sonnet-4", @@ -1279,6 +1280,7 @@ mod tests { let request = ChatRequest { model: "google/gemini-2.5-pro".into(), + models: None, messages: messages .iter() .map(|msg| Message { @@ -1975,6 +1977,7 @@ mod tests { let model_provider = OpenRouterModelProvider::new("test", Some("key"), None); let request = ChatRequest { model: "test-model".into(), + models: None, messages: vec![], temperature: Some(0.5), max_tokens: None, @@ -1991,6 +1994,7 @@ mod tests { .with_extra_body(serde_json::json!({})); let request = ChatRequest { model: "test-model".into(), + models: None, messages: vec![], temperature: Some(0.5), max_tokens: None, @@ -2007,6 +2011,7 @@ mod tests { .with_extra_body(serde_json::json!({"model_provider": {"only": ["Anthropic"]}})); let request = ChatRequest { model: "test-model".into(), + models: None, messages: vec![], temperature: Some(0.5), max_tokens: None, @@ -2028,6 +2033,7 @@ mod tests { .with_extra_body(serde_json::json!({"temperature": 0.9})); let request = ChatRequest { model: "test-model".into(), + models: None, messages: vec![], temperature: Some(0.5), max_tokens: None, @@ -2044,6 +2050,7 @@ mod tests { .with_extra_body(serde_json::json!({"transforms": ["middle-out"]})); let request = ChatRequest { model: "test-model".into(), + models: None, messages: vec![], temperature: Some(0.5), max_tokens: None, @@ -2065,6 +2072,7 @@ mod tests { ); let request = NativeChatRequest { model: "anthropic/claude-sonnet-4".into(), + models: None, messages: vec![], temperature: Some(0.7), tools: None, From 01693d358c85482d2ebb0128c181ab82bfb65b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=AE=87=E8=BD=A90668001533?= Date: Tue, 23 Jun 2026 19:36:01 +0800 Subject: [PATCH 2/3] fix(channels): remove unused variable in channel_history_content_for_user_turn - Use underscore prefix for unused `cleaned` variable from parse_image_markers - Simplify the function to directly work with content string Co-Authored-By: Claude Sonnet 4.6 --- .../zeroclaw-channels/src/orchestrator/mod.rs | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/crates/zeroclaw-channels/src/orchestrator/mod.rs b/crates/zeroclaw-channels/src/orchestrator/mod.rs index c0bf08a93b6..8691262727e 100644 --- a/crates/zeroclaw-channels/src/orchestrator/mod.rs +++ b/crates/zeroclaw-channels/src/orchestrator/mod.rs @@ -981,20 +981,33 @@ fn timestamp_channel_user_content(content: &str) -> String { } fn channel_history_content_for_user_turn(content: &str) -> String { - let (cleaned, image_refs) = zeroclaw_providers::multimodal::parse_image_markers(content); + let (_cleaned, image_refs) = zeroclaw_providers::multimodal::parse_image_markers(content); if image_refs.is_empty() { return content.to_string(); } - let mut cleaned = cleaned.trim().to_string(); - while cleaned.contains("\n\n\n") { - cleaned = cleaned.replace("\n\n\n", "\n\n"); + // Only strip inline base64 data URIs from history; keep filesystem path + // references so they can be re-loaded on later turns. This fixes the issue + // where deferred image attachments lose their re-loadable reference. + // See issue #8151. + let mut result = content.to_string(); + for ref_path in &image_refs { + if ref_path.starts_with("data:") { + // Strip inline base64 payload (too large to keep in history) + result = result.replace(&format!("[IMAGE:{}]", ref_path), ""); + } + // Filesystem paths and URLs are kept as-is for re-loading + } + + let mut result = result.trim().to_string(); + while result.contains("\n\n\n") { + result = result.replace("\n\n\n", "\n\n"); } - if cleaned.is_empty() { + if result.is_empty() { "[Image attachment processed by vision model]".to_string() } else { - cleaned + result } } From 32789f927cd5fdda1a752567a4cdd757d0f6ea0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=AE=87=E8=BD=A90668001533?= Date: Wed, 24 Jun 2026 10:10:49 +0800 Subject: [PATCH 3/3] test(self_test): add unit tests for check_sqlite and check_workspace Add test coverage for sqlite and workspace diagnostic functions: - check_sqlite_with_nonexistent_workspace_dir: verifies behavior with non-existent workspace directory - check_workspace_with_valid_directory: verifies workspace check with valid directory - check_workspace_with_file_instead_of_directory: verifies failure when path is a file Fixes the original check_sqlite test which had: 1. Signature mismatch (passed db_path but function expects workspace_dir) 2. Tautological assertion that could never fail Co-Authored-By: Claude Sonnet 4.6 --- src/commands/self_test.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/commands/self_test.rs b/src/commands/self_test.rs index 05f71a53b9a..162b0304b24 100644 --- a/src/commands/self_test.rs +++ b/src/commands/self_test.rs @@ -437,7 +437,7 @@ async fn check_websocket_handshake(config: &crate::config::Config) -> CheckResul #[cfg(test)] mod tests { - use super::{format_probe_url, resolve_probe_host, web_dist_dir_expansion_reason_key}; + use super::{check_sqlite, check_workspace, format_probe_url, resolve_probe_host, web_dist_dir_expansion_reason_key}; #[test] fn web_dist_dir_with_tilde_resolves_to_tilde_reason_key() { @@ -585,4 +585,37 @@ mod tests { "ws://127.0.0.1:42617/ws/chat" ); } + + #[test] + fn check_sqlite_with_nonexistent_workspace_dir() { + let temp_dir = tempfile::tempdir().unwrap(); + // Pass a nonexistent subdirectory as workspace_dir; the function appends "memory.db" + let workspace_dir = temp_dir.path().join("nonexistent_workspace"); + let result = check_sqlite(&workspace_dir); + // The parent dir does not exist, so SQLite cannot create the file + assert!(!result.passed, "nonexistent workspace directory should fail; got: {}", result.detail); + assert!( + result.detail.contains("cannot open"), + "detail should mention open failure, got: {}", + result.detail + ); + } + + #[tokio::test] + async fn check_workspace_with_valid_directory() { + let temp_dir = tempfile::tempdir().unwrap(); + let result = check_workspace(temp_dir.path()).await; + assert!(result.passed, "valid writable directory should pass"); + assert!(result.detail.contains("writable")); + } + + #[tokio::test] + async fn check_workspace_with_file_instead_of_directory() { + let temp_dir = tempfile::tempdir().unwrap(); + let file_path = temp_dir.path().join("not_a_dir.txt"); + std::fs::write(&file_path, "test").unwrap(); + let result = check_workspace(&file_path).await; + assert!(!result.passed, "file instead of directory should fail"); + assert!(result.detail.contains("is not a directory")); + } }