@symbolica/agentica 0.3.2 → 0.4.1

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.
Files changed (83) hide show
  1. package/LICENSE +21 -77
  2. package/README.md +7 -10
  3. package/dist/bin/agentica-setup.js +0 -0
  4. package/dist/src/agentica/agent.d.ts +3 -1
  5. package/dist/src/agentica/agent.d.ts.map +1 -1
  6. package/dist/src/agentica/agent.js +4 -5
  7. package/dist/src/agentica/agent.js.map +1 -1
  8. package/dist/src/agentica/agentic.d.ts +4 -1
  9. package/dist/src/agentica/agentic.d.ts.map +1 -1
  10. package/dist/src/agentica/agentic.js +4 -4
  11. package/dist/src/agentica/agentic.js.map +1 -1
  12. package/dist/src/agentica/common.d.ts +10 -6
  13. package/dist/src/agentica/common.d.ts.map +1 -1
  14. package/dist/src/agentica/common.js +33 -19
  15. package/dist/src/agentica/common.js.map +1 -1
  16. package/dist/src/client-session-manager/client-session-manager.d.ts +11 -9
  17. package/dist/src/client-session-manager/client-session-manager.d.ts.map +1 -1
  18. package/dist/src/client-session-manager/client-session-manager.js +186 -101
  19. package/dist/src/client-session-manager/client-session-manager.js.map +1 -1
  20. package/dist/src/client-session-manager/types.d.ts +1 -1
  21. package/dist/src/client-session-manager/types.d.ts.map +1 -1
  22. package/dist/src/coming-soon.d.ts +7 -18
  23. package/dist/src/coming-soon.d.ts.map +1 -1
  24. package/dist/src/coming-soon.js +21 -41
  25. package/dist/src/coming-soon.js.map +1 -1
  26. package/dist/src/errors/base.d.ts.map +1 -1
  27. package/dist/src/errors/base.js +3 -1
  28. package/dist/src/errors/base.js.map +1 -1
  29. package/dist/src/errors/bugs.js +1 -1
  30. package/dist/src/errors/bugs.js.map +1 -1
  31. package/dist/src/version.d.ts +1 -1
  32. package/dist/src/version.d.ts.map +1 -1
  33. package/dist/src/version.js +1 -1
  34. package/dist/src/version.js.map +1 -1
  35. package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.d.ts +7 -0
  36. package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.d.ts.map +1 -0
  37. package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.js +144 -0
  38. package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.js.map +1 -0
  39. package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.test.d.ts +2 -0
  40. package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.test.d.ts.map +1 -0
  41. package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.test.js +253 -0
  42. package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.test.js.map +1 -0
  43. package/dist/src/warpc/msg-protocol/concept/python-mirrors/utils.d.ts +5 -0
  44. package/dist/src/warpc/msg-protocol/concept/python-mirrors/utils.d.ts.map +1 -0
  45. package/dist/src/warpc/msg-protocol/concept/python-mirrors/utils.js +17 -0
  46. package/dist/src/warpc/msg-protocol/concept/python-mirrors/utils.js.map +1 -0
  47. package/dist/src/warpc/msg-protocol/concept/resource/system-msg.d.ts +2 -1
  48. package/dist/src/warpc/msg-protocol/concept/resource/system-msg.d.ts.map +1 -1
  49. package/dist/src/warpc/msg-protocol/concept/resource/system-msg.js +14 -10
  50. package/dist/src/warpc/msg-protocol/concept/resource/system-msg.js.map +1 -1
  51. package/dist-transformer/agentica/agent.d.ts +6 -1
  52. package/dist-transformer/agentica/agent.d.ts.map +1 -1
  53. package/dist-transformer/agentica/agent.js +4 -4
  54. package/dist-transformer/agentica/agentic.d.ts +6 -1
  55. package/dist-transformer/agentica/agentic.d.ts.map +1 -1
  56. package/dist-transformer/agentica/agentic.js +4 -4
  57. package/dist-transformer/agentica/common.d.ts +23 -14
  58. package/dist-transformer/agentica/common.d.ts.map +1 -1
  59. package/dist-transformer/agentica/common.js +47 -27
  60. package/dist-transformer/client-session-manager/client-session-manager.d.ts +21 -13
  61. package/dist-transformer/client-session-manager/client-session-manager.d.ts.map +1 -1
  62. package/dist-transformer/client-session-manager/client-session-manager.js +233 -115
  63. package/dist-transformer/client-session-manager/types.d.ts +1 -1
  64. package/dist-transformer/client-session-manager/types.d.ts.map +1 -1
  65. package/dist-transformer/coming-soon.d.ts +7 -18
  66. package/dist-transformer/coming-soon.d.ts.map +1 -1
  67. package/dist-transformer/coming-soon.js +21 -39
  68. package/dist-transformer/errors/base.d.ts.map +1 -1
  69. package/dist-transformer/errors/base.js +3 -1
  70. package/dist-transformer/errors/bugs.js +1 -1
  71. package/dist-transformer/version.d.ts +1 -1
  72. package/dist-transformer/version.d.ts.map +1 -1
  73. package/dist-transformer/version.js +1 -1
  74. package/dist-transformer/warpc/msg-protocol/concept/python-mirrors/str.d.ts +7 -0
  75. package/dist-transformer/warpc/msg-protocol/concept/python-mirrors/str.d.ts.map +1 -0
  76. package/dist-transformer/warpc/msg-protocol/concept/python-mirrors/str.js +151 -0
  77. package/dist-transformer/warpc/msg-protocol/concept/python-mirrors/utils.d.ts +5 -0
  78. package/dist-transformer/warpc/msg-protocol/concept/python-mirrors/utils.d.ts.map +1 -0
  79. package/dist-transformer/warpc/msg-protocol/concept/python-mirrors/utils.js +16 -0
  80. package/dist-transformer/warpc/msg-protocol/concept/resource/system-msg.d.ts +2 -1
  81. package/dist-transformer/warpc/msg-protocol/concept/resource/system-msg.d.ts.map +1 -1
  82. package/dist-transformer/warpc/msg-protocol/concept/resource/system-msg.js +14 -11
  83. package/package.json +143 -141
