@band-ai/sdk 0.1.6 → 0.2.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 (85) hide show
  1. package/README.md +372 -0
  2. package/dist/{ClaudeSDKAdapter-CRq4R-WB.d.cts → ClaudeSDKAdapter-BY4zEbCl.d.cts} +14 -10
  3. package/dist/{ClaudeSDKAdapter-BHOkjN3X.d.ts → ClaudeSDKAdapter-Cna5P1dR.d.ts} +14 -10
  4. package/dist/{acp-server-B1Da7L6u.d.ts → acp-server-CiUqN3G3.d.ts} +2 -2
  5. package/dist/{acp-server-BdRz9_UN.d.cts → acp-server-Dlj7D563.d.cts} +2 -2
  6. package/dist/adapters.cjs +734 -279
  7. package/dist/adapters.d.cts +21 -19
  8. package/dist/adapters.d.ts +21 -19
  9. package/dist/adapters.js +65 -44
  10. package/dist/backends-D-dBdDm-.d.ts +27 -0
  11. package/dist/backends-ZLxmQrTZ.d.cts +27 -0
  12. package/dist/{chunk-MSL6CWHY.js → chunk-4IAXZBXX.js} +77 -37
  13. package/dist/{chunk-SG4WLD2H.js → chunk-EAI2M3WM.js} +38 -94
  14. package/dist/{chunk-YR3BOQNW.js → chunk-JY75VSQK.js} +7 -7
  15. package/dist/{chunk-INA6YZTZ.js → chunk-LBWKISBS.js} +51 -31
  16. package/dist/{chunk-I5EZG52P.js → chunk-N2QTUMGO.js} +532 -327
  17. package/dist/{chunk-RHVH2GZR.js → chunk-NMZLX7TP.js} +110 -56
  18. package/dist/{chunk-EDPKUUJL.js → chunk-NT7TBFX2.js} +9 -1
  19. package/dist/{chunk-GVEQ3RAH.js → chunk-RV76SKFT.js} +169 -54
  20. package/dist/{chunk-W5NDZSYE.js → chunk-TLBCKDQO.js} +23 -23
  21. package/dist/chunk-US226AS4.js +130 -0
  22. package/dist/{chunk-IZPV6QS6.js → chunk-WL2HNJZ6.js} +38 -11
  23. package/dist/chunk-WLCM2OXG.js +181 -0
  24. package/dist/config.cjs +40 -13
  25. package/dist/config.d.cts +7 -1
  26. package/dist/config.d.ts +7 -1
  27. package/dist/config.js +2 -2
  28. package/dist/converters.cjs +491 -35
  29. package/dist/converters.d.cts +5 -5
  30. package/dist/converters.d.ts +5 -5
  31. package/dist/converters.js +4 -3
  32. package/dist/core.cjs +53 -10
  33. package/dist/core.d.cts +10 -9
  34. package/dist/core.d.ts +10 -9
  35. package/dist/core.js +8 -4
  36. package/dist/disconnectReason-IuADl6TH.d.cts +34 -0
  37. package/dist/disconnectReason-IuADl6TH.d.ts +34 -0
  38. package/dist/{dtos-DUpbIu8k.d.ts → dtos-B8KU_q4d.d.cts} +26 -6
  39. package/dist/{dtos-DUpbIu8k.d.cts → dtos-B8KU_q4d.d.ts} +26 -6
  40. package/dist/index.cjs +1510 -735
  41. package/dist/index.d.cts +18 -16
  42. package/dist/index.d.ts +18 -16
  43. package/dist/index.js +21 -13
  44. package/dist/linear.cjs +1044 -112
  45. package/dist/linear.d.cts +146 -16
  46. package/dist/linear.d.ts +146 -16
  47. package/dist/linear.js +1031 -110
  48. package/dist/mcp-claude.cjs +338 -66
  49. package/dist/mcp-claude.d.cts +3 -3
  50. package/dist/mcp-claude.d.ts +3 -3
  51. package/dist/mcp-claude.js +215 -16
  52. package/dist/mcp.cjs +414 -105
  53. package/dist/mcp.d.cts +29 -16
  54. package/dist/mcp.d.ts +29 -16
  55. package/dist/mcp.js +14 -14
  56. package/dist/{opencode-BBcDchcN.d.cts → opencode-2YdscIc1.d.cts} +5 -5
  57. package/dist/{opencode-DthQQUsZ.d.ts → opencode-Bld62x5t.d.ts} +5 -5
  58. package/dist/{pagination-CcDkVFxK.d.ts → pagination-BgEx-HeL.d.ts} +2 -2
  59. package/dist/{pagination-BPiys10t.d.cts → pagination-DqdkQArT.d.cts} +2 -2
  60. package/dist/{protocols-DrhzOH6K.d.ts → protocols-Bwwcql0h.d.ts} +1 -1
  61. package/dist/{protocols-CInhtFpA.d.cts → protocols-Bz1rxcUg.d.cts} +1 -1
  62. package/dist/rest.cjs +41 -97
  63. package/dist/rest.d.cts +7 -7
  64. package/dist/rest.d.ts +7 -7
  65. package/dist/rest.js +2 -2
  66. package/dist/runtime.cjs +998 -620
  67. package/dist/runtime.d.cts +22 -29
  68. package/dist/runtime.d.ts +22 -29
  69. package/dist/runtime.js +19 -67
  70. package/dist/{schemas-BpEBkq5V.d.cts → schemas-BotL3ZLH.d.cts} +51 -56
  71. package/dist/{schemas-vos1AaBc.d.ts → schemas-DBsC4J1g.d.ts} +51 -56
  72. package/dist/{sdk-CwTAxhwI.d.cts → sdk-BTciSLRQ.d.cts} +35 -5
  73. package/dist/{sdk-CcnQ5OUC.d.ts → sdk-qKJtMma_.d.ts} +35 -5
  74. package/dist/{simpleAdapter-9OE_p6QW.d.ts → simpleAdapter--wznuoOw.d.ts} +1 -1
  75. package/dist/{simpleAdapter-DqhvjsGr.d.cts → simpleAdapter-BpT4XbZC.d.cts} +1 -1
  76. package/dist/testing.d.cts +3 -3
  77. package/dist/testing.d.ts +3 -3
  78. package/dist/{types-7jVzOeOP.d.ts → types-BTN_ZETM.d.ts} +2 -2
  79. package/dist/{types-CxII3XkA.d.ts → types-BgYFd_Yw.d.cts} +5 -5
  80. package/dist/{types-CSHFipIc.d.cts → types-BivGO7I9.d.ts} +5 -5
  81. package/dist/{types-CovcHpYf.d.cts → types-D958TC71.d.cts} +2 -2
  82. package/package.json +25 -23
  83. package/dist/backends-CzDbiQar.d.ts +0 -20
  84. package/dist/backends-SVMT-7l4.d.cts +0 -20
  85. package/dist/chunk-SFVKOEQH.js +0 -103
@@ -3,21 +3,45 @@ import {
3
3
  SYNTHETIC_CONTACT_EVENTS_SENDER_ID,
4
4
  SYNTHETIC_CONTACT_EVENTS_SENDER_NAME,
5
5
  SYNTHETIC_SENDER_TYPE,
6
- buildParticipantsMessage
7
- } from "./chunk-INA6YZTZ.js";
6
+ buildParticipantsMessage,
7
+ toParticipantRecord,
8
+ toParticipantRecordFromRest
9
+ } from "./chunk-LBWKISBS.js";
8
10
  import {
9
11
  CHAT_EVENT_TYPES,
10
12
  CHAT_TOOL_NAMES,
13
+ MEMORY_LIST_SCOPES,
14
+ MEMORY_SEGMENTS,
15
+ MEMORY_STATUSES,
16
+ MEMORY_STORE_SCOPES,
17
+ MEMORY_SYSTEMS,
11
18
  MEMORY_TOOL_NAMES,
19
+ MEMORY_TYPES,
12
20
  TOOL_MODELS,
13
21
  assertChatEventType,
14
- getToolDescription
15
- } from "./chunk-GVEQ3RAH.js";
22
+ expectedList,
23
+ expectedMemoryTypesForSystem,
24
+ getToolDescription,
25
+ isMemoryListScope,
26
+ isMemorySegment,
27
+ isMemoryStatus,
28
+ isMemoryStoreScope,
29
+ isMemorySystem,
30
+ isMemoryType,
31
+ isMemoryTypeForSystem,
32
+ memoryTypeForSystemError
33
+ } from "./chunk-RV76SKFT.js";
34
+ import {
35
+ WebSocketDisconnectError,
36
+ genericCloseReason,
37
+ parseSupersedeDisconnectReason,
38
+ parseUpgradeDisconnectReason
39
+ } from "./chunk-US226AS4.js";
16
40
  import {
17
41
  DEFAULT_REQUEST_OPTIONS,
18
42
  FernRestAdapter,
19
43
  fetchPaginated
20
- } from "./chunk-SG4WLD2H.js";
44
+ } from "./chunk-EAI2M3WM.js";
21
45
  import {
22
46
  DEFAULT_AGENT_TOOLS_CAPABILITIES,
23
47
  createToolExecutorError,
@@ -31,7 +55,7 @@ import {
31
55
  TransportError,
32
56
  UnsupportedFeatureError,
33
57
  ValidationError
34
- } from "./chunk-YR3BOQNW.js";
58
+ } from "./chunk-JY75VSQK.js";
35
59
 
36
60
  // src/contracts/capabilities.ts
