From eb0d04904e93d3274af60b0ba0c149c9b3001cb8 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Wed, 22 Oct 2025 20:08:40 +0200 Subject: [PATCH] Based on the changes, here's the commit message: ``` refactor: convert AgentManager to global singleton across sessions Move AgentManager initialization from per-session to single global instance. Agents now persist across sessions, with sessions subscribing/unsubscribing to agent updates rather than creating/destroying agents. ``` --- packages/server/src/server/index.ts | 7 ++- packages/server/src/server/session.ts | 47 ++++++------------- .../server/src/server/websocket-server.ts | 9 +++- 3 files changed, 29 insertions(+), 34 deletions(-) diff --git a/packages/server/src/server/index.ts b/packages/server/src/server/index.ts index b92d15c23..a9479604d 100644 --- a/packages/server/src/server/index.ts +++ b/packages/server/src/server/index.ts @@ -9,6 +9,7 @@ import { listConversations, deleteConversation, } from "./persistence.js"; +import { AgentManager } from "./acp/agent-manager.js"; function createServer() { const app = express(); @@ -64,8 +65,12 @@ function main() { const app = createServer(); const httpServer = createHTTPServer(app); + // Initialize global agent manager + const agentManager = new AgentManager(); + console.log("✓ Global agent manager initialized"); + // Initialize WebSocket server - const wsServer = new VoiceAssistantWebSocketServer(httpServer); + const wsServer = new VoiceAssistantWebSocketServer(httpServer, agentManager); // Initialize OpenAI client const apiKey = process.env.OPENAI_API_KEY; diff --git a/packages/server/src/server/session.ts b/packages/server/src/server/session.ts index e74b3796f..c7f0b5658 100644 --- a/packages/server/src/server/session.ts +++ b/packages/server/src/server/session.ts @@ -76,12 +76,13 @@ export class Session { private terminalManager: any | null = null; private agentMcpClient: Awaited> | null = null; private agentTools: Record | null = null; - private agentManager: AgentManager | null = null; + private agentManager: AgentManager; private agentUpdateUnsubscribers: Map void> = new Map(); constructor( clientId: string, onMessage: (msg: SessionOutboundMessage) => void, + agentManager: AgentManager, options?: { conversationId?: string; initialMessages?: ModelMessage[]; @@ -90,6 +91,7 @@ export class Session { this.clientId = clientId; this.conversationId = options?.conversationId || uuidv4(); this.onMessage = onMessage; + this.agentManager = agentManager; this.abortController = new AbortController(); // Initialize conversation history @@ -122,6 +124,13 @@ export class Session { return this.conversationId; } + /** + * Send initial state to client after connection + */ + public async sendInitialState(): Promise { + await this.sendSessionState(); + } + /** * Subscribe to updates from an agent */ @@ -236,10 +245,7 @@ export class Session { */ private async initializeAgentMcp(): Promise { try { - // Create AgentManager instance - this.agentManager = new AgentManager(); - - // Create Agent MCP server with the manager + // Create Agent MCP server with the global manager const server = await createAgentMcpServer({ agentManager: this.agentManager, }); @@ -1164,33 +1170,10 @@ export class Session { this.ttsManager.cleanup(); this.sttManager.cleanup(); - // Kill all agents - if (this.agentManager) { - try { - const agents = this.agentManager.listAgents(); - console.log( - `[Session ${this.clientId}] Killing ${agents.length} agents` - ); - for (const agent of agents) { - try { - await this.agentManager.killAgent(agent.id); - console.log(`[Session ${this.clientId}] Killed agent ${agent.id}`); - } catch (error) { - console.error( - `[Session ${this.clientId}] Failed to kill agent ${agent.id}:`, - error - ); - } - } - } catch (error) { - console.error( - `[Session ${this.clientId}] Error during agent cleanup:`, - error - ); - } - } - - // Unsubscribe from all agent updates + // Unsubscribe from all agent updates (agents remain global and persist) + console.log( + `[Session ${this.clientId}] Unsubscribing from ${this.agentUpdateUnsubscribers.size} agent(s)` + ); for (const [agentId, unsubscribe] of this.agentUpdateUnsubscribers) { try { unsubscribe(); diff --git a/packages/server/src/server/websocket-server.ts b/packages/server/src/server/websocket-server.ts index b5bd96ccf..9bbcad5c8 100644 --- a/packages/server/src/server/websocket-server.ts +++ b/packages/server/src/server/websocket-server.ts @@ -9,6 +9,7 @@ import { } from "./messages.js"; import { Session } from "./session.js"; import { loadConversation } from "./persistence.js"; +import { AgentManager } from "./acp/agent-manager.js"; /** * WebSocket server that routes messages between clients and their sessions. @@ -19,8 +20,10 @@ export class VoiceAssistantWebSocketServer { private sessions: Map = new Map(); private conversationIdToWs: Map = new Map(); private clientIdCounter: number = 0; + private agentManager: AgentManager; - constructor(server: HTTPServer) { + constructor(server: HTTPServer, agentManager: AgentManager) { + this.agentManager = agentManager; this.wss = new WebSocketServer({ server, path: "/ws" }); this.wss.on("connection", (ws, request) => { @@ -66,6 +69,7 @@ export class VoiceAssistantWebSocketServer { (msg) => { this.sendToClient(ws, wrapSessionMessage(msg)); }, + this.agentManager, { conversationId, initialMessages: initialMessages || undefined, @@ -82,6 +86,9 @@ export class VoiceAssistantWebSocketServer { })` ); + // Send initial session state (global agents and commands) + await session.sendInitialState(); + // Set up message handler ws.on("message", (data) => { this.handleMessage(ws, data);