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, pub service_ws_listen_addr: Option, } pub fn load_startup_config( runtime_context: &AgentRuntimeContext, ) -> Result { 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 { 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) }