merge: integrate main deterministic submit into ws branch

Keep the ws submit path while bringing over main's deterministic lineloss routing and the focused merge verification updates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
木炎
2026-04-12 14:05:55 +08:00
14 changed files with 3278 additions and 118 deletions

View File

@@ -1,10 +1,11 @@
use std::path::Path;
use std::sync::Arc;
use reqwest::Url;
use serde_json::{Map, Value};
use zeroclaw::skills::load_skills_from_directory;
use zeroclaw::skills::{load_skills_from_directory, SkillTool};
use crate::browser::PipeBrowserBackend;
use crate::browser::{BrowserBackend, PipeBrowserBackend};
use crate::compat::browser_script_skill_tool::execute_browser_script_tool;
use crate::compat::config_adapter::resolve_skills_dir_from_sgclaw_settings;
use crate::compat::runtime::CompatTaskContext;
@@ -30,13 +31,96 @@ pub fn execute_direct_submit_skill<T: Transport + 'static>(
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| PipeError::Protocol("direct submit skill is not configured".to_string()))?;
let (skill_name, tool_name) = parse_configured_tool_name(configured_tool)?;
let expected_domain = derive_expected_domain(task_context)?;
let period = derive_period(instruction)?;
let mut args = Map::new();
args.insert("expected_domain".to_string(), Value::String(expected_domain));
args.insert("period".to_string(), Value::String(period));
let output = execute_browser_script_skill_raw_output(
browser_tool,
configured_tool,
workspace_root,
settings,
args,
)?;
Ok(interpret_direct_submit_output(&output))
}
pub fn execute_direct_submit_skill_with_browser_backend(
browser_backend: Arc<dyn BrowserBackend>,
instruction: &str,
task_context: &CompatTaskContext,
workspace_root: &Path,
settings: &SgClawSettings,
) -> Result<DirectSubmitOutcome, PipeError> {
let configured_tool = settings
.direct_submit_skill
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| PipeError::Protocol("direct submit skill is not configured".to_string()))?;
let expected_domain = derive_expected_domain(task_context)?;
let period = derive_period(instruction)?;
let mut args = Map::new();
args.insert("expected_domain".to_string(), Value::String(expected_domain));
args.insert("period".to_string(), Value::String(period));
let output = execute_browser_script_skill_raw_output_with_browser_backend(
browser_backend,
configured_tool,
workspace_root,
settings,
args,
)?;
Ok(interpret_direct_submit_output(&output))
}
pub fn execute_browser_script_skill_raw_output<T: Transport + 'static>(
browser_tool: BrowserPipeTool<T>,
configured_tool: &str,
workspace_root: &Path,
settings: &SgClawSettings,
args: Map<String, Value>,
) -> Result<String, PipeError> {
let (tool, skill_root) = resolve_browser_script_skill(configured_tool, workspace_root, settings)?;
execute_browser_script_tool_output(browser_tool, configured_tool, &tool, &skill_root, args)
}
pub fn execute_browser_script_skill_raw_output_with_browser_backend(
browser_backend: Arc<dyn BrowserBackend>,
configured_tool: &str,
workspace_root: &Path,
settings: &SgClawSettings,
args: Map<String, Value>,
) -> Result<String, PipeError> {
let (tool, skill_root) =
resolve_browser_script_skill(configured_tool, workspace_root, settings)?;
execute_browser_script_tool_output_with_backend(
browser_backend.as_ref(),
configured_tool,
&tool,
&skill_root,
args,
)
}
fn resolve_browser_script_skill(
configured_tool: &str,
workspace_root: &Path,
settings: &SgClawSettings,
) -> Result<(SkillTool, std::path::PathBuf), PipeError> {
let (skill_name, tool_name) = parse_configured_tool_name(configured_tool)?;
let skills_dir = resolve_skills_dir_from_sgclaw_settings(workspace_root, settings);
let skills = load_skills_from_directory(&skills_dir, true);
let skill = skills
.iter()
.into_iter()
.find(|skill| skill.name == skill_name)
.ok_or_else(|| {
PipeError::Protocol(format!(
@@ -44,16 +128,54 @@ pub fn execute_direct_submit_skill<T: Transport + 'static>(
skills_dir.display()
))
})?;
let skill_root = skill
.location
.as_deref()
.and_then(Path::parent)
.map(Path::to_path_buf)
.ok_or_else(|| {
PipeError::Protocol(format!(
"direct submit skill {skill_name} is missing a resolvable location"
))
})?;
let tool = skill
.tools
.iter()
.find(|tool| tool.name == tool_name)
.cloned()
.ok_or_else(|| {
PipeError::Protocol(format!(
"direct submit tool {configured_tool} was not found"
))
})?;
Ok((tool, skill_root))
}
fn execute_browser_script_tool_output<T: Transport + 'static>(
browser_tool: BrowserPipeTool<T>,
configured_tool: &str,
tool: &SkillTool,
skill_root: &Path,
args: Map<String, Value>,
) -> Result<String, PipeError> {
let browser_backend = PipeBrowserBackend::from_inner(browser_tool);
execute_browser_script_tool_output_with_backend(
&browser_backend,
configured_tool,
tool,
skill_root,
args,
)
}
fn execute_browser_script_tool_output_with_backend(
browser_backend: &dyn BrowserBackend,
configured_tool: &str,
tool: &SkillTool,
skill_root: &Path,
args: Map<String, Value>,
) -> Result<String, PipeError> {
if tool.kind != "browser_script" {
return Err(PipeError::Protocol(format!(
"direct submit tool {configured_tool} must be browser_script, got {}",
@@ -61,34 +183,22 @@ pub fn execute_direct_submit_skill<T: Transport + 'static>(
)));
}
let skill_root = skill
.location
.as_deref()
.and_then(Path::parent)
.ok_or_else(|| {
PipeError::Protocol(format!(
"direct submit skill {skill_name} is missing a resolvable location"
))
})?;
let mut args = Map::new();
args.insert("expected_domain".to_string(), Value::String(expected_domain));
args.insert("period".to_string(), Value::String(period));
let mut tool = tool.clone();
tool.args.remove("expected_domain");
let runtime = tokio::runtime::Runtime::new()
.map_err(|err| PipeError::Protocol(format!("failed to create tokio runtime: {err}")))?;
let browser_backend = PipeBrowserBackend::from_inner(browser_tool);
let result = runtime
.block_on(execute_browser_script_tool(
tool,
&tool,
skill_root,
&browser_backend,
browser_backend,
Value::Object(args),
))
.map_err(|err| PipeError::Protocol(err.to_string()))?;
if result.success {
Ok(interpret_direct_submit_output(&result.output))
Ok(result.output)
} else {
Err(PipeError::Protocol(
result