Consolidate the browser task runtime around the callback path, add safer artifact opening for Zhihu exports, and cover the new service/browser flows with focused tests and supporting docs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
146 lines
4.3 KiB
Rust
146 lines
4.3 KiB
Rust
mod common;
|
|
|
|
use std::collections::HashMap;
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
|
use common::MockTransport;
|
|
use sgclaw::browser::{BrowserBackend, PipeBrowserBackend};
|
|
use sgclaw::compat::browser_script_skill_tool::build_browser_script_skill_tools;
|
|
use sgclaw::pipe::{Action, CommandOutput, ExecutionSurfaceKind, ExecutionSurfaceMetadata};
|
|
use sgclaw::security::MacPolicy;
|
|
use zeroclaw::skills::{Skill, SkillTool};
|
|
|
|
fn backend_policy() -> MacPolicy {
|
|
MacPolicy::from_json_str(
|
|
r#"{
|
|
"version": "1.0",
|
|
"domains": { "allowed": ["oa.example.com", "erp.example.com"] },
|
|
"pipe_actions": {
|
|
"allowed": ["click", "type", "navigate", "getText"],
|
|
"blocked": ["eval", "executeJsInPage"]
|
|
}
|
|
}"#,
|
|
)
|
|
.unwrap()
|
|
}
|
|
|
|
fn eval_policy() -> MacPolicy {
|
|
MacPolicy::from_json_str(
|
|
r#"{
|
|
"version": "1.0",
|
|
"domains": { "allowed": ["www.zhihu.com"] },
|
|
"pipe_actions": {
|
|
"allowed": ["click", "type", "navigate", "getText", "eval"],
|
|
"blocked": []
|
|
}
|
|
}"#,
|
|
)
|
|
.unwrap()
|
|
}
|
|
|
|
#[test]
|
|
fn pipe_browser_backend_keeps_privileged_pipe_surface_metadata() {
|
|
let transport = Arc::new(MockTransport::new(vec![]));
|
|
let backend = PipeBrowserBackend::new(transport, backend_policy(), vec![1, 2, 3, 4]);
|
|
|
|
let metadata = backend.surface_metadata();
|
|
|
|
assert_eq!(metadata.kind, ExecutionSurfaceKind::PrivilegedBrowserPipe);
|
|
assert!(metadata.privileged);
|
|
assert!(!metadata.defines_runtime_identity);
|
|
assert_eq!(metadata.guard, "mac_policy");
|
|
assert_eq!(
|
|
metadata.allowed_domains,
|
|
vec!["oa.example.com", "erp.example.com"]
|
|
);
|
|
assert_eq!(
|
|
metadata.allowed_actions,
|
|
vec!["click", "type", "navigate", "getText"]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn pipe_browser_backend_reports_eval_capability_from_mac_policy() {
|
|
let transport = Arc::new(MockTransport::new(vec![]));
|
|
let backend = PipeBrowserBackend::new(transport, eval_policy(), vec![1, 2, 3, 4]);
|
|
|
|
assert!(backend.supports_eval());
|
|
}
|
|
|
|
#[test]
|
|
fn browser_script_tools_are_hidden_when_backend_cannot_eval() {
|
|
let skill_root = unique_temp_dir("sgclaw-browser-backend-capability");
|
|
let scripts_dir = skill_root.join("scripts");
|
|
fs::create_dir_all(&scripts_dir).unwrap();
|
|
fs::write(
|
|
scripts_dir.join("extract_hotlist.js"),
|
|
"return { rows: [[1, '标题', '10万热度']] };",
|
|
)
|
|
.unwrap();
|
|
|
|
let skills = vec![Skill {
|
|
name: "zhihu-hotlist".to_string(),
|
|
description: "Zhihu hotlist helpers".to_string(),
|
|
version: "1.0.0".to_string(),
|
|
author: None,
|
|
tags: vec![],
|
|
tools: vec![SkillTool {
|
|
name: "extract_hotlist".to_string(),
|
|
description: "Extract structured hotlist rows".to_string(),
|
|
kind: "browser_script".to_string(),
|
|
command: "scripts/extract_hotlist.js".to_string(),
|
|
args: HashMap::new(),
|
|
}],
|
|
prompts: vec![],
|
|
location: Some(skill_root.join("skill.json")),
|
|
}];
|
|
let backend: Arc<dyn BrowserBackend> = Arc::new(FakeBrowserBackend::new(false));
|
|
|
|
let tools = build_browser_script_skill_tools(&skills, backend).unwrap();
|
|
|
|
assert!(tools.is_empty());
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct FakeBrowserBackend {
|
|
supports_eval: bool,
|
|
}
|
|
|
|
impl FakeBrowserBackend {
|
|
fn new(supports_eval: bool) -> Self {
|
|
Self { supports_eval }
|
|
}
|
|
}
|
|
|
|
impl BrowserBackend for FakeBrowserBackend {
|
|
fn invoke(
|
|
&self,
|
|
_action: Action,
|
|
_params: serde_json::Value,
|
|
_expected_domain: &str,
|
|
) -> Result<CommandOutput, sgclaw::pipe::PipeError> {
|
|
panic!("invoke should not be called in this capability-gating test")
|
|
}
|
|
|
|
fn surface_metadata(&self) -> ExecutionSurfaceMetadata {
|
|
ExecutionSurfaceMetadata::privileged_browser_pipe("fake_backend")
|
|
}
|
|
|
|
fn supports_eval(&self) -> bool {
|
|
self.supports_eval
|
|
}
|
|
}
|
|
|
|
fn unique_temp_dir(prefix: &str) -> PathBuf {
|
|
let nanos = SystemTime::now()
|
|
.duration_since(UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_nanos();
|
|
let path = std::env::temp_dir().join(format!("{prefix}-{nanos}"));
|
|
fs::create_dir_all(&path).unwrap();
|
|
path
|
|
}
|