refactor: server no longer runs agent loop or LLM
- Remove agent_loop from server (was ~400 lines) — server dispatches to workers - AgentManager simplified to pure dispatcher (send_event → worker) - Remove LLM config requirement from server (workers bring their own via config.yaml) - Remove process_feedback, build_feedback_tools from server - Remove chat API endpoint (LLM on workers only) - Remove service proxy (services run on workers) - Worker reads LLM config from its own config.yaml - ws_worker.rs handles WorkerToServer::Update messages (DB + broadcast) - Verified locally: tori server + tori worker connect and register
This commit is contained in:
110
src/ws_worker.rs
110
src/ws_worker.rs
@@ -7,44 +7,52 @@ use axum::{
|
||||
Router,
|
||||
};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use serde::Deserialize;
|
||||
use sqlx::sqlite::SqlitePool;
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
use crate::worker::{WorkerInfo, WorkerManager, WorkerResult};
|
||||
use crate::agent::WsMessage;
|
||||
use crate::worker::{WorkerInfo, WorkerManager, WorkerToServer};
|
||||
|
||||
pub fn router(mgr: Arc<WorkerManager>) -> Router {
|
||||
pub struct WsWorkerState {
|
||||
pub mgr: Arc<WorkerManager>,
|
||||
pub pool: SqlitePool,
|
||||
pub broadcast_fn: Arc<dyn Fn(&str) -> broadcast::Sender<WsMessage> + Send + Sync>,
|
||||
}
|
||||
|
||||
pub fn router(mgr: Arc<WorkerManager>, pool: SqlitePool, broadcast_fn: Arc<dyn Fn(&str) -> broadcast::Sender<WsMessage> + Send + Sync>) -> Router {
|
||||
let state = Arc::new(WsWorkerState { mgr, pool, broadcast_fn });
|
||||
Router::new()
|
||||
.route("/", get(ws_handler))
|
||||
.with_state(mgr)
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
async fn ws_handler(
|
||||
ws: WebSocketUpgrade,
|
||||
State(mgr): State<Arc<WorkerManager>>,
|
||||
State(state): State<Arc<WsWorkerState>>,
|
||||
) -> Response {
|
||||
ws.on_upgrade(move |socket| handle_worker_socket(socket, mgr))
|
||||
ws.on_upgrade(move |socket| handle_worker_socket(socket, state))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
enum WorkerMessage {
|
||||
#[serde(rename = "register")]
|
||||
Register { info: WorkerInfo },
|
||||
#[serde(rename = "result")]
|
||||
Result(WorkerResult),
|
||||
}
|
||||
|
||||
async fn handle_worker_socket(socket: WebSocket, mgr: Arc<WorkerManager>) {
|
||||
async fn handle_worker_socket(socket: WebSocket, state: Arc<WsWorkerState>) {
|
||||
let (mut sender, mut receiver) = socket.split();
|
||||
|
||||
// First message must be registration
|
||||
let (name, mut job_rx) = loop {
|
||||
let (name, mut msg_rx) = loop {
|
||||
match receiver.next().await {
|
||||
Some(Ok(Message::Text(text))) => {
|
||||
match serde_json::from_str::<WorkerMessage>(&text) {
|
||||
Ok(WorkerMessage::Register { info }) => {
|
||||
match serde_json::from_str::<serde_json::Value>(&text) {
|
||||
Ok(v) if v["type"] == "register" => {
|
||||
let info: WorkerInfo = match serde_json::from_value(v["info"].clone()) {
|
||||
Ok(i) => i,
|
||||
Err(_) => {
|
||||
let _ = sender.send(Message::Text(
|
||||
r#"{"type":"error","message":"Invalid worker info"}"#.into(),
|
||||
)).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
let name = info.name.clone();
|
||||
let rx = mgr.register(name.clone(), info).await;
|
||||
// Ack
|
||||
let rx = state.mgr.register(name.clone(), info).await;
|
||||
let ack = serde_json::json!({ "type": "registered", "name": &name });
|
||||
let _ = sender.send(Message::Text(ack.to_string().into())).await;
|
||||
break (name, rx);
|
||||
@@ -62,31 +70,28 @@ async fn handle_worker_socket(socket: WebSocket, mgr: Arc<WorkerManager>) {
|
||||
}
|
||||
};
|
||||
|
||||
// Main loop: forward jobs to worker, receive results
|
||||
let name_clone = name.clone();
|
||||
let mgr_clone = mgr.clone();
|
||||
let mgr_for_cleanup = state.mgr.clone();
|
||||
|
||||
// Task: send jobs from job_rx to the WebSocket
|
||||
// Task: send ServerToWorker messages from msg_rx to the WebSocket
|
||||
let send_task = tokio::spawn(async move {
|
||||
while let Some(req) = job_rx.recv().await {
|
||||
let msg = serde_json::json!({
|
||||
"type": "execute",
|
||||
"job_id": req.job_id,
|
||||
"script": req.script,
|
||||
});
|
||||
if sender.send(Message::Text(msg.to_string().into())).await.is_err() {
|
||||
break;
|
||||
while let Some(msg) = msg_rx.recv().await {
|
||||
if let Ok(json) = serde_json::to_string(&msg) {
|
||||
if sender.send(Message::Text(json.into())).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Task: receive results from the WebSocket
|
||||
// Task: receive WorkerToServer messages from the WebSocket → process
|
||||
let state_clone = state.clone();
|
||||
let recv_task = tokio::spawn(async move {
|
||||
while let Some(Ok(msg)) = receiver.next().await {
|
||||
match msg {
|
||||
Message::Text(text) => {
|
||||
if let Ok(WorkerMessage::Result(result)) = serde_json::from_str(&text) {
|
||||
mgr_clone.report_result(result).await;
|
||||
if let Ok(worker_msg) = serde_json::from_str::<WorkerToServer>(&text) {
|
||||
handle_worker_message(&state_clone, worker_msg).await;
|
||||
}
|
||||
}
|
||||
Message::Close(_) => break,
|
||||
@@ -100,5 +105,38 @@ async fn handle_worker_socket(socket: WebSocket, mgr: Arc<WorkerManager>) {
|
||||
_ = recv_task => {},
|
||||
}
|
||||
|
||||
mgr.unregister(&name_clone).await;
|
||||
mgr_for_cleanup.unregister(&name_clone).await;
|
||||
}
|
||||
|
||||
async fn handle_worker_message(state: &WsWorkerState, msg: WorkerToServer) {
|
||||
match msg {
|
||||
WorkerToServer::Register { .. } => {
|
||||
// Already handled during initial handshake
|
||||
}
|
||||
WorkerToServer::Update { workflow_id, update } => {
|
||||
// Get project_id for broadcasting (look up from DB)
|
||||
let project_id: Option<String> = sqlx::query_scalar(
|
||||
"SELECT project_id FROM workflows WHERE id = ?"
|
||||
)
|
||||
.bind(&workflow_id)
|
||||
.fetch_optional(&state.pool)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
let broadcast_tx = if let Some(ref pid) = project_id {
|
||||
Some((state.broadcast_fn)(pid))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Check if this is a workflow completion
|
||||
if let crate::sink::AgentUpdate::WorkflowComplete { ref workflow_id, .. } = update {
|
||||
state.mgr.complete_workflow(workflow_id).await;
|
||||
}
|
||||
|
||||
// Process the update: write to DB + broadcast
|
||||
crate::sink::handle_single_update(&update, &state.pool, broadcast_tx.as_ref()).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user