refactor: remove ws-only scene routing remnants

Keep the ws branch focused on websocket and Zhihu behavior by dropping staged scene-routing artifacts and restoring single-path skills dir semantics.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
木炎
2026-04-10 22:35:43 +08:00
parent 81de162756
commit b454fa3f54
17 changed files with 107 additions and 2251 deletions

View File

@@ -118,17 +118,6 @@ fn real_skill_lib_root() -> PathBuf {
.join("skill_lib")
}
fn staged_skill_root() -> PathBuf {
PathBuf::from("D:/data/ideaSpace/rust/sgClaw/claw/claw/skills/skill_staging")
}
fn project_skills_root() -> PathBuf {
staged_skill_root()
.parent()
.expect("staged skill root should have parent")
.to_path_buf()
}
fn success_browser_response(seq: u64, data: Value) -> BrowserMessage {
BrowserMessage::Response {
seq,
@@ -423,7 +412,7 @@ fn compat_runtime_includes_default_workspace_skills_in_provider_request() {
api_key: "deepseek-test-key".to_string(),
base_url,
model: "deepseek-chat".to_string(),
skills_dir: Vec::new(),
skills_dir: None,
};
let transport = Arc::new(MockTransport::new(vec![]));
let browser_tool = BrowserPipeTool::new(
@@ -961,7 +950,7 @@ fn compat_runtime_includes_prior_turns_in_follow_up_provider_request() {
api_key: "deepseek-test-key".to_string(),
base_url,
model: "deepseek-chat".to_string(),
skills_dir: Vec::new(),
skills_dir: None,
};
let transport = Arc::new(MockTransport::new(vec![BrowserMessage::Response {
seq: 1,
@@ -1060,7 +1049,7 @@ fn compat_runtime_does_not_forward_raw_aom_snapshot_back_to_provider() {
api_key: "deepseek-test-key".to_string(),
base_url,
model: "deepseek-chat".to_string(),
skills_dir: Vec::new(),
skills_dir: None,
};
let large_snapshot_marker = "snapshot-marker ".repeat(2048);
@@ -1122,7 +1111,7 @@ fn compat_runtime_injects_browser_contract_and_page_context_into_provider_reques
api_key: "deepseek-test-key".to_string(),
base_url,
model: "deepseek-chat".to_string(),
skills_dir: Vec::new(),
skills_dir: None,
};
let transport = Arc::new(MockTransport::new(vec![]));
let browser_tool = BrowserPipeTool::new(
@@ -1198,7 +1187,7 @@ fn compat_runtime_can_complete_a_text_only_turn_without_browser_tool_calls() {
"deepseek-test-key".to_string(),
base_url,
"deepseek-chat".to_string(),
Vec::new(),
None,
)
.unwrap();
settings.runtime_profile = RuntimeProfile::GeneralAssistant;
@@ -1276,7 +1265,7 @@ fn compat_runtime_allows_read_skill_under_compact_mode_policy() {
"deepseek-test-key".to_string(),
base_url,
"deepseek-chat".to_string(),
Vec::new(),
None,
)
.unwrap();
let transport = Arc::new(MockTransport::new(vec![]));
@@ -1361,7 +1350,7 @@ top_n = "How many hotlist rows to extract."
"deepseek-test-key".to_string(),
base_url,
"deepseek-chat".to_string(),
Vec::new(),
None,
)
.unwrap();
settings.runtime_profile = RuntimeProfile::BrowserAttached;
@@ -1468,7 +1457,7 @@ return {
"deepseek-test-key".to_string(),
base_url,
"deepseek-chat".to_string(),
Vec::new(),
None,
)
.unwrap();
settings.runtime_profile = RuntimeProfile::BrowserAttached;
@@ -1539,7 +1528,7 @@ fn zhihu_hotlist_browser_skill_flow_does_not_expose_shell_or_glob_tools() {
"deepseek-test-key".to_string(),
base_url,
"deepseek-chat".to_string(),
vec![real_skill_lib_root()],
Some(real_skill_lib_root()),
)
.unwrap();
settings.runtime_profile = RuntimeProfile::BrowserAttached;
@@ -1593,7 +1582,7 @@ fn compat_runtime_browser_attached_profile_keeps_file_read_available_for_local_p
"deepseek-test-key".to_string(),
base_url,
"deepseek-chat".to_string(),
Vec::new(),
None,
)
.unwrap();
settings.runtime_profile = RuntimeProfile::BrowserAttached;
@@ -1644,7 +1633,7 @@ fn browser_attached_export_flow_exposes_browser_and_office_tools_only() {
"deepseek-test-key".to_string(),
base_url,
"deepseek-chat".to_string(),
vec![real_skill_lib_root()],
Some(real_skill_lib_root()),
)
.unwrap();
settings.runtime_profile = RuntimeProfile::BrowserAttached;
@@ -1699,7 +1688,7 @@ fn compat_runtime_allows_zhihu_hotlist_screen_export_tool_in_browser_profile() {
"deepseek-test-key".to_string(),
base_url,
"deepseek-chat".to_string(),
vec![real_skill_lib_root()],
Some(real_skill_lib_root()),
)
.unwrap();
settings.runtime_profile = RuntimeProfile::BrowserAttached;
@@ -1781,7 +1770,7 @@ fn compat_runtime_logs_read_skill_usage_with_skill_name() {
"deepseek-test-key".to_string(),
base_url,
"deepseek-chat".to_string(),
Vec::new(),
None,
)
.unwrap();
let transport = Arc::new(MockTransport::new(vec![]));
@@ -1918,7 +1907,7 @@ fn browser_attached_excel_request_uses_execution_contract_not_skill_source_stuff
"deepseek-test-key".to_string(),
base_url,
"deepseek-chat".to_string(),
vec![real_skill_lib_root()],
Some(real_skill_lib_root()),
)
.unwrap();
settings.runtime_profile = RuntimeProfile::BrowserAttached;
@@ -1970,7 +1959,7 @@ fn browser_attached_publish_request_injects_confirmation_contract() {
"deepseek-test-key".to_string(),
base_url,
"deepseek-chat".to_string(),
vec![real_skill_lib_root()],
Some(real_skill_lib_root()),
)
.unwrap();
settings.runtime_profile = RuntimeProfile::BrowserAttached;
@@ -2601,313 +2590,6 @@ fn browser_submit_path_prefers_zeroclaw_process_message_orchestrator_for_zhihu_p
}));
}
#[test]
fn handle_browser_message_exposes_project_skills_and_staged_scene_skills_together() {
let _guard = env_lock().lock().unwrap_or_else(|err| err.into_inner());
let response = json!({
"choices": [{
"message": {
"content": "已同时看到顶层与场景技能"
}
}]
});
let (base_url, requests, server_handle) = start_fake_deepseek_server(vec![response]);
let workspace_root = temp_workspace_root();
let config_path = write_deepseek_config_with_skills_dir(
&workspace_root,
"deepseek-test-key",
&base_url,
"deepseek-chat",
Some(project_skills_root().to_str().unwrap()),
);
let runtime_context = AgentRuntimeContext::new(Some(config_path), workspace_root.clone());
let transport = Arc::new(MockTransport::new(vec![]));
let browser_tool = BrowserPipeTool::new(
transport.clone(),
zhihu_test_policy(),
vec![1, 2, 3, 4, 5, 6, 7, 8],
)
.with_response_timeout(Duration::from_secs(1));
handle_browser_message_with_context(
transport.as_ref(),
&browser_tool,
&runtime_context,
BrowserMessage::SubmitTask {
instruction: "告诉我当前有哪些知乎和95598相关 skill".to_string(),
conversation_id: String::new(),
messages: vec![],
page_url: "https://example.net/".to_string(),
page_title: "Example Domain".to_string(),
},
)
.unwrap();
server_handle.join().unwrap();
let sent = transport.sent_messages();
let request_bodies = requests.lock().unwrap().clone();
let first_request = request_bodies[0].to_string();
let loaded_skills_message = sent
.iter()
.find_map(|message| match message {
AgentMessage::LogEntry { level, message }
if level == "info" && message.starts_with("loaded skills: ") =>
{
Some(message.clone())
}
_ => None,
})
.expect("expected loaded skills log entry");
assert!(sent.iter().any(|message| {
matches!(
message,
AgentMessage::TaskComplete { success, summary }
if *success && summary == "已同时看到顶层与场景技能"
)
}));
assert!(loaded_skills_message.contains("zhihu-hotlist@0.1.0"));
assert!(loaded_skills_message.contains("zhihu-write@0.1.0"));
assert!(loaded_skills_message.contains("fault-details-report@0.1.0"));
assert!(loaded_skills_message.contains("95598-repair-city-dispatch@0.1.0"));
assert!(first_request.contains("zhihu-hotlist"));
assert!(first_request.contains("zhihu-write"));
assert!(first_request.contains("fault-details-report"));
assert!(first_request.contains("95598-repair-city-dispatch"));
}
#[test]
fn fault_details_route_finds_staged_scene_skill_under_project_skills_root() {
let _guard = env_lock().lock().unwrap_or_else(|err| err.into_inner());
let workspace_root = temp_workspace_root();
let config_path = write_deepseek_config_with_skills_dir(
&workspace_root,
"deepseek-test-key",
"http://127.0.0.1:9",
"deepseek-chat",
Some(project_skills_root().to_str().unwrap()),
);
let runtime_context = AgentRuntimeContext::new(Some(config_path), workspace_root.clone());
let transport = Arc::new(MockTransport::new(vec![success_browser_response(
1,
json!({
"text": {
"sheet_name": "故障明细",
"rows": [["2026-04", "已完成"]]
}
}),
)]));
let browser_tool = BrowserPipeTool::new(
transport.clone(),
policy_for_domains(&["example.invalid"]),
vec![1, 2, 3, 4, 5, 6, 7, 8],
)
.with_response_timeout(Duration::from_secs(1));
handle_browser_message_with_context(
transport.as_ref(),
&browser_tool,
&runtime_context,
BrowserMessage::SubmitTask {
instruction: "导出 2026-04 故障明细".to_string(),
conversation_id: String::new(),
messages: vec![],
page_url: "https://example.invalid/workbench".to_string(),
page_title: "业务台账".to_string(),
},
)
.unwrap();
let sent = transport.sent_messages();
assert!(sent.iter().any(|message| {
matches!(
message,
AgentMessage::TaskComplete { success, summary }
if *success && summary.contains("sheet_name") && summary.contains("故障明细")
)
}));
assert!(sent.iter().any(|message| {
matches!(message, AgentMessage::Command { action, .. } if action == &Action::Eval)
}));
}
#[test]
fn handle_browser_message_exposes_staged_95598_scene_skills_and_contract_on_agent_path() {
let _guard = env_lock().lock().unwrap_or_else(|err| err.into_inner());
let response = json!({
"choices": [{
"message": {
"content": "已按95598场景进入通用代理路径"
}
}]
});
let (base_url, requests, server_handle) = start_fake_deepseek_server(vec![response]);
let workspace_root = temp_workspace_root();
let config_path = write_deepseek_config_with_skills_dir(
&workspace_root,
"deepseek-test-key",
&base_url,
"deepseek-chat",
Some(staged_skill_root().to_str().unwrap()),
);
let runtime_context = AgentRuntimeContext::new(Some(config_path), workspace_root.clone());
let transport = Arc::new(MockTransport::new(vec![]));
let browser_tool = BrowserPipeTool::new(
transport.clone(),
policy_for_domains(&["95598.example.invalid"]),
vec![1, 2, 3, 4, 5, 6, 7, 8],
)
.with_response_timeout(Duration::from_secs(1));
handle_browser_message_with_context(
transport.as_ref(),
&browser_tool,
&runtime_context,
BrowserMessage::SubmitTask {
instruction: "请处理95598-repair-city-dispatch场景查看抢修市指派单并汇总当前队列"
.to_string(),
conversation_id: String::new(),
messages: vec![],
page_url: "https://95598.example.invalid/dispatch".to_string(),
page_title: "95598抢修市指监测".to_string(),
},
)
.unwrap();
server_handle.join().unwrap();
let sent = transport.sent_messages();
let request_bodies = requests.lock().unwrap().clone();
let first_request = request_bodies[0].to_string();
let loaded_skills_message = sent
.iter()
.find_map(|message| match message {
AgentMessage::LogEntry { level, message }
if level == "info" && message.starts_with("loaded skills: ") =>
{
Some(message.clone())
}
_ => None,
})
.expect("expected loaded skills log entry");
assert!(sent.iter().any(|message| {
matches!(
message,
AgentMessage::LogEntry { level, message }
if level == "mode" && message == "compat_llm_primary"
)
}));
assert!(sent.iter().any(|message| {
matches!(
message,
AgentMessage::TaskComplete { success, summary }
if *success && summary == "已按95598场景进入通用代理路径"
)
}));
assert!(loaded_skills_message.contains("fault-details-report@0.1.0"));
assert!(loaded_skills_message.contains("95598-repair-city-dispatch@0.1.0"));
assert_eq!(request_bodies.len(), 1);
assert!(first_request.contains("95598-repair-city-dispatch.collect_repair_orders"));
assert!(first_request.contains("Current page URL: https://95598.example.invalid/dispatch"));
assert!(first_request.contains("Current page title: 95598抢修市指监测"));
}
#[test]
fn browser_surface_disabled_fault_details_turn_uses_general_assistant_provider_path() {
let _guard = env_lock().lock().unwrap_or_else(|err| err.into_inner());
let response = json!({
"choices": [{
"message": {
"content": "已走通用助手路径"
}
}]
});
let (base_url, requests, server_handle) = start_fake_deepseek_server(vec![response]);
let workspace_root = temp_workspace_root();
let config_path = workspace_root.join("sgclaw_config.json");
fs::write(
&config_path,
serde_json::to_string_pretty(&json!({
"apiKey": "deepseek-test-key",
"baseUrl": base_url,
"model": "deepseek-chat",
"runtimeProfile": "generalAssistant",
"skillsDir": staged_skill_root(),
}))
.unwrap(),
)
.unwrap();
let runtime_context = AgentRuntimeContext::new(Some(config_path), workspace_root.clone());
let transport = Arc::new(MockTransport::new(vec![]));
let browser_tool = BrowserPipeTool::new(
transport.clone(),
policy_for_domains(&["example.invalid"]),
vec![1, 2, 3, 4, 5, 6, 7, 8],
)
.with_response_timeout(Duration::from_secs(1));
handle_browser_message_with_context(
transport.as_ref(),
&browser_tool,
&runtime_context,
BrowserMessage::SubmitTask {
instruction: "导出 2026-04 故障明细".to_string(),
conversation_id: String::new(),
messages: vec![],
page_url: "https://example.invalid/workbench".to_string(),
page_title: "业务台账".to_string(),
},
)
.unwrap();
server_handle.join().unwrap();
let sent = transport.sent_messages();
let request_bodies = requests.lock().unwrap().clone();
let first_request = request_bodies[0].to_string();
let loaded_skills_message = sent.iter().find_map(|message| match message {
AgentMessage::LogEntry { level, message }
if level == "info" && message.starts_with("loaded skills: ") =>
{
Some(message.clone())
}
_ => None,
});
assert!(sent.iter().any(|message| {
matches!(
message,
AgentMessage::LogEntry { level, message }
if level == "mode" && message == "compat_llm_primary"
)
}));
assert!(sent.iter().any(|message| {
matches!(
message,
AgentMessage::TaskComplete { success, summary }
if *success && summary == "已走通用助手路径"
)
}));
assert_eq!(request_bodies.len(), 1);
assert!(!first_request.contains("95598-repair-city-dispatch.collect_repair_orders"));
assert!(!first_request.contains("browser workflow, not a text-only task"));
assert!(!first_request.contains("generic browser probing only after"));
assert!(loaded_skills_message.is_none());
assert!(!sent.iter().any(|message| {
matches!(message, AgentMessage::Command { action, .. } if action == &Action::Eval)
}));
}
#[test]
fn browser_attached_zhihu_hotlist_request_keeps_zhihu_contract_without_scene_injection() {
let _guard = env_lock().lock().unwrap_or_else(|err| err.into_inner());
@@ -2926,7 +2608,7 @@ fn browser_attached_zhihu_hotlist_request_keeps_zhihu_contract_without_scene_inj
"deepseek-test-key".to_string(),
base_url,
"deepseek-chat".to_string(),
vec![real_skill_lib_root()],
Some(real_skill_lib_root()),
)
.unwrap();
settings.runtime_profile = RuntimeProfile::BrowserAttached;
@@ -2964,348 +2646,28 @@ fn browser_attached_zhihu_hotlist_request_keeps_zhihu_contract_without_scene_inj
}
#[test]
fn fault_details_direct_browser_scene_matches_primary_orchestration_gate() {
assert!(sgclaw::compat::orchestration::should_use_primary_orchestration(
"导出故障明细",
Some("https://example.invalid/workbench"),
Some("业务台账"),
));
}
#[test]
fn fault_details_direct_browser_scene_detects_direct_route() {
use sgclaw::compat::workflow_executor::{detect_route, WorkflowRoute};
fn ws_cleanup_no_longer_detects_fault_details_scene_route() {
use sgclaw::compat::workflow_executor::detect_route;
assert_eq!(
detect_route(
"导出故障明细",
Some("https://example.invalid/workbench"),
Some("业务台账")
Some("业务台账"),
),
Some(WorkflowRoute::FaultDetailsReport)
None,
);
}
#[test]
fn missing_scene_metadata_keeps_unrelated_primary_routing_unchanged() {
let registry = [sgclaw::runtime::SceneRegistryEntry {
id: "unrelated-scene".to_string(),
name: "无关场景".to_string(),
summary: "与故障明细无关。".to_string(),
tags: vec!["other".to_string()],
inputs: vec!["period".to_string()],
outputs: vec!["artifact".to_string()],
skill_package: "unrelated-skill".to_string(),
skill_tool: "run_other".to_string(),
skill_artifact_type: "artifact".to_string(),
dispatch_mode: sgclaw::runtime::DispatchMode::DirectBrowser,
expected_domain: "other.example.invalid".to_string(),
aliases: vec!["别的事情".to_string()],
default_args: serde_json::Map::new(),
}];
assert!(sgclaw::runtime::match_scene_instruction_in_registry(&registry, "别的事情").is_some());
assert!(sgclaw::runtime::match_scene_instruction_in_registry(&registry, "导出故障明细").is_none());
fn ws_cleanup_scene_keywords_do_not_trigger_primary_orchestration() {
assert!(!sgclaw::compat::orchestration::should_use_primary_orchestration(
"帮我汇总今天待办",
Some("https://example.invalid/workbench"),
Some("业务台账"),
"请处理95598抢修市指监测",
Some("https://95598.example.invalid/dispatch"),
Some("95598抢修市指监测"),
));
}
#[test]
fn fault_details_route_returns_clear_failure_when_period_cannot_be_derived() {
let _guard = env_lock().lock().unwrap_or_else(|err| err.into_inner());
let workspace_root = temp_workspace_root();
let config_path = write_deepseek_config_with_skills_dir(
&workspace_root,
"deepseek-test-key",
"http://127.0.0.1:9",
"deepseek-chat",
Some(staged_skill_root().to_str().unwrap()),
);
let runtime_context = AgentRuntimeContext::new(Some(config_path), workspace_root.clone());
let transport = Arc::new(MockTransport::new(vec![]));
let browser_tool = BrowserPipeTool::new(
transport.clone(),
zhihu_test_policy(),
vec![1, 2, 3, 4, 5, 6, 7, 8],
)
.with_response_timeout(Duration::from_secs(1));
handle_browser_message_with_context(
transport.as_ref(),
&browser_tool,
&runtime_context,
BrowserMessage::SubmitTask {
instruction: "导出故障明细".to_string(),
conversation_id: String::new(),
messages: vec![],
page_url: "https://example.invalid/workbench".to_string(),
page_title: "业务台账".to_string(),
},
)
.unwrap();
let sent = transport.sent_messages();
assert!(sent.iter().any(|message| {
matches!(
message,
AgentMessage::LogEntry { level, message }
if level == "mode" && message == "zeroclaw_process_message_primary"
)
}));
assert!(sent.iter().any(|message| {
matches!(
message,
AgentMessage::TaskComplete { success, summary }
if !*success && summary.contains("period") && summary.contains("无法")
)
}));
assert!(!sent.iter().any(|message| {
matches!(message, AgentMessage::Command { action, .. } if action == &Action::Eval)
}));
}
#[test]
fn fault_details_route_uses_current_page_host_as_expected_domain() {
let _guard = env_lock().lock().unwrap_or_else(|err| err.into_inner());
let workspace_root = temp_workspace_root();
let config_path = write_deepseek_config_with_skills_dir(
&workspace_root,
"deepseek-test-key",
"http://127.0.0.1:9",
"deepseek-chat",
Some(staged_skill_root().to_str().unwrap()),
);
let runtime_context = AgentRuntimeContext::new(Some(config_path), workspace_root.clone());
let transport = Arc::new(MockTransport::new(vec![success_browser_response(
1,
json!({
"text": {
"sheet_name": "故障明细",
"rows": [["2026-04", "已完成"]]
}
}),
)]));
let browser_tool = BrowserPipeTool::new(
transport.clone(),
policy_for_domains(&["example.invalid"]),
vec![1, 2, 3, 4, 5, 6, 7, 8],
)
.with_response_timeout(Duration::from_secs(1));
handle_browser_message_with_context(
transport.as_ref(),
&browser_tool,
&runtime_context,
BrowserMessage::SubmitTask {
instruction: "导出 2026-04 故障明细".to_string(),
conversation_id: String::new(),
messages: vec![],
page_url: "https://example.invalid/workbench".to_string(),
page_title: "业务台账".to_string(),
},
)
.unwrap();
let sent = transport.sent_messages();
assert!(sent.iter().any(|message| {
matches!(
message,
AgentMessage::TaskComplete { success, summary }
if *success && summary.contains("sheet_name") && summary.contains("故障明细")
)
}));
let eval_command = sent.iter().find_map(|message| match message {
AgentMessage::Command {
action,
params,
security,
..
} if action == &Action::Eval => Some((params.clone(), security.expected_domain.clone())),
_ => None,
});
let (params, expected_domain) = eval_command.expect("direct route should call browser eval");
assert_eq!(expected_domain, "example.invalid");
let script = params["script"].as_str().unwrap_or_default();
assert!(script.contains("const args = {\"period\":\"2026-04\"};"));
}
#[test]
fn fault_details_route_uses_packaged_browser_script_from_configured_skills_root() {
let _guard = env_lock().lock().unwrap_or_else(|err| err.into_inner());
let workspace_root = temp_workspace_root();
let staged_root = workspace_root.join("custom_fault_details_staging");
let custom_skills_dir = staged_root.join("skills");
let skill_dir = write_skill_manifest_package(
&custom_skills_dir,
"fault-details-report",
r#"
[skill]
name = "fault-details-report"
description = "Collect fault detail rows via a packaged browser script."
version = "0.1.0"
[[tools]]
name = "collect_fault_details"
description = "Collect fault detail rows for the target period."
kind = "browser_script"
command = "scripts/custom_fault_details.js"
[tools.args]
period = "Target report period."
"#,
);
write_skill_script(
&skill_dir,
"scripts/custom_fault_details.js",
r#"
return {
sheet_name: "故障明细",
rows: [[args.period || "unknown", "CUSTOM_FAULT_DETAILS_MARKER"]],
marker: "CUSTOM_FAULT_DETAILS_MARKER"
};
"#,
);
let config_path = write_deepseek_config_with_skills_dir(
&workspace_root,
"deepseek-test-key",
"http://127.0.0.1:9",
"deepseek-chat",
Some(staged_root.to_str().unwrap()),
);
let runtime_context = AgentRuntimeContext::new(Some(config_path), workspace_root.clone());
let transport = Arc::new(MockTransport::new(vec![success_browser_response(
1,
json!({
"text": {
"sheet_name": "故障明细",
"rows": [["2026-04", "已完成"]]
}
}),
)]));
let browser_tool = BrowserPipeTool::new(
transport.clone(),
policy_for_domains(&["example.invalid"]),
vec![1, 2, 3, 4, 5, 6, 7, 8],
)
.with_response_timeout(Duration::from_secs(1));
handle_browser_message_with_context(
transport.as_ref(),
&browser_tool,
&runtime_context,
BrowserMessage::SubmitTask {
instruction: "导出 2026-04 故障明细".to_string(),
conversation_id: String::new(),
messages: vec![],
page_url: "https://example.invalid/workbench".to_string(),
page_title: "业务台账".to_string(),
},
)
.unwrap();
let sent = transport.sent_messages();
assert!(sent.iter().any(|message| {
matches!(
message,
AgentMessage::TaskComplete { success, summary }
if *success && summary.contains("sheet_name") && summary.contains("故障明细")
)
}));
let eval_command = sent.iter().find_map(|message| match message {
AgentMessage::Command {
action,
params,
security,
..
} if action == &Action::Eval => Some((params.clone(), security.expected_domain.clone())),
_ => None,
});
let (params, expected_domain) = eval_command.expect("direct route should call browser eval");
assert_eq!(expected_domain, "example.invalid");
let script = params["script"].as_str().unwrap_or_default();
assert!(script.contains("const args = {\"period\":\"2026-04\"};"));
assert!(script.contains("CUSTOM_FAULT_DETAILS_MARKER"));
assert!(!script.contains("collect_fault_details.js"));
}
#[test]
fn fault_details_route_executes_browser_script_eval_when_period_is_derived() {
let _guard = env_lock().lock().unwrap_or_else(|err| err.into_inner());
let workspace_root = temp_workspace_root();
let config_path = write_deepseek_config_with_skills_dir(
&workspace_root,
"deepseek-test-key",
"http://127.0.0.1:9",
"deepseek-chat",
Some(staged_skill_root().to_str().unwrap()),
);
let runtime_context = AgentRuntimeContext::new(Some(config_path), workspace_root.clone());
let transport = Arc::new(MockTransport::new(vec![success_browser_response(
1,
json!({
"text": {
"sheet_name": "故障明细",
"rows": [["2026-04", "已完成"]]
}
}),
)]));
let browser_tool = BrowserPipeTool::new(
transport.clone(),
policy_for_domains(&["example.invalid"]),
vec![1, 2, 3, 4, 5, 6, 7, 8],
)
.with_response_timeout(Duration::from_secs(1));
handle_browser_message_with_context(
transport.as_ref(),
&browser_tool,
&runtime_context,
BrowserMessage::SubmitTask {
instruction: "导出 2026-04 故障明细".to_string(),
conversation_id: String::new(),
messages: vec![],
page_url: "https://example.invalid/workbench".to_string(),
page_title: "业务台账".to_string(),
},
)
.unwrap();
let sent = transport.sent_messages();
assert!(sent.iter().any(|message| {
matches!(
message,
AgentMessage::TaskComplete { success, summary }
if *success && summary.contains("sheet_name") && summary.contains("故障明细")
)
}));
let eval_command = sent.iter().find_map(|message| match message {
AgentMessage::Command {
action,
params,
security,
..
} if action == &Action::Eval => Some((params.clone(), security.expected_domain.clone())),
_ => None,
});
let (params, expected_domain) = eval_command.expect("direct route should call browser eval");
assert_eq!(expected_domain, "example.invalid");
let script = params["script"].as_str().unwrap_or_default();
assert!(script.contains("const args = {\"period\":\"2026-04\"};"));
assert!(script.contains("sheet_name") || script.contains("return JSON.stringify") || script.contains("rows"));
}
#[test]
fn zhihu_generated_auto_publish_matches_primary_orchestration_gate() {
assert!(