@band-ai/sdk 0.1.6 → 0.1.10

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 (73) hide show
  1. package/README.md +372 -0
  2. package/dist/{ClaudeSDKAdapter-CRq4R-WB.d.cts → ClaudeSDKAdapter-DfjaI5ke.d.cts} +9 -7
  3. package/dist/{ClaudeSDKAdapter-BHOkjN3X.d.ts → ClaudeSDKAdapter-DonPGJ43.d.ts} +9 -7
  4. package/dist/{acp-server-BdRz9_UN.d.cts → acp-server-CUBXIiG6.d.cts} +2 -2
  5. package/dist/{acp-server-B1Da7L6u.d.ts → acp-server-NZAeg-WO.d.ts} +2 -2
  6. package/dist/adapters.cjs +494 -106
  7. package/dist/adapters.d.cts +12 -12
  8. package/dist/adapters.d.ts +12 -12
  9. package/dist/adapters.js +7 -7
  10. package/dist/{backends-SVMT-7l4.d.cts → backends-DYtEhCZG.d.cts} +7 -2
  11. package/dist/{backends-CzDbiQar.d.ts → backends-oYAb7EU-.d.ts} +7 -2
  12. package/dist/{chunk-MSL6CWHY.js → chunk-2BHHR6YW.js} +17 -16
  13. package/dist/{chunk-EDPKUUJL.js → chunk-6RXWL6TC.js} +9 -1
  14. package/dist/{chunk-SFVKOEQH.js → chunk-DNPVCR3S.js} +92 -14
  15. package/dist/{chunk-I5EZG52P.js → chunk-FP237EAG.js} +424 -136
  16. package/dist/{chunk-RHVH2GZR.js → chunk-J3M3PFFY.js} +81 -31
  17. package/dist/{chunk-GVEQ3RAH.js → chunk-MLROFDKR.js} +132 -23
  18. package/dist/{chunk-INA6YZTZ.js → chunk-RDM4TE4B.js} +32 -30
  19. package/dist/chunk-US226AS4.js +130 -0
  20. package/dist/{chunk-W5NDZSYE.js → chunk-XA4N2JPH.js} +1 -1
  21. package/dist/converters.cjs +469 -13
  22. package/dist/converters.d.cts +5 -5
  23. package/dist/converters.d.ts +5 -5
  24. package/dist/converters.js +3 -2
  25. package/dist/core.cjs +45 -2
  26. package/dist/core.d.cts +4 -3
  27. package/dist/core.d.ts +4 -3
  28. package/dist/core.js +5 -1
  29. package/dist/disconnectReason-IuADl6TH.d.cts +34 -0
  30. package/dist/disconnectReason-IuADl6TH.d.ts +34 -0
  31. package/dist/{dtos-DUpbIu8k.d.cts → dtos-Dd_fTfLn.d.cts} +26 -6
  32. package/dist/{dtos-DUpbIu8k.d.ts → dtos-Dd_fTfLn.d.ts} +26 -6
  33. package/dist/index.cjs +1089 -269
  34. package/dist/index.d.cts +16 -15
  35. package/dist/index.d.ts +16 -15
  36. package/dist/index.js +14 -8
  37. package/dist/linear.cjs +985 -53
  38. package/dist/linear.d.cts +137 -7
  39. package/dist/linear.d.ts +137 -7
  40. package/dist/linear.js +974 -53
  41. package/dist/mcp-claude.cjs +303 -32
  42. package/dist/mcp-claude.d.cts +3 -3
  43. package/dist/mcp-claude.d.ts +3 -3
  44. package/dist/mcp-claude.js +210 -12
  45. package/dist/mcp.cjs +313 -45
  46. package/dist/mcp.d.cts +5 -5
  47. package/dist/mcp.d.ts +5 -5
  48. package/dist/mcp.js +3 -3
  49. package/dist/{opencode-BBcDchcN.d.cts → opencode--iA5-KYk.d.cts} +2 -2
  50. package/dist/{opencode-DthQQUsZ.d.ts → opencode-YNacv-eJ.d.ts} +2 -2
  51. package/dist/{pagination-CcDkVFxK.d.ts → pagination-BBLsYLhT.d.ts} +2 -2
  52. package/dist/{pagination-BPiys10t.d.cts → pagination-CqidD-L_.d.cts} +2 -2
  53. package/dist/{protocols-DrhzOH6K.d.ts → protocols-BF6jamIl.d.ts} +1 -1
  54. package/dist/{protocols-CInhtFpA.d.cts → protocols-D2bTrQ_t.d.cts} +1 -1
  55. package/dist/rest.d.cts +5 -5
  56. package/dist/rest.d.ts +5 -5
  57. package/dist/runtime.cjs +721 -172
  58. package/dist/runtime.d.cts +15 -10
  59. package/dist/runtime.d.ts +15 -10
  60. package/dist/runtime.js +11 -8
  61. package/dist/{schemas-vos1AaBc.d.ts → schemas-B1Fd3oUM.d.ts} +17 -8
  62. package/dist/{schemas-BpEBkq5V.d.cts → schemas-CI0qgcp_.d.cts} +17 -8
  63. package/dist/{sdk-CcnQ5OUC.d.ts → sdk-CyqN1W5g.d.ts} +32 -2
  64. package/dist/{sdk-CwTAxhwI.d.cts → sdk-D2rTDU_o.d.cts} +32 -2
  65. package/dist/{simpleAdapter-DqhvjsGr.d.cts → simpleAdapter-C_f-01Cu.d.cts} +1 -1
  66. package/dist/{simpleAdapter-9OE_p6QW.d.ts → simpleAdapter-qQt-tSz4.d.ts} +1 -1
  67. package/dist/testing.d.cts +3 -3
  68. package/dist/testing.d.ts +3 -3
  69. package/dist/{types-CovcHpYf.d.cts → types-CKqrhGB1.d.cts} +2 -2
  70. package/dist/{types-CxII3XkA.d.ts → types-D3AsUnd0.d.ts} +1 -1
  71. package/dist/{types-7jVzOeOP.d.ts → types-DNySTI_8.d.ts} +2 -2
  72. package/dist/{types-CSHFipIc.d.cts → types-DtMms5Fi.d.cts} +1 -1
  73. package/package.json +22 -21