37
61
  var CAPABILITY_LABELS = {
@@ -143,55 +167,117 @@ var payloadSchemas = {
143
167
 
144
168
  // src/platform/streaming/PhoenixChannelsTransport.ts
145
169
  import { Socket } from "phoenix";
170
+
171
+ // src/platform/streaming/nodeWebSocketFactory.ts
146
172
  import { WebSocket as NodeWebSocket } from "ws";
173
+ function createNodeWebSocketFactory(headers) {
174
+ const Constructor = NodeWebSocket;
175
+ class BandNodeWebSocket {
176
+ constructor(address, protocols) {
177
+ const socket = new Constructor(address, protocols, headers ? { headers } : void 0);
178
+ socket.once("unexpected-response", (request, response) => {
179
+ void emitUpgradeError(socket, request, response);
180
+ });
181
+ return socket;
182
+ }
183
+ }
184
+ return BandNodeWebSocket;
185
+ }
186
+ async function emitUpgradeError(websocket, request, response) {
187
+ const body = await readResponseBody(response);
188
+ const error = new Error(
189
+ `Unexpected server response: ${response.statusCode ?? "unknown"}`
190
+ );
191
+ error.status = response.statusCode;
192
+ error.body = body;
193
+ error.headers = response.headers;
194
+ request.destroy();
195
+ websocket.emit("error", error);
196
+ websocket.emit("close", 1006, Buffer.alloc(0));
197
+ }
198
+ async function readResponseBody(response) {
199
+ const chunks = [];
200
+ for await (const chunk of response) {
201
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
202
+ }
203
+ return Buffer.concat(chunks).toString("utf8");
204
+ }
205
+
206
+ // src/platform/streaming/PhoenixChannelsTransport.ts
147
207
  var PhoenixChannelsTransport = class {
148
208
  socket;
209
+ agentId;
149
210
  channels = /* @__PURE__ */ new Map();
150
211
  channelRefs = /* @__PURE__ */ new Map();
151
212
  pendingJoins = /* @__PURE__ */ new Map();
152
213
  logger;
214
+ onTerminalDisconnect;
153
215
  onHandlerError;
154
216
  connected = false;
155
217
  connectPromise = null;
156
218
  connectResolve = null;
219
+ connectReject = null;
220
+ lastDisconnectReason = null;
221
+ terminalDisconnectError = null;
222
+ runForeverWaiters = /* @__PURE__ */ new Set();
223
+ stoppingReconnect = false;
224
+ suppressNextCloseReason = false;
157
225
  constructor(options) {
158
226
  this.logger = options.logger ?? new NoopLogger();
227
+ this.agentId = options.agentId;
228
+ this.onTerminalDisconnect = options.onTerminalDisconnect;
159
229
  let wsUrl = options.wsUrl;
160
230
  if (wsUrl.endsWith("/websocket")) {
161
231
  wsUrl = wsUrl.slice(0, -"/websocket".length);
162
232
  }
233
+ const reconnectAfterMs = options.reconnectAfterMs ?? ((tries) => [1e3, 2e3, 5e3, 1e4, 3e4][tries - 1] ?? 3e4);
163
234
  this.socket = new Socket(wsUrl, {
164
235
  params: {
165
- api_key: options.apiKey,
166
- agent_id: options.agentId
236
+ agent_id: options.agentId,
237
+ ...options.conflictPolicy ? { on_conflict: options.conflictPolicy } : {}
167
238
  },
168
239
  heartbeatIntervalMs: options.heartbeatIntervalMs,
169
- reconnectAfterMs: options.reconnectAfterMs ?? ((tries) => [1e3, 2e3, 5e3, 1e4, 3e4][tries - 1] ?? 3e4),
170
- transport: options.websocketFactory ?? resolveWebSocketFactory()
240
+ reconnectAfterMs: (tries) => {
241
+ if (this.terminalDisconnectError) {
242
+ return Number.POSITIVE_INFINITY;
243
+ }
244
+ return reconnectAfterMs(tries);
245
+ },
246
+ transport: options.websocketFactory ?? resolveWebSocketFactory(options.apiKey)
171
247
  });
172
248
  this.socket.onOpen(() => {
173
- this.connected = true;
174
- this.connectResolve?.();
175
- this.connectResolve = null;
176
- this.logger.info("Phoenix socket opened", {
177
- channels: getSocketChannelCount(this.socket)
178
- });
249
+ void this.handleOpen();
179
250
  });
180
251
  this.socket.onClose((event) => {
181
- this.connected = false;
182
- if (getSocketChannelCount(this.socket) === 0) {
183
- this.socket.disconnect();
184
- }
185
- this.logger.info("Phoenix socket closed", {
186
- code: event?.code ?? null,
187
- reason: event?.reason ?? null
188
- });
252
+ this.recordSocketClose(event);
189
253
  });
190
254
  this.socket.onError((event) => {
255
+ const errorEvent = unwrapErrorEvent(event);
256
+ const upgradeReason = parseUpgradeDisconnectReason(errorEvent);
257
+ if (upgradeReason) {
258
+ if (upgradeReason.retryable) {
259
+ this.lastDisconnectReason = upgradeReason;
260
+ this.connectReject?.(new WebSocketDisconnectError(upgradeReason));
261
+ this.stopReconnectIfNoChannels({ suppressCloseReason: true });
262
+ } else {
263
+ this.recordTerminalDisconnect(upgradeReason);
264
+ }
265
+ this.logger.warn("Phoenix socket upgrade failed", {
266
+ reason: upgradeReason
267
+ });
268
+ return;
269
+ }
270
+ this.connectReject?.(
271
+ new TransportError("Phoenix socket connection failed", errorEvent)
272
+ );
273
+ this.stopReconnectIfNoChannels({ suppressCloseReason: true });
191
274
  this.logger.warn("Phoenix socket error", { event });
192
275
  });
193
276
  }
194
277
  async connect() {
278
+ if (this.terminalDisconnectError) {
279
+ throw this.terminalDisconnectError;
280
+ }
195
281
  if (this.connected) {
196
282
  return;
197
283
  }
@@ -215,15 +301,30 @@ var PhoenixChannelsTransport = class {
215
301
  await this.connectPromise;
216
302
  }
217
303
  async disconnect() {
218
- for (const topic of this.channels.keys()) {
219
- await this.leave(topic);
220
- }
304
+ const results = await Promise.allSettled(
305
+ [...this.channels.keys()].map((topic) => this.leave(topic))
306
+ );
221
307
  this.socket.disconnect();
222
308
  this.connected = false;
309
+ const failures = [];
310
+ for (const result of results) {
311
+ if (result.status === "rejected") {
312
+ failures.push(result.reason);
313
+ }
314
+ }
315
+ if (failures.length > 0) {
316
+ throw new AggregateError(
317
+ failures,
318
+ "Failed to leave one or more Phoenix topics during disconnect"
319
+ );
320
+ }
223
321
  }
224
322
  isConnected() {
225
323
  return this.connected;
226
324
  }
325
+ getDisconnectReason() {
326
+ return this.lastDisconnectReason;
327
+ }
227
328
  async join(topic, handlers) {
228
329
  if (this.channels.has(topic)) {
229
330
  return;
@@ -259,7 +360,10 @@ var PhoenixChannelsTransport = class {
259
360
  channel.join().receive("ok", () => resolve()).receive(
260
361
  "error",
261
362
  (error) => reject(new TransportError(`Failed to join topic ${topic}`, error))
262
- ).receive("timeout", () => reject(new TransportError(`Timeout joining topic ${topic}`)));
363
+ ).receive(
364
+ "timeout",
365
+ () => reject(new TransportError(`Timeout joining topic ${topic}`))
366
+ );
263
367
  });
264
368
  } catch (error) {
265
369
  for (const [event, ref] of refs) {
@@ -287,19 +391,127 @@ var PhoenixChannelsTransport = class {
287
391
  channel.leave().receive("ok", () => resolve()).receive(
288
392
  "error",
289
393
  (error) => reject(new TransportError(`Failed to leave topic ${topic}`, error))
290
- ).receive("timeout", () => reject(new TransportError(`Timeout leaving topic ${topic}`)));
394
+ ).receive(
395
+ "timeout",
396
+ () => reject(new TransportError(`Timeout leaving topic ${topic}`))
397
+ );
291
398
  });
292
399
  this.channels.delete(topic);
293
400
  this.logger.debug("Left topic", { topic });
294
401
  }
295
402
  async runForever(signal) {
403
+ if (this.terminalDisconnectError) {
404
+ throw this.terminalDisconnectError;
405
+ }
296
406
  if (signal.aborted) {
297
407
  return;
298
408
  }
299
- await new Promise((resolve) => {
300
- signal.addEventListener("abort", () => resolve(), { once: true });
409
+ await new Promise((resolve, reject) => {
410
+ let settled = false;
411
+ const abortController = new AbortController();
412
+ const waiter = {
413
+ reject: (error) => {
414
+ if (settled) {
415
+ return;
416
+ }
417
+ settled = true;
418
+ this.runForeverWaiters.delete(waiter);
419
+ abortController.abort();
420
+ reject(error);
421
+ }
422
+ };
423
+ this.runForeverWaiters.add(waiter);
424
+ signal.addEventListener(
425
+ "abort",
426
+ () => {
427
+ if (settled) {
428
+ return;
429
+ }
430
+ settled = true;
431
+ this.runForeverWaiters.delete(waiter);
432
+ resolve();
433
+ },
434
+ { once: true, signal: abortController.signal }
435
+ );
436
+ });
437
+ }
438
+ async handleOpen() {
439
+ try {
440
+ await this.subscribeAgentControl();
441
+ } catch (error) {
442
+ this.connected = false;
443
+ this.socket.disconnect();
444
+ this.connectReject?.(
445
+ error instanceof Error ? error : new TransportError(String(error))
446
+ );
447
+ this.logger.warn("Failed to join mandatory agent_control channel", {
448
+ error
449
+ });
450
+ return;
451
+ }
452
+ this.connected = true;
453
+ this.connectResolve?.();
454
+ this.connectResolve = null;
455
+ this.connectReject = null;
456
+ this.logger.info("Phoenix socket opened", {
457
+ channels: getSocketChannelCount(this.socket)
458
+ });
459
+ }
460
+ stopReconnectIfNoChannels(options = {}) {
461
+ if (this.stoppingReconnect || getSocketChannelCount(this.socket) !== 0) {
462
+ return;
463
+ }
464
+ this.suppressNextCloseReason = options.suppressCloseReason ?? false;
465
+ this.stoppingReconnect = true;
466
+ this.socket.disconnect();
467
+ this.stoppingReconnect = false;
468
+ }
469
+ async subscribeAgentControl() {
470
+ if (!this.agentId) {
471
+ return;
472
+ }
473
+ await this.join(`agent_control:${this.agentId}`, {
474
+ supersede: (payload) => {
475
+ const reason = parseSupersedeDisconnectReason(payload);
476
+ if (!reason) {
477
+ this.logger.warn("Invalid agent_control supersede payload", {
478
+ payload
479
+ });
480
+ return;
481
+ }
482
+ this.recordTerminalDisconnect(reason);
483
+ }
301
484
  });
302
485
  }
486
+ recordSocketClose(event) {
487
+ this.connected = false;
488
+ const suppressCloseReason = this.suppressNextCloseReason;
489
+ this.suppressNextCloseReason = false;
490
+ this.stopReconnectIfNoChannels();
491
+ if (!suppressCloseReason && !this.terminalDisconnectError && !this.lastDisconnectReason) {
492
+ this.lastDisconnectReason = genericCloseReason(event);
493
+ }
494
+ this.logger.info("Phoenix socket closed", {
495
+ code: event?.code ?? null,
496
+ reason: event?.reason ?? null,
497
+ platformReason: this.lastDisconnectReason
498
+ });
499
+ }
500
+ recordTerminalDisconnect(reason) {
501
+ if (this.terminalDisconnectError) {
502
+ return;
503
+ }
504
+ const error = new WebSocketDisconnectError(reason);
505
+ this.lastDisconnectReason = reason;
506
+ this.terminalDisconnectError = error;
507
+ this.onTerminalDisconnect?.(reason);
508
+ this.connectReject?.(error);
509
+ for (const waiter of this.runForeverWaiters) {
510
+ waiter.reject(error);
511
+ }
512
+ this.runForeverWaiters.clear();
513
+ this.socket.disconnect();
514
+ }
303
515
  async waitForConnection(timeoutMs = 1e4) {
304
516
  if (this.connected) {
305
517
  return;
@@ -307,20 +519,43 @@ var PhoenixChannelsTransport = class {
307
519
  await new Promise((resolve, reject) => {
308
520
  const timeout = setTimeout(() => {
309
521
  this.connectResolve = null;
310
- reject(new TransportError("Timed out waiting for Phoenix socket connection"));
522
+ this.connectReject = null;
523
+ reject(
524
+ new TransportError("Timed out waiting for Phoenix socket connection")
525
+ );
311
526
  }, timeoutMs);
312
527
  this.connectResolve = () => {
313
528
  clearTimeout(timeout);
529
+ this.connectReject = null;
314
530
  resolve();
315
531
  };
532
+ this.connectReject = (error) => {
533
+ clearTimeout(timeout);
534
+ this.connectResolve = null;
535
+ this.connectReject = null;
536
+ reject(
537
+ error instanceof Error ? error : new TransportError(String(error))
538
+ );
539
+ };
316
540
  });
317
541
  }
318
542
  };
319
- function resolveWebSocketFactory() {
543
+ function resolveWebSocketFactory(apiKey) {
320
544
  if (typeof process !== "undefined" && process.versions?.node) {
321
- return NodeWebSocket;
545
+ return createNodeWebSocketFactory({ "x-api-key": apiKey });
322
546
  }
323
- return WebSocket;
547
+ throw new TransportError(
548
+ "Phoenix WebSocket API-key auth requires a WebSocket transport that can set handshake headers."
549
+ );
550
+ }
551
+ function unwrapErrorEvent(event) {
552
+ if (!isErrorEvent(event)) {
553
+ return event;
554
+ }
555
+ return event.error;
556
+ }
557
+ function isErrorEvent(event) {
558
+ return typeof event === "object" && event !== null && "error" in event;
324
559
  }
325
560
  function removeSocketChannel(socket, channel) {
326
561
  const candidate = socket;
@@ -334,14 +569,20 @@ function getSocketChannelCount(socket) {
334
569
  return candidate.channels.length;
335
570
  }
336
571
 
337
- // src/platform/ThenvoiLink.ts
338
- import { ThenvoiClient } from "@thenvoi/rest-client";
339
- var DEFAULT_WS_URL = "wss://app.thenvoi.com/api/v1/socket";
572
+ // src/platform/BandLink.ts
573
+ import { BandClient } from "@band-ai/rest-client";
574
+ var DEFAULT_WS_URL = "wss://app.band.ai/api/v1/socket";
340
575
  function deriveDefaultRestUrl(wsUrl) {
341
576
  const parsed = new URL(wsUrl);
342
577
  const protocol = parsed.protocol === "ws:" ? "http:" : "https:";
343
578
  return `${protocol}//${parsed.host}`;
344
579
  }
580
+ function roomTopics(roomId) {
581
+ return {
582
+ chat: `chat_room:${roomId}`,
583
+ participants: `room_participants:${roomId}`
584
+ };
585
+ }
345
586
  function toPlatformMessage(roomId, message) {
346
587
  return {
347
588
  id: message.id,
@@ -355,7 +596,7 @@ function toPlatformMessage(roomId, message) {
355
596
  createdAt: new Date(message.inserted_at)
356
597
  };
357
598
  }
358
- var ThenvoiLink = class {
599
+ var BandLink = class {
359
600
  agentId;
360
601
  apiKey;
361
602
  wsUrl;
@@ -368,6 +609,8 @@ var ThenvoiLink = class {
368
609
  eventQueue = [];
369
610
  waiters = [];
370
611
  connected = false;
612
+ lastDisconnectReason = null;
613
+ terminalDisconnectError = null;
371
614
  constructor(options) {
372
615
  this.agentId = options.agentId;
373
616
  this.apiKey = options.apiKey;
@@ -379,7 +622,7 @@ var ThenvoiLink = class {
379
622
  ...options.capabilities
380
623
  };
381
624
  const restApi = options.restApi ?? new FernRestAdapter(
382
- new ThenvoiClient({
625
+ new BandClient({
383
626
  apiKey: this.apiKey,
384
627
  baseUrl: this.restUrl
385
628
  })
@@ -389,17 +632,35 @@ var ThenvoiLink = class {
389
632
  wsUrl: this.wsUrl,
390
633
  apiKey: this.apiKey,
391
634
  agentId: this.agentId,
392
- logger: this.logger
635
+ logger: this.logger,
636
+ conflictPolicy: options.conflictPolicy,
637
+ onTerminalDisconnect: (reason) => {
638
+ this.recordDisconnectReason(reason);
639
+ }
393
640
  });
394
641
  }
395
642
  isConnected() {
396
643
  return this.connected;
397
644
  }
645
+ getDisconnectReason() {
646
+ return this.lastDisconnectReason;
647
+ }
398
648
  async connect() {
399
649
  if (this.connected) {
400
650
  return;
401
651
  }
402
- await this.transport.connect();
652
+ try {
653
+ await this.transport.connect();
654
+ } catch (error) {
655
+ if (error instanceof WebSocketDisconnectError) {
656
+ if (error.reason.retryable) {
657
+ this.lastDisconnectReason = error.reason;
658
+ } else {
659
+ this.recordDisconnectError(error);
660
+ }
661
+ }
662
+ throw error;
663
+ }
403
664
  this.connected = true;
404
665
  }
405
666
  async disconnect() {
@@ -413,8 +674,23 @@ var ThenvoiLink = class {
413
674
  this.connected = false;
414
675
  }
415
676
  async runForever(signal) {
677
+ if (this.terminalDisconnectError) {
678
+ throw this.terminalDisconnectError;
679
+ }
416
680
  await this.transport.runForever(signal);
417
681
  }
682
+ recordDisconnectReason(reason) {
683
+ this.recordDisconnectError(new WebSocketDisconnectError(reason));
684
+ }
685
+ recordDisconnectError(error) {
686
+ this.connected = false;
687
+ this.lastDisconnectReason = error.reason;
688
+ this.terminalDisconnectError = error;
689
+ while (this.waiters.length > 0) {
690
+ const waiter = this.waiters.shift();
691
+ waiter?.reject(error);
692
+ }
693
+ }
418
694
  queueEvent(event) {
419
695
  const waiter = this.waiters.shift();
420
696
  if (waiter) {
@@ -439,32 +715,42 @@ var ThenvoiLink = class {
439
715
  if (this.subscribedRooms.has(roomId)) {
440
716
  return;
441
717
  }
442
- await this.transport.join(`chat_room:${roomId}`, {
443
- message_created: (payload) => {
444
- this.emit("message_created", payload, roomId);
445
- }
446
- });
447
- await this.transport.join(`room_participants:${roomId}`, {
448
- participant_added: (payload) => {
449
- this.emit("participant_added", payload, roomId);
450
- },
451
- participant_removed: (payload) => {
452
- this.emit("participant_removed", payload, roomId);
453
- },
454
- room_deleted: (payload) => {
455
- this.emit("room_deleted", payload, roomId);
456
- }
457
- });
718
+ await this.joinRoomTopics(roomId);
458
719
  this.subscribedRooms.add(roomId);
459
720
  }
460
721
  async unsubscribeRoom(roomId) {
461
722
  if (!this.subscribedRooms.has(roomId)) {
462
723
  return;
463
724
  }
464
- await this.transport.leave(`chat_room:${roomId}`);
465
- await this.transport.leave(`room_participants:${roomId}`);
725
+ const topics = roomTopics(roomId);
726
+ await this.transport.leave(topics.chat);
727
+ await this.transport.leave(topics.participants);
466
728
  this.subscribedRooms.delete(roomId);
467
729
  }
730
+ async joinRoomTopics(roomId) {
731
+ const topics = roomTopics(roomId);
732
+ await this.transport.join(topics.chat, {
733
+ message_created: (payload) => {
734
+ this.emit("message_created", payload, roomId);
735
+ }
736
+ });
737
+ try {
738
+ await this.transport.join(topics.participants, {
739
+ participant_added: (payload) => {
740
+ this.emit("participant_added", payload, roomId);
741
+ },
742
+ participant_removed: (payload) => {
743
+ this.emit("participant_removed", payload, roomId);
744
+ },
745
+ room_deleted: (payload) => {
746
+ this.emit("room_deleted", payload, roomId);
747
+ }
748
+ });
749
+ } catch (error) {
750
+ await this.transport.leave(topics.chat);
751
+ throw error;
752
+ }
753
+ }
468
754
  async subscribeAgentContacts() {
469
755
  assertCapability(this.capabilities, "contacts", "Contacts streaming");
470
756
  await this.transport.join(`agent_contacts:${this.agentId}`, {
@@ -486,6 +772,9 @@ var ThenvoiLink = class {
486
772
  await this.transport.leave(`agent_contacts:${this.agentId}`);
487
773
  }
488
774
  async nextEvent(signal) {
775
+ if (this.terminalDisconnectError) {
776
+ throw this.terminalDisconnectError;
777
+ }
489
778
  if (signal?.aborted) {
490
779
  return null;
491
780
  }
@@ -493,19 +782,19 @@ var ThenvoiLink = class {
493
782
  if (queued) {
494
783
  return queued;
495
784
  }
496
- return new Promise((resolve) => {
785
+ return new Promise((resolve, reject) => {
497
786
  let settled = false;
498
787
  const onAbort = () => {
499
788
  const index = this.waiters.indexOf(waiter);
500
789
  if (index >= 0) {
501
790
  this.waiters.splice(index, 1);
502
791
  }
503
- finalize(null);
792
+ finalizeResolve(null);
504
793
  };
505
794
  const cleanup = () => {
506
795
  signal?.removeEventListener("abort", onAbort);
507
796
  };
508
- const finalize = (event) => {
797
+ const finalizeResolve = (event) => {
509
798
  if (settled) {
510
799
  return;
511
800
  }
@@ -513,8 +802,17 @@ var ThenvoiLink = class {
513
802
  cleanup();
514
803
  resolve(event);
515
804
  };
805
+ const finalizeReject = (error) => {
806
+ if (settled) {
807
+ return;
808
+ }
809
+ settled = true;
810
+ cleanup();
811
+ reject(error);
812
+ };
516
813
  const waiter = {
517
- resolve: finalize,
814
+ resolve: finalizeResolve,
815
+ reject: finalizeReject,
518
816
  cleanup
519
817
  };
520
818
  this.waiters.push(waiter);
@@ -526,7 +824,10 @@ var ThenvoiLink = class {
526
824
  next: async () => {
527
825
  const value = await this.nextEvent();
528
826
  if (value === null) {
529
- return { done: true, value: void 0 };
827
+ return {
828
+ done: true,
829
+ value: void 0
830
+ };
530
831
  }
531
832
  return { done: false, value };
532
833
  }
@@ -608,7 +909,9 @@ var ThenvoiLink = class {
608
909
  }
609
910
  async listChats(request, requestOptions) {
610
911
  if (!this.rest.listChats) {
611
- throw new UnsupportedFeatureError("Chat listing is not available in current REST adapter");
912
+ throw new UnsupportedFeatureError(
913
+ "Chat listing is not available in current REST adapter"
914
+ );
612
915
  }
613
916
  return this.rest.listChats(request, requestOptions);
614
917
  }
@@ -934,6 +1237,7 @@ var Execution = class {
934
1237
  };
935
1238
 
936
1239
  // src/runtime/tools/AgentTools.ts
1240
+ import { ParticipantRoster } from "@band-ai/band-sdk-core";
937
1241
  var REQUIRED_ADAPTER_TOOL_METHODS = [
938
1242
  "sendMessage",
939
1243
  "sendEvent",
@@ -968,51 +1272,17 @@ var CONTACT_REQUEST_ACTIONS = /* @__PURE__ */ new Set([
968
1272
  "reject",
969
1273
  "cancel"
970
1274
  ]);
971
- var MEMORY_SYSTEMS = /* @__PURE__ */ new Set([
972
- "sensory",
973
- "working",
974
- "long_term"
975
- ]);
976
- var MEMORY_TYPES = /* @__PURE__ */ new Set([
977
- "iconic",
978
- "echoic",
979
- "haptic",
980
- "episodic",
981
- "semantic",
982
- "procedural"
983
- ]);
984
- var MEMORY_SEGMENTS = /* @__PURE__ */ new Set([
985
- "user",
986
- "agent",
987
- "tool",
988
- "guideline"
989
- ]);
990
- var LIST_MEMORY_SCOPES = /* @__PURE__ */ new Set([
991
- "subject",
992
- "organization",
993
- "all"
994
- ]);
995
- var LIST_MEMORY_STATUSES = /* @__PURE__ */ new Set([
996
- "active",
997
- "superseded",
998
- "archived",
999
- "all"
1000
- ]);
1001
- var MEMORY_SCOPES = /* @__PURE__ */ new Set([
1002
- "subject",
1003
- "organization"
1004
- ]);
1005
1275
  var AgentTools = class {
1006
1276
  roomId;
1007
1277
  capabilities;
1008
1278
  rest;
1009
- participants;
1279
+ roster;
1010
1280
  adapterTools;
1011
1281
  toolHandlers;
1012
1282
  constructor(options) {
1013
1283
  this.roomId = options.roomId;
1014
1284
  this.rest = options.rest;
1015
- this.participants = options.participants ?? [];
1285
+ this.roster = options.roster ?? new ParticipantRoster();
1016
1286
  this.capabilities = {
1017
1287
  ...DEFAULT_AGENT_TOOLS_CAPABILITIES,
1018
1288
  ...options.capabilities
@@ -1024,17 +1294,20 @@ var AgentTools = class {
1024
1294
  return this.adapterTools;
1025
1295
  }
1026
1296
  async sendMessage(content, mentions = []) {
1027
- if (mentions.length > 0 && typeof mentions[0] === "string" && this.participants.length === 0) {
1028
- await this.syncParticipants();
1297
+ let participants;
1298
+ if (mentions.length > 0 && typeof mentions[0] === "string") {
1299
+ participants = this.roster.list();
1300
+ if (participants.length === 0) {
1301
+ participants = await this.syncParticipants();
1302
+ }
1029
1303
  }
1030
- const resolvedMentions = this.resolveMentions(mentions);
1304
+ const resolvedMentions = this.resolveMentions(mentions, participants);
1031
1305
  return this.rest.createChatMessage(
1032
1306
  this.roomId,
1033
1307
  {
1034
1308
  content,
1035
1309
  mentions: resolvedMentions
1036
- },
1037
- DEFAULT_REQUEST_OPTIONS
1310
+ }
1038
1311
  );
1039
1312
  }
1040
1313
  async sendEvent(content, messageType, metadata) {
@@ -1045,8 +1318,7 @@ var AgentTools = class {
1045
1318
  content,
1046
1319
  messageType,
1047
1320
  metadata
1048
- },
1049
- DEFAULT_REQUEST_OPTIONS
1321
+ }
1050
1322
  );
1051
1323
  }
1052
1324
  async createChatroom(taskId) {
@@ -1085,7 +1357,7 @@ var AgentTools = class {
1085
1357
  type: String(peer.type ?? "Agent"),
1086
1358
  handle: typeof peer.handle === "string" ? peer.handle : null
1087
1359
  };
1088
- this.participants.push(participantRecord);
1360
+ this.roster.add(participantRecord);
1089
1361
  return {
1090
1362
  ...participantRecord,
1091
1363
  status: "added"
@@ -1100,7 +1372,7 @@ var AgentTools = class {
1100
1372
  throw new ValidationError(`Participant '${name}' not found in room`);
1101
1373
  }
1102
1374
  await this.rest.removeChatParticipant(this.roomId, String(participant.id), DEFAULT_REQUEST_OPTIONS);
1103
- this.replaceParticipants(this.participants.filter((entry) => entry.id !== participant.id));
1375
+ this.roster.remove(participant.id);
1104
1376
  return {
1105
1377
  id: participant.id,
1106
1378
  name,
@@ -1128,17 +1400,12 @@ var AgentTools = class {
1128
1400
  }
1129
1401
  async fetchParticipants() {
1130
1402
  const participants = await this.rest.listChatParticipants(this.roomId, DEFAULT_REQUEST_OPTIONS);
1131
- return participants.map((participant) => ({
1132
- id: participant.id,
1133
- name: participant.name,
1134
- type: participant.type,
1135
- handle: participant.handle ?? null
1136
- }));
1403
+ return participants.map(toParticipantRecordFromRest);
1137
1404
  }
1138
1405
  async syncParticipants() {
1139
1406
  const participants = await this.fetchParticipants();
1140
- this.replaceParticipants(participants);
1141
- return [...this.participants];
1407
+ this.roster.setAll(participants);
1408
+ return this.roster.list().map(toParticipantRecord);
1142
1409
  }
1143
1410
  async executeToolCall(toolName, arguments_) {
1144
1411
  const validationError = validateToolArgs(toolName, arguments_);
@@ -1186,7 +1453,7 @@ var AgentTools = class {
1186
1453
  if (!CHAT_TOOL_NAMES.has(name) && !this.capabilities.contacts) {
1187
1454
  return false;
1188
1455
  }
1189
- if (name === "thenvoi_lookup_peers" && !this.capabilities.peers) {
1456
+ if (name === "band_lookup_peers" && !this.capabilities.peers) {
1190
1457
  return false;
1191
1458
  }
1192
1459
  return true;
@@ -1381,7 +1648,7 @@ var AgentTools = class {
1381
1648
  }
1382
1649
  return this.rest.archiveMemory(normalizedMemoryId, DEFAULT_REQUEST_OPTIONS);
1383
1650
  }
1384
- resolveMentions(mentions) {
1651
+ resolveMentions(mentions, participants) {
1385
1652
  if (mentions.length === 0) {
1386
1653
  return [];
1387
1654
  }
@@ -1394,7 +1661,7 @@ var AgentTools = class {
1394
1661
  const participantsByHandle = /* @__PURE__ */ new Map();
1395
1662
  const participantsById = /* @__PURE__ */ new Map();
1396
1663
  const participantsByName = /* @__PURE__ */ new Map();
1397
- for (const participant of this.participants) {
1664
+ for (const participant of participants ?? []) {
1398
1665
  const ref = {
1399
1666
  id: String(participant.id),
1400
1667
  handle: typeof participant.handle === "string" ? participant.handle : void 0
@@ -1450,9 +1717,6 @@ var AgentTools = class {
1450
1717
  normalizeMentionHandle(handle) {
1451
1718
  return handle.trim().replace(/^@+/, "").toLowerCase();
1452
1719
  }
1453
- replaceParticipants(participants) {
1454
- this.participants.splice(0, this.participants.length, ...participants);
1455
- }
1456
1720
  buildAdapterTools() {
1457
1721
  const tools = {
1458
1722
  capabilities: this.capabilities
@@ -1483,51 +1747,51 @@ var AgentTools = class {
1483
1747
  }
1484
1748
  buildMessagingToolHandlers() {
1485
1749
  return {
1486
- thenvoi_send_message: async (arguments_) => this.sendMessage(
1750
+ band_send_message: async (arguments_) => this.sendMessage(
1487
1751
  String(arguments_.content ?? ""),
1488
1752
  this.normalizeMentionInput(arguments_.mentions)
1489
1753
  ),
1490
- thenvoi_send_event: async (arguments_) => this.sendEvent(
1754
+ band_send_event: async (arguments_) => this.sendEvent(
1491
1755
  String(arguments_.content ?? ""),
1492
1756
  String(arguments_.message_type ?? "task"),
1493
1757
  this.normalizeOptionalMetadata(arguments_.metadata)
1494
1758
  ),
1495
- thenvoi_add_participant: async (arguments_) => this.addParticipant(String(arguments_.name ?? ""), String(arguments_.role ?? "member")),
1496
- thenvoi_remove_participant: async (arguments_) => this.removeParticipant(String(arguments_.name ?? "")),
1497
- thenvoi_lookup_peers: async (arguments_) => this.lookupPeers(
1759
+ band_add_participant: async (arguments_) => this.addParticipant(String(arguments_.name ?? ""), String(arguments_.role ?? "member")),
1760
+ band_remove_participant: async (arguments_) => this.removeParticipant(String(arguments_.name ?? "")),
1761
+ band_lookup_peers: async (arguments_) => this.lookupPeers(
1498
1762
  coercePositiveInt(arguments_.page, 1),
1499
1763
  coercePositiveInt(arguments_.page_size, 50)
1500
1764
  ),
1501
- thenvoi_get_participants: async () => this.getParticipants(),
1502
- thenvoi_create_chatroom: async (arguments_) => this.createChatroom(this.normalizeOptionalString(arguments_.task_id))
1765
+ band_get_participants: async () => this.getParticipants(),
1766
+ band_create_chatroom: async (arguments_) => this.createChatroom(this.normalizeOptionalString(arguments_.task_id))
1503
1767
  };
1504
1768
  }
1505
1769
  buildContactToolHandlers() {
1506
1770
  return {
1507
- thenvoi_list_contacts: async (arguments_) => this.listContacts({
1771
+ band_list_contacts: async (arguments_) => this.listContacts({
1508
1772
  page: coercePositiveInt(arguments_.page, 1),
1509
1773
  pageSize: coercePositiveInt(arguments_.page_size, 50)
1510
1774
  }),
1511
- thenvoi_add_contact: async (arguments_) => this.addContact({
1775
+ band_add_contact: async (arguments_) => this.addContact({
1512
1776
  handle: String(arguments_.handle ?? ""),
1513
1777
  ...typeof arguments_.message === "string" ? { message: arguments_.message } : {}
1514
1778
  }),
1515
- thenvoi_remove_contact: async (arguments_) => this.removeContact(this.toRemoveContactArgs(arguments_)),
1516
- thenvoi_list_contact_requests: async (arguments_) => this.listContactRequests({
1779
+ band_remove_contact: async (arguments_) => this.removeContact(this.toRemoveContactArgs(arguments_)),
1780
+ band_list_contact_requests: async (arguments_) => this.listContactRequests({
1517
1781
  page: coercePositiveInt(arguments_.page, 1),
1518
1782
  pageSize: coercePositiveInt(arguments_.page_size, 50),
1519
1783
  sentStatus: String(arguments_.sent_status ?? "pending")
1520
1784
  }),
1521
- thenvoi_respond_contact_request: async (arguments_) => this.respondContactRequest(this.toRespondContactRequestArgs(arguments_))
1785
+ band_respond_contact_request: async (arguments_) => this.respondContactRequest(this.toRespondContactRequestArgs(arguments_))
1522
1786
  };
1523
1787
  }
1524
1788
  buildMemoryToolHandlers() {
1525
1789
  return {
1526
- thenvoi_list_memories: async (arguments_) => this.listMemories(this.toListMemoriesArgs(arguments_)),
1527
- thenvoi_store_memory: async (arguments_) => this.storeMemory(this.toStoreMemoryArgs(arguments_)),
1528
- thenvoi_get_memory: async (arguments_) => this.getMemory(String(arguments_.memory_id ?? "")),
1529
- thenvoi_supersede_memory: async (arguments_) => this.supersedeMemory(String(arguments_.memory_id ?? "")),
1530
- thenvoi_archive_memory: async (arguments_) => this.archiveMemory(String(arguments_.memory_id ?? ""))
1790
+ band_list_memories: async (arguments_) => this.listMemories(this.toListMemoriesArgs(arguments_)),
1791
+ band_store_memory: async (arguments_) => this.storeMemory(this.toStoreMemoryArgs(arguments_)),
1792
+ band_get_memory: async (arguments_) => this.getMemory(String(arguments_.memory_id ?? "")),
1793
+ band_supersede_memory: async (arguments_) => this.supersedeMemory(String(arguments_.memory_id ?? "")),
1794
+ band_archive_memory: async (arguments_) => this.archiveMemory(String(arguments_.memory_id ?? ""))
1531
1795
  };
1532
1796
  }
1533
1797
  toRemoveContactArgs(arguments_) {
@@ -1579,6 +1843,9 @@ var AgentTools = class {
1579
1843
  const status = this.normalizeOptionalMemoryStatus(arguments_.status);
1580
1844
  const subjectId = this.normalizeOptionalString(arguments_.subject_id);
1581
1845
  const contentQuery = this.normalizeOptionalString(arguments_.content_query);
1846
+ if (system && type && !isMemoryTypeForSystem(system, type)) {
1847
+ throw new ValidationError(memoryTypeForSystemError(system));
1848
+ }
1582
1849
  return {
1583
1850
  ...subjectId ? { subject_id: subjectId } : {},
1584
1851
  ...scope ? { scope } : {},
@@ -1600,13 +1867,21 @@ var AgentTools = class {
1600
1867
  const subjectId = this.normalizeOptionalString(arguments_.subject_id);
1601
1868
  const metadata = this.normalizeOptionalMetadata(arguments_.metadata);
1602
1869
  if (!system) {
1603
- throw new ValidationError("system must be one of: sensory, working, long_term");
1870
+ throw new ValidationError(`system must be one of: ${expectedList(MEMORY_SYSTEMS)}`);
1604
1871
  }
1605
1872
  if (!type) {
1606
- throw new ValidationError("type must be one of: iconic, echoic, haptic, episodic, semantic, procedural");
1873
+ throw new ValidationError(`type must be one of: ${expectedList(MEMORY_TYPES)}`);
1874
+ }
1875
+ if (!isMemoryTypeForSystem(system, type)) {
1876
+ throw new ValidationError(memoryTypeForSystemError(system));
1607
1877
  }
1608
1878
  if (!segment) {
1609
- throw new ValidationError("segment must be one of: user, agent, tool, guideline");
1879
+ throw new ValidationError(`segment must be one of: ${expectedList(MEMORY_SEGMENTS)}`);
1880
+ }
1881
+ if (scope === "subject" && !subjectId) {
1882
+ throw new ValidationError(
1883
+ 'scope="subject" requires a subject_id (the UUID of the person or agent the memory is about). If you do not have a concrete subject UUID, retry with scope="organization" and omit subject_id. Do not invent a UUID.'
1884
+ );
1610
1885
  }
1611
1886
  return {
1612
1887
  content,
@@ -1686,8 +1961,8 @@ var AgentTools = class {
1686
1961
  if (!normalized) {
1687
1962
  return void 0;
1688
1963
  }
1689
- if (!isListMemoryScope(normalized)) {
1690
- throw new ValidationError("scope must be one of: subject, organization, all");
1964
+ if (!isMemoryListScope(normalized)) {
1965
+ throw new ValidationError(`scope must be one of: ${expectedList(MEMORY_LIST_SCOPES)}`);
1691
1966
  }
1692
1967
  return normalized;
1693
1968
  }
@@ -1697,7 +1972,7 @@ var AgentTools = class {
1697
1972
  return void 0;
1698
1973
  }
1699
1974
  if (!isMemorySystem(normalized)) {
1700
- throw new ValidationError("system must be one of: sensory, working, long_term");
1975
+ throw new ValidationError(`system must be one of: ${expectedList(MEMORY_SYSTEMS)}`);
1701
1976
  }
1702
1977
  return normalized;
1703
1978
  }
@@ -1707,7 +1982,7 @@ var AgentTools = class {
1707
1982
  return void 0;
1708
1983
  }
1709
1984
  if (!isMemoryType(normalized)) {
1710
- throw new ValidationError("type must be one of: iconic, echoic, haptic, episodic, semantic, procedural");
1985
+ throw new ValidationError(`type must be one of: ${expectedList(MEMORY_TYPES)}`);
1711
1986
  }
1712
1987
  return normalized;
1713
1988
  }
@@ -1717,7 +1992,7 @@ var AgentTools = class {
1717
1992
  return void 0;
1718
1993
  }
1719
1994
  if (!isMemorySegment(normalized)) {
1720
- throw new ValidationError("segment must be one of: user, agent, tool, guideline");
1995
+ throw new ValidationError(`segment must be one of: ${expectedList(MEMORY_SEGMENTS)}`);
1721
1996
  }
1722
1997
  return normalized;
1723
1998
  }
@@ -1727,7 +2002,7 @@ var AgentTools = class {
1727
2002
  return void 0;
1728
2003
  }
1729
2004
  if (!isMemoryStatus(normalized)) {
1730
- throw new ValidationError("status must be one of: active, superseded, archived, all");
2005
+ throw new ValidationError(`status must be one of: ${expectedList(MEMORY_STATUSES)}`);
1731
2006
  }
1732
2007
  return normalized;
1733
2008
  }
@@ -1736,8 +2011,8 @@ var AgentTools = class {
1736
2011
  if (!normalized) {
1737
2012
  return void 0;
1738
2013
  }
1739
- if (!isStoreMemoryScope(normalized)) {
1740
- throw new ValidationError("scope must be one of: subject, organization");
2014
+ if (!isMemoryStoreScope(normalized)) {
2015
+ throw new ValidationError(`scope must be one of: ${expectedList(MEMORY_STORE_SCOPES)}`);
1741
2016
  }
1742
2017
  return normalized;
1743
2018
  }
@@ -1745,24 +2020,6 @@ var AgentTools = class {
1745
2020
  function isContactRequestAction(value) {
1746
2021
  return CONTACT_REQUEST_ACTIONS.has(value);
1747
2022
  }
1748
- function isMemorySystem(value) {
1749
- return MEMORY_SYSTEMS.has(value);
1750
- }
1751
- function isMemoryType(value) {
1752
- return MEMORY_TYPES.has(value);
1753
- }
1754
- function isMemorySegment(value) {
1755
- return MEMORY_SEGMENTS.has(value);
1756
- }
1757
- function isStoreMemoryScope(value) {
1758
- return MEMORY_SCOPES.has(value);
1759
- }
1760
- function isListMemoryScope(value) {
1761
- return LIST_MEMORY_SCOPES.has(value);
1762
- }
1763
- function isMemoryStatus(value) {
1764
- return LIST_MEMORY_STATUSES.has(value);
1765
- }
1766
2023
  function coercePositiveInt(value, fallback) {
1767
2024
  const num = Number(value ?? fallback);
1768
2025
  return Number.isFinite(num) && num >= 1 ? Math.floor(num) : fallback;
@@ -1778,13 +2035,13 @@ function validateToolArgs(toolName, args) {
1778
2035
  errors.push(`${field}: Field required`);
1779
2036
  }
1780
2037
  }
1781
- if (toolName === "thenvoi_send_message") {
2038
+ if (toolName === "band_send_message") {
1782
2039
  const mentions = args.mentions;
1783
2040
  if (Array.isArray(mentions) && mentions.length === 0) {
1784
2041
  errors.push("mentions: At least one mention is required");
1785
2042
  }
1786
2043
  }
1787
- if (toolName === "thenvoi_send_event") {
2044
+ if (toolName === "band_send_event") {
1788
2045
  const messageType = args.message_type;
1789
2046
  if (typeof messageType === "string" && !CHAT_EVENT_TYPES.includes(messageType)) {
1790
2047
  errors.push(
@@ -1792,7 +2049,7 @@ function validateToolArgs(toolName, args) {
1792
2049
  );
1793
2050
  }
1794
2051
  }
1795
- if (toolName === "thenvoi_respond_contact_request") {
2052
+ if (toolName === "band_respond_contact_request") {
1796
2053
  const action = args.action;
1797
2054
  const validActions = ["approve", "reject", "cancel"];
1798
2055
  if (typeof action === "string" && !validActions.includes(action)) {
@@ -1801,18 +2058,20 @@ function validateToolArgs(toolName, args) {
1801
2058
  );
1802
2059
  }
1803
2060
  }
1804
- if (toolName === "thenvoi_store_memory") {
1805
- const validSystems = ["sensory", "working", "long_term"];
1806
- const validTypes = ["iconic", "echoic", "haptic", "episodic", "semantic", "procedural"];
1807
- const validSegments = ["user", "agent", "tool", "guideline"];
1808
- if (typeof args.system === "string" && !validSystems.includes(args.system)) {
1809
- errors.push(`system: Invalid value '${args.system}'. Expected one of: ${validSystems.join(", ")}`);
2061
+ if (toolName === "band_store_memory") {
2062
+ if (typeof args.system === "string" && !isMemorySystem(args.system)) {
2063
+ errors.push(`system: Invalid value '${args.system}'. Expected one of: ${expectedList(MEMORY_SYSTEMS)}`);
1810
2064
  }
1811
- if (typeof args.type === "string" && !validTypes.includes(args.type)) {
1812
- errors.push(`type: Invalid value '${args.type}'. Expected one of: ${validTypes.join(", ")}`);
2065
+ if (typeof args.type === "string" && !isMemoryType(args.type)) {
2066
+ errors.push(`type: Invalid value '${args.type}'. Expected one of: ${expectedList(MEMORY_TYPES)}`);
2067
+ }
2068
+ if (typeof args.system === "string" && isMemorySystem(args.system) && typeof args.type === "string" && isMemoryType(args.type) && !isMemoryTypeForSystem(args.system, args.type)) {
2069
+ errors.push(
2070
+ `type: Invalid value '${args.type}' for system '${args.system}'. Expected one of: ${expectedMemoryTypesForSystem(args.system)}`
2071
+ );
1813
2072
  }
1814
- if (typeof args.segment === "string" && !validSegments.includes(args.segment)) {
1815
- errors.push(`segment: Invalid value '${args.segment}'. Expected one of: ${validSegments.join(", ")}`);
2073
+ if (typeof args.segment === "string" && !isMemorySegment(args.segment)) {
2074
+ errors.push(`segment: Invalid value '${args.segment}'. Expected one of: ${expectedList(MEMORY_SEGMENTS)}`);
1816
2075
  }
1817
2076
  }
1818
2077
  if (errors.length > 0) {
@@ -1828,58 +2087,8 @@ function validateToolArgs(toolName, args) {
1828
2087
  return null;
1829
2088
  }
1830
2089
 
1831
- // src/runtime/retryTracker.ts
1832
- var DEFAULT_MAX_TRACKED = 1e4;
1833
- var MessageRetryTracker = class {
1834
- maxRetriesValue;
1835
- maxTracked;
1836
- attempts = /* @__PURE__ */ new Map();
1837
- failed = /* @__PURE__ */ new Set();
1838
- constructor(maxRetries = 1, maxTracked = DEFAULT_MAX_TRACKED) {
1839
- this.maxRetriesValue = maxRetries;
1840
- this.maxTracked = maxTracked;
1841
- }
1842
- get maxRetries() {
1843
- return this.maxRetriesValue;
1844
- }
1845
- isPermanentlyFailed(messageId) {
1846
- return this.failed.has(messageId);
1847
- }
1848
- recordAttempt(messageId) {
1849
- const attempts = (this.attempts.get(messageId) ?? 0) + 1;
1850
- this.attempts.set(messageId, attempts);
1851
- const exceeded = attempts > this.maxRetriesValue;
1852
- if (exceeded) {
1853
- this.evictSetIfNeeded(this.failed);
1854
- this.failed.add(messageId);
1855
- }
1856
- this.evictMapIfNeeded(this.attempts);
1857
- return [attempts, exceeded];
1858
- }
1859
- markSuccess(messageId) {
1860
- this.attempts.delete(messageId);
1861
- }
1862
- markPermanentlyFailed(messageId) {
1863
- this.evictSetIfNeeded(this.failed);
1864
- this.failed.add(messageId);
1865
- }
1866
- evictMapIfNeeded(map) {
1867
- while (map.size >= this.maxTracked) {
1868
- const first = map.keys().next();
1869
- if (first.done) break;
1870
- map.delete(first.value);
1871
- }
1872
- }
1873
- evictSetIfNeeded(set) {
1874
- while (set.size >= this.maxTracked) {
1875
- const first = set.values().next();
1876
- if (first.done) break;
1877
- set.delete(first.value);
1878
- }
1879
- }
1880
- };
1881
-
1882
2090
  // src/runtime/ExecutionContext.ts
2091
+ import { ParticipantRoster as ParticipantRoster2, RetryTracker } from "@band-ai/band-sdk-core";
1883
2092
  var DEDUP_CACHE_MAX = 500;
1884
2093
  var ExecutionContext = class {
1885
2094
  roomId;
@@ -1891,11 +2100,10 @@ var ExecutionContext = class {
1891
2100
  history = [];
1892
2101
  messageIds = /* @__PURE__ */ new Set();
1893
2102
  dedupCache = /* @__PURE__ */ new Map();
1894
- participants = [];
2103
+ roster = new ParticipantRoster2();
1895
2104
  tools;
1896
2105
  adapterTools;
1897
2106
  participantsMessage = null;
1898
- lastSentParticipantIds = null;
1899
2107
  contactsMessage = null;
1900
2108
  contextCache = null;
1901
2109
  contextCacheExpiresAt = 0;
@@ -1910,11 +2118,11 @@ var ExecutionContext = class {
1910
2118
  this.enableContextCache = options.enableContextCache ?? true;
1911
2119
  this.contextCacheTtlMs = Math.max(0, (options.contextCacheTtlSeconds ?? 300) * 1e3);
1912
2120
  this.enableContextHydration = options.enableContextHydration ?? true;
1913
- this.retryTrackerInstance = new MessageRetryTracker(options.maxMessageRetries ?? 1);
2121
+ this.retryTrackerInstance = new RetryTracker(options.maxMessageRetries ?? 1);
1914
2122
  this.tools = new AgentTools({
1915
2123
  roomId: this.roomId,
1916
2124
  rest: this.link.rest,
1917
- participants: this.participants,
2125
+ roster: this.roster,
1918
2126
  capabilities: this.link.capabilities
1919
2127
  });
1920
2128
  this.adapterTools = this.tools.getAdapterTools();
@@ -1982,23 +2190,20 @@ var ExecutionContext = class {
1982
2190
  }));
1983
2191
  }
1984
2192
  setParticipants(participants) {
1985
- this.replaceParticipants(participants);
2193
+ this.roster.setAll(participants);
1986
2194
  this.updateCachedParticipants();
1987
2195
  }
1988
2196
  addParticipant(participant) {
1989
- const existingIndex = this.participants.findIndex((entry) => entry.id === participant.id);
1990
- if (existingIndex >= 0) {
1991
- this.participants.splice(existingIndex, 1);
2197
+ const isNew = this.roster.add(participant);
2198
+ if (isNew) {
2199
+ const name = String(participant.name ?? "unknown");
2200
+ this.participantsMessage = `${name} joined the room.`;
1992
2201
  }
1993
- this.participants.push(participant);
1994
- const name = String(participant.name ?? "unknown");
1995
- this.participantsMessage = `${name} joined the room.`;
1996
2202
  this.updateCachedParticipants();
1997
2203
  }
1998
2204
  removeParticipant(participantId) {
1999
- const removed = this.participants.find((entry) => String(entry.id) === participantId);
2000
- const next = this.participants.filter((entry) => String(entry.id) !== participantId);
2001
- this.replaceParticipants(next);
2205
+ const removed = this.roster.list().find((entry) => entry.id === participantId);
2206
+ this.roster.remove(participantId);
2002
2207
  if (removed) {
2003
2208
  this.participantsMessage = `${String(removed.name ?? participantId)} left the room.`;
2004
2209
  }
@@ -2008,16 +2213,7 @@ var ExecutionContext = class {
2008
2213
  this.contactsMessage = message;
2009
2214
  }
2010
2215
  consumeParticipantsMessage() {
2011
- const currentIds = new Set(this.participants.map((p) => String(p.id)));
2012
- let changed = !this.lastSentParticipantIds || currentIds.size !== this.lastSentParticipantIds.size;
2013
- if (!changed) {
2014
- for (const id of currentIds) {
2015
- if (!this.lastSentParticipantIds.has(id)) {
2016
- changed = true;
2017
- break;
2018
- }
2019
- }
2020
- }
2216
+ const changed = this.roster.changed();
2021
2217
  if (!changed && !this.participantsMessage) {
2022
2218
  return null;
2023
2219
  }
@@ -2027,15 +2223,9 @@ var ExecutionContext = class {
2027
2223
  this.participantsMessage = null;
2028
2224
  }
2029
2225
  if (changed) {
2030
- const asRecords = this.participants.map((p) => ({
2031
- id: p.id,
2032
- name: p.name,
2033
- type: p.type,
2034
- handle: p.handle ?? null
2035
- }));
2036
- parts.push(buildParticipantsMessage(asRecords));
2037
- }
2038
- this.lastSentParticipantIds = currentIds;
2226
+ parts.push(buildParticipantsMessage(this.roster.list().map((p) => ({ ...toParticipantRecord(p) }))));
2227
+ }
2228
+ this.roster.markSent();
2039
2229
  return parts.length > 0 ? parts.join("\n") : null;
2040
2230
  }
2041
2231
  consumeContactsMessage() {
@@ -2086,27 +2276,19 @@ var ExecutionContext = class {
2086
2276
  return fallback;
2087
2277
  }
2088
2278
  }
2089
- replaceParticipants(participants) {
2090
- this.participants.splice(0, this.participants.length, ...participants);
2091
- }
2092
2279
  buildLocalContext() {
2093
2280
  return {
2094
2281
  roomId: this.roomId,
2095
2282
  messages: this.getRawHistory(),
2096
- participants: [...this.participants],
2283
+ participants: this.roster.list().map(toParticipantRecord),
2097
2284
  hydratedAt: /* @__PURE__ */ new Date()
2098
2285
  };
2099
2286
  }
2100
2287
  async loadParticipants() {
2101
2288
  const participants = await this.link.rest.listChatParticipants(this.roomId, DEFAULT_REQUEST_OPTIONS);
2102
- const normalized = participants.map((participant) => ({
2103
- id: participant.id,
2104
- name: participant.name,
2105
- type: participant.type,
2106
- handle: participant.handle ?? null
2107
- }));
2108
- this.replaceParticipants(normalized);
2109
- return [...this.participants];
2289
+ const normalized = participants.map(toParticipantRecordFromRest);
2290
+ this.roster.setAll(normalized);
2291
+ return this.roster.list().map(toParticipantRecord);
2110
2292
  }
2111
2293
  async loadHydratedMessages() {
2112
2294
  const messages = [];
@@ -2148,7 +2330,7 @@ var ExecutionContext = class {
2148
2330
  if (!this.contextCache) {
2149
2331
  return;
2150
2332
  }
2151
- this.contextCache.participants = [...this.participants];
2333
+ this.contextCache.participants = this.roster.list().map(toParticipantRecord);
2152
2334
  this.contextCache.hydratedAt = /* @__PURE__ */ new Date();
2153
2335
  this.contextCacheExpiresAt = this.nextCacheExpiry();
2154
2336
  }
@@ -2349,7 +2531,7 @@ var AgentRuntime = class {
2349
2531
  await this.link.disconnect();
2350
2532
  }
2351
2533
  async stop(timeoutMs) {
2352
- if (!this.running || this.stopping) {
2534
+ if (this.stopping || !this.running && !this.fatalError) {
2353
2535
  return true;
2354
2536
  }
2355
2537
  this.stopping = true;
@@ -2369,7 +2551,8 @@ var AgentRuntime = class {
2369
2551
  }
2370
2552
  }
2371
2553
  for (const roomId of [...this.subscribedRooms]) {
2372
- await this.leaveTrackedRoom(roomId);
2554
+ const remaining = deadline === void 0 ? void 0 : Math.max(0, deadline - Date.now());
2555
+ await this.leaveTrackedRoom(roomId, remaining);
2373
2556
  }
2374
2557
  for (const roomId of [...this.contexts.keys()]) {
2375
2558
  await this.onSessionCleanup(roomId);
@@ -2405,7 +2588,13 @@ var AgentRuntime = class {
2405
2588
  }
2406
2589
  async consumeLoop(signal) {
2407
2590
  while (!signal.aborted) {
2408
- const event = await this.link.nextEvent(signal);
2591
+ let event;
2592
+ try {
2593
+ event = await this.link.nextEvent(signal);
2594
+ } catch (error) {
2595
+ await this.failRuntime(error, syntheticRuntimeFailureEvent(this.agentId));
2596
+ return;
2597
+ }
2409
2598
  if (!event) {
2410
2599
  return;
2411
2600
  }
@@ -2568,13 +2757,13 @@ var AgentRuntime = class {
2568
2757
  }
2569
2758
  });
2570
2759
  }
2571
- async leaveTrackedRoom(roomId) {
2760
+ async leaveTrackedRoom(roomId, timeoutMs) {
2572
2761
  await trackRoomLeave({
2573
2762
  link: this.link,
2574
2763
  roomId,
2575
2764
  trackedRooms: this.subscribedRooms,
2576
2765
  onLeft: async (leftRoomId) => {
2577
- await this.executions.get(leftRoomId)?.stop();
2766
+ await this.executions.get(leftRoomId)?.stop(timeoutMs);
2578
2767
  this.contexts.delete(leftRoomId);
2579
2768
  this.executions.delete(leftRoomId);
2580
2769
  await this.onSessionCleanup(leftRoomId);
@@ -2611,6 +2800,23 @@ var AgentRuntime = class {
2611
2800
  }
2612
2801
  }
2613
2802
  };
2803
+ function syntheticRuntimeFailureEvent(agentId) {
2804
+ return {
2805
+ type: "message_created",
2806
+ roomId: null,
2807
+ payload: {
2808
+ id: "runtime-failed",
2809
+ content: "",
2810
+ sender_id: agentId,
2811
+ sender_type: "Agent",
2812
+ sender_name: null,
2813
+ message_type: "text",
2814
+ metadata: {},
2815
+ inserted_at: (/* @__PURE__ */ new Date(0)).toISOString(),
2816
+ updated_at: (/* @__PURE__ */ new Date(0)).toISOString()
2817
+ }
2818
+ };
2819
+ }
2614
2820
  function assertNever(value) {
2615
2821
  throw new Error(`Unhandled platform event: ${JSON.stringify(value)}`);
2616
2822
  }
@@ -2762,14 +2968,6 @@ var ContactToolsImpl = class {
2762
2968
  };
2763
2969
 
2764
2970
  // src/runtime/tools/ContactCallbackTools.ts
2765
- function toParticipantRecord(participant) {
2766
- return {
2767
- id: participant.id,
2768
- name: participant.name,
2769
- type: participant.type,
2770
- handle: participant.handle ?? null
2771
- };
2772
- }
2773
2971
  function normalizePage(value, fallback) {
2774
2972
  return Number.isInteger(value) && value && value > 0 ? value : fallback;
2775
2973
  }
@@ -2813,8 +3011,7 @@ var ContactCallbackTools = class {
2813
3011
  {
2814
3012
  content,
2815
3013
  ...normalizedMentions ? { mentions: normalizedMentions } : {}
2816
- },
2817
- DEFAULT_REQUEST_OPTIONS
3014
+ }
2818
3015
  );
2819
3016
  }
2820
3017
  async sendEvent(content, messageType, metadata) {
@@ -2828,8 +3025,7 @@ var ContactCallbackTools = class {
2828
3025
  content,
2829
3026
  messageType,
2830
3027
  ...metadata ? { metadata } : {}
2831
- },
2832
- DEFAULT_REQUEST_OPTIONS
3028
+ }
2833
3029
  );
2834
3030
  }
2835
3031
  async addParticipant() {
@@ -2843,7 +3039,7 @@ var ContactCallbackTools = class {
2843
3039
  if (!this.rest.listChatParticipants) {
2844
3040
  throw new UnsupportedFeatureError("Participant listing is not available in current REST adapter");
2845
3041
  }
2846
- return (await this.rest.listChatParticipants(roomId, DEFAULT_REQUEST_OPTIONS)).map(toParticipantRecord);
3042
+ return (await this.rest.listChatParticipants(roomId, DEFAULT_REQUEST_OPTIONS)).map(toParticipantRecordFromRest);
2847
3043
  }
2848
3044
  async createChatroom(taskId) {
2849
3045
  const chat = await this.rest.createChat(taskId, DEFAULT_REQUEST_OPTIONS);
@@ -2933,48 +3129,48 @@ var ContactCallbackTools = class {
2933
3129
  }
2934
3130
  async executeToolCall(toolName, toolArgs) {
2935
3131
  switch (toolName) {
2936
- case "thenvoi_send_message":
3132
+ case "band_send_message":
2937
3133
  return this.sendMessage(
2938
3134
  String(toolArgs.content ?? ""),
2939
3135
  toolArgs.mentions
2940
3136
  );
2941
- case "thenvoi_send_event":
3137
+ case "band_send_event":
2942
3138
  return this.sendEvent(
2943
3139
  String(toolArgs.content ?? ""),
2944
3140
  String(toolArgs.message_type ?? "task"),
2945
3141
  toolArgs.metadata
2946
3142
  );
2947
- case "thenvoi_get_participants":
3143
+ case "band_get_participants":
2948
3144
  return this.getParticipants();
2949
- case "thenvoi_create_chatroom":
3145
+ case "band_create_chatroom":
2950
3146
  return this.createChatroom(typeof toolArgs.task_id === "string" ? toolArgs.task_id : void 0);
2951
- case "thenvoi_lookup_peers":
3147
+ case "band_lookup_peers":
2952
3148
  return this.lookupPeers(
2953
3149
  typeof toolArgs.page === "number" ? toolArgs.page : void 0,
2954
3150
  typeof toolArgs.page_size === "number" ? toolArgs.page_size : void 0
2955
3151
  );
2956
- case "thenvoi_list_contacts":
3152
+ case "band_list_contacts":
2957
3153
  return this.listContacts({
2958
3154
  page: typeof toolArgs.page === "number" ? toolArgs.page : void 0,
2959
3155
  pageSize: typeof toolArgs.page_size === "number" ? toolArgs.page_size : void 0
2960
3156
  });
2961
- case "thenvoi_add_contact":
3157
+ case "band_add_contact":
2962
3158
  return this.addContact({
2963
3159
  handle: String(toolArgs.handle ?? ""),
2964
3160
  ...typeof toolArgs.message === "string" ? { message: toolArgs.message } : {}
2965
3161
  });
2966
- case "thenvoi_remove_contact":
3162
+ case "band_remove_contact":
2967
3163
  if (typeof toolArgs.contact_id === "string") {
2968
3164
  return this.removeContact({ target: "contactId", contactId: toolArgs.contact_id });
2969
3165
  }
2970
3166
  return this.removeContact({ target: "handle", handle: String(toolArgs.handle ?? "") });
2971
- case "thenvoi_list_contact_requests":
3167
+ case "band_list_contact_requests":
2972
3168
  return this.listContactRequests({
2973
3169
  page: typeof toolArgs.page === "number" ? toolArgs.page : void 0,
2974
3170
  pageSize: typeof toolArgs.page_size === "number" ? toolArgs.page_size : void 0,
2975
3171
  sentStatus: typeof toolArgs.sent_status === "string" ? toolArgs.sent_status : void 0
2976
3172
  });
2977
- case "thenvoi_respond_contact_request":
3173
+ case "band_respond_contact_request":
2978
3174
  if (typeof toolArgs.request_id === "string") {
2979
3175
  return this.respondContactRequest({
2980
3176
  action: String(toolArgs.action ?? "approve"),
@@ -2987,15 +3183,15 @@ var ContactCallbackTools = class {
2987
3183
  target: "handle",
2988
3184
  handle: String(toolArgs.handle ?? "")
2989
3185
  });
2990
- case "thenvoi_list_memories":
3186
+ case "band_list_memories":
2991
3187
  return this.listMemories(toolArgs);
2992
- case "thenvoi_store_memory":
3188
+ case "band_store_memory":
2993
3189
  return this.storeMemory(toolArgs);
2994
- case "thenvoi_get_memory":
3190
+ case "band_get_memory":
2995
3191
  return this.getMemory(String(toolArgs.memory_id ?? ""));
2996
- case "thenvoi_supersede_memory":
3192
+ case "band_supersede_memory":
2997
3193
  return this.supersedeMemory(String(toolArgs.memory_id ?? ""));
2998
- case "thenvoi_archive_memory":
3194
+ case "band_archive_memory":
2999
3195
  return this.archiveMemory(String(toolArgs.memory_id ?? ""));
3000
3196
  default:
3001
3197
  throw new UnsupportedFeatureError(`Unsupported tool call for contact callback: ${toolName}`);
@@ -3009,14 +3205,14 @@ var HUB_ROOM_SYSTEM_PROMPT = `## OVERRIDE: Contact Management Mode
3009
3205
 
3010
3206
  This is your CONTACTS HUB - a dedicated room for managing contact requests.
3011
3207
 
3012
- **IMPORTANT: Do NOT delegate or add participants here.** You handle contact events DIRECTLY using the contact tools below. Do NOT call thenvoi_lookup_peers() or thenvoi_add_participant() in this room.
3208
+ **IMPORTANT: Do NOT delegate or add participants here.** You handle contact events DIRECTLY using the contact tools below. Do NOT call band_lookup_peers() or band_add_participant() in this room.
3013
3209
 
3014
3210
  ## Your Role
3015
3211
 
3016
3212
  1. **Review incoming contact requests** - When you see a [Contact Request] message, evaluate it
3017
3213
  2. **Take action** - Use the contact tools to respond:
3018
- - \`thenvoi_respond_contact_request(action="approve", request_id="...")\` to accept
3019
- - \`thenvoi_respond_contact_request(action="reject", request_id="...")\` to decline
3214
+ - \`band_respond_contact_request(action="approve", request_id="...")\` to accept
3215
+ - \`band_respond_contact_request(action="reject", request_id="...")\` to decline
3020
3216
  3. **Report your decision** - Send a thought event explaining what you did
3021
3217
 
3022
3218
  ## Example
@@ -3025,14 +3221,14 @@ This is your CONTACTS HUB - a dedicated room for managing contact requests.
3025
3221
  Request ID: abc-123
3026
3222
 
3027
3223
  Your response:
3028
- 1. thenvoi_send_event("Received contact request from Alice. Approving.", message_type="thought")
3029
- 2. thenvoi_respond_contact_request(action="approve", request_id="abc-123")
3030
- 3. thenvoi_send_event("Approved contact request from Alice (@alice)", message_type="thought")
3224
+ 1. band_send_event("Received contact request from Alice. Approving.", message_type="thought")
3225
+ 2. band_respond_contact_request(action="approve", request_id="abc-123")
3226
+ 3. band_send_event("Approved contact request from Alice (@alice)", message_type="thought")
3031
3227
 
3032
3228
  ## Contact Tools (use these, NOT participant tools)
3033
- - \`thenvoi_respond_contact_request(action, request_id)\` - Approve/reject requests
3034
- - \`thenvoi_list_contact_requests()\` - List pending requests
3035
- - \`thenvoi_list_contacts()\` - List current contacts`;
3229
+ - \`band_respond_contact_request(action, request_id)\` - Approve/reject requests
3230
+ - \`band_list_contact_requests()\` - List pending requests
3231
+ - \`band_list_contacts()\` - List current contacts`;
3036
3232
  var ContactEventHandlerError = class extends Error {
3037
3233
  retryable;
3038
3234
  stage;
@@ -3360,6 +3556,8 @@ function normalizeHandle(handle) {
3360
3556
  }
3361
3557
 
3362
3558
  // src/runtime/PlatformRuntime.ts
3559
+ var MAX_MESSAGE_RETRIES = 4294967295;
3560
+ var isValidRetryCount = (value) => Number.isInteger(value) && value >= 0 && value <= MAX_MESSAGE_RETRIES;
3363
3561
  var PlatformRuntime = class {
3364
3562
  _agentId;
3365
3563
  _apiKey;
@@ -3396,6 +3594,12 @@ var PlatformRuntime = class {
3396
3594
  "apiKey is required and must be a non-empty string. Use loadAgentConfig() to load credentials from agent_config.yaml."
3397
3595
  );
3398
3596
  }
3597
+ const maxMessageRetries = options.sessionConfig?.maxMessageRetries;
3598
+ if (maxMessageRetries !== void 0 && !isValidRetryCount(maxMessageRetries)) {
3599
+ throw new ValidationError(
3600
+ `sessionConfig.maxMessageRetries must be an integer between 0 and ${MAX_MESSAGE_RETRIES}, got ${maxMessageRetries}.`
3601
+ );
3602
+ }
3399
3603
  this._agentId = options.agentId;
3400
3604
  this._apiKey = options.apiKey;
3401
3605
  this._wsUrl = options.wsUrl;
@@ -3448,12 +3652,13 @@ var PlatformRuntime = class {
3448
3652
  }
3449
3653
  async doInitialize() {
3450
3654
  if (!this.linkInstance) {
3451
- this.linkInstance = new ThenvoiLink({
3655
+ this.linkInstance = new BandLink({
3452
3656
  ...this.linkOptions,
3453
3657
  agentId: this._agentId,
3454
3658
  apiKey: this._apiKey,
3455
3659
  wsUrl: this._wsUrl,
3456
- restUrl: this._restUrl
3660
+ restUrl: this._restUrl,
3661
+ logger: this.logger
3457
3662
  });
3458
3663
  }
3459
3664
  if (this.configuredIdentity) {
@@ -3670,10 +3875,10 @@ async function runWithGracefulShutdown(agent, options) {
3670
3875
  }
3671
3876
 
3672
3877
  export {
3673
- ThenvoiLink,
3878
+ deriveDefaultRestUrl,
3879
+ BandLink,
3674
3880
  Execution,
3675
3881
  AgentTools,
3676
- MessageRetryTracker,
3677
3882
  ExecutionContext,
3678
3883
  trackRoomJoin,
3679
3884
  trackRoomLeave,