@@ -35,9 +35,10 @@ import { version } from '../version.js';
35
35
  */
36
36
  export class ClientSessionManager {
37
37
  constructor(config = {}) {
38
- this.websocket = null;
39
- this.tasks = null;
40
- this.sendQueue = null;
38
+ // Per-session-manager WebSocket state (sm_id → connection state)
39
+ this.sessionManagers = new Map();
40
+ this.smLocks = new Map();
41
+ this.uidToSm = new Map(); // agent uid → sm_id
41
42
  this.baseHttp = null;
42
43
  this.baseWs = null;
43
44
  this.apiKey = null;
@@ -158,31 +159,30 @@ export class ClientSessionManager {
158
159
  return headers;
159
160
  }
160
161
  /**
161
- * Background task for writing messages to WebSocket - matches Python _ws_background_task_writer
162
+ * Background task for writing messages to WebSocket for a specific session manager.
162
163
  */
163
- async wsBackgroundTaskWriter(control) {
164
+ async smWriter(smId, control) {
164
165
  await waitForTracing;
165
- const wsLogger = this.logger.withScope('ws-writer');
166
+ const wsLogger = this.logger.withScope(`ws-writer-${smId.slice(0, 8)}`);
166
167
  wsLogger.debug('Writer task started');
167
- const ws = this.websocket;
168
168
  try {
169
169
  while (!control.shouldStop) {
170
+ const smc = this.sessionManagers.get(smId);
171
+ if (!smc) {
172
+ wsLogger.debug('Session manager connection removed, exiting writer');
173
+ break;
174
+ }
170
175
  try {
171
- const sendQueue = this.sendQueue;
172
- if (!sendQueue) {
173
- wsLogger.debug('Send queue removed, exiting writer');
174
- break;
175
- }
176
- const msg = await sendQueue.get();
176
+ const msg = await smc.sendQueue.get();
177
177
  const msgUid = msg.uid;
178
178
  const msgLogger = msgUid ? (this.uidToLogger.get(msgUid) ?? wsLogger) : wsLogger;
179
- if (ws.readyState !== 1) {
179
+ if (smc.websocket.readyState !== 1) {
180
180
  const error = new WebSocketConnectionError(`cannot send as websocket is not open.`);
181
181
  enrichError(error, { uid: msgUid, sessionId: this.clientSessionId });
182
182
  throw error;
183
183
  }
184
184
  msgLogger.debug(`Sending ${msg.type} message`);
185
- ws.send(encodeMessage(msg));
185
+ smc.websocket.send(encodeMessage(msg));
186
186
  }
187
187
  catch (error) {
188
188
  if (error instanceof Error && error.message === 'Queue closed') {
@@ -196,26 +196,24 @@ export class ClientSessionManager {
196
196
  }
197
197
  catch (error) {
198
198
  wsLogger.error('Writer task failed', error);
199
- for (const [matchId, handlers] of this.matchIid.entries()) {
200
- handlers.reject(new WebSocketConnectionError(`WebSocket writer failed: ${error.message}`));
201
- this.matchIid.delete(matchId);
202
- }
203
- for (const [_, exceptionMap] of this.uidIidException.entries()) {
204
- for (const [_iid, handlers] of exceptionMap.entries()) {
205
- handlers.reject(new WebSocketConnectionError(`WebSocket writer failed: ${error.message}`));
206
- }
207
- }
199
+ // Clean up this session manager only - don't affect others
200
+ await this._cleanupSessionManager(smId);
208
201
  throw error;
209
202
  }
210
203
  }
211
204
  /**
212
- * Background task for reading messages from WebSocket - matches Python _ws_background_task_reader
205
+ * Background task for reading messages from WebSocket for a specific session manager.
213
206
  */
214
- async wsBackgroundTaskReader(control) {
207
+ async smReader(smId, control) {
215
208
  await waitForTracing;
216
- const wsLogger = this.logger.withScope('ws-reader');
209
+ const wsLogger = this.logger.withScope(`ws-reader-${smId.slice(0, 8)}`);
217
210
  wsLogger.debug('Reader task started');
218
- const ws = this.websocket;
211
+ const smc = this.sessionManagers.get(smId);
212
+ if (!smc) {
213
+ wsLogger.warn('Session manager connection not found');
214
+ return;
215
+ }
216
+ const ws = smc.websocket;
219
217
  ws.onmessage = (event) => {
220
218
  if (control.shouldStop)
221
219
  return;
@@ -317,48 +315,125 @@ export class ClientSessionManager {
317
315
  ws.onclose = (event) => {
318
316
  wsLogger.debug(`WebSocket closed (code=${event.code}, reason=${event.reason || 'none'})`);
319
317
  control.shouldStop = true;
320
- for (const [matchId, handlers] of this.matchIid.entries()) {
321
- handlers.reject(new WebSocketConnectionError(`WebSocket closed: ${event.reason || 'Connection closed'}`));
322
- this.matchIid.delete(matchId);
323
- }
324
- for (const [_, exceptionMap] of this.uidIidException.entries()) {
325
- for (const [_iid, handlers] of exceptionMap.entries()) {
326
- handlers.reject(new WebSocketConnectionError(`WebSocket closed: ${event.reason || 'Connection closed'}`));
327
- }
328
- exceptionMap.clear();
329
- }
318
+ // Only fail futures for agents on THIS session manager
319
+ this._failSmFutures(smId, `WebSocket closed: ${event.reason || 'Connection closed'}`);
330
320
  resolve();
331
321
  };
332
322
  ws.onerror = (_error) => {
333
323
  wsLogger.error('WebSocket error');
334
324
  control.shouldStop = true;
335
- for (const [matchId, handlers] of this.matchIid.entries()) {
336
- handlers.reject(new WebSocketConnectionError('WebSocket error occurred'));
337
- this.matchIid.delete(matchId);
338
- }
339
- for (const [_, exceptionMap] of this.uidIidException.entries()) {
340
- for (const [_iid, handlers] of exceptionMap.entries()) {
341
- handlers.reject(new WebSocketConnectionError('WebSocket error occurred'));
342
- }
343
- exceptionMap.clear();
344
- }
325
+ // Only fail futures for agents on THIS session manager
326
+ this._failSmFutures(smId, 'WebSocket error occurred');
345
327
  resolve();
346
328
  };
347
329
  });
348
330
  }
349
331
  /**
350
- * Ensure WebSocket connection exists, creating it if needed.
332
+ * Fail pending futures for agents on a specific session manager.
333
+ */
334
+ _failSmFutures(smId, reason) {
335
+ // Get all UIDs that belong to this session manager
336
+ const affectedUids = [];
337
+ for (const [uid, sid] of this.uidToSm.entries()) {
338
+ if (sid === smId) {
339
+ affectedUids.push(uid);
340
+ }
341
+ }
342
+ // Fail matchIid promises for affected UIDs (we don't track which session manager a match_id belongs to,
343
+ // so we fail all of them when any session manager fails - this is conservative but safe)
344
+ for (const [matchId, handlers] of this.matchIid.entries()) {
345
+ handlers.reject(new WebSocketConnectionError(reason));
346
+ this.matchIid.delete(matchId);
347
+ }
348
+ // Fail exception handlers for affected UIDs
349
+ for (const uid of affectedUids) {
350
+ const exceptionMap = this.uidIidException.get(uid);
351
+ if (exceptionMap) {
352
+ for (const [_iid, handlers] of exceptionMap.entries()) {
353
+ handlers.reject(new WebSocketConnectionError(reason));
354
+ }
355
+ exceptionMap.clear();
356
+ }
357
+ }
358
+ }
359
+ /**
360
+ * Clean up a specific session manager's connection without affecting others.
361
+ */
362
+ async _cleanupSessionManager(smId) {
363
+ const smc = this.sessionManagers.get(smId);
364
+ if (!smc) {
365
+ return;
366
+ }
367
+ this.logger.debug(`Cleaning up session manager ${smId.slice(0, 8)}`);
368
+ this.sessionManagers.delete(smId);
369
+ this.smLocks.delete(smId);
370
+ // Stop tasks
371
+ const [readerControl, writerControl] = smc.tasks;
372
+ readerControl.shouldStop = true;
373
+ writerControl.shouldStop = true;
374
+ // Close send queue
375
+ smc.sendQueue.close();
376
+ // Close WebSocket gracefully
377
+ try {
378
+ if (smc.websocket.readyState === 0 || smc.websocket.readyState === 1) {
379
+ smc.websocket.close(1000, 'Normal closure');
380
+ }
381
+ }
382
+ catch (error) {
383
+ this.logger.warn(`Failed to close WebSocket for session manager ${smId.slice(0, 8)}`, error);
384
+ }
385
+ // Wait for tasks to complete
386
+ await Promise.allSettled([readerControl.promise, writerControl.promise]);
387
+ // Fail pending futures for agents on this session manager
388
+ this._failSmFutures(smId, 'Session manager connection closed');
389
+ // Clean up agent-to-session-manager mappings
390
+ const affectedUids = [];
391
+ for (const [uid, sid] of this.uidToSm.entries()) {
392
+ if (sid === smId) {
393
+ affectedUids.push(uid);
394
+ }
395
+ }
396
+ for (const uid of affectedUids) {
397
+ this.uidToSm.delete(uid);
398
+ }
399
+ this.logger.debug(`Cleaned up session manager ${smId.slice(0, 8)}`);
400
+ }
401
+ /**
402
+ * Ensure WebSocket connection exists for a specific session manager, creating it if needed.
351
403
  */
352
- async _ensureWebSocketConnection() {
353
- if (this.websocket && this.websocket.readyState === 1) {
404
+ async _ensureSmConnection(smId) {
405
+ // Fast path: connection already exists
406
+ const existing = this.sessionManagers.get(smId);
407
+ if (existing && existing.websocket.readyState === 1) {
354
408
  return;
355
409
  }
410
+ // Check if another call is already creating this connection
411
+ const existingLock = this.smLocks.get(smId);
412
+ if (existingLock) {
413
+ await existingLock;
414
+ return;
415
+ }
416
+ // Create lock IMMEDIATELY after check - no await between check and set
417
+ let resolveLock;
418
+ const lockPromise = new Promise((resolve) => {
419
+ resolveLock = resolve;
420
+ });
421
+ this.smLocks.set(smId, lockPromise);
422
+ // Now safe to yield
356
423
  await waitForTracing;
357
- const span = this.logger.startSpan('sdk.websocket_connection');
424
+ const span = this.logger.startSpan('sdk.sm_connection');
425
+ span.setAttribute('session_manager.id', smId);
358
426
  try {
359
- this.sendQueue = new Queue();
427
+ // Double-check after acquiring lock
428
+ const doubleCheck = this.sessionManagers.get(smId);
429
+ if (doubleCheck && doubleCheck.websocket.readyState === 1) {
430
+ resolveLock();
431
+ this.smLocks.delete(smId);
432
+ return;
433
+ }
434
+ const sendQueue = new Queue();
360
435
  const websocket_uri = `${this.baseWs}/socket`;
361
- this.logger.debug(`Connecting WebSocket`);
436
+ this.logger.debug(`Connecting WebSocket for session manager ${smId.slice(0, 8)}`);
362
437
  span.setAttribute('websocket.uri', websocket_uri);
363
438
  span.setAttribute('websocket.state', 'connecting');
364
439
  // Create headers object with trace context for distributed tracing
@@ -368,22 +443,18 @@ export class ClientSessionManager {
368
443
  }
369
444
  headers['x-client-session-id'] = this.clientSessionId;
370
445
  // Inject trace context into WebSocket headers for distributed tracing
371
- // Execute in span's context to ensure proper trace propagation (like Python's inject(headers, context=ctx))
372
446
  if (span.executeInContext) {
373
447
  span.executeInContext(() => {
374
448
  injectTraceContext(headers);
375
449
  });
376
450
  }
377
451
  else {
378
- // Fallback: pass span context directly
379
452
  injectTraceContext(headers, span);
380
453
  }
381
- // Pass null for apiKey since headers already has Authorization
382
454
  const ws = await createWebSocket(websocket_uri, this.apiKey, headers);
383
455
  ws.binaryType = 'arraybuffer';
384
456
  return new Promise((resolve, reject) => {
385
457
  ws.onopen = () => {
386
- this.websocket = ws;
387
458
  try {
388
459
  ws._socket?.unref?.();
389
460
  }
@@ -398,23 +469,34 @@ export class ClientSessionManager {
398
469
  shouldStop: false,
399
470
  promise: Promise.resolve(),
400
471
  };
401
- readerControl.promise = this.wsBackgroundTaskReader(readerControl).catch((error) => {
402
- this.logger.error('Reader background task crashed', error);
472
+ // Store the session manager connection first so tasks can access it
473
+ const smc = {
474
+ websocket: ws,
475
+ sendQueue,
476
+ tasks: [readerControl, writerControl],
477
+ };
478
+ this.sessionManagers.set(smId, smc);
479
+ // Start per-session-manager reader and writer tasks
480
+ readerControl.promise = this.smReader(smId, readerControl).catch((error) => {
481
+ this.logger.error(`Reader task crashed for session manager ${smId.slice(0, 8)}`, error);
403
482
  });
404
- writerControl.promise = this.wsBackgroundTaskWriter(writerControl).catch((error) => {
405
- this.logger.error('Writer background task crashed', error);
483
+ writerControl.promise = this.smWriter(smId, writerControl).catch((error) => {
484
+ this.logger.error(`Writer task crashed for session manager ${smId.slice(0, 8)}`, error);
406
485
  });
407
- this.tasks = [readerControl, writerControl];
408
- this.logger.info(`WebSocket connected`);
486
+ this.logger.info(`WebSocket connected for session manager ${smId.slice(0, 8)}`);
409
487
  span.setAttribute('websocket.state', 'connected');
410
488
  span.end();
489
+ resolveLock();
490
+ this.smLocks.delete(smId);
411
491
  resolve();
412
492
  };
413
493
  ws.onerror = (error) => {
414
- this.logger.error(`WebSocket connection failed:\n${error.toString()}`);
494
+ this.logger.error(`WebSocket connection failed for session manager ${smId.slice(0, 8)}:\n${error.toString()}`);
415
495
  span.setAttribute('websocket.state', 'error');
416
496
  span.recordException(error);
417
497
  span.end();
498
+ resolveLock();
499
+ this.smLocks.delete(smId);
418
500
  reject(new WebSocketConnectionError('WebSocket connection failed'));
419
501
  };
420
502
  });
@@ -423,6 +505,8 @@ export class ClientSessionManager {
423
505
  span.setAttribute('websocket.state', 'error');
424
506
  span.recordException(error);
425
507
  span.end();
508
+ resolveLock();
509
+ this.smLocks.delete(smId);
426
510
  throw error;
427
511
  }
428
512
  }
@@ -503,10 +587,26 @@ export class ClientSessionManager {
503
587
  logger.warn(message);
504
588
  }
505
589
  }
506
- const uid = await response.text();
590
+ // Parse JSON response: { uid, session_manager_id }
591
+ const responseData = await response.json();
592
+ const uid = responseData.uid;
593
+ const smId = responseData.session_manager_id;
594
+ // Validate response fields
595
+ if (!uid) {
596
+ logger.error(`Server response missing "uid" field: ${JSON.stringify(responseData)}`);
597
+ throw new ConnectionError('Invalid server response: missing "uid" field');
598
+ }
599
+ if (!smId) {
600
+ logger.error(`Server response missing "session_manager_id" field: ${JSON.stringify(responseData)}`);
601
+ throw new ConnectionError('Invalid server response: missing "session_manager_id" field');
602
+ }
507
603
  logger.info('Created agent');
508
604
  span.setAttribute('agent.uid', uid);
509
- await this._ensureWebSocketConnection();
605
+ span.setAttribute('agent.session_manager_id', smId);
606
+ // Track which session manager this agent belongs to
607
+ this.uidToSm.set(uid, smId);
608
+ // Ensure WebSocket connection for this session manager
609
+ await this._ensureSmConnection(smId);
510
610
  this.knownUids.add(uid);
511
611
  this._initAgentState(uid, parentLogger);
512
612
  return uid;
@@ -542,8 +642,22 @@ export class ClientSessionManager {
542
642
  enrichError(error, { uid, sessionId: this.clientSessionId });
543
643
  throw error;
544
644
  }
645
+ // Get session manager for this agent
646
+ const smId = this.uidToSm.get(uid);
647
+ if (!smId) {
648
+ const error = new WebSocketConnectionError(`No session manager found for agent ${uid}`);
649
+ enrichError(error, { uid, sessionId: this.clientSessionId });
650
+ throw error;
651
+ }
652
+ const smc = this.sessionManagers.get(smId);
653
+ if (!smc || smc.websocket.readyState !== 1) {
654
+ const error = new WebSocketConnectionError(`No active connection for session manager ${smId}`);
655
+ enrichError(error, { uid, sessionId: this.clientSessionId });
656
+ throw error;
657
+ }
545
658
  // Add attributes matching Python SDK
546
659
  span.setAttribute('agent.uid', uid);
660
+ span.setAttribute('agent.session_manager_id', smId);
547
661
  span.setAttribute('agent.task_desc', typeof taskDesc === 'string' ? taskDesc : taskDesc.template);
548
662
  span.setAttribute('invocation.streaming', streaming);
549
663
  const matchId = this.idIssuer();
@@ -556,11 +670,12 @@ export class ClientSessionManager {
556
670
  prompt: taskDesc,
557
671
  streaming: streaming,
558
672
  };
559
- invLogger.debug(`Invoking with match_id=${matchId}`);
673
+ invLogger.debug(`Invoking with match_id=${matchId} on session manager ${smId.slice(0, 8)}`);
560
674
  const iidPromise = new Promise((resolve, reject) => {
561
675
  this.matchIid.set(matchId, { resolve, reject });
562
676
  });
563
- this.sendQueue.put(msg);
677
+ // Route to the correct session manager's send queue
678
+ smc.sendQueue.put(msg);
564
679
  const iid = await iidPromise;
565
680
  this.iidToUid.set(iid, uid);
566
681
  invLogger.info(`Invocation created with iid=${iid}`);
@@ -577,8 +692,14 @@ export class ClientSessionManager {
577
692
  return { resolvers: { resolve, reject, promise }, promise };
578
693
  })();
579
694
  this.uidIidException.get(uid).set(iid, resolvers);
695
+ // Capture smId for the closure
696
+ const capturedSmId = smId;
580
697
  return {
581
698
  send_message: async (data) => {
699
+ const currentSmc = this.sessionManagers.get(capturedSmId);
700
+ if (!currentSmc || currentSmc.websocket.readyState !== 1) {
701
+ throw new WebSocketConnectionError(`Session manager connection closed for ${capturedSmId}`);
702
+ }
582
703
  const dataMsg = {
583
704
  type: 'data',
584
705
  uid,
@@ -586,7 +707,7 @@ export class ClientSessionManager {
586
707
  data,
587
708
  timestamp: new Date().toISOString(),
588
709
  };
589
- this.sendQueue.put(dataMsg);
710
+ currentSmc.sendQueue.put(dataMsg);
590
711
  invLogger.debug(`Queued ${data.length} bytes to send`);
591
712
  },
592
713
  recv_message: async () => {
@@ -610,7 +731,7 @@ export class ClientSessionManager {
610
731
  /**
611
732
  * Echo streaming endpoint
612
733
  */
613
- async *echo(cancelSignal, uid, iid) {
734
+ async *echo(cancelSignal, uid, iid, includeUsage = false) {
614
735
  if (!this.baseHttp) {
615
736
  const error = new ConnectionError('Base HTTP endpoint must be set');
616
737
  enrichError(error, { uid, iid, sessionId: this.clientSessionId });
@@ -670,8 +791,16 @@ export class ClientSessionManager {
670
791
  const logmsg = JSON.parse(line);
671
792
  const typ = logmsg.type;
672
793
  if (typ === 'sm_invocation_exit') {
673
- this.logger.debug('Received invocation exit signal, ending stream');
674
- return;
794
+ // Only return if listening to a specific invocation.
795
+ // When iid is undefined, we're listening to all invocations
796
+ // for this agent and should continue across invocations.
797
+ if (iid !== undefined) {
798
+ this.logger.debug('Received invocation exit signal, ending stream');
799
+ return;
800
+ }
801
+ yield { role: 'agent', content: '', type: 'invocation_exit' };
802
+ isStreaming = false;
803
+ continue;
675
804
  }
676
805
  if (typ === 'sm_monad' && logmsg.body) {
677
806
  const body = JSON.parse(logmsg.body);
@@ -681,7 +810,7 @@ export class ClientSessionManager {
681
810
  isStreaming = true;
682
811
  const delta = body.args[0];
683
812
  if (delta && delta.content) {
684
- yield { role: 'agent', content: delta.content };
813
+ yield { role: 'agent', content: delta.content, type: delta.type };
685
814
  chunkCount++;
686
815
  }
687
816
  }
@@ -693,11 +822,15 @@ export class ClientSessionManager {
693
822
  // Skip system messages
694
823
  continue;
695
824
  }
696
- else if (isStreaming && role === 'agent') {
697
- // Skip agent messages as they're already handled in stream_chunk
825
+ else if (isStreaming && role === 'agent' && delta.type !== 'usage') {
826
+ // Skip agent messages as they're already handled in stream_chunk,
827
+ // except usage chunks which are only sent via delta
698
828
  continue;
699
829
  }
700
- yield { role, content: delta.content };
830
+ if (delta.type === 'usage' && !includeUsage) {
831
+ continue;
832
+ }
833
+ yield { role, content: delta.content, type: delta.type };
701
834
  chunkCount++;
702
835
  }
703
836
  }
@@ -804,29 +937,34 @@ export class ClientSessionManager {
804
937
  * @param lastTotal - Previous cumulative total (for cross-invocation adjustment)
805
938
  * @returns Object containing the usage for this invocation and the new cumulative total
806
939
  */
807
- async fetchUsage(uid, iid, lastTotal) {
940
+ async fetchUsage(uid, iid, _lastTotal) {
808
941
  // Fetch logs for this invocation and sum usages
942
+ // Each log entry contains non-cumulative usage values, so we just add them up
943
+ // The logs are already filtered by iid, so they only contain this invocation's data
809
944
  let total = Usage.zero();
810
945
  const logs = await this.logs(uid, iid, { type: 'sm_inference_usage' });
811
946
  for (const log of logs) {
812
947
  const logUsage = log.usage;
813
948
  if (logUsage) {
814
- total = total.add(Usage.fromCompletions(logUsage, total));
949
+ // Don't pass lastUsage - each log entry is already non-cumulative
950
+ total = total.add(Usage.fromCompletions(logUsage));
815
951
  }
816
952
  }
817
- let usage = total;
818
- if (lastTotal) {
819
- // Subtract previous cumulative total, but keep output_tokens as-is
820
- // since output_tokens is already absolute (not cumulative)
821
- usage = total.sub(lastTotal.replace({ outputTokens: 0 }));
822
- }
823
- return { usage, newTotal: total };
953
+ return { usage: total, newTotal: total };
824
954
  }
825
955
  /**
826
956
  * Check if agent exists - matches Python agent_exists
827
957
  */
828
958
  agentExists(uid) {
829
- return this.knownUids.has(uid) && this.websocket !== null && this.websocket.readyState === 1; // 1 = OPEN
959
+ if (!this.knownUids.has(uid)) {
960
+ return false;
961
+ }
962
+ const smId = this.uidToSm.get(uid);
963
+ if (!smId) {
964
+ return false;
965
+ }
966
+ const smc = this.sessionManagers.get(smId);
967
+ return smc !== undefined && smc.websocket.readyState === 1; // 1 = OPEN
830
968
  }
831
969
  /**
832
970
  * Get uid from iid (used for parent-child agent tracking)
@@ -867,7 +1005,7 @@ export class ClientSessionManager {
867
1005
  }
868
1006
  }
869
1007
  /**
870
- * Close a specific agent's resources (does not close the shared WebSocket)
1008
+ * Close a specific agent's resources (does not close the session manager's WebSocket unless it's the last agent)
871
1009
  */
872
1010
  async closeAgent(uid) {
873
1011
  const logger = this.uidToLogger.get(uid) ?? this.logger;
@@ -890,6 +1028,7 @@ export class ClientSessionManager {
890
1028
  this.uidIidException.delete(uid);
891
1029
  this.knownUids.delete(uid);
892
1030
  this.uidToLogger.delete(uid);
1031
+ this.uidToSm.delete(uid);
893
1032
  logger.debug(`Closed agent ${uid.slice(0, 8)}`);
894
1033
  // Finally, destroy the agent on the server
895
1034
  await this.destroyAgent(uid);
@@ -905,37 +1044,16 @@ export class ClientSessionManager {
905
1044
  const span = this.logger.startSpan('stop');
906
1045
  try {
907
1046
  const agentCount = this.knownUids.size;
908
- this.logger.debug(`CSM Stop: stopping with ${agentCount} agent(s)`);
1047
+ const smCount = this.sessionManagers.size;
1048
+ this.logger.debug(`CSM Stop: stopping with ${agentCount} agent(s) on ${smCount} session manager(s)`);
909
1049
  span.setAttribute('agent_count', agentCount);
1050
+ span.setAttribute('session_manager_count', smCount);
910
1051
  // Close all agents (cleans up per-agent state and destroys on server)
911
1052
  const uids = Array.from(this.knownUids);
912
1053
  await Promise.allSettled(uids.map((uid) => this.closeAgent(uid)));
913
- // Close send queue
914
- if (this.sendQueue) {
915
- this.sendQueue.close();
916
- this.sendQueue = null;
917
- }
918
- // Close WebSocket gracefully
919
- if (this.websocket) {
920
- try {
921
- // 0 = CONNECTING, 1 = OPEN
922
- if (this.websocket.readyState === 0 || this.websocket.readyState === 1) {
923
- this.websocket.close(1000, 'Normal closure');
924
- }
925
- }
926
- catch (error) {
927
- this.logger.warn('Failed to close WebSocket', error);
928
- }
929
- this.websocket = null;
930
- }
931
- // Stop tasks
932
- if (this.tasks) {
933
- const [readerControl, writerControl] = this.tasks;
934
- readerControl.shouldStop = true;
935
- writerControl.shouldStop = true;
936
- await Promise.allSettled([readerControl.promise, writerControl.promise]);
937
- this.tasks = null;
938
- }
1054
+ // Clean up all session manager connections
1055
+ const smIds = Array.from(this.sessionManagers.keys());
1056
+ await Promise.allSettled(smIds.map((smId) => this._cleanupSessionManager(smId)));
939
1057
  this.logger.debug('All resources cleaned up');
940
1058
  this.isStopped = true;
941
1059
  // Ensure invocation guard is cleared
@@ -1,6 +1,6 @@
1
1
  export type { components, operations, paths } from '../types/api.d';
2
2
  import type { components } from '../types/api.d';
3
- export type CreateAgentRequest = Omit<components['schemas']['CreateAgentRequest'], 'warp_globals_payload' | 'protocol'> & {
3
+ export type CreateAgentRequest = Omit<components['schemas']['CreateAgentRequest'], 'warp_globals_payload' | 'protocol' | 'json'> & {
4
4
  warp_globals_payload: Uint8Array;
5
5
  protocol?: string;
6
6
  };
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/client-session-manager/types.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAGpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAIjD,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACjC,UAAU,CAAC,SAAS,CAAC,CAAC,oBAAoB,CAAC,EAC3C,sBAAsB,GAAG,UAAU,CACtC,GAAG;IACA,oBAAoB,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,gBAAgB,CAAC,CAAC;AAGrE,MAAM,MAAM,sBAAsB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,wBAAwB,CAAC,CAAC;AACrF,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC,GAAG;IAC7F,IAAI,EAAE,UAAU,CAAC;CACpB,CAAC;AACF,MAAM,MAAM,qBAAqB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,uBAAuB,CAAC,CAAC;AACnF,MAAM,MAAM,0BAA0B,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,iCAAiC,CAAC,CAAC;AAClG,MAAM,MAAM,sBAAsB,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,wBAAwB,CAAC,EAAE,qBAAqB,CAAC,GAAG;IAChH,mBAAmB,EAAE,UAAU,CAAC;CACnC,CAAC;AACF,MAAM,MAAM,uBAAuB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,yBAAyB,CAAC,CAAC;AAGvF,MAAM,MAAM,gBAAgB,GACtB,oBAAoB,GACpB,qBAAqB,GACrB,0BAA0B,GAC1B,uBAAuB,GACvB,sBAAsB,GACtB,sBAAsB,CAAC;AAE7B,MAAM,MAAM,sBAAsB,GAAG,sBAAsB,GAAG,oBAAoB,GAAG,sBAAsB,CAAC;AAE5G,MAAM,MAAM,8BAA8B,GAAG,oBAAoB,GAAG,sBAAsB,CAAC;AAE3F,MAAM,MAAM,sBAAsB,GAC5B,uBAAuB,GACvB,qBAAqB,GACrB,oBAAoB,GACpB,0BAA0B,CAAC;AAEjC,MAAM,MAAM,8BAA8B,GAAG,qBAAqB,GAAG,oBAAoB,GAAG,0BAA0B,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/client-session-manager/types.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAGpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAIjD,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACjC,UAAU,CAAC,SAAS,CAAC,CAAC,oBAAoB,CAAC,EAC3C,sBAAsB,GAAG,UAAU,GAAG,MAAM,CAC/C,GAAG;IACA,oBAAoB,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,gBAAgB,CAAC,CAAC;AAGrE,MAAM,MAAM,sBAAsB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,wBAAwB,CAAC,CAAC;AACrF,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC,GAAG;IAC7F,IAAI,EAAE,UAAU,CAAC;CACpB,CAAC;AACF,MAAM,MAAM,qBAAqB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,uBAAuB,CAAC,CAAC;AACnF,MAAM,MAAM,0BAA0B,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,iCAAiC,CAAC,CAAC;AAClG,MAAM,MAAM,sBAAsB,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,wBAAwB,CAAC,EAAE,qBAAqB,CAAC,GAAG;IAChH,mBAAmB,EAAE,UAAU,CAAC;CACnC,CAAC;AACF,MAAM,MAAM,uBAAuB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,yBAAyB,CAAC,CAAC;AAGvF,MAAM,MAAM,gBAAgB,GACtB,oBAAoB,GACpB,qBAAqB,GACrB,0BAA0B,GAC1B,uBAAuB,GACvB,sBAAsB,GACtB,sBAAsB,CAAC;AAE7B,MAAM,MAAM,sBAAsB,GAAG,sBAAsB,GAAG,oBAAoB,GAAG,sBAAsB,CAAC;AAE5G,MAAM,MAAM,8BAA8B,GAAG,oBAAoB,GAAG,sBAAsB,CAAC;AAE3F,MAAM,MAAM,sBAAsB,GAC5B,uBAAuB,GACvB,qBAAqB,GACrB,oBAAoB,GACpB,0BAA0B,CAAC;AAEjC,MAAM,MAAM,8BAA8B,GAAG,qBAAqB,GAAG,oBAAoB,GAAG,0BAA0B,CAAC"}
@@ -1,7 +1,5 @@
1
1
  import type { FrameContext } from './warpc/frame-context/frame-ctx.js';
2
- import type { ToolModeStrings } from './agentica/common.js';
3
2
  declare enum Feature {
4
- JSON_MODE = "JSON mode",
5
3
  ENUM_VALUES = "Enum support",
6
4
  FUTURE_VALUES = "Promises and futures",
7
5
  LOCAL_FILE_ACCESS = "Local file access",
@@ -9,11 +7,9 @@ declare enum Feature {
9
7
  NUMERIC_RANGES = "Numeric range objects",
10
8
  BINARY_BUFFERS = "Binary buffer objects",
11
9
  URL_OBJECTS = "URL objects",
12
- JSON_BASE_EXCEPTIONS = "Richer exceptions in JSON mode",
13
10
  CUSTOM_EXCEPTIONS = "Custom exceptions",
14
11
  TYPE = "Type",
15
12
  TYPE_CONSTRUCTORS = "Type constructor",
16
- ARBITRARY = "Arbitrary",
17
13
  VIRTUALIZATION = "Virtualization",
18
14
  UNSUPPORTED_BUILTIN_CLASS = "Unsupported builtin class",
19
15
  GENERATORS = "Generators",
@@ -22,23 +18,16 @@ declare enum Feature {
22
18
  }
23
19
  export declare class ComingSoon extends Error {
24
20
  feature: Feature;
25
- mode?: ToolModeStrings;
26
21
  detail?: string;
27
- constructor(feature: Feature, mode?: ToolModeStrings, detail?: string);
28
- }
29
- export declare class TryCodeMode extends Error {
30
- feature: Feature;
31
- detail: string;
32
22
  constructor(feature: Feature, detail?: string);
33
23
  }
34
- export declare const validateFeature: (thing: any, mode?: ToolModeStrings) => void;
35
- export declare const constructorTypes: (mode?: ToolModeStrings) => ComingSoon;
36
- export declare const unsupportedBultinClass: (className: string, mode?: ToolModeStrings) => ComingSoon;
37
- export declare const unsupportedEnum: (enumName: string, mode?: ToolModeStrings) => ComingSoon;
38
- export declare const interfaceReturnType: (mode?: ToolModeStrings) => ComingSoon;
39
- export declare const intersectionReturnType: (mode?: ToolModeStrings) => ComingSoon;
40
- export declare const validateReturnType: (returnType: any, context: FrameContext, mode?: ToolModeStrings) => void;
41
- export declare const throwNoJsonMode: () => never;
24
+ export declare const validateFeature: (thing: any) => void;
25
+ export declare const constructorTypes: () => ComingSoon;
26
+ export declare const unsupportedBultinClass: (className: string) => ComingSoon;
27
+ export declare const unsupportedEnum: (enumName: string) => ComingSoon;
28
+ export declare const interfaceReturnType: () => ComingSoon;
29
+ export declare const intersectionReturnType: () => ComingSoon;
30
+ export declare const validateReturnType: (returnType: any, context: FrameContext) => void;
42
31
  export declare const throwNoVirtualization: () => never;
43
32
  export declare const throwNoBareFutures: () => never;
44
33
  export default ComingSoon;
@@ -1 +1 @@
1
- {"version":3,"file":"coming-soon.d.ts","sourceRoot":"","sources":["../src/coming-soon.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAIxD,aAAK,OAAO;IACR,SAAS,cAAc;IACvB,WAAW,iBAAiB;IAC5B,aAAa,yBAAyB;IACtC,iBAAiB,sBAAsB;IACvC,kBAAkB,8BAA8B;IAChD,cAAc,0BAA0B;IACxC,cAAc,0BAA0B;IACxC,WAAW,gBAAgB;IAC3B,oBAAoB,mCAAmC;IACvD,iBAAiB,sBAAsB;IACvC,IAAI,SAAS;IACb,iBAAiB,qBAAqB;IACtC,SAAS,cAAc;IACvB,cAAc,mBAAmB;IACjC,yBAAyB,8BAA8B;IACvD,UAAU,eAAe;IACzB,qBAAqB,6BAA6B;IAClD,wBAAwB,gCAAgC;CAC3D;AAED,qBAAa,UAAW,SAAQ,KAAK;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;gBAEJ,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,eAAe,EAAE,MAAM,GAAE,MAAW;CAM5E;AAED,qBAAa,WAAY,SAAQ,KAAK;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;gBAEH,OAAO,EAAE,OAAO,EAAE,MAAM,GAAE,MAAW;CAKpD;AAwDD,eAAO,MAAM,eAAe,GAAI,OAAO,GAAG,EAAE,OAAM,eAAwB,KAAG,IAmD5E,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,OAAM,eAAwB,KAAG,UAEjE,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,WAAW,MAAM,EAAE,OAAM,eAAwB,KAAG,UAE1F,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,UAAU,MAAM,EAAE,OAAM,eAAwB,KAAG,UAMlF,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,OAAM,eAAwB,KAAG,UAEpE,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,OAAM,eAAwB,KAAG,UAEvE,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,YAAY,GAAG,EAAE,SAAS,YAAY,EAAE,OAAM,eAAwB,KAAG,IAqC3G,CAAC;AAEF,eAAO,MAAM,eAAe,aAE3B,CAAC;AAEF,eAAO,MAAM,qBAAqB,aAEjC,CAAC;AAEF,eAAO,MAAM,kBAAkB,aAE9B,CAAC;AAEF,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"coming-soon.d.ts","sourceRoot":"","sources":["../src/coming-soon.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAIpE,aAAK,OAAO;IACR,WAAW,iBAAiB;IAC5B,aAAa,yBAAyB;IACtC,iBAAiB,sBAAsB;IACvC,kBAAkB,8BAA8B;IAChD,cAAc,0BAA0B;IACxC,cAAc,0BAA0B;IACxC,WAAW,gBAAgB;IAC3B,iBAAiB,sBAAsB;IACvC,IAAI,SAAS;IACb,iBAAiB,qBAAqB;IACtC,cAAc,mBAAmB;IACjC,yBAAyB,8BAA8B;IACvD,UAAU,eAAe;IACzB,qBAAqB,6BAA6B;IAClD,wBAAwB,gCAAgC;CAC3D;AAED,qBAAa,UAAW,SAAQ,KAAK;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;gBAEJ,OAAO,EAAE,OAAO,EAAE,MAAM,GAAE,MAAW;CAKpD;AAwDD,eAAO,MAAM,eAAe,GAAI,OAAO,GAAG,KAAG,IA8C5C,CAAC;AAEF,eAAO,MAAM,gBAAgB,QAAO,UAEnC,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,WAAW,MAAM,KAAG,UAE1D,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,UAAU,MAAM,KAAG,UAKlD,CAAC;AAEF,eAAO,MAAM,mBAAmB,QAAO,UAEtC,CAAC;AAEF,eAAO,MAAM,sBAAsB,QAAO,UAEzC,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,YAAY,GAAG,EAAE,SAAS,YAAY,KAAG,IAqC3E,CAAC;AAEF,eAAO,MAAM,qBAAqB,aAEjC,CAAC;AAEF,eAAO,MAAM,kBAAkB,aAE9B,CAAC;AAEF,eAAe,UAAU,CAAC"}