feat: add browser script skill execution
This commit is contained in:
33
third_party/zeroclaw/src/skills/mod.rs
vendored
33
third_party/zeroclaw/src/skills/mod.rs
vendored
@@ -810,18 +810,18 @@ pub fn skills_to_prompt_with_mode(
|
||||
}
|
||||
|
||||
if !skill.tools.is_empty() {
|
||||
// Tools with known kinds (shell, script, http) are registered as
|
||||
// Tools with known kinds (shell, script, http, browser_script) are registered as
|
||||
// callable tool specs and can be invoked directly via function calling.
|
||||
// We note them here for context but mark them as callable.
|
||||
let registered: Vec<_> = skill
|
||||
.tools
|
||||
.iter()
|
||||
.filter(|t| matches!(t.kind.as_str(), "shell" | "script" | "http"))
|
||||
.filter(|t| matches!(t.kind.as_str(), "shell" | "script" | "http" | "browser_script"))
|
||||
.collect();
|
||||
let unregistered: Vec<_> = skill
|
||||
.tools
|
||||
.iter()
|
||||
.filter(|t| !matches!(t.kind.as_str(), "shell" | "script" | "http"))
|
||||
.filter(|t| !matches!(t.kind.as_str(), "shell" | "script" | "http" | "browser_script"))
|
||||
.collect();
|
||||
|
||||
if !registered.is_empty() {
|
||||
@@ -887,6 +887,7 @@ pub fn skills_to_tools(
|
||||
tool,
|
||||
)));
|
||||
}
|
||||
"browser_script" => {}
|
||||
other => {
|
||||
tracing::warn!(
|
||||
"Unknown skill tool kind '{}' for {}.{}, skipping",
|
||||
@@ -1900,6 +1901,32 @@ description = "Bare minimum"
|
||||
assert!(prompt.contains("<description>Fetch forecast</description>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skills_to_prompt_marks_browser_script_tools_as_callable() {
|
||||
let skills = vec![Skill {
|
||||
name: "zhihu-hotlist".to_string(),
|
||||
description: "Collect hotlist rows".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 from the current page".to_string(),
|
||||
kind: "browser_script".to_string(),
|
||||
command: "scripts/extract_hotlist.js".to_string(),
|
||||
args: HashMap::new(),
|
||||
}],
|
||||
prompts: vec![],
|
||||
location: None,
|
||||
}];
|
||||
|
||||
let prompt = skills_to_prompt(&skills, Path::new("/tmp"));
|
||||
|
||||
assert!(prompt.contains("<callable_tools"));
|
||||
assert!(prompt.contains("<name>zhihu-hotlist.extract_hotlist</name>"));
|
||||
assert!(!prompt.contains("<kind>browser_script</kind>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skills_to_prompt_escapes_xml_content() {
|
||||
let skills = vec![Skill {
|
||||
|
||||
17
third_party/zeroclaw/src/tools/read_skill.rs
vendored
17
third_party/zeroclaw/src/tools/read_skill.rs
vendored
@@ -168,6 +168,23 @@ pub async fn read_skill_bundle(location: &Path) -> std::io::Result<String> {
|
||||
&mut pending,
|
||||
);
|
||||
|
||||
if location.file_name().and_then(|name| name.to_str()) == Some("SKILL.toml") {
|
||||
let sibling_markdown = skill_root.join("SKILL.md");
|
||||
if sibling_markdown.exists() {
|
||||
if let Ok(markdown) = tokio::fs::read_to_string(&sibling_markdown).await {
|
||||
output.push_str("\n\n## Referenced File: SKILL.md\n\n");
|
||||
output.push_str(&markdown);
|
||||
enqueue_reference_paths(
|
||||
&markdown,
|
||||
sibling_markdown.parent().unwrap_or(skill_root.as_path()),
|
||||
&skill_root,
|
||||
&mut queued,
|
||||
&mut pending,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(path) = pending.pop_front() {
|
||||
let canonical = path.canonicalize().unwrap_or(path.clone());
|
||||
if !canonical.starts_with(&skill_root) || !appended.insert(canonical.clone()) {
|
||||
|
||||
Reference in New Issue
Block a user