feat: persist sgclaw browser conversations
This commit is contained in:
@@ -201,3 +201,56 @@ async fn zeroclaw_browser_tool_keeps_domain_validation_in_mac_policy() {
|
||||
.contains("domain is not allowed")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn zeroclaw_browser_tool_rejects_missing_required_action_parameters() {
|
||||
let (transport, tool) = build_adapter(vec![]);
|
||||
|
||||
let missing_click_selector = tool
|
||||
.execute(json!({
|
||||
"action": "click",
|
||||
"expected_domain": "www.baidu.com"
|
||||
}))
|
||||
.await
|
||||
.unwrap();
|
||||
let missing_text_selector = tool
|
||||
.execute(json!({
|
||||
"action": "getText",
|
||||
"expected_domain": "www.baidu.com"
|
||||
}))
|
||||
.await
|
||||
.unwrap();
|
||||
let missing_navigate_url = tool
|
||||
.execute(json!({
|
||||
"action": "navigate",
|
||||
"expected_domain": "www.baidu.com"
|
||||
}))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(!missing_click_selector.success);
|
||||
assert!(!missing_text_selector.success);
|
||||
assert!(!missing_navigate_url.success);
|
||||
assert_eq!(transport.sent_messages().len(), 0);
|
||||
assert!(
|
||||
missing_click_selector
|
||||
.error
|
||||
.as_deref()
|
||||
.unwrap()
|
||||
.contains("click requires selector")
|
||||
);
|
||||
assert!(
|
||||
missing_text_selector
|
||||
.error
|
||||
.as_deref()
|
||||
.unwrap()
|
||||
.contains("getText requires selector")
|
||||
);
|
||||
assert!(
|
||||
missing_navigate_url
|
||||
.error
|
||||
.as_deref()
|
||||
.unwrap()
|
||||
.contains("navigate requires url")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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!("已在百度搜索天气")
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -68,6 +68,10 @@ fn submit_task_sends_three_commands_and_finishes_with_task_complete() {
|
||||
&tool,
|
||||
BrowserMessage::SubmitTask {
|
||||
instruction: "打开百度搜索天气".to_string(),
|
||||
conversation_id: String::new(),
|
||||
messages: vec![],
|
||||
page_url: String::new(),
|
||||
page_title: String::new(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -8,13 +8,20 @@ type HmacSha256 = Hmac<Sha256>;
|
||||
|
||||
#[test]
|
||||
fn browser_submit_task_round_trip_uses_task_wire_format() {
|
||||
let raw = r#"{"type":"submit_task","instruction":"打开百度并搜索今日汇率"}"#;
|
||||
let raw = r#"{"type":"submit_task","instruction":"打开百度并搜索今日汇率","conversation_id":"conversation-1","messages":[{"role":"assistant","content":"上一轮完成"}],"page_url":"https://www.baidu.com/","page_title":"百度一下"}"#;
|
||||
let message: BrowserMessage = serde_json::from_str(raw).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
message,
|
||||
BrowserMessage::SubmitTask {
|
||||
instruction: "打开百度并搜索今日汇率".to_string(),
|
||||
conversation_id: "conversation-1".to_string(),
|
||||
messages: vec![sgclaw::pipe::ConversationMessage {
|
||||
role: "assistant".to_string(),
|
||||
content: "上一轮完成".to_string(),
|
||||
}],
|
||||
page_url: "https://www.baidu.com/".to_string(),
|
||||
page_title: "百度一下".to_string(),
|
||||
}
|
||||
);
|
||||
assert_eq!(serde_json::to_string(&message).unwrap(), raw);
|
||||
|
||||
Reference in New Issue
Block a user