Files
claw/src/agent/mod.rs
木炎 883647dffc feat: add config-owned direct submit runtime
Keep browser-attached workflows on the configured direct-skill path and align the Zhihu export/browser regression contracts with the current ws merge state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 15:45:42 +08:00

154 lines
5.3 KiB
Rust

pub mod task_runner;
use std::sync::Arc;
use crate::browser::ws_backend::WsBrowserBackend;
use crate::browser::{BrowserBackend, PipeBrowserBackend};
use crate::pipe::{BrowserMessage, BrowserPipeTool, PipeError, Transport};
pub use task_runner::{
run_submit_task, run_submit_task_with_browser_backend, AgentEventSink, AgentRuntimeContext,
SubmitTaskRequest,
};
fn normalize_optional_submit_field(value: String) -> Option<String> {
let trimmed = value.trim();
(!trimmed.is_empty()).then(|| trimmed.to_string())
}
fn browser_backend_for_submit<T: Transport + 'static>(
browser_tool: &BrowserPipeTool<T>,
context: &AgentRuntimeContext,
request: &SubmitTaskRequest,
) -> Result<Arc<dyn BrowserBackend>, PipeError> {
if let Some(browser_ws_url) = configured_browser_ws_url(context) {
return Ok(Arc::new(
WsBrowserBackend::new(
Arc::new(crate::service::browser_ws_client::ServiceWsClient::connect(
&browser_ws_url,
)?),
browser_tool.mac_policy().clone(),
crate::service::browser_ws_client::initial_request_url_for_submit_task(request),
)
.with_response_timeout(browser_tool.response_timeout()),
));
}
Ok(Arc::new(PipeBrowserBackend::from_inner(browser_tool.clone())))
}
fn configured_browser_ws_url(context: &AgentRuntimeContext) -> Option<String> {
std::env::var("SGCLAW_BROWSER_WS_URL")
.ok()
.filter(|value| !value.trim().is_empty())
.or_else(|| {
context
.load_sgclaw_settings()
.ok()
.flatten()
.and_then(|settings| settings.browser_ws_url)
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
})
}
fn send_status_changed<T: Transport>(transport: &T, state: &str) -> Result<(), PipeError> {
transport.send(&crate::pipe::AgentMessage::StatusChanged {
state: state.to_string(),
})
}
pub fn handle_browser_message<T: Transport + 'static>(
transport: &T,
browser_tool: &BrowserPipeTool<T>,
message: BrowserMessage,
) -> Result<(), PipeError> {
handle_browser_message_with_context(
transport,
browser_tool,
&AgentRuntimeContext::default(),
message,
)
}
pub fn handle_browser_message_with_context<T: Transport + 'static>(
transport: &T,
browser_tool: &BrowserPipeTool<T>,
context: &AgentRuntimeContext,
message: BrowserMessage,
) -> Result<(), PipeError> {
match message {
BrowserMessage::Connect => send_status_changed(transport, "connected"),
BrowserMessage::Start => send_status_changed(transport, "started"),
BrowserMessage::Stop => send_status_changed(transport, "stopped"),
BrowserMessage::SubmitTask {
instruction,
conversation_id,
messages,
page_url,
page_title,
} => {
let request = SubmitTaskRequest {
instruction,
conversation_id: normalize_optional_submit_field(conversation_id),
messages,
page_url: normalize_optional_submit_field(page_url),
page_title: normalize_optional_submit_field(page_title),
};
if configured_browser_ws_url(context).is_some() {
let browser_backend = browser_backend_for_submit(browser_tool, context, &request)?;
run_submit_task_with_browser_backend(
transport,
transport,
browser_backend,
context,
request,
)
} else {
run_submit_task(transport, transport, browser_tool, context, request)
}
}
BrowserMessage::Init { .. } => {
eprintln!("ignoring duplicate init after handshake");
Ok(())
}
BrowserMessage::Response { seq, .. } => {
eprintln!("ignoring unsolicited response: seq={seq}");
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::normalize_optional_submit_field;
use std::fs;
use std::path::PathBuf;
#[test]
fn normalize_optional_submit_field_trims_and_drops_blank_values() {
assert_eq!(normalize_optional_submit_field(" \n\t ".to_string()), None);
assert_eq!(
normalize_optional_submit_field(" https://example.com/page ".to_string()),
Some("https://example.com/page".to_string())
);
}
#[test]
fn agent_module_cleanup_removes_legacy_runtime_and_planner_sources() {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let agent_module = fs::read_to_string(manifest_dir.join("src/agent/mod.rs")).unwrap();
let top_lines = agent_module
.lines()
.take(10)
.map(str::trim)
.collect::<Vec<_>>();
assert!(!manifest_dir.join("src/agent/runtime.rs").exists());
assert!(!manifest_dir.join("src/agent/planner.rs").exists());
assert!(!top_lines.iter().any(|line| *line == "pub mod runtime;"));
assert!(!top_lines.iter().any(|line| *line == "pub mod planner;"));
assert!(top_lines.iter().any(|line| *line == "pub mod task_runner;"));
}
}