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>
This commit is contained in:
111
src/service/mod.rs
Normal file
111
src/service/mod.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user