feat: persist sgclaw browser conversations

This commit is contained in:
zyl
2026-03-27 01:57:42 +08:00
parent bae0e452a5
commit d315c13f66
11 changed files with 402 additions and 20 deletions

View File

@@ -15,9 +15,11 @@ use sgclaw::agent::{
handle_browser_message_with_context,
AgentRuntimeContext,
};
use sgclaw::compat::runtime::execute_task;
use sgclaw::compat::runtime::{execute_task, CompatTaskContext};
use sgclaw::config::DeepSeekSettings;
use sgclaw::pipe::{Action, AgentMessage, BrowserMessage, BrowserPipeTool, Timing};
use sgclaw::pipe::{
Action, AgentMessage, BrowserMessage, BrowserPipeTool, ConversationMessage, Timing,
};
use sgclaw::security::MacPolicy;
use uuid::Uuid;
@@ -232,6 +234,7 @@ fn compat_runtime_uses_zeroclaw_provider_path_and_executes_browser_actions() {
transport.as_ref(),
browser_tool,
"打开百度搜索天气",
&CompatTaskContext::default(),
&workspace_root,
&settings,
)
@@ -396,6 +399,10 @@ fn handle_browser_message_prefers_compat_runtime_for_supported_instruction_when_
&runtime_context,
BrowserMessage::SubmitTask {
instruction: "打开百度搜索天气".to_string(),
conversation_id: String::new(),
messages: vec![],
page_url: String::new(),
page_title: String::new(),
},
)
.unwrap();
@@ -484,6 +491,10 @@ fn handle_browser_message_falls_back_to_compat_runtime_for_unsupported_instructi
&browser_tool,
BrowserMessage::SubmitTask {
instruction: "帮我打开百度首页".to_string(),
conversation_id: String::new(),
messages: vec![],
page_url: String::new(),
page_title: String::new(),
},
)
.unwrap();
@@ -509,3 +520,134 @@ fn handle_browser_message_falls_back_to_compat_runtime_for_unsupported_instructi
}));
assert_eq!(request_bodies.len(), 2);
}
#[test]
fn handle_browser_message_rejects_non_task_greeting_explicitly() {
let transport = Arc::new(MockTransport::new(vec![]));
let browser_tool = BrowserPipeTool::new(
transport.clone(),
test_policy(),
vec![1, 2, 3, 4, 5, 6, 7, 8],
)
.with_response_timeout(Duration::from_secs(1));
handle_browser_message(
transport.as_ref(),
&browser_tool,
BrowserMessage::SubmitTask {
instruction: "你好".to_string(),
conversation_id: String::new(),
messages: vec![],
page_url: String::new(),
page_title: String::new(),
},
)
.unwrap();
let sent = transport.sent_messages();
assert!(matches!(
sent.last(),
Some(AgentMessage::TaskComplete { success, summary })
if !success && summary.contains("浏览器任务入口")
));
}
#[test]
fn compat_runtime_includes_prior_turns_in_follow_up_provider_request() {
let _guard = env_lock().lock().unwrap_or_else(|err| err.into_inner());
let first_response = json!({
"choices": [{
"message": {
"content": "",
"tool_calls": [{
"id": "call_1",
"type": "function",
"function": {
"name": "browser_action",
"arguments": serde_json::to_string(&json!({
"action": "navigate",
"expected_domain": "www.zhihu.com",
"url": "https://www.zhihu.com/search?q=天气&type=content"
})).unwrap()
}
}]
}
}]
});
let second_response = json!({
"choices": [{
"message": {
"content": "已在知乎搜索天气"
}
}]
});
let (base_url, requests, server_handle) =
start_fake_deepseek_server(vec![first_response, second_response]);
let workspace_root = temp_workspace_root();
let settings = DeepSeekSettings {
api_key: "deepseek-test-key".to_string(),
base_url,
model: "deepseek-chat".to_string(),
};
let transport = Arc::new(MockTransport::new(vec![BrowserMessage::Response {
seq: 1,
success: true,
data: json!({ "navigated": true }),
aom_snapshot: vec![],
timing: Timing {
queue_ms: 1,
exec_ms: 10,
},
}]));
let browser_tool = BrowserPipeTool::new(
transport.clone(),
test_policy(),
vec![1, 2, 3, 4, 5, 6, 7, 8],
)
.with_response_timeout(Duration::from_secs(1));
let task_context = CompatTaskContext {
conversation_id: Some("conversation-1".to_string()),
messages: vec![
ConversationMessage {
role: "user".to_string(),
content: "打开百度搜索天气".to_string(),
},
ConversationMessage {
role: "assistant".to_string(),
content: "已在百度搜索天气".to_string(),
},
],
page_url: Some("https://www.zhihu.com/".to_string()),
page_title: Some("知乎".to_string()),
};
let summary = execute_task(
transport.as_ref(),
browser_tool,
"打开知乎搜索天气",
&task_context,
&workspace_root,
&settings,
)
.unwrap();
server_handle.join().unwrap();
let request_bodies = requests.lock().unwrap().clone();
let first_request_messages = request_bodies[0]["messages"]
.as_array()
.cloned()
.unwrap_or_default();
assert_eq!(summary, "已在知乎搜索天气");
assert!(first_request_messages.iter().any(|message| {
message["role"] == json!("user")
&& message["content"] == json!("打开百度搜索天气")
}));
assert!(first_request_messages.iter().any(|message| {
message["role"] == json!("assistant")
&& message["content"] == json!("已在百度搜索天气")
}));
}