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.
```
This commit is contained in:
Mohamed Boudra
2025-10-22 20:08:40 +02:00
parent ce07b54920
commit eb0d04904e
3 changed files with 29 additions and 34 deletions
+6 -1
View File
@@ -9,6 +9,7 @@ import {
listConversations, listConversations,
deleteConversation, deleteConversation,
} from "./persistence.js"; } from "./persistence.js";
import { AgentManager } from "./acp/agent-manager.js";
function createServer() { function createServer() {
const app = express(); const app = express();
@@ -64,8 +65,12 @@ function main() {
const app = createServer(); const app = createServer();
const httpServer = createHTTPServer(app); const httpServer = createHTTPServer(app);
// Initialize global agent manager
const agentManager = new AgentManager();
console.log("✓ Global agent manager initialized");
// Initialize WebSocket server // Initialize WebSocket server
const wsServer = new VoiceAssistantWebSocketServer(httpServer); const wsServer = new VoiceAssistantWebSocketServer(httpServer, agentManager);
// Initialize OpenAI client // Initialize OpenAI client
const apiKey = process.env.OPENAI_API_KEY; const apiKey = process.env.OPENAI_API_KEY;
+15 -32
View File
@@ -76,12 +76,13 @@ export class Session {
private terminalManager: any | null = null; private terminalManager: any | null = null;
private agentMcpClient: Awaited<ReturnType<typeof experimental_createMCPClient>> | null = null; private agentMcpClient: Awaited<ReturnType<typeof experimental_createMCPClient>> | null = null;
private agentTools: Record<string, any> | null = null; private agentTools: Record<string, any> | null = null;
private agentManager: AgentManager | null = null; private agentManager: AgentManager;
private agentUpdateUnsubscribers: Map<string, () => void> = new Map(); private agentUpdateUnsubscribers: Map<string, () => void> = new Map();
constructor( constructor(
clientId: string, clientId: string,
onMessage: (msg: SessionOutboundMessage) => void, onMessage: (msg: SessionOutboundMessage) => void,
agentManager: AgentManager,
options?: { options?: {
conversationId?: string; conversationId?: string;
initialMessages?: ModelMessage[]; initialMessages?: ModelMessage[];
@@ -90,6 +91,7 @@ export class Session {
this.clientId = clientId; this.clientId = clientId;
this.conversationId = options?.conversationId || uuidv4(); this.conversationId = options?.conversationId || uuidv4();
this.onMessage = onMessage; this.onMessage = onMessage;
this.agentManager = agentManager;
this.abortController = new AbortController(); this.abortController = new AbortController();
// Initialize conversation history // Initialize conversation history
@@ -122,6 +124,13 @@ export class Session {
return this.conversationId; return this.conversationId;
} }
/**
* Send initial state to client after connection
*/
public async sendInitialState(): Promise<void> {
await this.sendSessionState();
}
/** /**
* Subscribe to updates from an agent * Subscribe to updates from an agent
*/ */
@@ -236,10 +245,7 @@ export class Session {
*/ */
private async initializeAgentMcp(): Promise<void> { private async initializeAgentMcp(): Promise<void> {
try { try {
// Create AgentManager instance // Create Agent MCP server with the global manager
this.agentManager = new AgentManager();
// Create Agent MCP server with the manager
const server = await createAgentMcpServer({ const server = await createAgentMcpServer({
agentManager: this.agentManager, agentManager: this.agentManager,
}); });
@@ -1164,33 +1170,10 @@ export class Session {
this.ttsManager.cleanup(); this.ttsManager.cleanup();
this.sttManager.cleanup(); this.sttManager.cleanup();
// Kill all agents // Unsubscribe from all agent updates (agents remain global and persist)
if (this.agentManager) { console.log(
try { `[Session ${this.clientId}] Unsubscribing from ${this.agentUpdateUnsubscribers.size} agent(s)`
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
for (const [agentId, unsubscribe] of this.agentUpdateUnsubscribers) { for (const [agentId, unsubscribe] of this.agentUpdateUnsubscribers) {
try { try {
unsubscribe(); unsubscribe();
@@ -9,6 +9,7 @@ import {
} from "./messages.js"; } from "./messages.js";
import { Session } from "./session.js"; import { Session } from "./session.js";
import { loadConversation } from "./persistence.js"; import { loadConversation } from "./persistence.js";
import { AgentManager } from "./acp/agent-manager.js";
/** /**
* WebSocket server that routes messages between clients and their sessions. * WebSocket server that routes messages between clients and their sessions.
@@ -19,8 +20,10 @@ export class VoiceAssistantWebSocketServer {
private sessions: Map<WebSocket, Session> = new Map(); private sessions: Map<WebSocket, Session> = new Map();
private conversationIdToWs: Map<string, WebSocket> = new Map(); private conversationIdToWs: Map<string, WebSocket> = new Map();
private clientIdCounter: number = 0; 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 = new WebSocketServer({ server, path: "/ws" });
this.wss.on("connection", (ws, request) => { this.wss.on("connection", (ws, request) => {
@@ -66,6 +69,7 @@ export class VoiceAssistantWebSocketServer {
(msg) => { (msg) => {
this.sendToClient(ws, wrapSessionMessage(msg)); this.sendToClient(ws, wrapSessionMessage(msg));
}, },
this.agentManager,
{ {
conversationId, conversationId,
initialMessages: initialMessages || undefined, 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 // Set up message handler
ws.on("message", (data) => { ws.on("message", (data) => {
this.handleMessage(ws, data); this.handleMessage(ws, data);