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,
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;
+15 -32
View File
@@ -76,12 +76,13 @@ export class Session {
private terminalManager: any | null = null;
private agentMcpClient: Awaited<ReturnType<typeof experimental_createMCPClient>> | null = null;
private agentTools: Record<string, any> | null = null;
private agentManager: AgentManager | null = null;
private agentManager: AgentManager;
private agentUpdateUnsubscribers: Map<string, () => 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<void> {
await this.sendSessionState();
}
/**
* Subscribe to updates from an agent
*/
@@ -236,10 +245,7 @@ export class Session {
*/
private async initializeAgentMcp(): Promise<void> {
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();
@@ -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<WebSocket, Session> = new Map();
private conversationIdToWs: Map<string, WebSocket> = 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);