dexto 1.1.7 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/agent-registry.json +8 -1
- package/dist/agents/github-agent/README.md +58 -0
- package/dist/agents/github-agent/github-agent.yml +57 -0
- package/dist/api/server.d.ts +2 -2
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +239 -57
- package/dist/api/webhook-subscriber.d.ts +4 -0
- package/dist/api/webhook-subscriber.d.ts.map +1 -1
- package/dist/api/webhook-subscriber.js +15 -0
- package/dist/api/websocket-subscriber.d.ts +5 -0
- package/dist/api/websocket-subscriber.d.ts.map +1 -1
- package/dist/api/websocket-subscriber.js +16 -0
- package/dist/cli/cli.js +1 -1
- package/dist/index.js +2 -2
- package/dist/utils/graceful-shutdown.d.ts +1 -1
- package/dist/utils/graceful-shutdown.d.ts.map +1 -1
- package/dist/utils/graceful-shutdown.js +9 -6
- package/dist/webui/.next/standalone/.next/static/chunks/179-78abc2eacbc41da9.js +1 -0
- package/dist/webui/.next/standalone/{packages/webui/.next/static/chunks/app/page-cf95b233c1df6dcd.js → .next/static/chunks/app/page-1a1b5591a5f62ca5.js} +1 -1
- package/dist/webui/.next/standalone/.next/static/css/045cc65741e38fbd.css +3 -0
- package/dist/webui/.next/standalone/packages/webui/.next/BUILD_ID +1 -1
- package/dist/webui/.next/standalone/packages/webui/.next/app-build-manifest.json +3 -3
- package/dist/webui/.next/standalone/packages/webui/.next/build-manifest.json +2 -2
- package/dist/webui/.next/standalone/packages/webui/.next/prerender-manifest.json +3 -3
- package/dist/webui/.next/standalone/packages/webui/.next/required-server-files.json +1 -1
- package/dist/webui/.next/standalone/packages/webui/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/webui/.next/standalone/packages/webui/.next/server/app/page.js +2 -2
- package/dist/webui/.next/standalone/packages/webui/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/webui/.next/standalone/packages/webui/.next/server/app/playground/page_client-reference-manifest.js +1 -1
- package/dist/webui/.next/standalone/packages/webui/.next/server/pages/500.html +1 -1
- package/dist/webui/.next/standalone/packages/webui/.next/server/server-reference-manifest.json +1 -1
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/179-78abc2eacbc41da9.js +1 -0
- package/dist/webui/.next/standalone/{.next/static/chunks/app/page-cf95b233c1df6dcd.js → packages/webui/.next/static/chunks/app/page-1a1b5591a5f62ca5.js} +1 -1
- package/dist/webui/.next/standalone/packages/webui/.next/static/css/045cc65741e38fbd.css +3 -0
- package/dist/webui/.next/standalone/packages/webui/package.json +1 -1
- package/dist/webui/.next/standalone/packages/webui/server.js +1 -1
- package/dist/webui/.next/static/chunks/179-78abc2eacbc41da9.js +1 -0
- package/dist/webui/.next/static/chunks/app/{page-cf95b233c1df6dcd.js → page-1a1b5591a5f62ca5.js} +1 -1
- package/dist/webui/.next/static/css/045cc65741e38fbd.css +3 -0
- package/dist/webui/package.json +1 -1
- package/package.json +2 -2
- package/dist/webui/.next/standalone/.next/static/chunks/762-8cfe2287ad3f2d16.js +0 -1
- package/dist/webui/.next/standalone/.next/static/css/9cdfb06589a2f6ce.css +0 -3
- package/dist/webui/.next/standalone/packages/webui/.next/static/chunks/762-8cfe2287ad3f2d16.js +0 -1
- package/dist/webui/.next/standalone/packages/webui/.next/static/css/9cdfb06589a2f6ce.css +0 -3
- package/dist/webui/.next/static/chunks/762-8cfe2287ad3f2d16.js +0 -1
- package/dist/webui/.next/static/css/9cdfb06589a2f6ce.css +0 -3
- /package/dist/webui/.next/standalone/.next/static/{s008aheUrlJbknTlTTTmY → _5RTE-15cb-kjZPtF45Oe}/_buildManifest.js +0 -0
- /package/dist/webui/.next/standalone/.next/static/{s008aheUrlJbknTlTTTmY → _5RTE-15cb-kjZPtF45Oe}/_ssgManifest.js +0 -0
- /package/dist/webui/.next/standalone/packages/webui/.next/static/{s008aheUrlJbknTlTTTmY → _5RTE-15cb-kjZPtF45Oe}/_buildManifest.js +0 -0
- /package/dist/webui/.next/standalone/packages/webui/.next/static/{s008aheUrlJbknTlTTTmY → _5RTE-15cb-kjZPtF45Oe}/_ssgManifest.js +0 -0
- /package/dist/webui/.next/static/{s008aheUrlJbknTlTTTmY → _5RTE-15cb-kjZPtF45Oe}/_buildManifest.js +0 -0
- /package/dist/webui/.next/static/{s008aheUrlJbknTlTTTmY → _5RTE-15cb-kjZPtF45Oe}/_ssgManifest.js +0 -0
package/dist/api/server.js
CHANGED
|
@@ -6,7 +6,7 @@ import { WebhookEventSubscriber } from './webhook-subscriber.js';
|
|
|
6
6
|
import { logger, redactSensitiveData } from '@dexto/core';
|
|
7
7
|
import { setupA2ARoutes } from './a2a.js';
|
|
8
8
|
import { createMcpTransport, initializeMcpServer, initializeMcpServerApiEndpoints, } from './mcp/mcp_handler.js';
|
|
9
|
-
import { createAgentCard } from '@dexto/core';
|
|
9
|
+
import { createAgentCard, DextoAgent } from '@dexto/core';
|
|
10
10
|
import { stringify as yamlStringify } from 'yaml';
|
|
11
11
|
import os from 'os';
|
|
12
12
|
import { expressRedactionMiddleware } from './middleware/expressRedactionMiddleware.js';
|
|
@@ -19,7 +19,7 @@ import { getProviderKeyStatus, saveProviderApiKey } from '@dexto/core';
|
|
|
19
19
|
import { errorHandler } from './middleware/errorHandler.js';
|
|
20
20
|
import { McpServerConfigSchema } from '@dexto/core';
|
|
21
21
|
import { sendWebSocketError, sendWebSocketValidationError } from './websocket-error-handler.js';
|
|
22
|
-
import { DextoValidationError, ErrorScope, ErrorType, AgentErrorCode } from '@dexto/core';
|
|
22
|
+
import { DextoValidationError, ErrorScope, ErrorType, AgentErrorCode, AgentError, } from '@dexto/core';
|
|
23
23
|
/**
|
|
24
24
|
* Helper function to send JSON response with optional pretty printing
|
|
25
25
|
*/
|
|
@@ -95,27 +95,125 @@ function parseQuery(schema, query) {
|
|
|
95
95
|
return schema.parse(query); // ZodError handled by error middleware
|
|
96
96
|
}
|
|
97
97
|
// TODO: API endpoint names are work in progress and might be refactored/renamed in future versions
|
|
98
|
-
export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
98
|
+
export async function initializeApi(agent, agentCardOverride, listenPort, agentName) {
|
|
99
99
|
const app = express();
|
|
100
|
-
|
|
100
|
+
// Declare before registering shutdown hook to avoid TDZ on signals
|
|
101
|
+
let activeAgent = agent;
|
|
102
|
+
let activeAgentName = agentName || 'default';
|
|
103
|
+
let isSwitchingAgent = false;
|
|
104
|
+
registerGracefulShutdown(() => activeAgent);
|
|
101
105
|
// this will apply middleware to all /api/llm/* routes
|
|
102
106
|
app.use('/api/llm', expressRedactionMiddleware);
|
|
103
107
|
app.use('/api/config.yaml', expressRedactionMiddleware);
|
|
104
108
|
const server = http.createServer(app);
|
|
105
109
|
const wss = new WebSocketServer({ server });
|
|
106
|
-
|
|
110
|
+
logger.info(`Initializing API server with agent: ${activeAgentName}`);
|
|
111
|
+
// Ensure the initial agent is started
|
|
112
|
+
if (!activeAgent.isStarted() && !activeAgent.isStopped()) {
|
|
113
|
+
logger.info('Starting initial agent...');
|
|
114
|
+
await activeAgent.start();
|
|
115
|
+
}
|
|
116
|
+
else if (activeAgent.isStopped()) {
|
|
117
|
+
logger.warn('Initial agent is stopped, this may cause issues');
|
|
118
|
+
}
|
|
107
119
|
const webSubscriber = new WebSocketEventSubscriber(wss);
|
|
108
120
|
logger.info('Setting up API event subscriptions...');
|
|
109
|
-
webSubscriber.subscribe(
|
|
121
|
+
webSubscriber.subscribe(activeAgent.agentEventBus);
|
|
122
|
+
// Initialize webhook subscriber
|
|
123
|
+
const webhookSubscriber = new WebhookEventSubscriber();
|
|
124
|
+
logger.info('Setting up webhook event subscriptions...');
|
|
125
|
+
webhookSubscriber.subscribe(activeAgent.agentEventBus);
|
|
110
126
|
// Tool confirmation responses are handled by the main WebSocket handler below
|
|
127
|
+
function ensureAgentAvailable() {
|
|
128
|
+
// Gate requests during agent switching
|
|
129
|
+
if (isSwitchingAgent) {
|
|
130
|
+
throw AgentError.apiValidationError('Agent switch already in progress');
|
|
131
|
+
}
|
|
132
|
+
// Fast path: most common case is agent is started and running
|
|
133
|
+
if (activeAgent.isStarted() && !activeAgent.isStopped()) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
// Provide specific error messages for better debugging
|
|
137
|
+
if (activeAgent.isStopped()) {
|
|
138
|
+
throw AgentError.stopped();
|
|
139
|
+
}
|
|
140
|
+
if (!activeAgent.isStarted()) {
|
|
141
|
+
throw AgentError.notStarted();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function switchAgentByName(name) {
|
|
145
|
+
if (isSwitchingAgent) {
|
|
146
|
+
throw AgentError.apiValidationError('Agent switch already in progress');
|
|
147
|
+
}
|
|
148
|
+
isSwitchingAgent = true;
|
|
149
|
+
let newAgent;
|
|
150
|
+
try {
|
|
151
|
+
// Use domain layer method to create new agent
|
|
152
|
+
newAgent = await DextoAgent.createAgent(name);
|
|
153
|
+
logger.info(`Starting new agent: ${name}`);
|
|
154
|
+
await newAgent.start();
|
|
155
|
+
// Rewire event/webhook subscribers to new agent bus
|
|
156
|
+
logger.info('Rewiring event subscribers...');
|
|
157
|
+
try {
|
|
158
|
+
webSubscriber.unsubscribe();
|
|
159
|
+
}
|
|
160
|
+
catch (_err) {
|
|
161
|
+
logger.debug(`Failed to unsubscribe webSubscriber: ${_err instanceof Error ? _err.message : String(_err)}`);
|
|
162
|
+
}
|
|
163
|
+
webSubscriber.subscribe(newAgent.agentEventBus);
|
|
164
|
+
try {
|
|
165
|
+
webhookSubscriber.unsubscribe();
|
|
166
|
+
}
|
|
167
|
+
catch (_err) {
|
|
168
|
+
logger.debug(`Failed to unsubscribe webhookSubscriber: ${_err instanceof Error ? _err.message : String(_err)}`);
|
|
169
|
+
}
|
|
170
|
+
webhookSubscriber.subscribe(newAgent.agentEventBus);
|
|
171
|
+
// Stop previous agent last (only after new one is fully operational)
|
|
172
|
+
const previousAgent = activeAgent;
|
|
173
|
+
activeAgent = newAgent;
|
|
174
|
+
activeAgentName = name;
|
|
175
|
+
logger.info(`Successfully switched to agent: ${name}`);
|
|
176
|
+
// Now safely stop the previous agent
|
|
177
|
+
try {
|
|
178
|
+
if (previousAgent && previousAgent !== newAgent) {
|
|
179
|
+
logger.info('Stopping previous agent...');
|
|
180
|
+
await previousAgent.stop();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
logger.warn(`Stopping previous agent failed: ${err}`);
|
|
185
|
+
// Don't throw here as the switch was successful
|
|
186
|
+
}
|
|
187
|
+
return { name };
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
logger.error(`Failed to switch to agent '${name}': ${error instanceof Error ? error.message : String(error)}`, { error });
|
|
191
|
+
// Clean up the failed new agent if it was created
|
|
192
|
+
if (newAgent) {
|
|
193
|
+
try {
|
|
194
|
+
await newAgent.stop();
|
|
195
|
+
}
|
|
196
|
+
catch (cleanupErr) {
|
|
197
|
+
logger.warn(`Failed to cleanup new agent: ${cleanupErr}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
isSwitchingAgent = false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
111
206
|
// HTTP endpoints
|
|
112
207
|
// Health check endpoint
|
|
113
208
|
app.get('/health', (req, res) => {
|
|
114
209
|
res.status(200).send('OK');
|
|
115
210
|
});
|
|
116
|
-
|
|
211
|
+
// JSON body size limit for message endpoints supporting base64 image/file payloads
|
|
212
|
+
// Both /api/message and /api/message-sync accept base64 attachments; increased limit to avoid 413s.
|
|
213
|
+
app.post('/api/message', express.json({ limit: process.env.MESSAGE_JSON_LIMIT || '10mb' }), async (req, res, next) => {
|
|
117
214
|
logger.info('Received message via POST /api/message');
|
|
118
215
|
try {
|
|
216
|
+
ensureAgentAvailable();
|
|
119
217
|
const { message, sessionId, stream, imageData, fileData } = parseBody(MessageRequestSchema, req.body);
|
|
120
218
|
const imageDataInput = imageData
|
|
121
219
|
? { image: imageData.base64, mimeType: imageData.mimeType }
|
|
@@ -134,7 +232,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
134
232
|
logger.info('File data included in message.');
|
|
135
233
|
if (sessionId)
|
|
136
234
|
logger.info(`Message for session: ${sessionId}`);
|
|
137
|
-
const response = await
|
|
235
|
+
const response = await activeAgent.run(message || '', imageDataInput, fileDataInput, sessionId, stream || false);
|
|
138
236
|
return res.status(202).send({ response, sessionId });
|
|
139
237
|
}
|
|
140
238
|
catch (error) {
|
|
@@ -145,7 +243,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
145
243
|
app.post('/api/sessions/:sessionId/cancel', async (req, res, next) => {
|
|
146
244
|
try {
|
|
147
245
|
const { sessionId } = parseQuery(CancelRequestSchema, req.params);
|
|
148
|
-
const cancelled = await
|
|
246
|
+
const cancelled = await activeAgent.cancel(sessionId);
|
|
149
247
|
if (!cancelled) {
|
|
150
248
|
logger.debug(`No in-flight run to cancel for session: ${sessionId}`);
|
|
151
249
|
}
|
|
@@ -156,9 +254,11 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
156
254
|
}
|
|
157
255
|
});
|
|
158
256
|
// Synchronous endpoint: await the full AI response and return it in one go
|
|
159
|
-
|
|
257
|
+
// JSON body size limit increased for image/file uploads
|
|
258
|
+
app.post('/api/message-sync', express.json({ limit: process.env.MESSAGE_JSON_LIMIT || '10mb' }), async (req, res, next) => {
|
|
160
259
|
logger.info('Received message via POST /api/message-sync');
|
|
161
260
|
try {
|
|
261
|
+
ensureAgentAvailable();
|
|
162
262
|
const { message, sessionId, imageData, fileData } = parseBody(MessageRequestSchema, req.body);
|
|
163
263
|
// Extract optional image and file data
|
|
164
264
|
const imageDataInput = imageData
|
|
@@ -178,7 +278,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
178
278
|
logger.info('File data included in message.');
|
|
179
279
|
if (sessionId)
|
|
180
280
|
logger.info(`Message for session: ${sessionId}`);
|
|
181
|
-
const response = await
|
|
281
|
+
const response = await activeAgent.run(message || '', imageDataInput, fileDataInput, sessionId, false // Force non-streaming for sync endpoint
|
|
182
282
|
);
|
|
183
283
|
return res.status(200).json({ response, sessionId });
|
|
184
284
|
}
|
|
@@ -189,8 +289,9 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
189
289
|
app.post('/api/reset', express.json(), async (req, res, next) => {
|
|
190
290
|
logger.info('Received request via POST /api/reset');
|
|
191
291
|
try {
|
|
292
|
+
ensureAgentAvailable();
|
|
192
293
|
const { sessionId } = parseBody(z.object({ sessionId: z.string().optional() }), req.body);
|
|
193
|
-
await
|
|
294
|
+
await activeAgent.resetConversation(sessionId);
|
|
194
295
|
return res.status(200).send({ status: 'reset initiated', sessionId });
|
|
195
296
|
}
|
|
196
297
|
catch (error) {
|
|
@@ -200,8 +301,9 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
200
301
|
// Dynamic MCP server connection endpoint (legacy)
|
|
201
302
|
app.post('/api/connect-server', express.json(), async (req, res, next) => {
|
|
202
303
|
try {
|
|
304
|
+
ensureAgentAvailable();
|
|
203
305
|
const { name, config } = parseBody(McpServerRequestSchema, req.body);
|
|
204
|
-
await
|
|
306
|
+
await activeAgent.connectMcpServer(name, config);
|
|
205
307
|
logger.info(`Successfully connected to new server '${name}' via API request.`);
|
|
206
308
|
return res.status(200).send({ status: 'connected', name });
|
|
207
309
|
}
|
|
@@ -212,8 +314,9 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
212
314
|
// Add a new MCP server
|
|
213
315
|
app.post('/api/mcp/servers', express.json(), async (req, res, next) => {
|
|
214
316
|
try {
|
|
317
|
+
ensureAgentAvailable();
|
|
215
318
|
const { name, config } = parseBody(McpServerRequestSchema, req.body);
|
|
216
|
-
await
|
|
319
|
+
await activeAgent.connectMcpServer(name, config);
|
|
217
320
|
return res.status(201).json({ status: 'connected', name });
|
|
218
321
|
}
|
|
219
322
|
catch (error) {
|
|
@@ -223,8 +326,9 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
223
326
|
// Add MCP servers listing endpoint
|
|
224
327
|
app.get('/api/mcp/servers', async (req, res, next) => {
|
|
225
328
|
try {
|
|
226
|
-
|
|
227
|
-
const
|
|
329
|
+
ensureAgentAvailable();
|
|
330
|
+
const clientsMap = activeAgent.getMcpClients();
|
|
331
|
+
const failedConnections = activeAgent.getMcpFailedConnections();
|
|
228
332
|
const servers = [];
|
|
229
333
|
for (const name of clientsMap.keys()) {
|
|
230
334
|
servers.push({ id: name, name, status: 'connected' });
|
|
@@ -240,12 +344,13 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
240
344
|
});
|
|
241
345
|
// Add MCP server tools listing endpoint
|
|
242
346
|
app.get('/api/mcp/servers/:serverId/tools', async (req, res, next) => {
|
|
243
|
-
const serverId = req.params.serverId;
|
|
244
|
-
const client = agent.getMcpClients().get(serverId);
|
|
245
|
-
if (!client) {
|
|
246
|
-
return res.status(404).json({ error: `Server '${serverId}' not found` });
|
|
247
|
-
}
|
|
248
347
|
try {
|
|
348
|
+
ensureAgentAvailable();
|
|
349
|
+
const serverId = req.params.serverId;
|
|
350
|
+
const client = activeAgent.getMcpClients().get(serverId);
|
|
351
|
+
if (!client) {
|
|
352
|
+
return res.status(404).json({ error: `Server '${serverId}' not found` });
|
|
353
|
+
}
|
|
249
354
|
const toolsMap = await client.getTools();
|
|
250
355
|
const tools = Object.entries(toolsMap).map(([toolName, toolDef]) => ({
|
|
251
356
|
id: toolName,
|
|
@@ -265,12 +370,13 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
265
370
|
logger.info(`Received request to DELETE /api/mcp/servers/${serverId}`);
|
|
266
371
|
try {
|
|
267
372
|
// Check if server exists before attempting to disconnect
|
|
268
|
-
const clientExists =
|
|
373
|
+
const clientExists = activeAgent.getMcpClients().has(serverId) ||
|
|
374
|
+
activeAgent.getMcpFailedConnections()[serverId];
|
|
269
375
|
if (!clientExists) {
|
|
270
376
|
logger.warn(`Attempted to delete non-existent server: ${serverId}`);
|
|
271
377
|
return res.status(404).json({ error: `Server '${serverId}' not found.` });
|
|
272
378
|
}
|
|
273
|
-
await
|
|
379
|
+
await activeAgent.removeMcpServer(serverId);
|
|
274
380
|
return res.status(200).json({ status: 'disconnected', id: serverId });
|
|
275
381
|
}
|
|
276
382
|
catch (error) {
|
|
@@ -281,7 +387,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
281
387
|
app.post('/api/mcp/servers/:serverId/tools/:toolName/execute', express.json(), async (req, res, next) => {
|
|
282
388
|
const { serverId, toolName } = req.params;
|
|
283
389
|
// Verify server exists
|
|
284
|
-
const client =
|
|
390
|
+
const client = activeAgent.getMcpClients().get(serverId);
|
|
285
391
|
if (!client) {
|
|
286
392
|
return res
|
|
287
393
|
.status(404)
|
|
@@ -289,7 +395,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
289
395
|
}
|
|
290
396
|
try {
|
|
291
397
|
// Execute tool through the agent's unified wrapper method
|
|
292
|
-
const rawResult = await
|
|
398
|
+
const rawResult = await activeAgent.executeTool(toolName, req.body);
|
|
293
399
|
// Return standardized result shape
|
|
294
400
|
return res.json({ success: true, data: rawResult });
|
|
295
401
|
}
|
|
@@ -320,7 +426,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
320
426
|
const data = JSON.parse(messageString);
|
|
321
427
|
if (data.type === 'toolConfirmationResponse' && data.data) {
|
|
322
428
|
// Route confirmation back via AgentEventBus and do not broadcast an error
|
|
323
|
-
|
|
429
|
+
activeAgent.agentEventBus.emit('dexto:toolConfirmationResponse', data.data);
|
|
324
430
|
return;
|
|
325
431
|
}
|
|
326
432
|
else if (data.type === 'message' &&
|
|
@@ -349,8 +455,17 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
349
455
|
logger.info('File data included in message.');
|
|
350
456
|
if (sessionId)
|
|
351
457
|
logger.info(`Message for session: ${sessionId}`);
|
|
458
|
+
// Check if agent is available before processing
|
|
459
|
+
try {
|
|
460
|
+
ensureAgentAvailable();
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
logger.error(`Agent not available for WebSocket message: ${error}`);
|
|
464
|
+
sendWebSocketError(ws, error instanceof Error ? error.message : 'Agent not available', sessionId);
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
352
467
|
// Comprehensive input validation
|
|
353
|
-
const currentConfig =
|
|
468
|
+
const currentConfig = activeAgent.getEffectiveConfig(sessionId);
|
|
354
469
|
const validation = validateInputForLLM({
|
|
355
470
|
text: data.content,
|
|
356
471
|
...(imageDataInput && { imageData: imageDataInput }),
|
|
@@ -386,17 +501,35 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
386
501
|
sendWebSocketError(ws, hierarchicalError, sessionId);
|
|
387
502
|
return;
|
|
388
503
|
}
|
|
389
|
-
await
|
|
504
|
+
await activeAgent.run(data.content, imageDataInput, fileDataInput, sessionId, stream);
|
|
390
505
|
}
|
|
391
506
|
else if (data.type === 'reset') {
|
|
392
507
|
const sessionId = data.sessionId;
|
|
393
508
|
logger.info(`Processing reset command from WebSocket${sessionId ? ` for session: ${sessionId}` : ''}.`);
|
|
394
|
-
|
|
509
|
+
// Check if agent is available before processing
|
|
510
|
+
try {
|
|
511
|
+
ensureAgentAvailable();
|
|
512
|
+
}
|
|
513
|
+
catch (error) {
|
|
514
|
+
logger.error(`Agent not available for WebSocket reset: ${error}`);
|
|
515
|
+
sendWebSocketError(ws, error instanceof Error ? error.message : 'Agent not available', sessionId || 'unknown');
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
await activeAgent.resetConversation(sessionId);
|
|
395
519
|
}
|
|
396
520
|
else if (data.type === 'cancel') {
|
|
397
521
|
const sessionId = data.sessionId;
|
|
398
522
|
logger.info(`Processing cancel command from WebSocket${sessionId ? ` for session: ${sessionId}` : ''}.`);
|
|
399
|
-
|
|
523
|
+
// Check if agent is available before processing
|
|
524
|
+
try {
|
|
525
|
+
ensureAgentAvailable();
|
|
526
|
+
}
|
|
527
|
+
catch (error) {
|
|
528
|
+
logger.error(`Agent not available for WebSocket cancel: ${error}`);
|
|
529
|
+
sendWebSocketError(ws, error instanceof Error ? error.message : 'Agent not available', sessionId || 'unknown');
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
const cancelled = await activeAgent.cancel(sessionId);
|
|
400
533
|
if (!cancelled) {
|
|
401
534
|
logger.debug('No in-flight run to cancel');
|
|
402
535
|
}
|
|
@@ -459,7 +592,9 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
459
592
|
try {
|
|
460
593
|
const transportType = process.env.DEXTO_MCP_TRANSPORT_TYPE || 'http';
|
|
461
594
|
const mcpTransport = await createMcpTransport(transportType);
|
|
462
|
-
// TODO:
|
|
595
|
+
// TODO: MCP server is bound to the initial agent; breaks after agent switch
|
|
596
|
+
// initializeMcpServer receives the original agent, so MCP endpoints keep talking to the stale instance post-switch.
|
|
597
|
+
// Make MCP consume the current agent via a getter to stay in sync.
|
|
463
598
|
await initializeMcpServer(agent, agentCardData, // Pass the agent card data for the MCP resource
|
|
464
599
|
mcpTransport);
|
|
465
600
|
await initializeMcpServerApiEndpoints(app, mcpTransport);
|
|
@@ -471,6 +606,57 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
471
606
|
res.status(500).json({ error: 'MCP server initialization failed' });
|
|
472
607
|
});
|
|
473
608
|
}
|
|
609
|
+
// ===== Agents API =====
|
|
610
|
+
app.get('/api/agents', async (_req, res, next) => {
|
|
611
|
+
try {
|
|
612
|
+
ensureAgentAvailable();
|
|
613
|
+
const agents = await activeAgent.listAgents();
|
|
614
|
+
return sendJsonResponse(res, {
|
|
615
|
+
installed: agents.installed,
|
|
616
|
+
available: agents.available,
|
|
617
|
+
current: { name: activeAgentName ?? 'default' },
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
catch (error) {
|
|
621
|
+
return next(error);
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
app.get('/api/agents/current', async (_req, res, next) => {
|
|
625
|
+
try {
|
|
626
|
+
// TODO: Consider exposing agent.getName() method or config.name for more accurate tracking
|
|
627
|
+
return sendJsonResponse(res, { name: activeAgentName ?? 'default' });
|
|
628
|
+
}
|
|
629
|
+
catch (error) {
|
|
630
|
+
return next(error);
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
const AgentNameSchema = z.object({ name: z.string().min(1) }).strict();
|
|
634
|
+
app.post('/api/agents/install', express.json(), async (req, res, next) => {
|
|
635
|
+
try {
|
|
636
|
+
ensureAgentAvailable();
|
|
637
|
+
const { name } = AgentNameSchema.parse(req.body);
|
|
638
|
+
await activeAgent.installAgent(name);
|
|
639
|
+
return sendJsonResponse(res, { installed: true, name }, 201);
|
|
640
|
+
}
|
|
641
|
+
catch (error) {
|
|
642
|
+
return next(error);
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
app.post('/api/agents/switch', express.json(), async (req, res, next) => {
|
|
646
|
+
try {
|
|
647
|
+
const { name } = AgentNameSchema.parse(req.body);
|
|
648
|
+
const result = await switchAgentByName(name);
|
|
649
|
+
return sendJsonResponse(res, { switched: true, ...result });
|
|
650
|
+
}
|
|
651
|
+
catch (error) {
|
|
652
|
+
if (error instanceof Error &&
|
|
653
|
+
error.message &&
|
|
654
|
+
error.message.includes('already in progress')) {
|
|
655
|
+
return res.status(409).json({ error: error.message });
|
|
656
|
+
}
|
|
657
|
+
return next(error);
|
|
658
|
+
}
|
|
659
|
+
});
|
|
474
660
|
// Configuration export endpoint
|
|
475
661
|
/**
|
|
476
662
|
* Helper function to redact sensitive environment variables
|
|
@@ -513,7 +699,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
513
699
|
app.get('/api/config.yaml', async (req, res, next) => {
|
|
514
700
|
try {
|
|
515
701
|
const sessionId = req.query.sessionId;
|
|
516
|
-
const config =
|
|
702
|
+
const config = activeAgent.getEffectiveConfig(sessionId);
|
|
517
703
|
// Export config as YAML, masking sensitive data
|
|
518
704
|
const maskedConfig = {
|
|
519
705
|
...config,
|
|
@@ -535,7 +721,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
535
721
|
app.get('/api/greeting', async (req, res, next) => {
|
|
536
722
|
try {
|
|
537
723
|
const sessionId = req.query.sessionId;
|
|
538
|
-
const config =
|
|
724
|
+
const config = activeAgent.getEffectiveConfig(sessionId);
|
|
539
725
|
res.json({ greeting: config.greeting });
|
|
540
726
|
}
|
|
541
727
|
catch (error) {
|
|
@@ -548,8 +734,8 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
548
734
|
const { sessionId } = req.query;
|
|
549
735
|
// Use session-specific config if sessionId is provided, otherwise use default
|
|
550
736
|
const currentConfig = sessionId
|
|
551
|
-
?
|
|
552
|
-
:
|
|
737
|
+
? activeAgent.getEffectiveConfig(sessionId).llm
|
|
738
|
+
: activeAgent.getCurrentLLMConfig();
|
|
553
739
|
// Attach displayName for the current model if available in registry
|
|
554
740
|
let displayName;
|
|
555
741
|
try {
|
|
@@ -711,7 +897,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
711
897
|
const sessionId = typeof body.sessionId === 'string' ? body.sessionId : undefined;
|
|
712
898
|
const { sessionId: _omit, ...llmCandidate } = body;
|
|
713
899
|
const llmConfig = LLMUpdatesSchema.parse(llmCandidate);
|
|
714
|
-
const config = await
|
|
900
|
+
const config = await activeAgent.switchLLM(llmConfig, sessionId);
|
|
715
901
|
return res.status(200).json({ config, sessionId });
|
|
716
902
|
}
|
|
717
903
|
catch (error) {
|
|
@@ -722,10 +908,10 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
722
908
|
// List all active sessions
|
|
723
909
|
app.get('/api/sessions', async (req, res, next) => {
|
|
724
910
|
try {
|
|
725
|
-
const sessionIds = await
|
|
911
|
+
const sessionIds = await activeAgent.listSessions();
|
|
726
912
|
const sessions = await Promise.all(sessionIds.map(async (id) => {
|
|
727
913
|
try {
|
|
728
|
-
const metadata = await
|
|
914
|
+
const metadata = await activeAgent.getSessionMetadata(id);
|
|
729
915
|
return {
|
|
730
916
|
id,
|
|
731
917
|
createdAt: metadata?.createdAt || null,
|
|
@@ -753,8 +939,8 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
753
939
|
app.post('/api/sessions', express.json(), async (req, res, next) => {
|
|
754
940
|
try {
|
|
755
941
|
const { sessionId } = req.body;
|
|
756
|
-
const session = await
|
|
757
|
-
const metadata = await
|
|
942
|
+
const session = await activeAgent.createSession(sessionId);
|
|
943
|
+
const metadata = await activeAgent.getSessionMetadata(session.id);
|
|
758
944
|
return res.status(201).json({
|
|
759
945
|
session: {
|
|
760
946
|
id: session.id,
|
|
@@ -771,7 +957,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
771
957
|
// Get current working session (must come before parameterized route)
|
|
772
958
|
app.get('/api/sessions/current', async (req, res, next) => {
|
|
773
959
|
try {
|
|
774
|
-
const currentSessionId =
|
|
960
|
+
const currentSessionId = activeAgent.getCurrentSessionId();
|
|
775
961
|
return res.json({ currentSessionId });
|
|
776
962
|
}
|
|
777
963
|
catch (error) {
|
|
@@ -782,8 +968,8 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
782
968
|
app.get('/api/sessions/:sessionId', async (req, res, next) => {
|
|
783
969
|
try {
|
|
784
970
|
const { sessionId } = req.params;
|
|
785
|
-
const metadata = await
|
|
786
|
-
const history = await
|
|
971
|
+
const metadata = await activeAgent.getSessionMetadata(sessionId);
|
|
972
|
+
const history = await activeAgent.getSessionHistory(sessionId);
|
|
787
973
|
return res.json({
|
|
788
974
|
session: {
|
|
789
975
|
id: sessionId,
|
|
@@ -803,7 +989,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
803
989
|
try {
|
|
804
990
|
const { sessionId } = req.params;
|
|
805
991
|
// getSessionHistory already checks existence via getSession
|
|
806
|
-
const history = await
|
|
992
|
+
const history = await activeAgent.getSessionHistory(sessionId);
|
|
807
993
|
return res.json({ history });
|
|
808
994
|
}
|
|
809
995
|
catch (error) {
|
|
@@ -820,7 +1006,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
820
1006
|
...(sessionId && { sessionId }),
|
|
821
1007
|
...(role && { role }),
|
|
822
1008
|
};
|
|
823
|
-
const searchResults = await
|
|
1009
|
+
const searchResults = await activeAgent.searchMessages(query, options);
|
|
824
1010
|
return sendJsonResponse(res, searchResults);
|
|
825
1011
|
}
|
|
826
1012
|
catch (error) {
|
|
@@ -831,7 +1017,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
831
1017
|
app.get('/api/search/sessions', async (req, res, next) => {
|
|
832
1018
|
try {
|
|
833
1019
|
const { q: query } = parseQuery(z.object({ q: z.string().min(1, 'Search query is required') }), req.query);
|
|
834
|
-
const searchResults = await
|
|
1020
|
+
const searchResults = await activeAgent.searchSessions(query);
|
|
835
1021
|
return sendJsonResponse(res, searchResults);
|
|
836
1022
|
}
|
|
837
1023
|
catch (error) {
|
|
@@ -843,7 +1029,7 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
843
1029
|
try {
|
|
844
1030
|
const { sessionId } = req.params;
|
|
845
1031
|
// deleteSession already checks existence internally
|
|
846
|
-
await
|
|
1032
|
+
await activeAgent.deleteSession(sessionId);
|
|
847
1033
|
return res.json({ status: 'deleted', sessionId });
|
|
848
1034
|
}
|
|
849
1035
|
catch (error) {
|
|
@@ -856,20 +1042,20 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
856
1042
|
const { sessionId } = req.params;
|
|
857
1043
|
// Handle null/reset case
|
|
858
1044
|
if (sessionId === 'null' || sessionId === 'undefined') {
|
|
859
|
-
await
|
|
1045
|
+
await activeAgent.loadSessionAsDefault(null);
|
|
860
1046
|
res.json({
|
|
861
1047
|
status: 'reset',
|
|
862
1048
|
sessionId: null,
|
|
863
|
-
currentSession:
|
|
1049
|
+
currentSession: activeAgent.getCurrentSessionId(),
|
|
864
1050
|
});
|
|
865
1051
|
return;
|
|
866
1052
|
}
|
|
867
1053
|
// loadSession already checks session existence
|
|
868
|
-
await
|
|
1054
|
+
await activeAgent.loadSessionAsDefault(sessionId);
|
|
869
1055
|
return res.json({
|
|
870
1056
|
status: 'loaded',
|
|
871
1057
|
sessionId,
|
|
872
|
-
currentSession:
|
|
1058
|
+
currentSession: activeAgent.getCurrentSessionId(),
|
|
873
1059
|
});
|
|
874
1060
|
}
|
|
875
1061
|
catch (error) {
|
|
@@ -877,10 +1063,6 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
877
1063
|
}
|
|
878
1064
|
});
|
|
879
1065
|
// Webhook Management APIs
|
|
880
|
-
// Initialize webhook subscriber
|
|
881
|
-
const webhookSubscriber = new WebhookEventSubscriber();
|
|
882
|
-
logger.info('Setting up webhook event subscriptions...');
|
|
883
|
-
webhookSubscriber.subscribe(agent.agentEventBus);
|
|
884
1066
|
// Register a new webhook endpoint
|
|
885
1067
|
app.post('/api/webhooks', express.json(), async (req, res, next) => {
|
|
886
1068
|
try {
|
|
@@ -988,8 +1170,8 @@ export async function initializeApi(agent, agentCardOverride, listenPort) {
|
|
|
988
1170
|
app.use(errorHandler);
|
|
989
1171
|
return { app, server, wss, webSubscriber, webhookSubscriber };
|
|
990
1172
|
}
|
|
991
|
-
export async function startApiServer(agent, port = 3000, agentCardOverride) {
|
|
992
|
-
const { server, wss, webSubscriber, webhookSubscriber } = await initializeApi(agent, agentCardOverride, port);
|
|
1173
|
+
export async function startApiServer(agent, port = 3000, agentCardOverride, agentName) {
|
|
1174
|
+
const { server, wss, webSubscriber, webhookSubscriber } = await initializeApi(agent, agentCardOverride, port, agentName);
|
|
993
1175
|
// API server for REST endpoints and WebSocket connections
|
|
994
1176
|
server.listen(port, '0.0.0.0', () => {
|
|
995
1177
|
const networkInterfaces = os.networkInterfaces();
|
|
@@ -41,6 +41,10 @@ export declare class WebhookEventSubscriber implements EventSubscriber {
|
|
|
41
41
|
* Clean up event listeners and resources
|
|
42
42
|
*/
|
|
43
43
|
cleanup(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Unsubscribe from current event bus without clearing registered webhooks
|
|
46
|
+
*/
|
|
47
|
+
unsubscribe(): void;
|
|
44
48
|
/**
|
|
45
49
|
* Deliver an event to all registered webhooks
|
|
46
50
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-subscriber.d.ts","sourceRoot":"","sources":["../../src/api/webhook-subscriber.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAmD,MAAM,aAAa,CAAC;AAC7F,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EACH,KAAK,aAAa,EAElB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC9B,MAAM,oBAAoB,CAAC;AAW5B;;;GAGG;AACH,qBAAa,sBAAuB,YAAW,eAAe;IAC1D,OAAO,CAAC,QAAQ,CAAyC;IACzD,OAAO,CAAC,eAAe,CAAC,CAAkB;IAC1C,OAAO,CAAC,eAAe,CAAmC;IAC1D,OAAO,CAAC,OAAO,CAA0B;gBAE7B,EACR,OAAO,EACP,GAAG,eAAe,EACrB,GAAE,sBAAsB,GAAG;QAAE,OAAO,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;KAAO;IAOtE;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI;IA0CxC;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAKxC;;OAEG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAUzC;;OAEG;IACH,WAAW,IAAI,aAAa,EAAE;IAI9B;;OAEG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAIxD;;OAEG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAoBpE;;OAEG;IACH,OAAO,IAAI,IAAI;IAUf;;OAEG;YACW,YAAY;IA+C1B;;OAEG;YACW,gBAAgB;IAoD9B;;OAEG;YACW,kBAAkB;IA+DhC;;OAEG;IACH,OAAO,CAAC,iBAAiB;CAK5B"}
|
|
1
|
+
{"version":3,"file":"webhook-subscriber.d.ts","sourceRoot":"","sources":["../../src/api/webhook-subscriber.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAmD,MAAM,aAAa,CAAC;AAC7F,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EACH,KAAK,aAAa,EAElB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC9B,MAAM,oBAAoB,CAAC;AAW5B;;;GAGG;AACH,qBAAa,sBAAuB,YAAW,eAAe;IAC1D,OAAO,CAAC,QAAQ,CAAyC;IACzD,OAAO,CAAC,eAAe,CAAC,CAAkB;IAC1C,OAAO,CAAC,eAAe,CAAmC;IAC1D,OAAO,CAAC,OAAO,CAA0B;gBAE7B,EACR,OAAO,EACP,GAAG,eAAe,EACrB,GAAE,sBAAsB,GAAG;QAAE,OAAO,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;KAAO;IAOtE;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI;IA0CxC;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAKxC;;OAEG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAUzC;;OAEG;IACH,WAAW,IAAI,aAAa,EAAE;IAI9B;;OAEG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAIxD;;OAEG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAoBpE;;OAEG;IACH,OAAO,IAAI,IAAI;IAUf;;OAEG;IACH,WAAW,IAAI,IAAI;IAYnB;;OAEG;YACW,YAAY;IA+C1B;;OAEG;YACW,gBAAgB;IAoD9B;;OAEG;YACW,kBAAkB;IA+DhC;;OAEG;IACH,OAAO,CAAC,iBAAiB;CAK5B"}
|
|
@@ -122,6 +122,21 @@ export class WebhookEventSubscriber {
|
|
|
122
122
|
this.webhooks.clear();
|
|
123
123
|
logger.debug('Webhook event subscriber cleaned up');
|
|
124
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Unsubscribe from current event bus without clearing registered webhooks
|
|
127
|
+
*/
|
|
128
|
+
unsubscribe() {
|
|
129
|
+
if (this.abortController) {
|
|
130
|
+
const controller = this.abortController;
|
|
131
|
+
delete this.abortController;
|
|
132
|
+
try {
|
|
133
|
+
controller.abort();
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
logger.debug('Error aborting controller during unsubscribe:', error);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
125
140
|
/**
|
|
126
141
|
* Deliver an event to all registered webhooks
|
|
127
142
|
*/
|
|
@@ -17,6 +17,11 @@ export declare class WebSocketEventSubscriber implements EventSubscriber {
|
|
|
17
17
|
* Clean up event listeners and resources
|
|
18
18
|
*/
|
|
19
19
|
cleanup(): void;
|
|
20
|
+
/**
|
|
21
|
+
* Unsubscribe from current event bus without closing WebSocket clients.
|
|
22
|
+
* Useful when switching the active agent and re-subscribing to a new bus.
|
|
23
|
+
*/
|
|
24
|
+
unsubscribe(): void;
|
|
20
25
|
private broadcast;
|
|
21
26
|
}
|
|
22
27
|
//# sourceMappingURL=websocket-subscriber.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket-subscriber.d.ts","sourceRoot":"","sources":["../../src/api/websocket-subscriber.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAa,MAAM,IAAI,CAAC;AAEhD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C;;GAEG;AACH,qBAAa,wBAAyB,YAAW,eAAe;IAIhD,OAAO,CAAC,GAAG;IAHvB,OAAO,CAAC,WAAW,CAA6B;IAChD,OAAO,CAAC,eAAe,CAAC,CAAkB;gBAEtB,GAAG,EAAE,eAAe;IAmBxC;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI;IAyKxC;;OAEG;IACH,OAAO,IAAI,IAAI;IAiBf,OAAO,CAAC,SAAS;CAUpB"}
|
|
1
|
+
{"version":3,"file":"websocket-subscriber.d.ts","sourceRoot":"","sources":["../../src/api/websocket-subscriber.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAa,MAAM,IAAI,CAAC;AAEhD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C;;GAEG;AACH,qBAAa,wBAAyB,YAAW,eAAe;IAIhD,OAAO,CAAC,GAAG;IAHvB,OAAO,CAAC,WAAW,CAA6B;IAChD,OAAO,CAAC,eAAe,CAAC,CAAkB;gBAEtB,GAAG,EAAE,eAAe;IAmBxC;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI;IAyKxC;;OAEG;IACH,OAAO,IAAI,IAAI;IAiBf;;;OAGG;IACH,WAAW,IAAI,IAAI;IAYnB,OAAO,CAAC,SAAS;CAUpB"}
|
|
@@ -159,6 +159,22 @@ export class WebSocketEventSubscriber {
|
|
|
159
159
|
this.connections.clear();
|
|
160
160
|
logger.debug('WebSocket event subscriber cleaned up');
|
|
161
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Unsubscribe from current event bus without closing WebSocket clients.
|
|
164
|
+
* Useful when switching the active agent and re-subscribing to a new bus.
|
|
165
|
+
*/
|
|
166
|
+
unsubscribe() {
|
|
167
|
+
if (this.abortController) {
|
|
168
|
+
const controller = this.abortController;
|
|
169
|
+
delete this.abortController;
|
|
170
|
+
try {
|
|
171
|
+
controller.abort();
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
logger.debug('Error aborting controller during unsubscribe:', error);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
162
178
|
broadcast(message) {
|
|
163
179
|
const messageString = JSON.stringify(message);
|
|
164
180
|
for (const client of this.connections) {
|