@band-ai/sdk 0.2.0 → 0.3.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 (37) hide show
  1. package/dist/{ClaudeSDKAdapter-Cna5P1dR.d.ts → ClaudeSDKAdapter-CXud2DBE.d.ts} +2 -1
  2. package/dist/{ClaudeSDKAdapter-BY4zEbCl.d.cts → ClaudeSDKAdapter-Cx6zhSaG.d.cts} +2 -1
  3. package/dist/adapters.cjs +347 -172
  4. package/dist/adapters.d.cts +22 -5
  5. package/dist/adapters.d.ts +22 -5
  6. package/dist/adapters.js +204 -60
  7. package/dist/chunk-F63TPOI3.js +29 -0
  8. package/dist/{chunk-3BKAC4OZ.js → chunk-JADCAMZY.js} +8 -0
  9. package/dist/{chunk-N2QTUMGO.js → chunk-JJF44XWA.js} +365 -249
  10. package/dist/{chunk-TLBCKDQO.js → chunk-K5KAEYPF.js} +2 -2
  11. package/dist/{chunk-ZZJRRBPQ.js → chunk-KXJ74J73.js} +1 -1
  12. package/dist/{chunk-NMZLX7TP.js → chunk-MQBBWYRP.js} +52 -45
  13. package/dist/{chunk-RV76SKFT.js → chunk-MQY74R7D.js} +2 -20
  14. package/dist/{chunk-LBWKISBS.js → chunk-OH6VVVKT.js} +21 -0
  15. package/dist/{chunk-EAI2M3WM.js → chunk-W5IAI6Z4.js} +1 -1
  16. package/dist/{chunk-4IAXZBXX.js → chunk-WA7CHXUS.js} +2 -2
  17. package/dist/{chunk-NT7TBFX2.js → chunk-XWHWJP4I.js} +2 -2
  18. package/dist/{chunk-WLCM2OXG.js → chunk-YJ42AZO4.js} +1 -1
  19. package/dist/converters.js +6 -5
  20. package/dist/index.cjs +405 -277
  21. package/dist/index.d.cts +5 -5
  22. package/dist/index.d.ts +5 -5
  23. package/dist/index.js +12 -11
  24. package/dist/mcp-claude.js +4 -3
  25. package/dist/mcp.js +5 -4
  26. package/dist/rest.js +3 -3
  27. package/dist/runtime.cjs +387 -362
  28. package/dist/runtime.d.cts +10 -39
  29. package/dist/runtime.d.ts +10 -39
  30. package/dist/runtime.js +20 -142
  31. package/dist/{schemas-BotL3ZLH.d.cts → schemas-BgJhlqqO.d.cts} +57 -13
  32. package/dist/{schemas-DBsC4J1g.d.ts → schemas-C4mK1WSs.d.ts} +57 -13
  33. package/dist/testing.cjs +11 -0
  34. package/dist/testing.js +8 -1
  35. package/dist/{types-D958TC71.d.cts → types-BNNtOXu3.d.cts} +1 -1
  36. package/dist/{types-BTN_ZETM.d.ts → types-DFUtpxah.d.ts} +1 -1
  37. package/package.json +2 -2
package/dist/index.cjs CHANGED
@@ -86,6 +86,13 @@ function isToolExecutorError(value) {
86
86
  const candidate = value;
87
87
  return candidate.ok === false && typeof candidate.errorType === "string" && TOOL_EXECUTOR_ERROR_TYPES.includes(candidate.errorType) && typeof candidate.toolName === "string" && typeof candidate.message === "string" && typeof candidate.legacyMessage === "string";
88
88
  }
