@symbolica/agentica 0.3.1 → 0.4.0

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 (74) hide show
  1. package/LICENSE +21 -77
  2. package/README.md +10 -9
  3. package/THIRD_PARTY_NOTICES.md +27 -0
  4. package/dist/src/agentica/agent.d.ts +3 -2
  5. package/dist/src/agentica/agent.d.ts.map +1 -1
  6. package/dist/src/agentica/agent.js +3 -4
  7. package/dist/src/agentica/agent.js.map +1 -1
  8. package/dist/src/agentica/agentic.d.ts.map +1 -1
  9. package/dist/src/agentica/agentic.js +1 -3
  10. package/dist/src/agentica/agentic.js.map +1 -1
  11. package/dist/src/agentica/common.d.ts +10 -5
  12. package/dist/src/agentica/common.d.ts.map +1 -1
  13. package/dist/src/agentica/common.js +32 -17
  14. package/dist/src/agentica/common.js.map +1 -1
  15. package/dist/src/agentica-unplugin.d.ts +2 -2
  16. package/dist/src/agentica-unplugin.d.ts.map +1 -1
  17. package/dist/src/bundlers/esbuild.d.ts +1 -1
  18. package/dist/src/bundlers/vite.d.ts +1 -1
  19. package/dist/src/client-session-manager/client-session-manager.d.ts +10 -8
  20. package/dist/src/client-session-manager/client-session-manager.d.ts.map +1 -1
  21. package/dist/src/client-session-manager/client-session-manager.js +172 -95
  22. package/dist/src/client-session-manager/client-session-manager.js.map +1 -1
  23. package/dist/src/client-session-manager/types.d.ts +1 -1
  24. package/dist/src/client-session-manager/types.d.ts.map +1 -1
  25. package/dist/src/coming-soon.d.ts +7 -18
  26. package/dist/src/coming-soon.d.ts.map +1 -1
  27. package/dist/src/coming-soon.js +21 -41
  28. package/dist/src/coming-soon.js.map +1 -1
  29. package/dist/src/version.d.ts +1 -1
  30. package/dist/src/version.js +1 -1
  31. package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.d.ts +7 -0
  32. package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.d.ts.map +1 -0
  33. package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.js +144 -0
  34. package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.js.map +1 -0
  35. package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.test.d.ts +2 -0
  36. package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.test.d.ts.map +1 -0
  37. package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.test.js +253 -0
  38. package/dist/src/warpc/msg-protocol/concept/python-mirrors/str.test.js.map +1 -0
  39. package/dist/src/warpc/msg-protocol/concept/python-mirrors/utils.d.ts +5 -0
  40. package/dist/src/warpc/msg-protocol/concept/python-mirrors/utils.d.ts.map +1 -0
  41. package/dist/src/warpc/msg-protocol/concept/python-mirrors/utils.js +17 -0
  42. package/dist/src/warpc/msg-protocol/concept/python-mirrors/utils.js.map +1 -0
  43. package/dist/src/warpc/msg-protocol/concept/resource/system-msg.d.ts +2 -1
  44. package/dist/src/warpc/msg-protocol/concept/resource/system-msg.d.ts.map +1 -1
  45. package/dist/src/warpc/msg-protocol/concept/resource/system-msg.js +14 -10
  46. package/dist/src/warpc/msg-protocol/concept/resource/system-msg.js.map +1 -1
  47. package/dist-transformer/agentica/agent.d.ts +4 -2
  48. package/dist-transformer/agentica/agent.d.ts.map +1 -1
  49. package/dist-transformer/agentica/agent.js +3 -3
  50. package/dist-transformer/agentica/agentic.d.ts.map +1 -1
  51. package/dist-transformer/agentica/agentic.js +1 -3
  52. package/dist-transformer/agentica/common.d.ts +23 -13
  53. package/dist-transformer/agentica/common.d.ts.map +1 -1
  54. package/dist-transformer/agentica/common.js +46 -25
  55. package/dist-transformer/client-session-manager/client-session-manager.d.ts +20 -12
  56. package/dist-transformer/client-session-manager/client-session-manager.d.ts.map +1 -1
  57. package/dist-transformer/client-session-manager/client-session-manager.js +214 -108
  58. package/dist-transformer/client-session-manager/types.d.ts +1 -1
  59. package/dist-transformer/client-session-manager/types.d.ts.map +1 -1
  60. package/dist-transformer/coming-soon.d.ts +7 -18
  61. package/dist-transformer/coming-soon.d.ts.map +1 -1
  62. package/dist-transformer/coming-soon.js +21 -39
  63. package/dist-transformer/version.d.ts +1 -1
  64. package/dist-transformer/version.js +1 -1
  65. package/dist-transformer/warpc/msg-protocol/concept/python-mirrors/str.d.ts +7 -0
  66. package/dist-transformer/warpc/msg-protocol/concept/python-mirrors/str.d.ts.map +1 -0
  67. package/dist-transformer/warpc/msg-protocol/concept/python-mirrors/str.js +151 -0
  68. package/dist-transformer/warpc/msg-protocol/concept/python-mirrors/utils.d.ts +5 -0
  69. package/dist-transformer/warpc/msg-protocol/concept/python-mirrors/utils.d.ts.map +1 -0
  70. package/dist-transformer/warpc/msg-protocol/concept/python-mirrors/utils.js +16 -0
  71. package/dist-transformer/warpc/msg-protocol/concept/resource/system-msg.d.ts +2 -1
  72. package/dist-transformer/warpc/msg-protocol/concept/resource/system-msg.d.ts.map +1 -1
  73. package/dist-transformer/warpc/msg-protocol/concept/resource/system-msg.js +14 -11
  74. package/package.json +5 -3
