feat: route staged scene skills through runtime

Add registry-driven scene routing and multi-root skill loading so fault-details and 95598 scene skills can be triggered from natural language while still running through the browser-backed runtime.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
木炎
2026-04-07 16:17:17 +08:00
parent bdf8e12246
commit 96c3bf1dee
21 changed files with 2846 additions and 240 deletions

View File

@@ -1,5 +1,73 @@
use std::fs;
use std::path::PathBuf;
use sgclaw::compat::config_adapter::{
build_zeroclaw_config_from_sgclaw_settings, resolve_skills_dir_from_sgclaw_settings,
};
use sgclaw::config::{BrowserBackend, OfficeBackend, PlannerMode, SgClawSettings};
use sgclaw::runtime::{RuntimeEngine, RuntimeProfile, ToolPolicy};
use uuid::Uuid;
fn temp_skill_root() -> PathBuf {
let root = std::env::temp_dir().join(format!(
"sgclaw-runtime-profile-skills-{}",
Uuid::new_v4()
));
fs::create_dir_all(root.join("skills")).unwrap();
root
}
fn write_browser_script_skill(skill_root: &std::path::Path, skill_name: &str) {
let skill_dir = skill_root.join("skills").join(skill_name);
fs::create_dir_all(&skill_dir).unwrap();
fs::write(
skill_dir.join("SKILL.toml"),
format!(
r#"
[skill]
name = "{skill_name}"
description = "Browser-only test skill."
version = "0.1.0"
[[tools]]
name = "run"
description = "Run browser-only script."
kind = "browser_script"
command = "scripts/run.js"
"#
),
)
.unwrap();
fs::create_dir_all(skill_dir.join("scripts")).unwrap();
fs::write(skill_dir.join("scripts/run.js"), "return { ok: true };\n").unwrap();
}
#[test]
fn loaded_skills_excludes_browser_script_tools_when_browser_surface_is_unavailable() {
let workspace_root = std::env::temp_dir().join(format!(
"sgclaw-runtime-profile-workspace-{}",
Uuid::new_v4()
));
fs::create_dir_all(&workspace_root).unwrap();
let skill_root = temp_skill_root();
write_browser_script_skill(&skill_root, "fault-details-report");
let mut settings = SgClawSettings::from_legacy_deepseek_fields(
"sk-test".to_string(),
"https://api.deepseek.com".to_string(),
"deepseek-chat".to_string(),
vec![skill_root.clone()],
)
.unwrap();
settings.runtime_profile = RuntimeProfile::GeneralAssistant;
let config = build_zeroclaw_config_from_sgclaw_settings(&workspace_root, &settings);
let skills_dir = resolve_skills_dir_from_sgclaw_settings(&workspace_root, &settings);
let engine = RuntimeEngine::new(RuntimeProfile::GeneralAssistant);
let loaded_skills = engine.loaded_skills(&config, &skills_dir);
assert!(loaded_skills.is_empty());
}
#[test]
fn browser_attached_profile_exposes_browser_surface_without_becoming_browser_only() {
@@ -56,13 +124,61 @@ fn browser_attached_publish_prompt_requires_explicit_confirmation_before_clickin
assert!(instruction.contains("stop after the confirmation request"));
}
#[test]
fn browser_attached_95598_scene_prompt_requires_scene_tool_before_generic_browser_probing() {
let engine = RuntimeEngine::new(RuntimeProfile::BrowserAttached);
let instruction = engine.build_instruction(
"请处理95598-repair-city-dispatch场景查看抢修市指派单并汇总当前队列",
Some("https://95598.example.invalid/dispatch"),
Some("95598抢修市指监测"),
true,
);
assert!(instruction.contains("95598-repair-city-dispatch.collect_repair_orders"));
assert!(instruction.contains("browser workflow, not a text-only task"));
assert!(instruction.contains("generic browser probing only after"));
}
#[test]
fn browser_attached_unrelated_task_does_not_receive_95598_scene_contract() {
let engine = RuntimeEngine::new(RuntimeProfile::BrowserAttached);
let instruction = engine.build_instruction(
"帮我总结今天的会议纪要",
None,
None,
true,
);
assert!(!instruction.contains("95598-repair-city-dispatch.collect_repair_orders"));
assert!(!instruction.contains("browser workflow, not a text-only task"));
assert!(!instruction.contains("generic browser probing only after"));
}
#[test]
fn general_assistant_95598_scene_prompt_does_not_receive_browser_scene_contract() {
let engine = RuntimeEngine::new(RuntimeProfile::GeneralAssistant);
let instruction = engine.build_instruction(
"请处理95598-repair-city-dispatch场景查看抢修市指派单并汇总当前队列",
Some("https://95598.example.invalid/dispatch"),
Some("95598抢修市指监测"),
false,
);
assert!(!instruction.contains("95598-repair-city-dispatch.collect_repair_orders"));
assert!(!instruction.contains("browser workflow, not a text-only task"));
assert!(!instruction.contains("generic browser probing only after"));
}
#[test]
fn legacy_settings_default_to_plan_first_superrpa_and_openxml_backends() {
let settings = SgClawSettings::from_legacy_deepseek_fields(
"sk-test".to_string(),
"https://api.deepseek.com".to_string(),
"deepseek-chat".to_string(),
None,
Vec::new(),
)
.unwrap();