package/dist/runtime.cjs CHANGED
@@ -38,6 +38,7 @@ __export(runtime_exports, {
38
38
  HUB_ROOM_SYSTEM_PROMPT: () => HUB_ROOM_SYSTEM_PROMPT,
39
39
  HistoryProvider: () => HistoryProvider,
40
40
  MCP_TOOL_PREFIX: () => MCP_TOOL_PREFIX,
41
+ MEMORY_SECTION: () => MEMORY_SECTION,
41
42
  MEMORY_TOOL_NAMES: () => MEMORY_TOOL_NAMES,
42
43
  MessageRetryTracker: () => MessageRetryTracker,
43
44
  ParticipantTracker: () => ParticipantTracker,
@@ -1172,6 +1173,108 @@ var DEFAULT_AGENT_TOOLS_CAPABILITIES = {
1172
1173
  memory: true
1173
1174
  };
1174
1175
 
1176
+ // src/contracts/memory.ts
1177
+ var SENSORY_MEMORY_TYPES = [
1178
+ "iconic",
1179
+ // Visual input
1180
+ "echoic",
1181
+ // Auditory input
1182
+ "haptic"
1183
+ // Tactile input
1184
+ ];
1185
+ var COGNITIVE_MEMORY_TYPES = [
1186
+ "episodic",
1187
+ // Events that occurred
1188
+ "semantic",
1189
+ // Facts, preferences, learned knowledge
1190
+ "procedural"
1191
+ // How to perform tasks
1192
+ ];
1193
+ function namedValues(values) {
1194
+ return Object.fromEntries(values.map((value) => [value, value]));
1195
+ }
1196
+ var MEMORY_SYSTEM_TYPES = {
1197
+ sensory: SENSORY_MEMORY_TYPES,
1198
+ // Brief sensory inputs
1199
+ working: COGNITIVE_MEMORY_TYPES,
1200
+ // Short-term session context
1201
+ long_term: COGNITIVE_MEMORY_TYPES
1202
+ // Persistent cross-conversation memory
1203
+ };
1204
+ var MEMORY_SYSTEMS = Object.keys(MEMORY_SYSTEM_TYPES);
1205
+ var MEMORY_TYPES = [
1206
+ ...SENSORY_MEMORY_TYPES,
1207
+ ...COGNITIVE_MEMORY_TYPES
1208
+ ];
1209
+ var MEMORY_SEGMENTS = [
1210
+ "user",
1211
+ // User preferences or profile info
1212
+ "agent",
1213
+ // Facts or events about agents/entities
1214
+ "tool",
1215
+ // Tool usage or task procedures
1216
+ "guideline"
1217
+ // Behavioral rules or policies
1218
+ ];
1219
+ var MEMORY_STORE_SCOPES = [
1220
+ "subject",
1221
+ // About one person/agent; requires subject_id
1222
+ "organization"
1223
+ // Shared org-wide
1224
+ ];
1225
+ var MEMORY_LIST_SCOPES = [
1226
+ "subject",
1227
+ // Subject-scoped memories only
1228
+ "organization",
1229
+ // Organization-scoped memories only
1230
+ "all"
1231
+ // Both scopes (no scope filter)
1232
+ ];
1233
+ var MEMORY_STATUSES = [
1234
+ "active",
1235
+ // Normal, visible memories
1236
+ "superseded",
1237
+ // Outdated; soft-deleted via thenvoi_supersede_memory
1238
+ "archived",
1239
+ // Hidden but preserved via thenvoi_archive_memory
1240
+ "all"
1241
+ // Any status (no filter)
1242
+ ];
1243
+ var MEMORY_SYSTEM = namedValues(MEMORY_SYSTEMS);
1244
+ var MEMORY_TYPE = namedValues(MEMORY_TYPES);
1245
+ var MEMORY_SEGMENT = namedValues(MEMORY_SEGMENTS);
1246
+ var MEMORY_STORE_SCOPE = namedValues(MEMORY_STORE_SCOPES);
1247
+ function isMemorySystem(value) {
1248
+ return MEMORY_SYSTEMS.includes(value);
1249
+ }
1250
+ function isMemoryType(value) {
1251
+ return MEMORY_TYPES.includes(value);
1252
+ }
1253
+ function isMemoryTypeForSystem(system, type) {
1254
+ return MEMORY_SYSTEM_TYPES[system].includes(type);
1255
+ }
1256
+ function isMemorySegment(value) {
1257
+ return MEMORY_SEGMENTS.includes(value);
1258
+ }
1259
+ function isMemoryStoreScope(value) {
1260
+ return MEMORY_STORE_SCOPES.includes(value);
1261
+ }
1262
+ function isMemoryListScope(value) {
1263
+ return MEMORY_LIST_SCOPES.includes(value);
1264
+ }
1265
+ function isMemoryStatus(value) {
1266
+ return MEMORY_STATUSES.includes(value);
1267
+ }
1268
+ function expectedList(values) {
1269
+ return values.join(", ");
1270
+ }
1271
+ function expectedMemoryTypesForSystem(system) {
1272
+ return expectedList(MEMORY_SYSTEM_TYPES[system]);
1273
+ }
1274
+ function memoryTypeForSystemError(system) {
1275
+ return `type must be one of: ${expectedMemoryTypesForSystem(system)} for system "${system}"`;
1276
+ }
1277
+
1175
1278
  // src/runtime/tools/schemas.ts
1176
1279
  var TOOL_MODELS = {
1177
1280
  thenvoi_send_message: {
@@ -1359,29 +1462,22 @@ var TOOL_MODELS = {
1359
1462
  },
1360
1463
  scope: {
1361
1464
  type: "string",
1362
- enum: ["subject", "organization", "all"],
1465
+ enum: [...MEMORY_LIST_SCOPES],
1363
1466
  description: "Filter by scope."
1364
1467
  },
1365
1468
  system: {
1366
1469
  type: "string",
1367
- enum: ["sensory", "working", "long_term"],
1470
+ enum: [...MEMORY_SYSTEMS],
1368
1471
  description: "Filter by memory system."
1369
1472
  },
1370
1473
  type: {
1371
1474
  type: "string",
1372
- enum: [
1373
- "iconic",
1374
- "echoic",
1375
- "haptic",
1376
- "episodic",
1377
- "semantic",
1378
- "procedural"
1379
- ],
1475
+ enum: [...MEMORY_TYPES],
1380
1476
  description: "Filter by memory type."
1381
1477
  },
1382
1478
  segment: {
1383
1479
  type: "string",
1384
- enum: ["user", "agent", "tool", "guideline"],
1480
+ enum: [...MEMORY_SEGMENTS],
1385
1481
  description: "Filter by segment."
1386
1482
  },
1387
1483
  content_query: {
@@ -1396,7 +1492,7 @@ var TOOL_MODELS = {
1396
1492
  },
1397
1493
  status: {
1398
1494
  type: "string",
1399
- enum: ["active", "superseded", "archived", "all"],
1495
+ enum: [...MEMORY_STATUSES],
1400
1496
  description: "Filter by status."
1401
1497
  }
1402
1498
  },
@@ -1411,24 +1507,17 @@ var TOOL_MODELS = {
1411
1507
  },
1412
1508
  system: {
1413
1509
  type: "string",
1414
- enum: ["sensory", "working", "long_term"],
1510
+ enum: [...MEMORY_SYSTEMS],
1415
1511
  description: "Memory system tier."
1416
1512
  },
1417
1513
  type: {
1418
1514
  type: "string",
1419
- enum: [
1420
- "iconic",
1421
- "echoic",
1422
- "haptic",
1423
- "episodic",
1424
- "semantic",
1425
- "procedural"
1426
- ],
1515
+ enum: [...MEMORY_TYPES],
1427
1516
  description: "Memory type (must be valid for selected system)."
1428
1517
  },
1429
1518
  segment: {
1430
1519
  type: "string",
1431
- enum: ["user", "agent", "tool", "guideline"],
1520
+ enum: [...MEMORY_SEGMENTS],
1432
1521
  description: "Logical segment."
1433
1522
  },
1434
1523
  thought: {
@@ -1437,7 +1526,7 @@ var TOOL_MODELS = {
1437
1526
  },
1438
1527
  scope: {
1439
1528
  type: "string",
1440
- enum: ["subject", "organization"],
1529
+ enum: [...MEMORY_STORE_SCOPES],
1441
1530
  description: "Visibility scope."
1442
1531
  },
1443
1532
  subject_id: {
@@ -1547,40 +1636,6 @@ var CONTACT_REQUEST_ACTIONS = /* @__PURE__ */ new Set([
1547
1636
  "reject",
1548
1637
  "cancel"
1549
1638
  ]);
1550
- var MEMORY_SYSTEMS = /* @__PURE__ */ new Set([
1551
- "sensory",
1552
- "working",
1553
- "long_term"
1554
- ]);
1555
- var MEMORY_TYPES = /* @__PURE__ */ new Set([
1556
- "iconic",
1557
- "echoic",
1558
- "haptic",
1559
- "episodic",
1560
- "semantic",
1561
- "procedural"
1562
- ]);
1563
- var MEMORY_SEGMENTS = /* @__PURE__ */ new Set([
1564
- "user",
1565
- "agent",
1566
- "tool",
1567
- "guideline"
1568
- ]);
1569
- var LIST_MEMORY_SCOPES = /* @__PURE__ */ new Set([
1570
- "subject",
1571
- "organization",
1572
- "all"
1573
- ]);
1574
- var LIST_MEMORY_STATUSES = /* @__PURE__ */ new Set([
1575
- "active",
1576
- "superseded",
1577
- "archived",
1578
- "all"
1579
- ]);
1580
- var MEMORY_SCOPES = /* @__PURE__ */ new Set([
1581
- "subject",
1582
- "organization"
1583
- ]);
1584
1639
  var AgentTools = class {
1585
1640
  roomId;
1586
1641
  capabilities;
@@ -2158,6 +2213,9 @@ var AgentTools = class {
2158
2213
  const status = this.normalizeOptionalMemoryStatus(arguments_.status);
2159
2214
  const subjectId = this.normalizeOptionalString(arguments_.subject_id);
2160
2215
  const contentQuery = this.normalizeOptionalString(arguments_.content_query);
2216
+ if (system && type && !isMemoryTypeForSystem(system, type)) {
2217
+ throw new ValidationError(memoryTypeForSystemError(system));
2218
+ }
2161
2219
  return {
2162
2220
  ...subjectId ? { subject_id: subjectId } : {},
2163
2221
  ...scope ? { scope } : {},
@@ -2179,13 +2237,21 @@ var AgentTools = class {
2179
2237
  const subjectId = this.normalizeOptionalString(arguments_.subject_id);
2180
2238
  const metadata = this.normalizeOptionalMetadata(arguments_.metadata);
2181
2239
  if (!system) {
2182
- throw new ValidationError("system must be one of: sensory, working, long_term");
2240
+ throw new ValidationError(`system must be one of: ${expectedList(MEMORY_SYSTEMS)}`);
2183
2241
  }
2184
2242
  if (!type) {
2185
- throw new ValidationError("type must be one of: iconic, echoic, haptic, episodic, semantic, procedural");
2243
+ throw new ValidationError(`type must be one of: ${expectedList(MEMORY_TYPES)}`);
2244
+ }
2245
+ if (!isMemoryTypeForSystem(system, type)) {
2246
+ throw new ValidationError(memoryTypeForSystemError(system));
2186
2247
  }
2187
2248
  if (!segment) {
2188
- throw new ValidationError("segment must be one of: user, agent, tool, guideline");
2249
+ throw new ValidationError(`segment must be one of: ${expectedList(MEMORY_SEGMENTS)}`);
2250
+ }
2251
+ if (scope === "subject" && !subjectId) {
2252
+ throw new ValidationError(
2253
+ '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.'
2254
+ );
2189
2255
  }
2190
2256
  return {
2191
2257
  content,
@@ -2265,8 +2331,8 @@ var AgentTools = class {
2265
2331
  if (!normalized) {
2266
2332
  return void 0;
2267
2333
  }
2268
- if (!isListMemoryScope(normalized)) {
2269
- throw new ValidationError("scope must be one of: subject, organization, all");
2334
+ if (!isMemoryListScope(normalized)) {
2335
+ throw new ValidationError(`scope must be one of: ${expectedList(MEMORY_LIST_SCOPES)}`);
2270
2336
  }
2271
2337
  return normalized;
2272
2338
  }
@@ -2276,7 +2342,7 @@ var AgentTools = class {
2276
2342
  return void 0;
2277
2343
  }
2278
2344
  if (!isMemorySystem(normalized)) {
2279
- throw new ValidationError("system must be one of: sensory, working, long_term");
2345
+ throw new ValidationError(`system must be one of: ${expectedList(MEMORY_SYSTEMS)}`);
2280
2346
  }
2281
2347
  return normalized;
2282
2348
  }
@@ -2286,7 +2352,7 @@ var AgentTools = class {
2286
2352
  return void 0;
2287
2353
  }
2288
2354
  if (!isMemoryType(normalized)) {
2289
- throw new ValidationError("type must be one of: iconic, echoic, haptic, episodic, semantic, procedural");
2355
+ throw new ValidationError(`type must be one of: ${expectedList(MEMORY_TYPES)}`);
2290
2356
  }
2291
2357
  return normalized;
2292
2358
  }
@@ -2296,7 +2362,7 @@ var AgentTools = class {
2296
2362
  return void 0;
2297
2363
  }
2298
2364
  if (!isMemorySegment(normalized)) {
2299
- throw new ValidationError("segment must be one of: user, agent, tool, guideline");
2365
+ throw new ValidationError(`segment must be one of: ${expectedList(MEMORY_SEGMENTS)}`);
2300
2366
  }
2301
2367
  return normalized;
2302
2368
  }
@@ -2306,7 +2372,7 @@ var AgentTools = class {
2306
2372
  return void 0;
2307
2373
  }
2308
2374
  if (!isMemoryStatus(normalized)) {
2309
- throw new ValidationError("status must be one of: active, superseded, archived, all");
2375
+ throw new ValidationError(`status must be one of: ${expectedList(MEMORY_STATUSES)}`);
2310
2376
  }
2311
2377
  return normalized;
2312
2378
  }
@@ -2315,8 +2381,8 @@ var AgentTools = class {
2315
2381
  if (!normalized) {
2316
2382
  return void 0;
2317
2383
  }
2318
- if (!isStoreMemoryScope(normalized)) {
2319
- throw new ValidationError("scope must be one of: subject, organization");
2384
+ if (!isMemoryStoreScope(normalized)) {
2385
+ throw new ValidationError(`scope must be one of: ${expectedList(MEMORY_STORE_SCOPES)}`);
2320
2386
  }
2321
2387
  return normalized;
2322
2388
  }
@@ -2324,24 +2390,6 @@ var AgentTools = class {
2324
2390
  function isContactRequestAction(value) {
2325
2391
  return CONTACT_REQUEST_ACTIONS.has(value);
2326
2392
  }
2327
- function isMemorySystem(value) {
2328
- return MEMORY_SYSTEMS.has(value);
2329
- }
2330
- function isMemoryType(value) {
2331
- return MEMORY_TYPES.has(value);
2332
- }
2333
- function isMemorySegment(value) {
2334
- return MEMORY_SEGMENTS.has(value);
2335
- }
2336
- function isStoreMemoryScope(value) {
2337
- return MEMORY_SCOPES.has(value);
2338
- }
2339
- function isListMemoryScope(value) {
2340
- return LIST_MEMORY_SCOPES.has(value);
2341
- }
2342
- function isMemoryStatus(value) {
2343
- return LIST_MEMORY_STATUSES.has(value);
2344
- }
2345
2393
  function coercePositiveInt(value, fallback) {
2346
2394
  const num = Number(value ?? fallback);
2347
2395
  return Number.isFinite(num) && num >= 1 ? Math.floor(num) : fallback;
@@ -2381,17 +2429,19 @@ function validateToolArgs(toolName, args) {
2381
2429
  }
2382
2430
  }
2383
2431
  if (toolName === "thenvoi_store_memory") {
2384
- const validSystems = ["sensory", "working", "long_term"];
2385
- const validTypes = ["iconic", "echoic", "haptic", "episodic", "semantic", "procedural"];
2386
- const validSegments = ["user", "agent", "tool", "guideline"];
2387
- if (typeof args.system === "string" && !validSystems.includes(args.system)) {
2388
- errors.push(`system: Invalid value '${args.system}'. Expected one of: ${validSystems.join(", ")}`);
2432
+ if (typeof args.system === "string" && !isMemorySystem(args.system)) {
2433
+ errors.push(`system: Invalid value '${args.system}'. Expected one of: ${expectedList(MEMORY_SYSTEMS)}`);
2389
2434
  }
2390
- if (typeof args.type === "string" && !validTypes.includes(args.type)) {
2391
- errors.push(`type: Invalid value '${args.type}'. Expected one of: ${validTypes.join(", ")}`);
2435
+ if (typeof args.type === "string" && !isMemoryType(args.type)) {
2436
+ errors.push(`type: Invalid value '${args.type}'. Expected one of: ${expectedList(MEMORY_TYPES)}`);
2392
2437
  }
2393
- if (typeof args.segment === "string" && !validSegments.includes(args.segment)) {
2394
- errors.push(`segment: Invalid value '${args.segment}'. Expected one of: ${validSegments.join(", ")}`);
2438
+ if (typeof args.system === "string" && isMemorySystem(args.system) && typeof args.type === "string" && isMemoryType(args.type) && !isMemoryTypeForSystem(args.system, args.type)) {
2439
+ errors.push(
2440
+ `type: Invalid value '${args.type}' for system '${args.system}'. Expected one of: ${expectedMemoryTypesForSystem(args.system)}`
2441
+ );
2442
+ }
2443
+ if (typeof args.segment === "string" && !isMemorySegment(args.segment)) {
2444
+ errors.push(`segment: Invalid value '${args.segment}'. Expected one of: ${expectedList(MEMORY_SEGMENTS)}`);
2395
2445
  }
2396
2446
  }
2397
2447
  if (errors.length > 0) {
@@ -2471,7 +2521,7 @@ function replaceUuidMentions(content, participants) {
2471
2521
  const participantId = participant.id;
2472
2522
  const handle = participant.handle;
2473
2523
  if (typeof participantId === "string" && typeof handle === "string") {
2474
- next = next.replaceAll(`@[[${participantId}]]`, `@${handle}`);
2524
+ next = next.replaceAll(`@[[${participantId}]]`, ensureHandlePrefix(handle) ?? "");
2475
2525
  }
2476
2526
  }
2477
2527
  return next;
@@ -2503,7 +2553,9 @@ function buildParticipantsMessage(participants) {
2503
2553
  const participantType = String(participant.type ?? "Unknown");
2504
2554
  const participantName = String(participant.name ?? "Unknown");
2505
2555
  const participantHandle = String(participant.handle ?? "Unknown");
2506
- lines.push(`- @${participantHandle} \u2014 ${participantName} (${participantType})`);
2556
+ lines.push(
2557
+ `- ${ensureHandlePrefix(participantHandle) ?? ""} \u2014 ${participantName} (${participantType})`
2558
+ );
2507
2559
  }
2508
2560
  lines.push("");
2509
2561
  lines.push(
@@ -2982,7 +3034,7 @@ var AgentRuntime = class {
2982
3034
  await this.link.disconnect();
2983
3035
  }
2984
3036
  async stop(timeoutMs) {
2985
- if (!this.running || this.stopping) {
3037
+ if (this.stopping || !this.running && !this.fatalError) {
2986
3038
  return true;
2987
3039
  }
2988
3040
  this.stopping = true;
@@ -3002,7 +3054,8 @@ var AgentRuntime = class {
3002
3054
  }
3003
3055
  }
3004
3056
  for (const roomId of [...this.subscribedRooms]) {
3005
- await this.leaveTrackedRoom(roomId);
3057
+ const remaining = deadline === void 0 ? void 0 : Math.max(0, deadline - Date.now());
3058
+ await this.leaveTrackedRoom(roomId, remaining);
3006
3059
  }
3007
3060
  for (const roomId of [...this.contexts.keys()]) {
3008
3061
  await this.onSessionCleanup(roomId);
@@ -3038,7 +3091,13 @@ var AgentRuntime = class {
3038
3091
  }
3039
3092
  async consumeLoop(signal) {
3040
3093
  while (!signal.aborted) {
3041
- const event = await this.link.nextEvent(signal);
3094
+ let event;
3095
+ try {
3096
+ event = await this.link.nextEvent(signal);
3097
+ } catch (error) {
3098
+ await this.failRuntime(error, syntheticRuntimeFailureEvent(this.agentId));
3099
+ return;
3100
+ }
3042
3101
  if (!event) {
3043
3102
  return;
3044
3103
  }
@@ -3201,13 +3260,13 @@ var AgentRuntime = class {
3201
3260
  }
3202
3261
  });
3203
3262
  }
3204
- async leaveTrackedRoom(roomId) {
3263
+ async leaveTrackedRoom(roomId, timeoutMs) {
3205
3264
  await trackRoomLeave({
3206
3265
  link: this.link,
3207
3266
  roomId,
3208
3267
  trackedRooms: this.subscribedRooms,
3209
3268
  onLeft: async (leftRoomId) => {
3210
- await this.executions.get(leftRoomId)?.stop();
3269
+ await this.executions.get(leftRoomId)?.stop(timeoutMs);
3211
3270
  this.contexts.delete(leftRoomId);
3212
3271
  this.executions.delete(leftRoomId);
3213
3272
  await this.onSessionCleanup(leftRoomId);
@@ -3244,6 +3303,23 @@ var AgentRuntime = class {
3244
3303
  }
3245
3304
  }
3246
3305
  };
3306
+ function syntheticRuntimeFailureEvent(agentId) {
3307
+ return {
3308
+ type: "message_created",
3309
+ roomId: null,
3310
+ payload: {
3311
+ id: "runtime-failed",
3312
+ content: "",
3313
+ sender_id: agentId,
3314
+ sender_type: "Agent",
3315
+ sender_name: null,
3316
+ message_type: "text",
3317
+ metadata: {},
3318
+ inserted_at: (/* @__PURE__ */ new Date(0)).toISOString(),
3319
+ updated_at: (/* @__PURE__ */ new Date(0)).toISOString()
3320
+ }
3321
+ };
3322
+ }
3247
3323
  function assertNever2(value) {
3248
3324
  throw new Error(`Unhandled platform event: ${JSON.stringify(value)}`);
3249
3325
  }
@@ -4185,55 +4261,241 @@ var payloadSchemas = {
4185
4261
 
4186
4262
  // src/platform/streaming/PhoenixChannelsTransport.ts
4187
4263
  var import_phoenix = require("phoenix");
4264
+
4265
+ // src/platform/streaming/disconnectReason.ts
4266
+ var import_zod2 = require("zod");
4267
+ var UPGRADE_CODES = [
4268
+ "invalid_on_conflict",
4269
+ "connection_conflict",
4270
+ "too_many_requests",
4271
+ "tracking_failed"
4272
+ ];
4273
+ var UPGRADE_REASONS = {
4274
+ invalid_on_conflict: {
4275
+ status: 400,
4276
+ message: "Invalid websocket conflict policy.",
4277
+ retryable: false
4278
+ },
4279
+ connection_conflict: {
4280
+ status: 409,
4281
+ message: "Websocket connection conflict.",
4282
+ retryable: false
4283
+ },
4284
+ too_many_requests: {
4285
+ status: 429,
4286
+ message: "Too many websocket connection attempts.",
4287
+ retryable: true
4288
+ },
4289
+ tracking_failed: {
4290
+ status: 503,
4291
+ message: "Websocket connection tracking failed.",
4292
+ retryable: true
4293
+ }
4294
+ };
4295
+ var upgradeErrorSchema = import_zod2.z.object({
4296
+ status: import_zod2.z.number(),
4297
+ body: import_zod2.z.preprocess(
4298
+ (body) => {
4299
+ if (typeof body !== "string") {
4300
+ return body;
4301
+ }
4302
+ try {
4303
+ return JSON.parse(body);
4304
+ } catch {
4305
+ return null;
4306
+ }
4307
+ },
4308
+ import_zod2.z.object({
4309
+ error: import_zod2.z.object({
4310
+ code: import_zod2.z.enum(UPGRADE_CODES),
4311
+ message: import_zod2.z.string().optional(),
4312
+ retry_after: import_zod2.z.number().nullable().optional(),
4313
+ request_id: import_zod2.z.string().nullable().optional()
4314
+ })
4315
+ })
4316
+ ),
4317
+ headers: import_zod2.z.record(import_zod2.z.unknown()).optional()
4318
+ });
4319
+ var WebSocketDisconnectError = class extends Error {
4320
+ reason;
4321
+ constructor(reason) {
4322
+ super(reason.message);
4323
+ this.name = "WebSocketDisconnectError";
4324
+ this.reason = reason;
4325
+ }
4326
+ };
4327
+ function parseSupersedeDisconnectReason(payload) {
4328
+ if (typeof payload.reason !== "string" || typeof payload.message !== "string") {
4329
+ return null;
4330
+ }
4331
+ return {
4332
+ source: "agent_control",
4333
+ code: payload.reason,
4334
+ message: payload.message,
4335
+ retryable: false,
4336
+ retryAfter: typeof payload.retry_after === "number" ? payload.retry_after : null,
4337
+ targetSocketId: typeof payload.target_socket_id === "string" ? payload.target_socket_id : null,
4338
+ correlationId: typeof payload.correlation_id === "string" ? payload.correlation_id : null
4339
+ };
4340
+ }
4341
+ function parseUpgradeDisconnectReason(event) {
4342
+ const parsed = upgradeErrorSchema.safeParse(event);
4343
+ if (!parsed.success) {
4344
+ return null;
4345
+ }
4346
+ const { status, body, headers } = parsed.data;
4347
+ const {
4348
+ code,
4349
+ message,
4350
+ retry_after: retryAfter,
4351
+ request_id: requestId
4352
+ } = body.error;
4353
+ const reason = UPGRADE_REASONS[code];
4354
+ if (status !== reason.status) {
4355
+ return null;
4356
+ }
4357
+ return {
4358
+ source: "upgrade",
4359
+ status: reason.status,
4360
+ code,
4361
+ message: message ?? reason.message,
4362
+ retryable: reason.retryable,
4363
+ retryAfter: retryAfter ?? retryAfterFromHeaders(headers),
4364
+ requestId: requestId ?? null
4365
+ };
4366
+ }
4367
+ function genericCloseReason(event) {
4368
+ return {
4369
+ source: "websocket_close",
4370
+ code: "websocket.closed",
4371
+ message: "Phoenix socket closed without a platform disconnect reason.",
4372
+ retryable: true,
4373
+ closeCode: typeof event?.code === "number" ? event.code : null,
4374
+ closeReason: typeof event?.reason === "string" && event.reason.length > 0 ? event.reason : null
4375
+ };
4376
+ }
4377
+ function retryAfterFromHeaders(headers) {
4378
+ const value = headers?.["retry-after"] ?? headers?.["Retry-After"];
4379
+ if (typeof value === "number") {
4380
+ return value;
4381
+ }
4382
+ if (typeof value === "string" && value.trim() !== "") {
4383
+ const seconds = Number(value);
4384
+ return Number.isFinite(seconds) ? seconds : null;
4385
+ }
4386
+ return null;
4387
+ }
4388
+
4389
+ // src/platform/streaming/nodeWebSocketFactory.ts
4188
4390
  var import_ws = require("ws");
4391
+ function createNodeWebSocketFactory(headers) {
4392
+ const Constructor = import_ws.WebSocket;
4393
+ class ThenvoiNodeWebSocket {
4394
+ constructor(address, protocols) {
4395
+ const socket = new Constructor(address, protocols, headers ? { headers } : void 0);
4396
+ socket.once("unexpected-response", (request, response) => {
4397
+ void emitUpgradeError(socket, request, response);
4398
+ });
4399
+ return socket;
4400
+ }
4401
+ }
4402
+ return ThenvoiNodeWebSocket;
4403
+ }
4404
+ async function emitUpgradeError(websocket, request, response) {
4405
+ const body = await readResponseBody(response);
4406
+ const error = new Error(
4407
+ `Unexpected server response: ${response.statusCode ?? "unknown"}`
4408
+ );
4409
+ error.status = response.statusCode;
4410
+ error.body = body;
4411
+ error.headers = response.headers;
4412
+ request.destroy();
4413
+ websocket.emit("error", error);
4414
+ websocket.emit("close", 1006, Buffer.alloc(0));
4415
+ }
4416
+ async function readResponseBody(response) {
4417
+ const chunks = [];
4418
+ for await (const chunk of response) {
4419
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
4420
+ }
4421
+ return Buffer.concat(chunks).toString("utf8");
4422
+ }
4423
+
4424
+ // src/platform/streaming/PhoenixChannelsTransport.ts
4189
4425
  var PhoenixChannelsTransport = class {
4190
4426
  socket;
4427
+ agentId;
4191
4428
  channels = /* @__PURE__ */ new Map();
4192
4429
  channelRefs = /* @__PURE__ */ new Map();
4193
4430
  pendingJoins = /* @__PURE__ */ new Map();
4194
4431
  logger;
4432
+ onTerminalDisconnect;
4195
4433
  onHandlerError;
4196
4434
  connected = false;
4197
4435
  connectPromise = null;
4198
4436
  connectResolve = null;
4437
+ connectReject = null;
4438
+ lastDisconnectReason = null;
4439
+ terminalDisconnectError = null;
4440
+ runForeverWaiters = /* @__PURE__ */ new Set();
4441
+ stoppingReconnect = false;
4442
+ suppressNextCloseReason = false;
4199
4443
  constructor(options) {
4200
4444
  this.logger = options.logger ?? new NoopLogger();
4445
+ this.agentId = options.agentId;
4446
+ this.onTerminalDisconnect = options.onTerminalDisconnect;
4201
4447
  let wsUrl = options.wsUrl;
4202
4448
  if (wsUrl.endsWith("/websocket")) {
4203
4449
  wsUrl = wsUrl.slice(0, -"/websocket".length);
4204
4450
  }
4451
+ const reconnectAfterMs = options.reconnectAfterMs ?? ((tries) => [1e3, 2e3, 5e3, 1e4, 3e4][tries - 1] ?? 3e4);
4205
4452
  this.socket = new import_phoenix.Socket(wsUrl, {
4206
4453
  params: {
4207
- api_key: options.apiKey,
4208
- agent_id: options.agentId
4454
+ agent_id: options.agentId,
4455
+ ...options.conflictPolicy ? { on_conflict: options.conflictPolicy } : {}
4209
4456
  },
4210
4457
  heartbeatIntervalMs: options.heartbeatIntervalMs,
4211
- reconnectAfterMs: options.reconnectAfterMs ?? ((tries) => [1e3, 2e3, 5e3, 1e4, 3e4][tries - 1] ?? 3e4),
4212
- transport: options.websocketFactory ?? resolveWebSocketFactory()
4458
+ reconnectAfterMs: (tries) => {
4459
+ if (this.terminalDisconnectError) {
4460
+ return Number.POSITIVE_INFINITY;
4461
+ }
4462
+ return reconnectAfterMs(tries);
4463
+ },
4464
+ transport: options.websocketFactory ?? resolveWebSocketFactory(options.apiKey)
4213
4465
  });
4214
4466
  this.socket.onOpen(() => {
4215
- this.connected = true;
4216
- this.connectResolve?.();
4217
- this.connectResolve = null;
4218
- this.logger.info("Phoenix socket opened", {
4219
- channels: getSocketChannelCount(this.socket)
4220
- });
4467
+ void this.handleOpen();
4221
4468
  });
4222
4469
  this.socket.onClose((event) => {
4223
- this.connected = false;
4224
- if (getSocketChannelCount(this.socket) === 0) {
4225
- this.socket.disconnect();
4226
- }
4227
- this.logger.info("Phoenix socket closed", {
4228
- code: event?.code ?? null,
4229
- reason: event?.reason ?? null
4230
- });
4470
+ this.recordSocketClose(event);
4231
4471
  });
4232
4472
  this.socket.onError((event) => {
4473
+ const errorEvent = unwrapErrorEvent(event);
4474
+ const upgradeReason = parseUpgradeDisconnectReason(errorEvent);
4475
+ if (upgradeReason) {
4476
+ if (upgradeReason.retryable) {
4477
+ this.lastDisconnectReason = upgradeReason;
4478
+ this.connectReject?.(new WebSocketDisconnectError(upgradeReason));
4479
+ this.stopReconnectIfNoChannels({ suppressCloseReason: true });
4480
+ } else {
4481
+ this.recordTerminalDisconnect(upgradeReason);
4482
+ }
4483
+ this.logger.warn("Phoenix socket upgrade failed", {
4484
+ reason: upgradeReason
4485
+ });
4486
+ return;
4487
+ }
4488
+ this.connectReject?.(
4489
+ new TransportError("Phoenix socket connection failed", errorEvent)
4490
+ );
4491
+ this.stopReconnectIfNoChannels({ suppressCloseReason: true });
4233
4492
  this.logger.warn("Phoenix socket error", { event });
4234
4493
  });
4235
4494
  }
4236
4495
  async connect() {
4496
+ if (this.terminalDisconnectError) {
4497
+ throw this.terminalDisconnectError;
4498
+ }
4237
4499
  if (this.connected) {
4238
4500
  return;
4239
4501
  }
@@ -4257,15 +4519,30 @@ var PhoenixChannelsTransport = class {
4257
4519
  await this.connectPromise;
4258
4520
  }
4259
4521
  async disconnect() {
4260
- for (const topic of this.channels.keys()) {
4261
- await this.leave(topic);
4262
- }
4522
+ const results = await Promise.allSettled(
4523
+ [...this.channels.keys()].map((topic) => this.leave(topic))
4524
+ );
4263
4525
  this.socket.disconnect();
4264
4526
  this.connected = false;
4527
+ const failures = [];
4528
+ for (const result of results) {
4529
+ if (result.status === "rejected") {
4530
+ failures.push(result.reason);
4531
+ }
4532
+ }
4533
+ if (failures.length > 0) {
4534
+ throw new AggregateError(
4535
+ failures,
4536
+ "Failed to leave one or more Phoenix topics during disconnect"
4537
+ );
4538
+ }
4265
4539
  }
4266
4540
  isConnected() {
4267
4541
  return this.connected;
4268
4542
  }
4543
+ getDisconnectReason() {
4544
+ return this.lastDisconnectReason;
4545
+ }
4269
4546
  async join(topic, handlers) {
4270
4547
  if (this.channels.has(topic)) {
4271
4548
  return;
@@ -4301,7 +4578,10 @@ var PhoenixChannelsTransport = class {
4301
4578
  channel.join().receive("ok", () => resolve()).receive(
4302
4579
  "error",
4303
4580
  (error) => reject(new TransportError(`Failed to join topic ${topic}`, error))
4304
- ).receive("timeout", () => reject(new TransportError(`Timeout joining topic ${topic}`)));
4581
+ ).receive(
4582
+ "timeout",
4583
+ () => reject(new TransportError(`Timeout joining topic ${topic}`))
4584
+ );
4305
4585
  });
4306
4586
  } catch (error) {
4307
4587
  for (const [event, ref] of refs) {
@@ -4329,19 +4609,127 @@ var PhoenixChannelsTransport = class {
4329
4609
  channel.leave().receive("ok", () => resolve()).receive(
4330
4610
  "error",
4331
4611
  (error) => reject(new TransportError(`Failed to leave topic ${topic}`, error))
4332
- ).receive("timeout", () => reject(new TransportError(`Timeout leaving topic ${topic}`)));
4612
+ ).receive(
4613
+ "timeout",
4614
+ () => reject(new TransportError(`Timeout leaving topic ${topic}`))
4615
+ );
4333
4616
  });
4334
4617
  this.channels.delete(topic);
4335
4618
  this.logger.debug("Left topic", { topic });
4336
4619
  }
4337
4620
  async runForever(signal) {
4621
+ if (this.terminalDisconnectError) {
4622
+ throw this.terminalDisconnectError;
4623
+ }
4338
4624
  if (signal.aborted) {
4339
4625
  return;
4340
4626
  }
4341
- await new Promise((resolve) => {
4342
- signal.addEventListener("abort", () => resolve(), { once: true });
4627
+ await new Promise((resolve, reject) => {
4628
+ let settled = false;
4629
+ const abortController = new AbortController();
4630
+ const waiter = {
4631
+ reject: (error) => {
4632
+ if (settled) {
4633
+ return;
4634
+ }
4635
+ settled = true;
4636
+ this.runForeverWaiters.delete(waiter);
4637
+ abortController.abort();
4638
+ reject(error);
4639
+ }
4640
+ };
4641
+ this.runForeverWaiters.add(waiter);
4642
+ signal.addEventListener(
4643
+ "abort",
4644
+ () => {
4645
+ if (settled) {
4646
+ return;
4647
+ }
4648
+ settled = true;
4649
+ this.runForeverWaiters.delete(waiter);
4650
+ resolve();
4651
+ },
4652
+ { once: true, signal: abortController.signal }
4653
+ );
4654
+ });
4655
+ }
4656
+ async handleOpen() {
4657
+ try {
4658
+ await this.subscribeAgentControl();
4659
+ } catch (error) {
4660
+ this.connected = false;
4661
+ this.socket.disconnect();
4662
+ this.connectReject?.(
4663
+ error instanceof Error ? error : new TransportError(String(error))
4664
+ );
4665
+ this.logger.warn("Failed to join mandatory agent_control channel", {
4666
+ error
4667
+ });
4668
+ return;
4669
+ }
4670
+ this.connected = true;
4671
+ this.connectResolve?.();
4672
+ this.connectResolve = null;
4673
+ this.connectReject = null;
4674
+ this.logger.info("Phoenix socket opened", {
4675
+ channels: getSocketChannelCount(this.socket)
4343
4676
  });
4344
4677
  }
4678
+ stopReconnectIfNoChannels(options = {}) {
4679
+ if (this.stoppingReconnect || getSocketChannelCount(this.socket) !== 0) {
4680
+ return;
4681
+ }
4682
+ this.suppressNextCloseReason = options.suppressCloseReason ?? false;
4683
+ this.stoppingReconnect = true;
4684
+ this.socket.disconnect();
4685
+ this.stoppingReconnect = false;
4686
+ }
4687
+ async subscribeAgentControl() {
4688
+ if (!this.agentId) {
4689
+ return;
4690
+ }
4691
+ await this.join(`agent_control:${this.agentId}`, {
4692
+ supersede: (payload) => {
4693
+ const reason = parseSupersedeDisconnectReason(payload);
4694
+ if (!reason) {
4695
+ this.logger.warn("Invalid agent_control supersede payload", {
4696
+ payload
4697
+ });
4698
+ return;
4699
+ }
4700
+ this.recordTerminalDisconnect(reason);
4701
+ }
4702
+ });
4703
+ }
4704
+ recordSocketClose(event) {
4705
+ this.connected = false;
4706
+ const suppressCloseReason = this.suppressNextCloseReason;
4707
+ this.suppressNextCloseReason = false;
4708
+ this.stopReconnectIfNoChannels();
4709
+ if (!suppressCloseReason && !this.terminalDisconnectError && !this.lastDisconnectReason) {
4710
+ this.lastDisconnectReason = genericCloseReason(event);
4711
+ }
4712
+ this.logger.info("Phoenix socket closed", {
4713
+ code: event?.code ?? null,
4714
+ reason: event?.reason ?? null,
4715
+ platformReason: this.lastDisconnectReason
4716
+ });
4717
+ }
4718
+ recordTerminalDisconnect(reason) {
4719
+ if (this.terminalDisconnectError) {
4720
+ return;
4721
+ }
4722
+ const error = new WebSocketDisconnectError(reason);
4723
+ this.lastDisconnectReason = reason;
4724
+ this.terminalDisconnectError = error;
4725
+ this.onTerminalDisconnect?.(reason);
4726
+ this.connectReject?.(error);
4727
+ for (const waiter of this.runForeverWaiters) {
4728
+ waiter.reject(error);
4729
+ }
4730
+ this.runForeverWaiters.clear();
4731
+ this.socket.disconnect();
4732
+ }
4345
4733
  async waitForConnection(timeoutMs = 1e4) {
4346
4734
  if (this.connected) {
4347
4735
  return;
@@ -4349,20 +4737,43 @@ var PhoenixChannelsTransport = class {
4349
4737
  await new Promise((resolve, reject) => {
4350
4738
  const timeout = setTimeout(() => {
4351
4739
  this.connectResolve = null;
4352
- reject(new TransportError("Timed out waiting for Phoenix socket connection"));
4740
+ this.connectReject = null;
4741
+ reject(
4742
+ new TransportError("Timed out waiting for Phoenix socket connection")
4743
+ );
4353
4744
  }, timeoutMs);
4354
4745
  this.connectResolve = () => {
4355
4746
  clearTimeout(timeout);
4747
+ this.connectReject = null;
4356
4748
  resolve();
4357
4749
  };
4750
+ this.connectReject = (error) => {
4751
+ clearTimeout(timeout);
4752
+ this.connectResolve = null;
4753
+ this.connectReject = null;
4754
+ reject(
4755
+ error instanceof Error ? error : new TransportError(String(error))
4756
+ );
4757
+ };
4358
4758
  });
4359
4759
  }
4360
4760
  };
4361
- function resolveWebSocketFactory() {
4761
+ function resolveWebSocketFactory(apiKey) {
4362
4762
  if (typeof process !== "undefined" && process.versions?.node) {
4363
- return import_ws.WebSocket;
4763
+ return createNodeWebSocketFactory({ "x-api-key": apiKey });
4764
+ }
4765
+ throw new TransportError(
4766
+ "Phoenix WebSocket API-key auth requires a WebSocket transport that can set handshake headers."
4767
+ );
4768
+ }
4769
+ function unwrapErrorEvent(event) {
4770
+ if (!isErrorEvent(event)) {
4771
+ return event;
4364
4772
  }
4365
- return WebSocket;
4773
+ return event.error;
4774
+ }
4775
+ function isErrorEvent(event) {
4776
+ return typeof event === "object" && event !== null && "error" in event;
4366
4777
  }
4367
4778
  function removeSocketChannel(socket, channel) {
4368
4779
  const candidate = socket;
@@ -4384,6 +4795,12 @@ function deriveDefaultRestUrl(wsUrl) {
4384
4795
  const protocol = parsed.protocol === "ws:" ? "http:" : "https:";
4385
4796
  return `${protocol}//${parsed.host}`;
4386
4797
  }
4798
+ function roomTopics(roomId) {
4799
+ return {
4800
+ chat: `chat_room:${roomId}`,
4801
+ participants: `room_participants:${roomId}`
4802
+ };
4803
+ }
4387
4804
  function toPlatformMessage(roomId, message) {
4388
4805
  return {
4389
4806
  id: message.id,
@@ -4410,6 +4827,8 @@ var ThenvoiLink = class {
4410
4827
  eventQueue = [];
4411
4828
  waiters = [];
4412
4829
  connected = false;
4830
+ lastDisconnectReason = null;
4831
+ terminalDisconnectError = null;
4413
4832
  constructor(options) {
4414
4833
  this.agentId = options.agentId;
4415
4834
  this.apiKey = options.apiKey;
@@ -4431,17 +4850,35 @@ var ThenvoiLink = class {
4431
4850
  wsUrl: this.wsUrl,
4432
4851
  apiKey: this.apiKey,
4433
4852
  agentId: this.agentId,
4434
- logger: this.logger
4853
+ logger: this.logger,
4854
+ conflictPolicy: options.conflictPolicy,
4855
+ onTerminalDisconnect: (reason) => {
4856
+ this.recordDisconnectReason(reason);
4857
+ }
4435
4858
  });
4436
4859
  }
4437
4860
  isConnected() {
4438
4861
  return this.connected;
4439
4862
  }
4863
+ getDisconnectReason() {
4864
+ return this.lastDisconnectReason;
4865
+ }
4440
4866
  async connect() {
4441
4867
  if (this.connected) {
4442
4868
  return;
4443
4869
  }
4444
- await this.transport.connect();
4870
+ try {
4871
+ await this.transport.connect();
4872
+ } catch (error) {
4873
+ if (error instanceof WebSocketDisconnectError) {
4874
+ if (error.reason.retryable) {
4875
+ this.lastDisconnectReason = error.reason;
4876
+ } else {
4877
+ this.recordDisconnectError(error);
4878
+ }
4879
+ }
4880
+ throw error;
4881
+ }
4445
4882
  this.connected = true;
4446
4883
  }
4447
4884
  async disconnect() {
@@ -4455,8 +4892,23 @@ var ThenvoiLink = class {
4455
4892
  this.connected = false;
4456
4893
  }
4457
4894
  async runForever(signal) {
4895
+ if (this.terminalDisconnectError) {
4896
+ throw this.terminalDisconnectError;
4897
+ }
4458
4898
  await this.transport.runForever(signal);
4459
4899
  }
4900
+ recordDisconnectReason(reason) {
4901
+ this.recordDisconnectError(new WebSocketDisconnectError(reason));
4902
+ }
4903
+ recordDisconnectError(error) {
4904
+ this.connected = false;
4905
+ this.lastDisconnectReason = error.reason;
4906
+ this.terminalDisconnectError = error;
4907
+ while (this.waiters.length > 0) {
4908
+ const waiter = this.waiters.shift();
4909
+ waiter?.reject(error);
4910
+ }
4911
+ }
4460
4912
  queueEvent(event) {
4461
4913
  const waiter = this.waiters.shift();
4462
4914
  if (waiter) {
@@ -4481,32 +4933,42 @@ var ThenvoiLink = class {
4481
4933
  if (this.subscribedRooms.has(roomId)) {
4482
4934
  return;
4483
4935
  }
4484
- await this.transport.join(`chat_room:${roomId}`, {
4485
- message_created: (payload) => {
4486
- this.emit("message_created", payload, roomId);
4487
- }
4488
- });
4489
- await this.transport.join(`room_participants:${roomId}`, {
4490
- participant_added: (payload) => {
4491
- this.emit("participant_added", payload, roomId);
4492
- },
4493
- participant_removed: (payload) => {
4494
- this.emit("participant_removed", payload, roomId);
4495
- },
4496
- room_deleted: (payload) => {
4497
- this.emit("room_deleted", payload, roomId);
4498
- }
4499
- });
4936
+ await this.joinRoomTopics(roomId);
4500
4937
  this.subscribedRooms.add(roomId);
4501
4938
  }
4502
4939
  async unsubscribeRoom(roomId) {
4503
4940
  if (!this.subscribedRooms.has(roomId)) {
4504
4941
  return;
4505
4942
  }
4506
- await this.transport.leave(`chat_room:${roomId}`);
4507
- await this.transport.leave(`room_participants:${roomId}`);
4943
+ const topics = roomTopics(roomId);
4944
+ await this.transport.leave(topics.chat);
4945
+ await this.transport.leave(topics.participants);
4508
4946
  this.subscribedRooms.delete(roomId);
4509
4947
  }
4948
+ async joinRoomTopics(roomId) {
4949
+ const topics = roomTopics(roomId);
4950
+ await this.transport.join(topics.chat, {
4951
+ message_created: (payload) => {
4952
+ this.emit("message_created", payload, roomId);
4953
+ }
4954
+ });
4955
+ try {
4956
+ await this.transport.join(topics.participants, {
4957
+ participant_added: (payload) => {
4958
+ this.emit("participant_added", payload, roomId);
4959
+ },
4960
+ participant_removed: (payload) => {
4961
+ this.emit("participant_removed", payload, roomId);
4962
+ },
4963
+ room_deleted: (payload) => {
4964
+ this.emit("room_deleted", payload, roomId);
4965
+ }
4966
+ });
4967
+ } catch (error) {
4968
+ await this.transport.leave(topics.chat);
4969
+ throw error;
4970
+ }
4971
+ }
4510
4972
  async subscribeAgentContacts() {
4511
4973
  assertCapability(this.capabilities, "contacts", "Contacts streaming");
4512
4974
  await this.transport.join(`agent_contacts:${this.agentId}`, {
@@ -4528,6 +4990,9 @@ var ThenvoiLink = class {
4528
4990
  await this.transport.leave(`agent_contacts:${this.agentId}`);
4529
4991
  }
4530
4992
  async nextEvent(signal) {
4993
+ if (this.terminalDisconnectError) {
4994
+ throw this.terminalDisconnectError;
4995
+ }
4531
4996
  if (signal?.aborted) {
4532
4997
  return null;
4533
4998
  }
@@ -4535,19 +5000,19 @@ var ThenvoiLink = class {
4535
5000
  if (queued) {
4536
5001
  return queued;
4537
5002
  }
4538
- return new Promise((resolve) => {
5003
+ return new Promise((resolve, reject) => {
4539
5004
  let settled = false;
4540
5005
  const onAbort = () => {
4541
5006
  const index = this.waiters.indexOf(waiter);
4542
5007
  if (index >= 0) {
4543
5008
  this.waiters.splice(index, 1);
4544
5009
  }
4545
- finalize(null);
5010
+ finalizeResolve(null);
4546
5011
  };
4547
5012
  const cleanup = () => {
4548
5013
  signal?.removeEventListener("abort", onAbort);
4549
5014
  };
4550
- const finalize = (event) => {
5015
+ const finalizeResolve = (event) => {
4551
5016
  if (settled) {
4552
5017
  return;
4553
5018
  }
@@ -4555,8 +5020,17 @@ var ThenvoiLink = class {
4555
5020
  cleanup();
4556
5021
  resolve(event);
4557
5022
  };
5023
+ const finalizeReject = (error) => {
5024
+ if (settled) {
5025
+ return;
5026
+ }
5027
+ settled = true;
5028
+ cleanup();
5029
+ reject(error);
5030
+ };
4558
5031
  const waiter = {
4559
- resolve: finalize,
5032
+ resolve: finalizeResolve,
5033
+ reject: finalizeReject,
4560
5034
  cleanup
4561
5035
  };
4562
5036
  this.waiters.push(waiter);
@@ -4568,7 +5042,10 @@ var ThenvoiLink = class {
4568
5042
  next: async () => {
4569
5043
  const value = await this.nextEvent();
4570
5044
  if (value === null) {
4571
- return { done: true, value: void 0 };
5045
+ return {
5046
+ done: true,
5047
+ value: void 0
5048
+ };
4572
5049
  }
4573
5050
  return { done: false, value };
4574
5051
  }
@@ -4650,7 +5127,9 @@ var ThenvoiLink = class {
4650
5127
  }
4651
5128
  async listChats(request, requestOptions) {
4652
5129
  if (!this.rest.listChats) {
4653
- throw new UnsupportedFeatureError("Chat listing is not available in current REST adapter");
5130
+ throw new UnsupportedFeatureError(
5131
+ "Chat listing is not available in current REST adapter"
5132
+ );
4654
5133
  }
4655
5134
  return this.rest.listChats(request, requestOptions);
4656
5135
  }
@@ -4827,7 +5306,8 @@ var PlatformRuntime = class {
4827
5306
  agentId: this._agentId,
4828
5307
  apiKey: this._apiKey,
4829
5308
  wsUrl: this._wsUrl,
4830
- restUrl: this._restUrl
5309
+ restUrl: this._restUrl,
5310
+ logger: this.logger
4831
5311
  });
4832
5312
  }
4833
5313
  if (this.configuredIdentity) {
@@ -5104,16 +5584,15 @@ var RoomPresence = class {
5104
5584
  }
5105
5585
  };
5106
5586
 
5107
- // src/runtime/prompts.ts
5108
- var BASE_INSTRUCTIONS = `
5587
+ // src/runtime/prompts/base.ts
5588
+ var ENVIRONMENT_SECTION = `
5109
5589
  ## Environment
5110
5590
 
5111
5591
  Multi-participant chat. Messages show sender: [Name]: content.
5112
5592
  Messages prefixed with [System]: are platform updates (participant changes, contact updates, etc.) \u2014 not messages from users.
5113
5593
  Use \`thenvoi_send_message(content, mentions)\` to respond. Plain text output is not delivered.
5114
- Mentions use handles: @<username> for users, @<username>/<agent-name> for agents.
5115
-
5116
- ## CRITICAL: Delegate When You Cannot Help Directly
5594
+ Mentions use handles: @<username> for users, @<username>/<agent-name> for agents.`;
5595
+ var MULTI_AGENT_RULES_SECTION = `## CRITICAL: Delegate When You Cannot Help Directly
5117
5596
 
5118
5597
  You have NO internet access and NO real-time data. When asked about weather, news, stock prices,
5119
5598
  or any current information you cannot answer directly:
@@ -5139,14 +5618,12 @@ Only remove agents if the user explicitly requests it.
5139
5618
  When someone asks you to get information from another agent:
5140
5619
  1. Ask the other agent for the information
5141
5620
  2. When you receive the response, IMMEDIATELY relay it back to the ORIGINAL REQUESTER
5142
- 3. Do NOT just thank the helper agent - the requester is waiting for their answer!
5143
-
5144
- ## IMPORTANT: Always Share Your Thinking
5621
+ 3. Do NOT just thank the helper agent - the requester is waiting for their answer!`;
5622
+ var THINKING_SECTION = `## IMPORTANT: Always Share Your Thinking
5145
5623
 
5146
5624
  You MUST call \`thenvoi_send_event(content, message_type="thought")\` BEFORE every action.
5147
- This is required so users can see your reasoning process.
5148
-
5149
- ## Examples
5625
+ This is required so users can see your reasoning process.`;
5626
+ var EXAMPLES_SECTION = `## Examples
5150
5627
 
5151
5628
  ### Simple question - answer directly
5152
5629
  [John Doe]: What's 2+2?
@@ -5179,8 +5656,73 @@ This is required so users can see your reasoning process.
5179
5656
 
5180
5657
  [Weather Agent]: London is 8\xB0C and rainy.
5181
5658
  -> thenvoi_send_event("Got London weather. Relaying to John Doe.", message_type="thought")
5182
- -> thenvoi_send_message("London is 8\xB0C and rainy.", mentions=["@john"])
5183
- `;
5659
+ -> thenvoi_send_message("London is 8\xB0C and rainy.", mentions=["@john"])`;
5660
+ var BASE_INSTRUCTIONS = [
5661
+ ENVIRONMENT_SECTION,
5662
+ MULTI_AGENT_RULES_SECTION,
5663
+ THINKING_SECTION,
5664
+ EXAMPLES_SECTION
5665
+ ].join("\n\n") + "\n";
5666
+
5667
+ // src/runtime/prompts/memory.ts
5668
+ var MEMORY_INTRO = `## Memory Tools
5669
+
5670
+ You have access to memory tools for storing and retrieving information
5671
+ across conversations. Use \`thenvoi_store_memory\` to persist important
5672
+ information and \`thenvoi_list_memories\` / \`thenvoi_get_memory\` to recall it.
5673
+ Use \`thenvoi_supersede_memory\` to mark outdated memories and
5674
+ \`thenvoi_archive_memory\` to hide memories that should be preserved.`;
5675
+ function longTermPattern(label, type, segment, scope) {
5676
+ return {
5677
+ label,
5678
+ system: MEMORY_SYSTEM.long_term,
5679
+ type,
5680
+ segment,
5681
+ scope
5682
+ };
5683
+ }
5684
+ var COMMON_MEMORY_PATTERNS = [
5685
+ longTermPattern("Facts learned about a specific agent/entity", MEMORY_TYPE.semantic, MEMORY_SEGMENT.agent, MEMORY_STORE_SCOPE.subject),
5686
+ longTermPattern("Events involving a specific person/agent", MEMORY_TYPE.episodic, MEMORY_SEGMENT.agent, MEMORY_STORE_SCOPE.subject),
5687
+ longTermPattern("A user's preferences or profile info", MEMORY_TYPE.semantic, MEMORY_SEGMENT.user, MEMORY_STORE_SCOPE.subject),
5688
+ longTermPattern("Org-wide knowledge or how to perform a task", MEMORY_TYPE.procedural, MEMORY_SEGMENT.tool, MEMORY_STORE_SCOPE.organization)
5689
+ ];
5690
+ function renderMemoryPattern(pattern) {
5691
+ return `- ${pattern.label}: \`system="${pattern.system}"\`, \`type="${pattern.type}"\`, \`segment="${pattern.segment}"\`, \`scope="${pattern.scope}"\``;
5692
+ }
5693
+ var MEMORY_COMMON_PATTERNS = [
5694
+ "Common patterns:",
5695
+ ...COMMON_MEMORY_PATTERNS.map(renderMemoryPattern)
5696
+ ].join("\n");
5697
+ var MEMORY_SCOPE_GUIDANCE = `Prefer \`scope="${MEMORY_STORE_SCOPE.subject}"\` whenever the memory is about a specific person or agent, so it
5698
+ stays attached to that subject rather than leaking org-wide. Storing with \`scope="${MEMORY_STORE_SCOPE.subject}"\` requires a
5699
+ real \`subject_id\` UUID, so resolve it first via \`thenvoi_lookup_peers\` or the participant list.
5700
+ Reserve \`scope="${MEMORY_STORE_SCOPE.organization}"\` for knowledge that is genuinely shared across the whole organization and
5701
+ is not about any one subject.`;
5702
+ function quoteChoices(values) {
5703
+ return values.map((value) => `\`"${value}"\``).join(" | ");
5704
+ }
5705
+ function memoryTypeLines() {
5706
+ return Object.entries(MEMORY_SYSTEM_TYPES).map(([system, types]) => ` - ${system}: ${quoteChoices(types)}`).join("\n");
5707
+ }
5708
+ function buildMemorySection() {
5709
+ const storeMemoryProps = TOOL_MODELS.thenvoi_store_memory.properties;
5710
+ const systems = storeMemoryProps.system.enum;
5711
+ const segments = storeMemoryProps.segment.enum;
5712
+ const scopes = storeMemoryProps.scope.enum;
5713
+ const fieldRules = `When calling \`thenvoi_store_memory\`, the \`system\`, \`type\`, \`segment\`, and \`scope\` fields
5714
+ must use these exact values (case-sensitive):
5715
+
5716
+ - **system**: ${quoteChoices(systems)}
5717
+ - **type** (must match the chosen system):
5718
+ ${memoryTypeLines()}
5719
+ - **segment**: ${quoteChoices(segments)}
5720
+ - **scope**: ${quoteChoices(scopes)}`;
5721
+ return [MEMORY_INTRO, fieldRules, MEMORY_COMMON_PATTERNS, MEMORY_SCOPE_GUIDANCE].join("\n\n");
5722
+ }
5723
+ var MEMORY_SECTION = buildMemorySection();
5724
+
5725
+ // src/runtime/prompts/templates.ts
5184
5726
  var TEMPLATES = {
5185
5727
  default: `You are {agent_name}, {agent_description}.
5186
5728
 
@@ -5199,7 +5741,13 @@ ${customSection}`.trim();
5199
5741
  }
5200
5742
  const template = options?.template ?? "default";
5201
5743
  const templateString = TEMPLATES[template] ?? TEMPLATES.default;
5202
- return templateString.replaceAll("{agent_name}", agentName).replaceAll("{agent_description}", agentDescription).replaceAll("{custom_section}", customSection);
5744
+ const parts = [
5745
+ templateString.replaceAll("{agent_name}", agentName).replaceAll("{agent_description}", agentDescription).replaceAll("{custom_section}", customSection)
5746
+ ];
5747
+ if (options?.capabilities?.memory) {
5748
+ parts.push(MEMORY_SECTION);
5749
+ }
5750
+ return parts.join("\n\n");
5203
5751
  }
5204
5752
 
5205
5753
  // src/runtime/participantTracker.ts
@@ -5336,6 +5884,7 @@ async function runWithGracefulShutdown(agent, options) {
5336
5884
  HUB_ROOM_SYSTEM_PROMPT,
5337
5885
  HistoryProvider,
5338
5886
  MCP_TOOL_PREFIX,
5887
+ MEMORY_SECTION,
5339
5888
  MEMORY_TOOL_NAMES,
5340
5889
  MessageRetryTracker,
5341
5890
  ParticipantTracker,