89
+ function isStructuredToolFailure(value) {
90
+ if (!value || typeof value !== "object") {
91
+ return false;
92
+ }
93
+ const payload = value;
94
+ return payload.ok === false && typeof payload.message === "string";
95
+ }
89
96
  function toLegacyToolExecutorErrorMessage(value) {
90
97
  if (typeof value === "string") {
91
98
  return value;
@@ -3349,10 +3356,12 @@ var AgentTools = class {
3349
3356
  roster;
3350
3357
  adapterTools;
3351
3358
  toolHandlers;
3359
+ logger;
3352
3360
  constructor(options) {
3353
3361
  this.roomId = options.roomId;
3354
3362
  this.rest = options.rest;
3355
3363
  this.roster = options.roster ?? new import_band_sdk_core.ParticipantRoster();
3364
+ this.logger = options.logger ?? new NoopLogger();
3356
3365
  this.capabilities = {
3357
3366
  ...DEFAULT_AGENT_TOOLS_CAPABILITIES,
3358
3367
  ...options.capabilities
@@ -3382,14 +3391,23 @@ var AgentTools = class {
3382
3391
  }
3383
3392
  async sendEvent(content, messageType, metadata) {
3384
3393
  assertChatEventType(messageType);
3385
- return this.rest.createChatEvent(
3386
- this.roomId,
3387
- {
3388
- content,
3389
- messageType,
3390
- metadata
3394
+ try {
3395
+ return await this.rest.createChatEvent(
3396
+ this.roomId,
3397
+ {
3398
+ content,
3399
+ messageType,
3400
+ metadata
3401
+ }
3402
+ );
3403
+ } catch (error) {
3404
+ try {
3405
+ this.logger.warn("chat event send failed", { roomId: this.roomId, messageType, error });
3406
+ } catch {
3391
3407
  }
3392
- );
3408
+ const message = error instanceof Error ? error.message : String(error);
3409
+ return { ok: false, status: "failed", message };
3410
+ }
3393
3411
  }
3394
3412
  async createChatroom(taskId) {
3395
3413
  const room = await this.rest.createChat(taskId, DEFAULT_REQUEST_OPTIONS);
@@ -3492,7 +3510,16 @@ var AgentTools = class {
3492
3510
  });
3493
3511
  }
3494
3512
  try {
3495
- return await handler(arguments_);
3513
+ const result = await handler(arguments_);
3514
+ if (toolName === "band_send_event" && isStructuredToolFailure(result) && !isToolExecutorError(result)) {
3515
+ return createToolExecutorError({
3516
+ errorType: "ToolExecutionError",
3517
+ toolName,
3518
+ message: result.message,
3519
+ legacyMessage: `Error executing ${toolName}: ${result.message}`
3520
+ });
3521
+ }
3522
+ return result;
3496
3523
  } catch (error) {
3497
3524
  if (isToolExecutorError(error)) {
3498
3525
  return error;
@@ -4193,7 +4220,8 @@ var ExecutionContext = class {
4193
4220
  roomId: this.roomId,
4194
4221
  rest: this.link.rest,
4195
4222
  roster: this.roster,
4196
- capabilities: this.link.capabilities
4223
+ capabilities: this.link.capabilities,
4224
+ logger: options.logger ?? new NoopLogger()
4197
4225
  });
4198
4226
  this.adapterTools = this.tools.getAdapterTools();
4199
4227
  }
@@ -4441,71 +4469,280 @@ var ExecutionContext = class {
4441
4469
  }
4442
4470
  };
4443
4471
 
4472
+ // src/runtime/rooms/RoomPresence.ts
4473
+ init_errors();
4474
+ var import_band_sdk_core3 = require("@band-ai/band-sdk-core");
4475
+
4444
4476
  // src/runtime/rooms/subscriptions.ts
4445
4477
  init_errors();
4446
- function hasRoomId(roomId) {
4447
- return typeof roomId === "string" && roomId.length > 0;
4478
+ async function hydrateExistingRooms(options) {
4479
+ try {
4480
+ const rooms = await options.link.listAllChats(void 0, options.requestOptions);
4481
+ for (const room of rooms) {
4482
+ const roomId = typeof room.id === "string" ? room.id : null;
4483
+ if (!roomId) {
4484
+ continue;
4485
+ }
4486
+ if (options.roomFilter && !options.roomFilter(room)) {
4487
+ continue;
4488
+ }
4489
+ await options.onRoom(roomId, room);
4490
+ }
4491
+ } catch (error) {
4492
+ if (error instanceof UnsupportedFeatureError) {
4493
+ return;
4494
+ }
4495
+ if (options.onError) {
4496
+ await options.onError(error);
4497
+ return;
4498
+ }
4499
+ throw error;
4500
+ }
4448
4501
  }
4449
- async function trackRoomJoin(options) {
4450
- if (!hasRoomId(options.roomId)) {
4451
- return false;
4502
+
4503
+ // src/runtime/rooms/RoomPresence.ts
4504
+ var RoomPresence = class {
4505
+ roster = new import_band_sdk_core3.RoomRoster();
4506
+ onRoomJoined = null;
4507
+ onRoomLeft = null;
4508
+ onRoomEvent = null;
4509
+ onContactEvent = null;
4510
+ link;
4511
+ roomFilter;
4512
+ autoSubscribeExistingRooms;
4513
+ logger;
4514
+ eventController = null;
4515
+ eventTask = null;
4516
+ contactsSubscribed = false;
4517
+ lifecycle = Promise.resolve();
4518
+ admissionInFlight = /* @__PURE__ */ new Map();
4519
+ // Read once by `admitRoomOrThrow` right after a failed admission; a
4520
+ // subsequent successful subscribe clears it so a caller never attributes
4521
+ // today's failure to a stale reason from an earlier attempt.
4522
+ lastSubscribeError = /* @__PURE__ */ new Map();
4523
+ constructor(options) {
4524
+ this.link = options.link;
4525
+ this.roomFilter = options.roomFilter;
4526
+ this.autoSubscribeExistingRooms = options.autoSubscribeExistingRooms ?? true;
4527
+ this.logger = options.logger ?? new NoopLogger();
4452
4528
  }
4453
- if (options.roomFilter && !options.roomFilter(options.payload)) {
4454
- return false;
4529
+ async start() {
4530
+ return this.serialize(() => this.startBody());
4455
4531
  }
4456
- if (options.trackedRooms.has(options.roomId)) {
4457
- return false;
4532
+ async stop() {
4533
+ return this.serialize(() => this.stopBody());
4458
4534
  }
4459
- await options.link.subscribeRoom(options.roomId);
4460
- options.trackedRooms.add(options.roomId);
4461
- if (options.onJoined) {
4462
- await options.onJoined(options.roomId, options.payload);
4535
+ async [Symbol.asyncDispose]() {
4536
+ await this.stop();
4463
4537
  }
4464
- return true;
4465
- }
4466
- async function trackRoomLeave(options) {
4467
- if (!hasRoomId(options.roomId) || !options.trackedRooms.has(options.roomId)) {
4468
- return false;
4538
+ abortEventLoop() {
4539
+ this.eventController?.abort();
4469
4540
  }
4470
- await options.link.unsubscribeRoom(options.roomId);
4471
- options.trackedRooms.delete(options.roomId);
4472
- if (options.onLeft) {
4473
- await options.onLeft(options.roomId);
4541
+ async waitUntilStopped() {
4542
+ await this.eventTask;
4474
4543
  }
4475
- return true;
4476
- }
4477
- async function hydrateTrackedRooms(options) {
4478
- const pageSize = options.pageSize ?? 100;
4479
- const maxPages = options.maxPages ?? 100;
4480
- try {
4481
- const rooms = await options.link.listAllChats(
4482
- { pageSize, maxPages },
4483
- options.requestOptions
4544
+ async admitRoom(roomId, payload, notify = true) {
4545
+ const ticket = this.roster.beginRoomAdmission(roomId, true);
4546
+ const admitted = ticket === void 0 ? await this.awaitInFlightAdmission(roomId) : await this.performAdmission(roomId, ticket);
4547
+ if (admitted && notify) {
4548
+ await this.onRoomJoined?.(roomId, payload);
4549
+ }
4550
+ return admitted;
4551
+ }
4552
+ /** Admit a room for a caller that needs to know *why* a failed subscribe happened, not just that it did. */
4553
+ async admitRoomOrThrow(roomId) {
4554
+ if (!await this.admitRoom(roomId, {}, false)) {
4555
+ throw new TransportError(`Failed to subscribe to room ${roomId}`, this.lastSubscribeError.get(roomId));
4556
+ }
4557
+ }
4558
+ async awaitInFlightAdmission(roomId) {
4559
+ const inFlight = this.admissionInFlight.get(roomId);
4560
+ return inFlight ? await inFlight : this.roster.roomMembership(roomId) === "admitted";
4561
+ }
4562
+ async performAdmission(roomId, ticket) {
4563
+ const admission = this.completeAdmission(roomId, ticket);
4564
+ this.admissionInFlight.set(roomId, admission);
4565
+ try {
4566
+ return await admission;
4567
+ } finally {
4568
+ if (this.admissionInFlight.get(roomId) === admission) {
4569
+ this.admissionInFlight.delete(roomId);
4570
+ }
4571
+ }
4572
+ }
4573
+ async completeAdmission(roomId, ticket) {
4574
+ let succeeded = false;
4575
+ let admitted = false;
4576
+ let subscribeError;
4577
+ try {
4578
+ await this.link.subscribeRoom(roomId);
4579
+ succeeded = true;
4580
+ } catch (error) {
4581
+ this.logger.warn("RoomPresence failed to subscribe room", { roomId, error });
4582
+ subscribeError = error;
4583
+ } finally {
4584
+ admitted = this.roster.recordRoomAdmission(roomId, ticket, succeeded);
4585
+ }
4586
+ const roomIsAdmitted = admitted || this.roster.roomMembership(roomId) === "admitted";
4587
+ if (succeeded && !admitted && !roomIsAdmitted) {
4588
+ this.logger.debug("RoomPresence admission ticket went stale", { roomId });
4589
+ await this.unsubscribeRoom(roomId);
4590
+ }
4591
+ if (roomIsAdmitted) {
4592
+ this.lastSubscribeError.delete(roomId);
4593
+ } else if (subscribeError !== void 0) {
4594
+ this.lastSubscribeError.set(roomId, subscribeError);
4595
+ }
4596
+ return roomIsAdmitted;
4597
+ }
4598
+ async serialize(body) {
4599
+ const run = this.lifecycle.then(body, body);
4600
+ this.lifecycle = run.then(
4601
+ () => void 0,
4602
+ () => void 0
4484
4603
  );
4485
- for (const room of rooms) {
4486
- await trackRoomJoin({
4487
- link: options.link,
4488
- roomId: typeof room.id === "string" ? room.id : null,
4489
- payload: room,
4490
- trackedRooms: options.trackedRooms,
4491
- roomFilter: options.roomFilter,
4492
- onJoined: options.onJoined
4604
+ return run;
4605
+ }
4606
+ async startBody() {
4607
+ if (this.eventTask) {
4608
+ throw new RuntimeStateError("RoomPresence is already started");
4609
+ }
4610
+ if (!this.link.isConnected()) {
4611
+ await this.link.connect();
4612
+ }
4613
+ const contactsReady = this.subscribeContacts();
4614
+ try {
4615
+ await this.link.subscribeAgentRooms();
4616
+ } catch (error) {
4617
+ this.logger.warn("RoomPresence failed to subscribe agent_rooms channel, continuing without it", {
4618
+ error
4493
4619
  });
4494
4620
  }
4495
- } catch (error) {
4496
- if (error instanceof UnsupportedFeatureError) {
4621
+ if (this.autoSubscribeExistingRooms) {
4622
+ await this.subscribeExistingRooms();
4623
+ }
4624
+ await contactsReady;
4625
+ this.eventController = new AbortController();
4626
+ this.eventTask = this.consumeEvents(this.eventController.signal);
4627
+ }
4628
+ async subscribeContacts() {
4629
+ if (!this.link.capabilities.contacts) {
4497
4630
  return;
4498
4631
  }
4499
- if (options.onError) {
4500
- await options.onError(error);
4632
+ try {
4633
+ await this.link.subscribeAgentContacts();
4634
+ this.contactsSubscribed = true;
4635
+ } catch (error) {
4636
+ this.logger.warn("RoomPresence failed to subscribe agent_contacts channel, continuing without it", {
4637
+ error
4638
+ });
4639
+ }
4640
+ }
4641
+ async stopBody() {
4642
+ this.abortEventLoop();
4643
+ await this.eventTask?.catch(() => void 0);
4644
+ this.eventTask = null;
4645
+ this.eventController = null;
4646
+ if (this.contactsSubscribed) {
4647
+ try {
4648
+ await this.link.unsubscribeAgentContacts();
4649
+ } catch (error) {
4650
+ this.logger.warn("RoomPresence failed to unsubscribe agent_contacts channel", { error });
4651
+ } finally {
4652
+ this.contactsSubscribed = false;
4653
+ }
4654
+ }
4655
+ const roomIds = this.roster.trackedRoomIds();
4656
+ this.roster.clear();
4657
+ this.lastSubscribeError.clear();
4658
+ await Promise.all(
4659
+ roomIds.map(async (roomId) => {
4660
+ await this.unsubscribeRoom(roomId);
4661
+ await this.onRoomLeft?.(roomId);
4662
+ })
4663
+ );
4664
+ }
4665
+ async consumeEvents(signal) {
4666
+ while (!signal.aborted) {
4667
+ const event = await this.link.nextEvent(signal);
4668
+ if (!event) {
4669
+ return;
4670
+ }
4671
+ switch (event.type) {
4672
+ case "room_added":
4673
+ await this.handleRoomAdded(event.roomId, event.payload);
4674
+ break;
4675
+ case "room_removed":
4676
+ case "room_deleted":
4677
+ await this.handleRoomRemoved(event.roomId);
4678
+ break;
4679
+ case "contact_request_received":
4680
+ case "contact_request_updated":
4681
+ case "contact_added":
4682
+ case "contact_removed":
4683
+ await this.onContactEvent?.(event);
4684
+ break;
4685
+ case "message_created":
4686
+ case "participant_added":
4687
+ case "participant_removed":
4688
+ if (event.roomId && this.roster.roomMembership(event.roomId) === "admitted") {
4689
+ await this.onRoomEvent?.(event.roomId, event);
4690
+ }
4691
+ break;
4692
+ default:
4693
+ assertNever(event);
4694
+ }
4695
+ }
4696
+ }
4697
+ async handleRoomAdded(roomId, payload) {
4698
+ if (!roomId) {
4501
4699
  return;
4502
4700
  }
4503
- throw error;
4701
+ if (this.roomFilter && !this.roomFilter(payload)) {
4702
+ return;
4703
+ }
4704
+ await this.admitRoom(roomId, payload);
4504
4705
  }
4706
+ async handleRoomRemoved(roomId) {
4707
+ if (!roomId) {
4708
+ return;
4709
+ }
4710
+ await this.unsubscribeRoom(roomId);
4711
+ this.lastSubscribeError.delete(roomId);
4712
+ if (!this.roster.recordRoomRemoved(roomId)) {
4713
+ this.logger.debug("RoomPresence ignoring removal for untracked room", { roomId });
4714
+ return;
4715
+ }
4716
+ await this.onRoomLeft?.(roomId);
4717
+ }
4718
+ async unsubscribeRoom(roomId) {
4719
+ try {
4720
+ await this.link.unsubscribeRoom(roomId);
4721
+ } catch (error) {
4722
+ this.logger.warn("RoomPresence failed to unsubscribe room", { roomId, error });
4723
+ }
4724
+ }
4725
+ async subscribeExistingRooms() {
4726
+ await hydrateExistingRooms({
4727
+ link: this.link,
4728
+ roomFilter: this.roomFilter,
4729
+ requestOptions: DEFAULT_REQUEST_OPTIONS,
4730
+ onRoom: async (roomId, payload) => {
4731
+ await this.admitRoom(roomId, payload);
4732
+ },
4733
+ onError: (error) => {
4734
+ this.logger.warn("RoomPresence failed to subscribe existing rooms", { error });
4735
+ }
4736
+ });
4737
+ }
4738
+ };
4739
+ function assertNever(value) {
4740
+ throw new Error(`Unhandled platform event: ${JSON.stringify(value)}`);
4505
4741
  }
4506
4742
 
4507
4743
  // src/runtime/rooms/AgentRuntime.ts
4508
4744
  var AgentRuntime = class {
4745
+ presence;
4509
4746
  link;
4510
4747
  agentId;
4511
4748
  onExecute;
@@ -4516,19 +4753,14 @@ var AgentRuntime = class {
4516
4753
  onParticipantAdded;
4517
4754
  onParticipantRemoved;
4518
4755
  onError;
4519
- roomFilter;
4520
4756
  contextFactory;
4521
4757
  sessionConfig;
4522
- autoSubscribeExistingRooms;
4523
- subscribedRooms = /* @__PURE__ */ new Set();
4524
4758
  contexts = /* @__PURE__ */ new Map();
4525
4759
  executions = /* @__PURE__ */ new Map();
4526
4760
  executionWatchers = /* @__PURE__ */ new Map();
4527
4761
  logger;
4528
4762
  running = false;
4529
4763
  stopping = false;
4530
- stopController = new AbortController();
4531
- consumeTask = null;
4532
4764
  fatalError = null;
4533
4765
  constructor(options) {
4534
4766
  this.link = options.link;
@@ -4542,7 +4774,6 @@ var AgentRuntime = class {
4542
4774
  this.onContactEvent = options.onContactEvent;
4543
4775
  this.onParticipantAdded = options.onParticipantAdded;
4544
4776
  this.onParticipantRemoved = options.onParticipantRemoved;
4545
- this.roomFilter = options.roomFilter;
4546
4777
  this.contextFactory = options.contextFactory;
4547
4778
  this.sessionConfig = {
4548
4779
  enableContextCache: options.sessionConfig?.enableContextCache ?? true,
@@ -4551,7 +4782,48 @@ var AgentRuntime = class {
4551
4782
  maxMessageRetries: options.sessionConfig?.maxMessageRetries ?? 1,
4552
4783
  enableContextHydration: options.sessionConfig?.enableContextHydration ?? true
4553
4784
  };
4554
- this.autoSubscribeExistingRooms = options.agentConfig?.autoSubscribeExistingRooms ?? false;
4785
+ this.presence = new RoomPresence({
4786
+ link: this.link,
4787
+ roomFilter: options.roomFilter,
4788
+ autoSubscribeExistingRooms: options.agentConfig?.autoSubscribeExistingRooms ?? false,
4789
+ logger: this.logger
4790
+ });
4791
+ this.presence.onRoomJoined = async (roomId, payload) => {
4792
+ this.getOrCreateExecution(roomId);
4793
+ await this.onRoomJoined?.(roomId, payload);
4794
+ };
4795
+ this.presence.onRoomLeft = async (roomId) => {
4796
+ await this.teardownExecution(roomId);
4797
+ await this.onRoomLeft?.(roomId);
4798
+ };
4799
+ this.presence.onRoomEvent = async (roomId, event) => {
4800
+ switch (event.type) {
4801
+ case "participant_added": {
4802
+ const context = this.getOrCreateContext(roomId);
4803
+ const participant = {
4804
+ id: event.payload.id,
4805
+ name: event.payload.name,
4806
+ type: event.payload.type,
4807
+ handle: event.payload.handle
4808
+ };
4809
+ context.addParticipant(participant);
4810
+ await this.onParticipantAdded?.(roomId, participant);
4811
+ return;
4812
+ }
4813
+ case "participant_removed": {
4814
+ const context = this.getOrCreateContext(roomId);
4815
+ context.removeParticipant(event.payload.id);
4816
+ await this.onParticipantRemoved?.(roomId, event.payload.id);
4817
+ return;
4818
+ }
4819
+ case "message_created":
4820
+ await this.getOrCreateExecution(roomId).enqueue(event);
4821
+ return;
4822
+ default:
4823
+ assertNever2(event);
4824
+ }
4825
+ };
4826
+ this.presence.onContactEvent = this.onContactEvent ?? null;
4555
4827
  }
4556
4828
  async start() {
4557
4829
  if (this.running) {
@@ -4560,45 +4832,17 @@ var AgentRuntime = class {
4560
4832
  this.running = true;
4561
4833
  this.stopping = false;
4562
4834
  this.fatalError = null;
4563
- if (!this.stopController.signal.aborted) {
4564
- this.stopController.abort();
4565
- }
4566
- this.stopController = new AbortController();
4567
- try {
4568
- await this.link.connect();
4569
- } catch (error) {
4570
- await this.handleStartFailure();
4571
- throw error;
4572
- }
4573
- try {
4574
- await this.link.subscribeAgentRooms();
4575
- } catch {
4576
- this.logger.warn("AgentRuntime failed to subscribe agent_rooms channel, continuing without it");
4577
- }
4578
4835
  try {
4579
- await this.subscribeExistingRooms();
4836
+ await this.presence.start();
4580
4837
  } catch (error) {
4581
4838
  await this.handleStartFailure();
4582
4839
  throw error;
4583
4840
  }
4584
- this.consumeTask = this.consumeLoop(this.stopController.signal);
4585
- if (!this.link.capabilities.contacts) {
4586
- return;
4587
- }
4588
- try {
4589
- await this.link.subscribeAgentContacts();
4590
- } catch {
4591
- this.logger.warn("AgentRuntime failed to subscribe agent_contacts channel, continuing without it");
4592
- }
4841
+ void this.presence.waitUntilStopped().catch((error) => this.failRuntime(error, syntheticRuntimeFailureEvent(this.agentId)));
4593
4842
  }
4594
4843
  async handleStartFailure() {
4595
4844
  this.running = false;
4596
4845
  this.stopping = false;
4597
- this.stopController.abort();
4598
- if (this.consumeTask) {
4599
- await this.consumeTask;
4600
- this.consumeTask = null;
4601
- }
4602
4846
  await this.link.disconnect();
4603
4847
  }
4604
4848
  async stop(timeoutMs) {
@@ -4607,34 +4851,25 @@ var AgentRuntime = class {
4607
4851
  }
4608
4852
  this.stopping = true;
4609
4853
  this.running = false;
4610
- this.stopController.abort();
4611
- if (this.consumeTask) {
4612
- await this.consumeTask;
4613
- this.consumeTask = null;
4614
- }
4615
- const deadline = timeoutMs === void 0 ? void 0 : Date.now() + timeoutMs;
4854
+ this.presence.abortEventLoop();
4855
+ await this.presence.waitUntilStopped().catch(() => void 0);
4616
4856
  let graceful = true;
4617
- for (const execution of this.executions.values()) {
4618
- const remaining = deadline === void 0 ? void 0 : Math.max(0, deadline - Date.now());
4619
- const stopped = await execution.stop(remaining);
4620
- if (!stopped) {
4621
- graceful = false;
4622
- }
4623
- }
4624
- for (const roomId of [...this.subscribedRooms]) {
4625
- const remaining = deadline === void 0 ? void 0 : Math.max(0, deadline - Date.now());
4626
- await this.leaveTrackedRoom(roomId, remaining);
4627
- }
4857
+ await Promise.all(
4858
+ [...this.executions].map(async ([roomId, execution]) => {
4859
+ const stopped = await execution.stop(timeoutMs);
4860
+ if (!stopped) {
4861
+ graceful = false;
4862
+ }
4863
+ this.executions.delete(roomId);
4864
+ })
4865
+ );
4866
+ await this.presence.stop();
4628
4867
  for (const roomId of [...this.contexts.keys()]) {
4629
4868
  await this.onSessionCleanup(roomId);
4630
4869
  }
4631
- this.subscribedRooms.clear();
4632
4870
  this.contexts.clear();
4633
4871
  this.executions.clear();
4634
4872
  this.executionWatchers.clear();
4635
- if (this.link.capabilities.contacts) {
4636
- await this.link.unsubscribeAgentContacts();
4637
- }
4638
4873
  await this.link.disconnect();
4639
4874
  if (this.fatalError) {
4640
4875
  throw this.fatalError instanceof Error ? this.fatalError : new Error(String(this.fatalError));
@@ -4645,11 +4880,7 @@ var AgentRuntime = class {
4645
4880
  return this.contexts.get(roomId);
4646
4881
  }
4647
4882
  async waitUntilStopped() {
4648
- if (this.consumeTask) {
4649
- await this.consumeTask;
4650
- } else {
4651
- await this.link.runForever(this.stopController.signal);
4652
- }
4883
+ await this.presence.waitUntilStopped().catch(() => void 0);
4653
4884
  if (this.fatalError) {
4654
4885
  throw this.fatalError instanceof Error ? this.fatalError : new Error(String(this.fatalError));
4655
4886
  }
@@ -4657,97 +4888,19 @@ var AgentRuntime = class {
4657
4888
  getContexts() {
4658
4889
  return [...this.contexts.values()];
4659
4890
  }
4660
- async consumeLoop(signal) {
4661
- while (!signal.aborted) {
4662
- let event;
4663
- try {
4664
- event = await this.link.nextEvent(signal);
4665
- } catch (error) {
4666
- await this.failRuntime(error, syntheticRuntimeFailureEvent(this.agentId));
4667
- return;
4668
- }
4669
- if (!event) {
4670
- return;
4671
- }
4672
- try {
4673
- await this.handleEvent(event);
4674
- } catch (error) {
4675
- await this.failRuntime(error, event);
4676
- return;
4677
- }
4678
- }
4679
- }
4680
- async handleEvent(event) {
4681
- switch (event.type) {
4682
- case "room_added":
4683
- await trackRoomJoin({
4684
- link: this.link,
4685
- roomId: event.roomId,
4686
- payload: event.payload,
4687
- trackedRooms: this.subscribedRooms,
4688
- roomFilter: this.roomFilter,
4689
- onJoined: async (roomId) => {
4690
- this.getOrCreateExecution(roomId);
4691
- await this.onRoomJoined?.(roomId, event.payload);
4692
- }
4693
- });
4694
- return;
4695
- case "room_removed":
4696
- case "room_deleted":
4697
- if (event.roomId) {
4698
- await this.onRoomLeft?.(event.roomId);
4699
- await this.leaveTrackedRoom(event.roomId);
4700
- }
4701
- return;
4702
- case "participant_added":
4703
- if (event.roomId) {
4704
- const context = this.getOrCreateContext(event.roomId);
4705
- const participant = {
4706
- id: event.payload.id,
4707
- name: event.payload.name,
4708
- type: event.payload.type,
4709
- handle: event.payload.handle
4710
- };
4711
- context.addParticipant(participant);
4712
- await this.onParticipantAdded?.(event.roomId, participant);
4713
- }
4714
- return;
4715
- case "participant_removed":
4716
- if (event.roomId) {
4717
- const context = this.getOrCreateContext(event.roomId);
4718
- context.removeParticipant(event.payload.id);
4719
- await this.onParticipantRemoved?.(event.roomId, event.payload.id);
4720
- }
4721
- return;
4722
- case "contact_request_received":
4723
- case "contact_request_updated":
4724
- case "contact_added":
4725
- case "contact_removed":
4726
- await this.onContactEvent?.(event);
4727
- return;
4728
- case "message_created":
4729
- if (!event.roomId) {
4730
- return;
4731
- }
4732
- await this.getOrCreateExecution(event.roomId).enqueue(event);
4733
- return;
4734
- }
4735
- return assertNever(event);
4736
- }
4737
4891
  async enqueueEvent(roomId, event) {
4738
4892
  await this.getOrCreateExecution(roomId).enqueue(event);
4739
4893
  }
4740
4894
  async bootstrapRoomMessage(roomId, message) {
4741
- await this.link.subscribeRoom(roomId);
4742
- this.subscribedRooms.add(roomId);
4895
+ await this.presence.admitRoomOrThrow(roomId);
4743
4896
  await this.getOrCreateExecution(roomId).bootstrapMessage(message);
4744
4897
  }
4745
4898
  async resetRoomSession(roomId, timeoutMs) {
4899
+ return this.teardownExecution(roomId, timeoutMs);
4900
+ }
4901
+ async teardownExecution(roomId, timeoutMs) {
4746
4902
  const execution = this.executions.get(roomId);
4747
- let graceful = true;
4748
- if (execution) {
4749
- graceful = await execution.stop(timeoutMs);
4750
- }
4903
+ const graceful = execution ? await execution.stop(timeoutMs) : true;
4751
4904
  this.executions.delete(roomId);
4752
4905
  this.contexts.delete(roomId);
4753
4906
  await this.onSessionCleanup(roomId);
@@ -4803,44 +4956,13 @@ var AgentRuntime = class {
4803
4956
  maxMessageRetries: this.sessionConfig.maxMessageRetries,
4804
4957
  enableContextCache: this.sessionConfig.enableContextCache,
4805
4958
  contextCacheTtlSeconds: this.sessionConfig.contextCacheTtlSeconds,
4806
- enableContextHydration: this.sessionConfig.enableContextHydration
4959
+ enableContextHydration: this.sessionConfig.enableContextHydration,
4960
+ logger: this.logger
4807
4961
  };
4808
4962
  const context = this.contextFactory ? this.contextFactory(roomId, defaults) : new ExecutionContext(defaults);
4809
4963
  this.contexts.set(roomId, context);
4810
4964
  return context;
4811
4965
  }
4812
- async subscribeExistingRooms() {
4813
- if (!this.autoSubscribeExistingRooms) {
4814
- return;
4815
- }
4816
- await hydrateTrackedRooms({
4817
- link: this.link,
4818
- trackedRooms: this.subscribedRooms,
4819
- roomFilter: this.roomFilter,
4820
- onJoined: async (roomId, payload) => {
4821
- this.getOrCreateExecution(roomId);
4822
- await this.onRoomJoined?.(roomId, payload);
4823
- },
4824
- onError: async (error) => {
4825
- this.logger.warn("AgentRuntime failed to subscribe existing rooms", {
4826
- error
4827
- });
4828
- }
4829
- });
4830
- }
4831
- async leaveTrackedRoom(roomId, timeoutMs) {
4832
- await trackRoomLeave({
4833
- link: this.link,
4834
- roomId,
4835
- trackedRooms: this.subscribedRooms,
4836
- onLeft: async (leftRoomId) => {
4837
- await this.executions.get(leftRoomId)?.stop(timeoutMs);
4838
- this.contexts.delete(leftRoomId);
4839
- this.executions.delete(leftRoomId);
4840
- await this.onSessionCleanup(leftRoomId);
4841
- }
4842
- });
4843
- }
4844
4966
  async failRuntime(error, event) {
4845
4967
  if (!this.fatalError) {
4846
4968
  this.fatalError = error;
@@ -4852,9 +4974,7 @@ var AgentRuntime = class {
4852
4974
  });
4853
4975
  this.notifyOnError(error, event);
4854
4976
  }
4855
- if (!this.stopController.signal.aborted) {
4856
- this.stopController.abort();
4857
- }
4977
+ this.presence.abortEventLoop();
4858
4978
  }
4859
4979
  notifyOnError(error, event) {
4860
4980
  if (!this.onError) {
@@ -4871,6 +4991,9 @@ var AgentRuntime = class {
4871
4991
  }
4872
4992
  }
4873
4993
  };
4994
+ function assertNever2(value) {
4995
+ throw new Error(`Unhandled room event: ${JSON.stringify(value)}`);
4996
+ }
4874
4997
  function syntheticRuntimeFailureEvent(agentId) {
4875
4998
  return {
4876
4999
  type: "message_created",
@@ -4888,9 +5011,6 @@ function syntheticRuntimeFailureEvent(agentId) {
4888
5011
  }
4889
5012
  };
4890
5013
  }
4891
- function assertNever(value) {
4892
- throw new Error(`Unhandled platform event: ${JSON.stringify(value)}`);
4893
- }
4894
5014
 
4895
5015
  // src/runtime/PlatformRuntime.ts
4896
5016
  init_errors();
@@ -5495,7 +5615,7 @@ Type: ${event.payload.type}, ID: ${event.payload.id}`;
5495
5615
  case "contact_removed":
5496
5616
  return `[Contact Removed] Contact ${event.payload.id} was removed.`;
5497
5617
  }
5498
- return assertNever2(event);
5618
+ return assertNever3(event);
5499
5619
  }
5500
5620
  formatBroadcast(event) {
5501
5621
  switch (event.type) {
@@ -5625,7 +5745,7 @@ Type: ${event.payload.type}, ID: ${event.payload.id}`;
5625
5745
  return { message: String(error) };
5626
5746
  }
5627
5747
  };
5628
- function assertNever2(value) {
5748
+ function assertNever3(value) {
5629
5749
  throw new Error(`Unhandled contact event: ${JSON.stringify(value)}`);
5630
5750
  }
5631
5751
  function normalizeHandle(handle) {
@@ -5842,6 +5962,9 @@ var PlatformRuntime = class {
5842
5962
  this.stopping = false;
5843
5963
  return graceful;
5844
5964
  }
5965
+ async [Symbol.asyncDispose]() {
5966
+ await this.stop();
5967
+ }
5845
5968
  async runForever() {
5846
5969
  if (!this.runtime) {
5847
5970
  throw new RuntimeStateError("Runtime not started");
@@ -6437,6 +6560,34 @@ ${customSection}`.trim();
6437
6560
  // src/adapters/codex/CodexAdapter.ts
6438
6561
  init_schemas();
6439
6562
 
6563
+ // src/adapters/shared/conversationPrompt.ts
6564
+ function systemUpdateParts(participantsMessage, contactsMessage) {
6565
+ const parts = [];
6566
+ if (participantsMessage) {
6567
+ parts.push(`[System]: ${participantsMessage}`);
6568
+ }
6569
+ if (contactsMessage) {
6570
+ parts.push(`[System]: ${contactsMessage}`);
6571
+ }
6572
+ return parts;
6573
+ }
6574
+ function buildConversationPrompt(options) {
6575
+ const parts = [];
6576
+ if (options.isSessionBootstrap && options.history.length > 0) {
6577
+ const historyText = options.history.raw.slice(-(options.maxHistoryMessages ?? 50)).map(formatHistoryLine).join("\n");
6578
+ parts.push(`${options.historyHeader}
6579
+ ${historyText}`);
6580
+ }
6581
+ parts.push(...systemUpdateParts(options.participantsMessage, options.contactsMessage));
6582
+ parts.push(options.currentMessage);
6583
+ return parts.join("\n\n");
6584
+ }
6585
+ function formatHistoryLine(entry) {
6586
+ const sender = String(entry.sender_name ?? entry.sender_type ?? "Unknown");
6587
+ const content = String(entry.content ?? "");
6588
+ return `[${sender}]: ${content}`;
6589
+ }
6590
+
6440
6591
  // src/runtime/tools/customTools.ts
6441
6592
  var import_zod3 = require("zod");
6442
6593
  var import_zod_to_json_schema = require("zod-to-json-schema");
@@ -7334,11 +7485,8 @@ var CodexAdapter = class extends SimpleAdapter {
7334
7485
  items.push({ type: "text", text: context });
7335
7486
  }
7336
7487
  }
7337
- if (input.participantsMessage) {
7338
- items.push({ type: "text", text: `[System]: ${input.participantsMessage}` });
7339
- }
7340
- if (input.contactsMessage) {
7341
- items.push({ type: "text", text: `[System]: ${input.contactsMessage}` });
7488
+ for (const part of systemUpdateParts(input.participantsMessage, input.contactsMessage)) {
7489
+ items.push({ type: "text", text: part });
7342
7490
  }
7343
7491
  items.push({
7344
7492
  type: "text",
@@ -7766,13 +7914,6 @@ var TURN_STATUS_VALUES = /* @__PURE__ */ new Set([
7766
7914
  "failed",
7767
7915
  "inProgress"
7768
7916
  ]);
7769
- function isStructuredToolFailure(value) {
7770
- if (!value || typeof value !== "object") {
7771
- return false;
7772
- }
7773
- const payload = value;
7774
- return payload.ok === false && typeof payload.message === "string";
7775
- }
7776
7917
  function isCodexToolOutputError(value) {
7777
7918
  return isToolExecutorError(value) || isStructuredToolFailure(value);
7778
7919
  }
@@ -9400,18 +9541,28 @@ var GoogleADKAdapter = class extends SimpleAdapter {
9400
9541
  }
9401
9542
  async reportExecutionEvent(sdk, event, tools) {
9402
9543
  for (const functionCall of sdk.getFunctionCalls(event)) {
9403
- await tools.sendEvent(JSON.stringify({
9544
+ const result = await tools.sendEvent(JSON.stringify({
9404
9545
  name: functionCall.name ?? "unknown",
9405
9546
  args: asToolArgs(functionCall.args),
9406
9547
  tool_call_id: functionCall.id ?? ""
9407
9548
  }), "tool_call");
9549
+ this.warnOnFailedSend(result, "Google ADK tool_call event send failed", { toolCallId: functionCall.id ?? "" });
9408
9550
  }
9409
9551
  for (const functionResponse of sdk.getFunctionResponses(event)) {
9410
- await tools.sendEvent(JSON.stringify({
9552
+ const result = await tools.sendEvent(JSON.stringify({
9411
9553
  name: functionResponse.name ?? "unknown",
9412
9554
  output: String(functionResponse.response ?? ""),
9413
9555
  tool_call_id: functionResponse.id ?? ""
9414
9556
  }), "tool_result");
9557
+ this.warnOnFailedSend(result, "Google ADK tool_result event send failed", { toolCallId: functionResponse.id ?? "" });
9558
+ }
9559
+ }
9560
+ warnOnFailedSend(result, message, meta) {
9561
+ if (result.ok === false) {
9562
+ try {
9563
+ this.logger.warn(message, meta);
9564
+ } catch {
9565
+ }
9415
9566
  }
9416
9567
  }
9417
9568
  };
@@ -14426,29 +14577,6 @@ function delay(ms) {
14426
14577
  init_errors();
14427
14578
  init_schemas();
14428
14579
 
14429
- // src/adapters/shared/conversationPrompt.ts
14430
- function buildConversationPrompt(options) {
14431
- const parts = [];
14432
- if (options.isSessionBootstrap && options.history.length > 0) {
14433
- const historyText = options.history.raw.slice(-(options.maxHistoryMessages ?? 50)).map(formatHistoryLine).join("\n");
14434
- parts.push(`${options.historyHeader}
14435
- ${historyText}`);
14436
- }
14437
- if (options.participantsMessage) {
14438
- parts.push(`[System]: ${options.participantsMessage}`);
14439
- }
14440
- if (options.contactsMessage) {
14441
- parts.push(`[System]: ${options.contactsMessage}`);
14442
- }
14443
- parts.push(options.currentMessage);
14444
- return parts.join("\n\n");
14445
- }
14446
- function formatHistoryLine(entry) {
14447
- const sender = String(entry.sender_name ?? entry.sender_type ?? "Unknown");
14448
- const content = String(entry.content ?? "");
14449
- return `[${sender}]: ${content}`;
14450
- }
14451
-
14452
14580
  // src/converters/claude-sdk.ts
14453
14581
  function extractClaudeSessionId(raw) {
14454
14582
  const metadata = findLatestTaskMetadata(