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:
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user