Files
claw/src/service/mod.rs
木炎 3e18350320 feat: add websocket browser service runtime
Wire the service/browser runtime onto the websocket-driven execution path and add the new browser/service modules needed for the submit flow and runtime integration.

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

112 lines
3.7 KiB
Rust

mod protocol;
pub(crate) mod server;
use std::net::TcpListener;
use std::sync::Arc;
use tungstenite::accept;
use crate::agent::AgentRuntimeContext;
use crate::pipe::PipeError;
use crate::security::MacPolicy;
const DEFAULT_BROWSER_WS_URL: &str = "ws://127.0.0.1:12345";
const DEFAULT_SERVICE_WS_LISTEN_ADDR: &str = "127.0.0.1:42321";
pub use protocol::{ClientMessage, ServiceMessage};
pub use server::{serve_client, ServiceEventSink, ServiceSession};
pub(crate) mod browser_ws_client {
pub(crate) use super::server::{initial_request_url_for_submit_task, ServiceWsClient};
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ServiceStartupConfig {
pub browser_ws_url: Option<String>,
pub service_ws_listen_addr: Option<String>,
}
pub fn load_startup_config(
runtime_context: &AgentRuntimeContext,
) -> Result<ServiceStartupConfig, PipeError> {
let settings = runtime_context
.load_sgclaw_settings()?
.ok_or_else(|| PipeError::Protocol("missing environment variable: DEEPSEEK_API_KEY".to_string()))?;
Ok(ServiceStartupConfig {
browser_ws_url: Some(
settings
.browser_ws_url
.unwrap_or_else(|| DEFAULT_BROWSER_WS_URL.to_string()),
),
service_ws_listen_addr: Some(
settings
.service_ws_listen_addr
.unwrap_or_else(|| DEFAULT_SERVICE_WS_LISTEN_ADDR.to_string()),
),
})
}
pub fn run() -> Result<(), PipeError> {
let runtime_context = AgentRuntimeContext::from_process_args(std::env::args_os())?;
let startup = load_startup_config(&runtime_context)?;
let service_ws_listen_addr = startup
.service_ws_listen_addr
.as_deref()
.unwrap_or(DEFAULT_SERVICE_WS_LISTEN_ADDR);
let browser_ws_url = startup
.browser_ws_url
.as_deref()
.unwrap_or(DEFAULT_BROWSER_WS_URL);
let listener = TcpListener::bind(service_ws_listen_addr)
.map_err(|err| PipeError::Protocol(format!("failed to bind service listener {service_ws_listen_addr}: {err}")))?;
let mac_policy = load_service_mac_policy()?;
let session = ServiceSession::new();
eprintln!(
"sg_claw ready: service_ws_listen_addr={}, browser_ws_url={}",
service_ws_listen_addr,
browser_ws_url,
);
loop {
let (stream, _) = listener.accept()?;
let websocket = accept(stream)
.map_err(|err| PipeError::Protocol(format!("service websocket accept failed: {err}")))?;
let sink = Arc::new(ServiceEventSink::from_websocket(websocket));
match session.try_attach_client() {
Ok(()) => {
let result = serve_client(
&runtime_context,
&session,
sink.clone(),
browser_ws_url,
&mac_policy,
);
session.detach_client();
match result {
Ok(()) | Err(PipeError::PipeClosed) => {}
Err(err) => return Err(err),
}
}
Err(message) => {
sink.send_service_message(message)?;
}
}
}
}
fn load_service_mac_policy() -> Result<MacPolicy, PipeError> {
let current_exe = std::env::current_exe()?;
let candidate = current_exe
.parent()
.map(|dir| dir.join("resources").join("rules.json"))
.unwrap_or_else(|| std::path::PathBuf::from("resources").join("rules.json"));
let path = if candidate.exists() {
candidate
} else {
std::env::current_dir()?.join("resources").join("rules.json")
};
MacPolicy::load_from_path(&path).map_err(PipeError::from)
}