@@ -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.
351
333
  */
352
- async _ensureWebSocketConnection() {
353
- if (this.websocket && this.websocket.readyState === 1) {
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) {
354
365
  return;
355
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.
403
+ */
404
+ async _ensureSmConnection(smId) {
405
+ // Fast path: connection already exists
406
+ const existing = this.sessionManagers.get(smId);
407
+ if (existing && existing.websocket.readyState === 1) {
408
+ return;
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 () => {
@@ -804,29 +925,34 @@ export class ClientSessionManager {
804
925
  * @param lastTotal - Previous cumulative total (for cross-invocation adjustment)
805
926
  * @returns Object containing the usage for this invocation and the new cumulative total
806
927
  */
807
- async fetchUsage(uid, iid, lastTotal) {
928
+ async fetchUsage(uid, iid, _lastTotal) {
808
929
  // Fetch logs for this invocation and sum usages
930
+ // Each log entry contains non-cumulative usage values, so we just add them up
931
+ // The logs are already filtered by iid, so they only contain this invocation's data
809
932
  let total = Usage.zero();
810
933
  const logs = await this.logs(uid, iid, { type: 'sm_inference_usage' });
811
934
  for (const log of logs) {
812
935
  const logUsage = log.usage;
813
936
  if (logUsage) {
814
- total = total.add(Usage.fromCompletions(logUsage, total));
937
+ // Don't pass lastUsage - each log entry is already non-cumulative
938
+ total = total.add(Usage.fromCompletions(logUsage));
815
939
  }
816
940
  }
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 };
941
+ return { usage: total, newTotal: total };
824
942
  }
825
943
  /**
826
944
  * Check if agent exists - matches Python agent_exists
827
945
  */
828
946
  agentExists(uid) {
829
- return this.knownUids.has(uid) && this.websocket !== null && this.websocket.readyState === 1; // 1 = OPEN
947
+ if (!this.knownUids.has(uid)) {
948
+ return false;
949
+ }
950
+ const smId = this.uidToSm.get(uid);
951
+ if (!smId) {
952
+ return false;
953
+ }
954
+ const smc = this.sessionManagers.get(smId);
955
+ return smc !== undefined && smc.websocket.readyState === 1; // 1 = OPEN
830
956
  }
831
957
  /**
832
958
  * Get uid from iid (used for parent-child agent tracking)
@@ -867,7 +993,7 @@ export class ClientSessionManager {
867
993
  }
868
994
  }
869
995
  /**
870
- * Close a specific agent's resources (does not close the shared WebSocket)
996
+ * Close a specific agent's resources (does not close the session manager's WebSocket unless it's the last agent)
871
997
  */
872
998
  async closeAgent(uid) {
873
999
  const logger = this.uidToLogger.get(uid) ?? this.logger;
@@ -890,6 +1016,7 @@ export class ClientSessionManager {
890
1016
  this.uidIidException.delete(uid);
891
1017
  this.knownUids.delete(uid);
892
1018
  this.uidToLogger.delete(uid);
1019
+ this.uidToSm.delete(uid);
893
1020
  logger.debug(`Closed agent ${uid.slice(0, 8)}`);
894
1021
  // Finally, destroy the agent on the server
895
1022
  await this.destroyAgent(uid);
@@ -905,37 +1032,16 @@ export class ClientSessionManager {
905
1032
  const span = this.logger.startSpan('stop');
906
1033
  try {
907
1034
  const agentCount = this.knownUids.size;
908
- this.logger.debug(`CSM Stop: stopping with ${agentCount} agent(s)`);
1035
+ const smCount = this.sessionManagers.size;
1036
+ this.logger.debug(`CSM Stop: stopping with ${agentCount} agent(s) on ${smCount} session manager(s)`);
909
1037
  span.setAttribute('agent_count', agentCount);
1038
+ span.setAttribute('session_manager_count', smCount);
910
1039
  // Close all agents (cleans up per-agent state and destroys on server)
911
1040
  const uids = Array.from(this.knownUids);
912
1041
  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
- }
1042
+ // Clean up all session manager connections
1043
+ const smIds = Array.from(this.sessionManagers.keys());
1044
+ await Promise.allSettled(smIds.map((smId) => this._cleanupSessionManager(smId)));
939
1045
  this.logger.debug('All resources cleaned up');
940
1046
  this.isStopped = true;
941
1047
  // 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"}