@band-ai/sdk 0.1.2

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 (91) hide show
  1. package/dist/CodexAdapter-A1k_LMdZ.d.ts +1278 -0
  2. package/dist/CodexAdapter-BbZD-nBd.d.cts +1278 -0
  3. package/dist/adapters.cjs +10643 -0
  4. package/dist/adapters.d.cts +217 -0
  5. package/dist/adapters.d.ts +217 -0
  6. package/dist/adapters.js +92 -0
  7. package/dist/backends-BnOLsG7w.d.cts +20 -0
  8. package/dist/backends-C4n6cKOf.d.ts +20 -0
  9. package/dist/chunk-3BKAC4OZ.js +44 -0
  10. package/dist/chunk-5CXIMAH2.js +52 -0
  11. package/dist/chunk-6AJA2OPC.js +3682 -0
  12. package/dist/chunk-A3TDK6GP.js +6674 -0
  13. package/dist/chunk-AVVDPXP4.js +39 -0
  14. package/dist/chunk-EDPKUUJL.js +177 -0
  15. package/dist/chunk-FUODYQRE.js +2562 -0
  16. package/dist/chunk-GVEQ3RAH.js +373 -0
  17. package/dist/chunk-INA6YZTZ.js +88 -0
  18. package/dist/chunk-IZPV6QS6.js +115 -0
  19. package/dist/chunk-LY55KYVI.js +0 -0
  20. package/dist/chunk-NIAQBANR.js +577 -0
  21. package/dist/chunk-OD2G5LFQ.js +78 -0
  22. package/dist/chunk-SFVKOEQH.js +103 -0
  23. package/dist/chunk-SG4WLD2H.js +1093 -0
  24. package/dist/chunk-YR3BOQNW.js +39 -0
  25. package/dist/chunk-ZZJRRBPQ.js +82 -0
  26. package/dist/claude-CcnQ5OUC.d.ts +53 -0
  27. package/dist/claude-CwTAxhwI.d.cts +53 -0
  28. package/dist/config.cjs +164 -0
  29. package/dist/config.d.cts +19 -0
  30. package/dist/config.d.ts +19 -0
  31. package/dist/config.js +9 -0
  32. package/dist/converters.cjs +1086 -0
  33. package/dist/converters.d.cts +143 -0
  34. package/dist/converters.d.ts +143 -0
  35. package/dist/converters.js +468 -0
  36. package/dist/core.cjs +185 -0
  37. package/dist/core.d.cts +22 -0
  38. package/dist/core.d.ts +22 -0
  39. package/dist/core.js +24 -0
  40. package/dist/customTools-BzF0IISO.d.cts +10 -0
  41. package/dist/customTools-BzF0IISO.d.ts +10 -0
  42. package/dist/dtos-DUpbIu8k.d.cts +163 -0
  43. package/dist/dtos-DUpbIu8k.d.ts +163 -0
  44. package/dist/history-ByessXNq.d.ts +24 -0
  45. package/dist/history-i6yN7neC.d.cts +24 -0
  46. package/dist/index.cjs +13914 -0
  47. package/dist/index.d.cts +63 -0
  48. package/dist/index.d.ts +63 -0
  49. package/dist/index.js +198 -0
  50. package/dist/linear.cjs +2207 -0
  51. package/dist/linear.d.cts +167 -0
  52. package/dist/linear.d.ts +167 -0
  53. package/dist/linear.js +2139 -0
  54. package/dist/logger-CABQOnlR.d.cts +21 -0
  55. package/dist/logger-CABQOnlR.d.ts +21 -0
  56. package/dist/mcp-claude.cjs +584 -0
  57. package/dist/mcp-claude.d.cts +4 -0
  58. package/dist/mcp-claude.d.ts +4 -0
  59. package/dist/mcp-claude.js +11 -0
  60. package/dist/mcp.cjs +1247 -0
  61. package/dist/mcp.d.cts +97 -0
  62. package/dist/mcp.d.ts +97 -0
  63. package/dist/mcp.js +27 -0
  64. package/dist/opencode-BBcDchcN.d.cts +159 -0
  65. package/dist/opencode-DthQQUsZ.d.ts +159 -0
  66. package/dist/pagination-BPiys10t.d.cts +25 -0
  67. package/dist/pagination-CcDkVFxK.d.ts +25 -0
  68. package/dist/protocols-CInhtFpA.d.cts +116 -0
  69. package/dist/protocols-DrhzOH6K.d.ts +116 -0
  70. package/dist/rest.cjs +1159 -0
  71. package/dist/rest.d.cts +144 -0
  72. package/dist/rest.d.ts +144 -0
  73. package/dist/rest.js +18 -0
  74. package/dist/runtime.cjs +5353 -0
  75. package/dist/runtime.d.cts +276 -0
  76. package/dist/runtime.d.ts +276 -0
  77. package/dist/runtime.js +274 -0
  78. package/dist/schemas-BpEBkq5V.d.cts +592 -0
  79. package/dist/schemas-vos1AaBc.d.ts +592 -0
  80. package/dist/sdk-HJUVSSWA.js +10 -0
  81. package/dist/simpleAdapter-9OE_p6QW.d.ts +29 -0
  82. package/dist/simpleAdapter-DqhvjsGr.d.cts +29 -0
  83. package/dist/testing.cjs +217 -0
  84. package/dist/testing.d.cts +126 -0
  85. package/dist/testing.d.ts +126 -0
  86. package/dist/testing.js +186 -0
  87. package/dist/types-7jVzOeOP.d.ts +446 -0
  88. package/dist/types-CSHFipIc.d.cts +331 -0
  89. package/dist/types-CovcHpYf.d.cts +446 -0
  90. package/dist/types-CxII3XkA.d.ts +331 -0
  91. package/package.json +189 -0
@@ -0,0 +1,3682 @@
1
+ import {
2
+ HistoryProvider,
3
+ SYNTHETIC_CONTACT_EVENTS_SENDER_ID,
4
+ SYNTHETIC_CONTACT_EVENTS_SENDER_NAME,
5
+ SYNTHETIC_SENDER_TYPE,
6
+ buildParticipantsMessage
7
+ } from "./chunk-INA6YZTZ.js";
8
+ import {
9
+ CHAT_EVENT_TYPES,
10
+ CHAT_TOOL_NAMES,
11
+ MEMORY_TOOL_NAMES,
12
+ TOOL_MODELS,
13
+ assertChatEventType,
14
+ getToolDescription
15
+ } from "./chunk-GVEQ3RAH.js";
16
+ import {
17
+ DEFAULT_REQUEST_OPTIONS,
18
+ FernRestAdapter,
19
+ fetchPaginated
20
+ } from "./chunk-SG4WLD2H.js";
21
+ import {
22
+ DEFAULT_AGENT_TOOLS_CAPABILITIES,
23
+ createToolExecutorError,
24
+ isToolExecutorError
25
+ } from "./chunk-3BKAC4OZ.js";
26
+ import {
27
+ NoopLogger
28
+ } from "./chunk-OD2G5LFQ.js";
29
+ import {
30
+ RuntimeStateError,
31
+ TransportError,
32
+ UnsupportedFeatureError,
33
+ ValidationError
34
+ } from "./chunk-YR3BOQNW.js";
35
+
36
+ // src/contracts/capabilities.ts
37
+ var CAPABILITY_LABELS = {
38
+ peers: "Peer lookup",
39
+ contacts: "Contacts",
40
+ memory: "Memory"
41
+ };
42
+ function assertCapability(capabilities, capability, label = CAPABILITY_LABELS[capability]) {
43
+ if (!capabilities[capability]) {
44
+ throw new UnsupportedFeatureError(`${label} is disabled by runtime capabilities`);
45
+ }
46
+ }
47
+
48
+ // src/platform/streaming/payloadSchemas.ts
49
+ import { z } from "zod";
50
+ var mentionSchema = z.object({
51
+ id: z.string(),
52
+ handle: z.string().nullish(),
53
+ name: z.string().nullish(),
54
+ username: z.string().nullish()
55
+ }).passthrough();
56
+ var messageMetadataSchema = z.object({
57
+ mentions: z.array(mentionSchema).nullish()
58
+ }).passthrough();
59
+ var messageCreatedPayloadSchema = z.object({
60
+ id: z.string(),
61
+ content: z.string(),
62
+ message_type: z.string(),
63
+ metadata: messageMetadataSchema.nullish(),
64
+ sender_id: z.string(),
65
+ sender_type: z.string(),
66
+ sender_name: z.string().nullish(),
67
+ chat_room_id: z.string().nullish(),
68
+ inserted_at: z.string(),
69
+ updated_at: z.string()
70
+ }).passthrough();
71
+ var roomOwnerSchema = z.object({
72
+ id: z.string(),
73
+ name: z.string(),
74
+ type: z.string()
75
+ }).passthrough();
76
+ var roomAddedPayloadSchema = z.object({
77
+ id: z.string(),
78
+ title: z.string().nullish(),
79
+ task_id: z.string().nullish(),
80
+ inserted_at: z.string().nullish(),
81
+ updated_at: z.string().nullish(),
82
+ owner: roomOwnerSchema.nullish(),
83
+ status: z.string().nullish(),
84
+ type: z.string().nullish(),
85
+ created_at: z.string().nullish(),
86
+ participant_role: z.string().nullish()
87
+ }).passthrough();
88
+ var roomRemovedPayloadSchema = z.object({
89
+ id: z.string(),
90
+ status: z.string(),
91
+ type: z.string(),
92
+ title: z.string(),
93
+ removed_at: z.string()
94
+ }).passthrough();
95
+ var participantAddedPayloadSchema = z.object({
96
+ id: z.string(),
97
+ name: z.string(),
98
+ type: z.string(),
99
+ handle: z.string().nullish()
100
+ }).passthrough();
101
+ var participantRemovedPayloadSchema = z.object({
102
+ id: z.string()
103
+ }).passthrough();
104
+ var roomDeletedPayloadSchema = z.object({
105
+ id: z.string()
106
+ }).passthrough();
107
+ var contactRequestReceivedPayloadSchema = z.object({
108
+ id: z.string(),
109
+ from_handle: z.string(),
110
+ from_name: z.string(),
111
+ message: z.string().nullish(),
112
+ status: z.string(),
113
+ inserted_at: z.string()
114
+ }).passthrough();
115
+ var contactRequestUpdatedPayloadSchema = z.object({
116
+ id: z.string(),
117
+ status: z.string()
118
+ }).passthrough();
119
+ var contactAddedPayloadSchema = z.object({
120
+ id: z.string(),
121
+ handle: z.string(),
122
+ name: z.string(),
123
+ type: z.string(),
124
+ description: z.string().nullish(),
125
+ is_external: z.boolean().nullish(),
126
+ inserted_at: z.string()
127
+ }).passthrough();
128
+ var contactRemovedPayloadSchema = z.object({
129
+ id: z.string()
130
+ }).passthrough();
131
+ var payloadSchemas = {
132
+ message_created: messageCreatedPayloadSchema,
133
+ room_added: roomAddedPayloadSchema,
134
+ room_removed: roomRemovedPayloadSchema,
135
+ participant_added: participantAddedPayloadSchema,
136
+ participant_removed: participantRemovedPayloadSchema,
137
+ room_deleted: roomDeletedPayloadSchema,
138
+ contact_request_received: contactRequestReceivedPayloadSchema,
139
+ contact_request_updated: contactRequestUpdatedPayloadSchema,
140
+ contact_added: contactAddedPayloadSchema,
141
+ contact_removed: contactRemovedPayloadSchema
142
+ };
143
+
144
+ // src/platform/streaming/PhoenixChannelsTransport.ts
145
+ import { Socket } from "phoenix";
146
+ import { WebSocket as NodeWebSocket } from "ws";
147
+ var PhoenixChannelsTransport = class {
148
+ socket;
149
+ channels = /* @__PURE__ */ new Map();
150
+ channelRefs = /* @__PURE__ */ new Map();
151
+ pendingJoins = /* @__PURE__ */ new Map();
152
+ logger;
153
+ onHandlerError;
154
+ connected = false;
155
+ connectPromise = null;
156
+ connectResolve = null;
157
+ constructor(options) {
158
+ this.logger = options.logger ?? new NoopLogger();
159
+ let wsUrl = options.wsUrl;
160
+ if (wsUrl.endsWith("/websocket")) {
161
+ wsUrl = wsUrl.slice(0, -"/websocket".length);
162
+ }
163
+ this.socket = new Socket(wsUrl, {
164
+ params: {
165
+ api_key: options.apiKey,
166
+ agent_id: options.agentId
167
+ },
168
+ heartbeatIntervalMs: options.heartbeatIntervalMs,
169
+ reconnectAfterMs: options.reconnectAfterMs ?? ((tries) => [1e3, 2e3, 5e3, 1e4, 3e4][tries - 1] ?? 3e4),
170
+ transport: options.websocketFactory ?? resolveWebSocketFactory()
171
+ });
172
+ 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
+ });
179
+ });
180
+ 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
+ });
189
+ });
190
+ this.socket.onError((event) => {
191
+ this.logger.warn("Phoenix socket error", { event });
192
+ });
193
+ }
194
+ async connect() {
195
+ if (this.connected) {
196
+ return;
197
+ }
198
+ if (!this.connectPromise) {
199
+ this.socket.connect();
200
+ const pending = this.waitForConnection();
201
+ this.connectPromise = pending;
202
+ void pending.then(
203
+ () => {
204
+ if (this.connectPromise === pending) {
205
+ this.connectPromise = null;
206
+ }
207
+ },
208
+ () => {
209
+ if (this.connectPromise === pending) {
210
+ this.connectPromise = null;
211
+ }
212
+ }
213
+ );
214
+ }
215
+ await this.connectPromise;
216
+ }
217
+ async disconnect() {
218
+ for (const topic of this.channels.keys()) {
219
+ await this.leave(topic);
220
+ }
221
+ this.socket.disconnect();
222
+ this.connected = false;
223
+ }
224
+ isConnected() {
225
+ return this.connected;
226
+ }
227
+ async join(topic, handlers) {
228
+ if (this.channels.has(topic)) {
229
+ return;
230
+ }
231
+ const pendingJoin = this.pendingJoins.get(topic);
232
+ if (pendingJoin) {
233
+ return pendingJoin;
234
+ }
235
+ const joinPromise = this.doJoin(topic, handlers).finally(() => {
236
+ this.pendingJoins.delete(topic);
237
+ });
238
+ this.pendingJoins.set(topic, joinPromise);
239
+ return joinPromise;
240
+ }
241
+ async doJoin(topic, handlers) {
242
+ const channel = this.socket.channel(topic, {});
243
+ const refs = [];
244
+ for (const [event, handler] of Object.entries(handlers)) {
245
+ const ref = channel.on(event, (payload) => {
246
+ Promise.resolve(handler(payload)).catch((error) => {
247
+ this.logger.error("Unhandled topic handler error", {
248
+ topic,
249
+ event,
250
+ error
251
+ });
252
+ this.onHandlerError?.(error);
253
+ });
254
+ });
255
+ refs.push([event, ref]);
256
+ }
257
+ try {
258
+ await new Promise((resolve, reject) => {
259
+ channel.join().receive("ok", () => resolve()).receive(
260
+ "error",
261
+ (error) => reject(new TransportError(`Failed to join topic ${topic}`, error))
262
+ ).receive("timeout", () => reject(new TransportError(`Timeout joining topic ${topic}`)));
263
+ });
264
+ } catch (error) {
265
+ for (const [event, ref] of refs) {
266
+ channel.off(event, ref);
267
+ }
268
+ channel.leave();
269
+ removeSocketChannel(this.socket, channel);
270
+ throw error;
271
+ }
272
+ this.channels.set(topic, channel);
273
+ this.channelRefs.set(topic, refs);
274
+ this.logger.debug("Joined topic", { topic });
275
+ }
276
+ async leave(topic) {
277
+ const channel = this.channels.get(topic);
278
+ if (!channel) {
279
+ return;
280
+ }
281
+ const refs = this.channelRefs.get(topic) ?? [];
282
+ for (const [event, ref] of refs) {
283
+ channel.off(event, ref);
284
+ }
285
+ this.channelRefs.delete(topic);
286
+ await new Promise((resolve, reject) => {
287
+ channel.leave().receive("ok", () => resolve()).receive(
288
+ "error",
289
+ (error) => reject(new TransportError(`Failed to leave topic ${topic}`, error))
290
+ ).receive("timeout", () => reject(new TransportError(`Timeout leaving topic ${topic}`)));
291
+ });
292
+ this.channels.delete(topic);
293
+ this.logger.debug("Left topic", { topic });
294
+ }
295
+ async runForever(signal) {
296
+ if (signal.aborted) {
297
+ return;
298
+ }
299
+ await new Promise((resolve) => {
300
+ signal.addEventListener("abort", () => resolve(), { once: true });
301
+ });
302
+ }
303
+ async waitForConnection(timeoutMs = 1e4) {
304
+ if (this.connected) {
305
+ return;
306
+ }
307
+ await new Promise((resolve, reject) => {
308
+ const timeout = setTimeout(() => {
309
+ this.connectResolve = null;
310
+ reject(new TransportError("Timed out waiting for Phoenix socket connection"));
311
+ }, timeoutMs);
312
+ this.connectResolve = () => {
313
+ clearTimeout(timeout);
314
+ resolve();
315
+ };
316
+ });
317
+ }
318
+ };
319
+ function resolveWebSocketFactory() {
320
+ if (typeof process !== "undefined" && process.versions?.node) {
321
+ return NodeWebSocket;
322
+ }
323
+ return WebSocket;
324
+ }
325
+ function removeSocketChannel(socket, channel) {
326
+ const candidate = socket;
327
+ candidate.remove?.(channel);
328
+ }
329
+ function getSocketChannelCount(socket) {
330
+ const candidate = socket;
331
+ if (!Array.isArray(candidate.channels)) {
332
+ return "unknown";
333
+ }
334
+ return candidate.channels.length;
335
+ }
336
+
337
+ // src/platform/ThenvoiLink.ts
338
+ import { ThenvoiClient } from "@thenvoi/rest-client";
339
+ var DEFAULT_WS_URL = "wss://app.thenvoi.com/api/v1/socket";
340
+ function deriveDefaultRestUrl(wsUrl) {
341
+ const parsed = new URL(wsUrl);
342
+ const protocol = parsed.protocol === "ws:" ? "http:" : "https:";
343
+ return `${protocol}//${parsed.host}`;
344
+ }
345
+ function toPlatformMessage(roomId, message) {
346
+ return {
347
+ id: message.id,
348
+ roomId,
349
+ content: message.content,
350
+ senderId: message.sender_id,
351
+ senderType: message.sender_type,
352
+ senderName: message.sender_name ?? null,
353
+ messageType: message.message_type,
354
+ metadata: message.metadata ?? {},
355
+ createdAt: new Date(message.inserted_at)
356
+ };
357
+ }
358
+ var ThenvoiLink = class {
359
+ agentId;
360
+ apiKey;
361
+ wsUrl;
362
+ restUrl;
363
+ rest;
364
+ capabilities;
365
+ logger;
366
+ transport;
367
+ subscribedRooms = /* @__PURE__ */ new Set();
368
+ eventQueue = [];
369
+ waiters = [];
370
+ connected = false;
371
+ constructor(options) {
372
+ this.agentId = options.agentId;
373
+ this.apiKey = options.apiKey;
374
+ this.wsUrl = options.wsUrl ?? DEFAULT_WS_URL;
375
+ this.restUrl = options.restUrl ?? deriveDefaultRestUrl(this.wsUrl);
376
+ this.logger = options.logger ?? new NoopLogger();
377
+ this.capabilities = {
378
+ ...DEFAULT_AGENT_TOOLS_CAPABILITIES,
379
+ ...options.capabilities
380
+ };
381
+ const restApi = options.restApi ?? new FernRestAdapter(
382
+ new ThenvoiClient({
383
+ apiKey: this.apiKey,
384
+ baseUrl: this.restUrl
385
+ })
386
+ );
387
+ this.rest = restApi;
388
+ this.transport = options.transport ?? new PhoenixChannelsTransport({
389
+ wsUrl: this.wsUrl,
390
+ apiKey: this.apiKey,
391
+ agentId: this.agentId,
392
+ logger: this.logger
393
+ });
394
+ }
395
+ isConnected() {
396
+ return this.connected;
397
+ }
398
+ async connect() {
399
+ if (this.connected) {
400
+ return;
401
+ }
402
+ await this.transport.connect();
403
+ this.connected = true;
404
+ }
405
+ async disconnect() {
406
+ if (!this.connected) {
407
+ return;
408
+ }
409
+ await Promise.allSettled(
410
+ [...this.subscribedRooms].map((roomId) => this.unsubscribeRoom(roomId))
411
+ );
412
+ await this.transport.disconnect();
413
+ this.connected = false;
414
+ }
415
+ async runForever(signal) {
416
+ await this.transport.runForever(signal);
417
+ }
418
+ queueEvent(event) {
419
+ const waiter = this.waiters.shift();
420
+ if (waiter) {
421
+ waiter.resolve(event);
422
+ return;
423
+ }
424
+ this.eventQueue.push(event);
425
+ }
426
+ async subscribeAgentRooms() {
427
+ await this.transport.join(`agent_rooms:${this.agentId}`, {
428
+ room_added: (payload) => {
429
+ const roomId = typeof payload.id === "string" ? payload.id : "";
430
+ this.emit("room_added", payload, roomId);
431
+ },
432
+ room_removed: (payload) => {
433
+ const roomId = typeof payload.id === "string" ? payload.id : "";
434
+ this.emit("room_removed", payload, roomId);
435
+ }
436
+ });
437
+ }
438
+ async subscribeRoom(roomId) {
439
+ if (this.subscribedRooms.has(roomId)) {
440
+ return;
441
+ }
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
+ });
458
+ this.subscribedRooms.add(roomId);
459
+ }
460
+ async unsubscribeRoom(roomId) {
461
+ if (!this.subscribedRooms.has(roomId)) {
462
+ return;
463
+ }
464
+ await this.transport.leave(`chat_room:${roomId}`);
465
+ await this.transport.leave(`room_participants:${roomId}`);
466
+ this.subscribedRooms.delete(roomId);
467
+ }
468
+ async subscribeAgentContacts() {
469
+ assertCapability(this.capabilities, "contacts", "Contacts streaming");
470
+ await this.transport.join(`agent_contacts:${this.agentId}`, {
471
+ contact_request_received: (payload) => {
472
+ this.emit("contact_request_received", payload, null);
473
+ },
474
+ contact_request_updated: (payload) => {
475
+ this.emit("contact_request_updated", payload, null);
476
+ },
477
+ contact_added: (payload) => {
478
+ this.emit("contact_added", payload, null);
479
+ },
480
+ contact_removed: (payload) => {
481
+ this.emit("contact_removed", payload, null);
482
+ }
483
+ });
484
+ }
485
+ async unsubscribeAgentContacts() {
486
+ await this.transport.leave(`agent_contacts:${this.agentId}`);
487
+ }
488
+ async nextEvent(signal) {
489
+ if (signal?.aborted) {
490
+ return null;
491
+ }
492
+ const queued = this.eventQueue.shift();
493
+ if (queued) {
494
+ return queued;
495
+ }
496
+ return new Promise((resolve) => {
497
+ let settled = false;
498
+ const onAbort = () => {
499
+ const index = this.waiters.indexOf(waiter);
500
+ if (index >= 0) {
501
+ this.waiters.splice(index, 1);
502
+ }
503
+ finalize(null);
504
+ };
505
+ const cleanup = () => {
506
+ signal?.removeEventListener("abort", onAbort);
507
+ };
508
+ const finalize = (event) => {
509
+ if (settled) {
510
+ return;
511
+ }
512
+ settled = true;
513
+ cleanup();
514
+ resolve(event);
515
+ };
516
+ const waiter = {
517
+ resolve: finalize,
518
+ cleanup
519
+ };
520
+ this.waiters.push(waiter);
521
+ signal?.addEventListener("abort", onAbort, { once: true });
522
+ });
523
+ }
524
+ [Symbol.asyncIterator]() {
525
+ return {
526
+ next: async () => {
527
+ const value = await this.nextEvent();
528
+ if (value === null) {
529
+ return { done: true, value: void 0 };
530
+ }
531
+ return { done: false, value };
532
+ }
533
+ };
534
+ }
535
+ async markProcessing(roomId, messageId, options) {
536
+ await this.markMessageStatus(
537
+ "markProcessing",
538
+ {
539
+ roomId,
540
+ messageId
541
+ },
542
+ () => this.rest.markMessageProcessing(roomId, messageId),
543
+ options
544
+ );
545
+ }
546
+ async markProcessed(roomId, messageId, options) {
547
+ await this.markMessageStatus(
548
+ "markProcessed",
549
+ {
550
+ roomId,
551
+ messageId
552
+ },
553
+ () => this.rest.markMessageProcessed(roomId, messageId),
554
+ options
555
+ );
556
+ }
557
+ async markFailed(roomId, messageId, error, options) {
558
+ const normalizedError = error.trim() || "Unknown error";
559
+ await this.markMessageStatus(
560
+ "markFailed",
561
+ {
562
+ roomId,
563
+ messageId,
564
+ error: normalizedError
565
+ },
566
+ () => this.rest.markMessageFailed(roomId, messageId, normalizedError),
567
+ options
568
+ );
569
+ }
570
+ async markMessageStatus(operation, context, mark, options) {
571
+ try {
572
+ await mark();
573
+ } catch (error) {
574
+ if (!options?.bestEffort) {
575
+ throw error;
576
+ }
577
+ this.logger.warn(`${operation} failed (best-effort)`, {
578
+ ...context,
579
+ error
580
+ });
581
+ }
582
+ }
583
+ async getNextMessage(roomId) {
584
+ if (!this.rest.getNextMessage) {
585
+ throw new UnsupportedFeatureError("Message queue access is not available in current REST adapter");
586
+ }
587
+ const message = await this.rest.getNextMessage({ chatId: roomId });
588
+ return message ? toPlatformMessage(roomId, message) : null;
589
+ }
590
+ async getStaleProcessingMessages(roomId) {
591
+ if (!this.rest.listMessages) {
592
+ return [];
593
+ }
594
+ const response = await this.rest.listMessages({
595
+ chatId: roomId,
596
+ page: 1,
597
+ pageSize: 50,
598
+ status: "processing"
599
+ });
600
+ return response.data.map((msg) => toPlatformMessage(roomId, msg));
601
+ }
602
+ async listChats(request, requestOptions) {
603
+ if (!this.rest.listChats) {
604
+ throw new UnsupportedFeatureError("Chat listing is not available in current REST adapter");
605
+ }
606
+ return this.rest.listChats(request, requestOptions);
607
+ }
608
+ async listAllChats(options, requestOptions) {
609
+ return fetchPaginated({
610
+ fetchPage: ({ page, pageSize }) => this.listChats({ page, pageSize }, requestOptions),
611
+ pageSize: options?.pageSize,
612
+ maxPages: options?.maxPages,
613
+ strategy: options?.strategy,
614
+ metadataValidation: options?.metadataValidation
615
+ });
616
+ }
617
+ emit(eventType, payload, roomId) {
618
+ const schema = payloadSchemas[eventType];
619
+ const parsed = schema.safeParse(payload);
620
+ if (!parsed.success) {
621
+ this.logger.warn(`Invalid ${eventType} payload, dropping event`, {
622
+ error: parsed.error.message,
623
+ roomId
624
+ });
625
+ return;
626
+ }
627
+ this.queueEvent({
628
+ type: eventType,
629
+ roomId,
630
+ payload: parsed.data,
631
+ raw: payload
632
+ });
633
+ }
634
+ };
635
+
636
+ // src/runtime/Execution.ts
637
+ function toMessageEvent(message) {
638
+ const insertedAt = message.createdAt.toISOString();
639
+ return {
640
+ type: "message_created",
641
+ roomId: message.roomId,
642
+ payload: {
643
+ id: message.id,
644
+ content: message.content,
645
+ sender_id: message.senderId,
646
+ sender_type: message.senderType,
647
+ sender_name: message.senderName ?? null,
648
+ message_type: message.messageType,
649
+ metadata: message.metadata,
650
+ inserted_at: insertedAt,
651
+ updated_at: insertedAt
652
+ }
653
+ };
654
+ }
655
+ var Execution = class {
656
+ roomId;
657
+ link;
658
+ context;
659
+ retryTracker;
660
+ onExecute;
661
+ onFailure;
662
+ logger;
663
+ eventQueue = [];
664
+ waiters = [];
665
+ idleWaiters = /* @__PURE__ */ new Set();
666
+ drainedWsMessageIds = /* @__PURE__ */ new Set();
667
+ syncProcessedIds = /* @__PURE__ */ new Set();
668
+ processTask;
669
+ firstWsMessageId = null;
670
+ syncComplete = false;
671
+ running = true;
672
+ inFlight = 0;
673
+ constructor(options) {
674
+ this.roomId = options.roomId;
675
+ this.link = options.link;
676
+ this.context = options.context;
677
+ this.retryTracker = this.context.getRetryTracker();
678
+ this.onExecute = options.onExecute;
679
+ this.onFailure = options.onFailure;
680
+ this.logger = options.logger ?? new NoopLogger();
681
+ this.processTask = this.processLoop();
682
+ }
683
+ enqueue(event) {
684
+ if (event.type === "message_created" && !this.syncComplete && this.firstWsMessageId === null) {
685
+ this.firstWsMessageId = event.payload.id;
686
+ }
687
+ const waiter = this.waiters.shift();
688
+ if (waiter) {
689
+ waiter(event);
690
+ } else {
691
+ this.eventQueue.push(event);
692
+ }
693
+ return Promise.resolve();
694
+ }
695
+ async bootstrapMessage(message) {
696
+ this.syncProcessedIds.add(message.id);
697
+ await this.executeSyncMessage(toMessageEvent(message), message.id);
698
+ }
699
+ isIdle() {
700
+ return this.syncComplete && this.inFlight === 0 && this.eventQueue.length === 0;
701
+ }
702
+ async waitForIdle(timeoutMs) {
703
+ if (this.isIdle()) {
704
+ return true;
705
+ }
706
+ return new Promise((resolve) => {
707
+ let settled = false;
708
+ let timeoutHandle = null;
709
+ const idleWaiter = () => {
710
+ if (settled) {
711
+ return;
712
+ }
713
+ settled = true;
714
+ this.idleWaiters.delete(idleWaiter);
715
+ if (timeoutHandle !== null) {
716
+ clearTimeout(timeoutHandle);
717
+ }
718
+ resolve(true);
719
+ };
720
+ this.idleWaiters.add(idleWaiter);
721
+ if (timeoutMs === void 0) {
722
+ return;
723
+ }
724
+ timeoutHandle = setTimeout(() => {
725
+ if (settled) {
726
+ return;
727
+ }
728
+ settled = true;
729
+ this.idleWaiters.delete(idleWaiter);
730
+ resolve(false);
731
+ }, timeoutMs);
732
+ });
733
+ }
734
+ async stop(timeoutMs) {
735
+ const graceful = await this.waitForIdle(timeoutMs);
736
+ this.running = false;
737
+ this.resolveEventWaiters(null);
738
+ if (graceful || timeoutMs === void 0) {
739
+ await this.processTask;
740
+ }
741
+ return graceful;
742
+ }
743
+ async waitUntilStopped() {
744
+ await this.processTask;
745
+ }
746
+ async processLoop() {
747
+ await this.recoverStaleProcessingMessages();
748
+ await this.synchronizeWithNext();
749
+ while (this.running) {
750
+ const event = await this.nextQueuedEvent();
751
+ if (!event) {
752
+ return;
753
+ }
754
+ if (event.type === "message_created" && this.drainedWsMessageIds.has(event.payload.id)) {
755
+ this.drainedWsMessageIds.delete(event.payload.id);
756
+ this.notifyIfIdle();
757
+ continue;
758
+ }
759
+ await this.executeEvent(event);
760
+ }
761
+ }
762
+ async recoverStaleProcessingMessages() {
763
+ let staleMessages;
764
+ try {
765
+ staleMessages = await this.link.getStaleProcessingMessages(this.roomId);
766
+ } catch {
767
+ this.logger.warn("Failed to fetch stale processing messages, skipping recovery", {
768
+ roomId: this.roomId
769
+ });
770
+ return;
771
+ }
772
+ if (staleMessages.length === 0) {
773
+ return;
774
+ }
775
+ this.logger.info("Recovering stale processing messages", {
776
+ roomId: this.roomId,
777
+ count: staleMessages.length
778
+ });
779
+ for (const message of staleMessages) {
780
+ if (!this.running) {
781
+ break;
782
+ }
783
+ if (this.retryTracker.isPermanentlyFailed(message.id)) {
784
+ this.logger.warn("Skipping permanently failed stale message", {
785
+ roomId: this.roomId,
786
+ messageId: message.id
787
+ });
788
+ continue;
789
+ }
790
+ await this.executeSyncMessage(toMessageEvent(message), message.id);
791
+ this.syncProcessedIds.add(message.id);
792
+ }
793
+ }
794
+ async synchronizeWithNext() {
795
+ while (this.running) {
796
+ const nextMessage = await this.link.getNextMessage(this.roomId);
797
+ if (!nextMessage) {
798
+ break;
799
+ }
800
+ if (this.syncProcessedIds.has(nextMessage.id)) {
801
+ const isSyncPoint2 = this.firstWsMessageId !== null && nextMessage.id === this.firstWsMessageId;
802
+ if (isSyncPoint2) {
803
+ this.drainedWsMessageIds.add(nextMessage.id);
804
+ this.firstWsMessageId = null;
805
+ break;
806
+ }
807
+ continue;
808
+ }
809
+ if (this.retryTracker.isPermanentlyFailed(nextMessage.id)) {
810
+ this.logger.warn("Skipping permanently failed message during sync", {
811
+ roomId: this.roomId,
812
+ messageId: nextMessage.id
813
+ });
814
+ await this.markMessageFailed(nextMessage.id, "Message permanently failed after max retries");
815
+ const isSyncPoint2 = this.firstWsMessageId !== null && nextMessage.id === this.firstWsMessageId;
816
+ if (isSyncPoint2) {
817
+ this.drainedWsMessageIds.add(nextMessage.id);
818
+ this.firstWsMessageId = null;
819
+ break;
820
+ }
821
+ continue;
822
+ }
823
+ const isSyncPoint = this.firstWsMessageId !== null && nextMessage.id === this.firstWsMessageId;
824
+ await this.executeSyncMessage(toMessageEvent(nextMessage), nextMessage.id);
825
+ this.syncProcessedIds.add(nextMessage.id);
826
+ if (isSyncPoint) {
827
+ this.drainedWsMessageIds.add(nextMessage.id);
828
+ this.firstWsMessageId = null;
829
+ break;
830
+ }
831
+ }
832
+ this.syncProcessedIds.clear();
833
+ this.syncComplete = true;
834
+ this.notifyIfIdle();
835
+ }
836
+ async executeSyncMessage(event, messageId) {
837
+ const [, exceeded] = this.retryTracker.recordAttempt(messageId);
838
+ if (exceeded) {
839
+ this.logger.error("Message exceeded max retries during sync, marking permanently failed", {
840
+ roomId: this.roomId,
841
+ messageId,
842
+ maxRetries: this.retryTracker.maxRetries
843
+ });
844
+ await this.markMessageFailed(messageId, "Message permanently failed after max retries");
845
+ return;
846
+ }
847
+ this.inFlight += 1;
848
+ this.context.setState("processing");
849
+ try {
850
+ await this.onExecute(this.context, event);
851
+ this.retryTracker.markSuccess(messageId);
852
+ } catch (error) {
853
+ const label = error instanceof Error ? error.message : String(error);
854
+ this.logger.error("Sync message execution failed", {
855
+ roomId: this.roomId,
856
+ messageId,
857
+ error: label
858
+ });
859
+ } finally {
860
+ this.inFlight -= 1;
861
+ this.context.setState("idle");
862
+ }
863
+ }
864
+ async executeEvent(event) {
865
+ this.inFlight += 1;
866
+ this.context.setState("processing");
867
+ try {
868
+ await this.onExecute(this.context, event);
869
+ } catch (error) {
870
+ if (this.onFailure) {
871
+ await this.onFailure(error, event);
872
+ } else {
873
+ this.logger.error("Execution queue task failed", {
874
+ roomId: this.roomId,
875
+ eventType: event.type,
876
+ error
877
+ });
878
+ }
879
+ this.running = false;
880
+ this.eventQueue.splice(0, this.eventQueue.length);
881
+ this.resolveEventWaiters(null);
882
+ throw error;
883
+ } finally {
884
+ this.inFlight -= 1;
885
+ this.context.setState("idle");
886
+ this.notifyIfIdle();
887
+ }
888
+ }
889
+ async nextQueuedEvent() {
890
+ const queued = this.eventQueue.shift();
891
+ if (queued) {
892
+ return queued;
893
+ }
894
+ if (!this.running) {
895
+ return null;
896
+ }
897
+ return new Promise((resolve) => {
898
+ this.waiters.push(resolve);
899
+ });
900
+ }
901
+ notifyIfIdle() {
902
+ if (!this.isIdle()) {
903
+ return;
904
+ }
905
+ const waiters = [...this.idleWaiters];
906
+ this.idleWaiters.clear();
907
+ for (const waiter of waiters) {
908
+ waiter();
909
+ }
910
+ }
911
+ resolveEventWaiters(event) {
912
+ const waiters = this.waiters.splice(0, this.waiters.length);
913
+ for (const waiter of waiters) {
914
+ waiter(event);
915
+ }
916
+ }
917
+ async markMessageFailed(messageId, error) {
918
+ try {
919
+ await this.link.markFailed(this.roomId, messageId, error, { bestEffort: true });
920
+ } catch {
921
+ this.logger.warn("Failed to mark message as failed on server", {
922
+ roomId: this.roomId,
923
+ messageId
924
+ });
925
+ }
926
+ }
927
+ };
928
+
929
+ // src/runtime/tools/AgentTools.ts
930
+ var REQUIRED_ADAPTER_TOOL_METHODS = [
931
+ "sendMessage",
932
+ "sendEvent",
933
+ "addParticipant",
934
+ "removeParticipant",
935
+ "getParticipants",
936
+ "createChatroom",
937
+ "getToolSchemas",
938
+ "getAnthropicToolSchemas",
939
+ "getOpenAIToolSchemas",
940
+ "executeToolCall"
941
+ ];
942
+ var OPTIONAL_ADAPTER_TOOL_METHODS = {
943
+ peers: ["lookupPeers"],
944
+ contacts: [
945
+ "listContacts",
946
+ "addContact",
947
+ "removeContact",
948
+ "listContactRequests",
949
+ "respondContactRequest"
950
+ ],
951
+ memory: [
952
+ "listMemories",
953
+ "storeMemory",
954
+ "getMemory",
955
+ "supersedeMemory",
956
+ "archiveMemory"
957
+ ]
958
+ };
959
+ var CONTACT_REQUEST_ACTIONS = /* @__PURE__ */ new Set([
960
+ "approve",
961
+ "reject",
962
+ "cancel"
963
+ ]);
964
+ var MEMORY_SYSTEMS = /* @__PURE__ */ new Set([
965
+ "sensory",
966
+ "working",
967
+ "long_term"
968
+ ]);
969
+ var MEMORY_TYPES = /* @__PURE__ */ new Set([
970
+ "iconic",
971
+ "echoic",
972
+ "haptic",
973
+ "episodic",
974
+ "semantic",
975
+ "procedural"
976
+ ]);
977
+ var MEMORY_SEGMENTS = /* @__PURE__ */ new Set([
978
+ "user",
979
+ "agent",
980
+ "tool",
981
+ "guideline"
982
+ ]);
983
+ var LIST_MEMORY_SCOPES = /* @__PURE__ */ new Set([
984
+ "subject",
985
+ "organization",
986
+ "all"
987
+ ]);
988
+ var LIST_MEMORY_STATUSES = /* @__PURE__ */ new Set([
989
+ "active",
990
+ "superseded",
991
+ "archived",
992
+ "all"
993
+ ]);
994
+ var MEMORY_SCOPES = /* @__PURE__ */ new Set([
995
+ "subject",
996
+ "organization"
997
+ ]);
998
+ var AgentTools = class {
999
+ roomId;
1000
+ capabilities;
1001
+ rest;
1002
+ participants;
1003
+ adapterTools;
1004
+ toolHandlers;
1005
+ constructor(options) {
1006
+ this.roomId = options.roomId;
1007
+ this.rest = options.rest;
1008
+ this.participants = options.participants ?? [];
1009
+ this.capabilities = {
1010
+ ...DEFAULT_AGENT_TOOLS_CAPABILITIES,
1011
+ ...options.capabilities
1012
+ };
1013
+ this.toolHandlers = this.buildToolHandlers();
1014
+ this.adapterTools = this.buildAdapterTools();
1015
+ }
1016
+ getAdapterTools() {
1017
+ return this.adapterTools;
1018
+ }
1019
+ async sendMessage(content, mentions = []) {
1020
+ if (mentions.length > 0 && typeof mentions[0] === "string" && this.participants.length === 0) {
1021
+ await this.syncParticipants();
1022
+ }
1023
+ const resolvedMentions = this.resolveMentions(mentions);
1024
+ return this.rest.createChatMessage(
1025
+ this.roomId,
1026
+ {
1027
+ content,
1028
+ mentions: resolvedMentions
1029
+ },
1030
+ DEFAULT_REQUEST_OPTIONS
1031
+ );
1032
+ }
1033
+ async sendEvent(content, messageType, metadata) {
1034
+ assertChatEventType(messageType);
1035
+ return this.rest.createChatEvent(
1036
+ this.roomId,
1037
+ {
1038
+ content,
1039
+ messageType,
1040
+ metadata
1041
+ },
1042
+ DEFAULT_REQUEST_OPTIONS
1043
+ );
1044
+ }
1045
+ async createChatroom(taskId) {
1046
+ const room = await this.rest.createChat(taskId, DEFAULT_REQUEST_OPTIONS);
1047
+ return room.id;
1048
+ }
1049
+ async addParticipant(name, role = "member") {
1050
+ const existing = await this.syncParticipants();
1051
+ const alreadyInRoom = existing.find(
1052
+ (participant) => String(participant.name ?? "").toLowerCase() === name.toLowerCase()
1053
+ );
1054
+ if (alreadyInRoom) {
1055
+ return {
1056
+ ...alreadyInRoom,
1057
+ status: "already_in_room"
1058
+ };
1059
+ }
1060
+ const peer = await this.lookupPeerByName(name);
1061
+ if (!peer?.id) {
1062
+ throw new UnsupportedFeatureError(
1063
+ `Participant '${name}' not found. lookupPeers requires peer endpoint availability.`
1064
+ );
1065
+ }
1066
+ await this.rest.addChatParticipant(
1067
+ this.roomId,
1068
+ {
1069
+ participantId: String(peer.id),
1070
+ role
1071
+ },
1072
+ DEFAULT_REQUEST_OPTIONS
1073
+ );
1074
+ const participantRecord = {
1075
+ id: String(peer.id),
1076
+ name,
1077
+ role,
1078
+ type: String(peer.type ?? "Agent"),
1079
+ handle: typeof peer.handle === "string" ? peer.handle : null
1080
+ };
1081
+ this.participants.push(participantRecord);
1082
+ return {
1083
+ ...participantRecord,
1084
+ status: "added"
1085
+ };
1086
+ }
1087
+ async removeParticipant(name) {
1088
+ const participants = await this.syncParticipants();
1089
+ const participant = participants.find(
1090
+ (entry) => String(entry.name ?? "").toLowerCase() === name.toLowerCase()
1091
+ );
1092
+ if (!participant?.id) {
1093
+ throw new ValidationError(`Participant '${name}' not found in room`);
1094
+ }
1095
+ await this.rest.removeChatParticipant(this.roomId, String(participant.id), DEFAULT_REQUEST_OPTIONS);
1096
+ this.replaceParticipants(this.participants.filter((entry) => entry.id !== participant.id));
1097
+ return {
1098
+ id: participant.id,
1099
+ name,
1100
+ status: "removed"
1101
+ };
1102
+ }
1103
+ async lookupPeers(page = 1, pageSize = 50) {
1104
+ assertCapability(this.capabilities, "peers");
1105
+ if (!this.rest.listPeers) {
1106
+ throw new UnsupportedFeatureError(
1107
+ "Peer listing is not available in current REST adapter"
1108
+ );
1109
+ }
1110
+ return this.rest.listPeers(
1111
+ {
1112
+ page,
1113
+ pageSize,
1114
+ notInChat: this.roomId
1115
+ },
1116
+ DEFAULT_REQUEST_OPTIONS
1117
+ );
1118
+ }
1119
+ async getParticipants() {
1120
+ return this.fetchParticipants();
1121
+ }
1122
+ async fetchParticipants() {
1123
+ const participants = await this.rest.listChatParticipants(this.roomId, DEFAULT_REQUEST_OPTIONS);
1124
+ return participants.map((participant) => ({
1125
+ id: participant.id,
1126
+ name: participant.name,
1127
+ type: participant.type,
1128
+ handle: participant.handle ?? null
1129
+ }));
1130
+ }
1131
+ async syncParticipants() {
1132
+ const participants = await this.fetchParticipants();
1133
+ this.replaceParticipants(participants);
1134
+ return [...this.participants];
1135
+ }
1136
+ async executeToolCall(toolName, arguments_) {
1137
+ const validationError = validateToolArgs(toolName, arguments_);
1138
+ if (validationError) {
1139
+ return validationError;
1140
+ }
1141
+ const handler = this.toolHandlers[toolName];
1142
+ if (!handler) {
1143
+ return createToolExecutorError({
1144
+ errorType: "ToolNotFoundError",
1145
+ toolName,
1146
+ message: `Tool '${toolName}' is not registered`,
1147
+ legacyMessage: `Unknown tool: ${toolName}`
1148
+ });
1149
+ }
1150
+ try {
1151
+ return await handler(arguments_);
1152
+ } catch (error) {
1153
+ if (isToolExecutorError(error)) {
1154
+ return error;
1155
+ }
1156
+ if (error instanceof ValidationError) {
1157
+ return createToolExecutorError({
1158
+ errorType: "ToolArgumentsValidationError",
1159
+ toolName,
1160
+ message: error.message,
1161
+ legacyMessage: `Invalid arguments for ${toolName}: ${error.message}`
1162
+ });
1163
+ }
1164
+ const message = error instanceof Error ? error.message : String(error);
1165
+ return createToolExecutorError({
1166
+ errorType: "ToolExecutionError",
1167
+ toolName,
1168
+ message,
1169
+ legacyMessage: `Error executing ${toolName}: ${message}`
1170
+ });
1171
+ }
1172
+ }
1173
+ getToolSchemas(format, options) {
1174
+ const includeMemory = options?.includeMemory ?? false;
1175
+ const tools = Object.entries(TOOL_MODELS).filter(([name]) => {
1176
+ if (MEMORY_TOOL_NAMES.has(name)) {
1177
+ return includeMemory && this.capabilities.memory;
1178
+ }
1179
+ if (!CHAT_TOOL_NAMES.has(name) && !this.capabilities.contacts) {
1180
+ return false;
1181
+ }
1182
+ if (name === "thenvoi_lookup_peers" && !this.capabilities.peers) {
1183
+ return false;
1184
+ }
1185
+ return true;
1186
+ }).map(([name, model]) => {
1187
+ if (format === "anthropic") {
1188
+ return {
1189
+ name,
1190
+ description: getToolDescription(name),
1191
+ input_schema: {
1192
+ type: "object",
1193
+ properties: model.properties,
1194
+ required: model.required
1195
+ }
1196
+ };
1197
+ }
1198
+ return {
1199
+ type: "function",
1200
+ function: {
1201
+ name,
1202
+ description: getToolDescription(name),
1203
+ parameters: {
1204
+ type: "object",
1205
+ properties: model.properties,
1206
+ required: model.required
1207
+ }
1208
+ }
1209
+ };
1210
+ });
1211
+ return tools;
1212
+ }
1213
+ getAnthropicToolSchemas(options) {
1214
+ return this.getToolSchemas("anthropic", options);
1215
+ }
1216
+ getOpenAIToolSchemas(options) {
1217
+ return this.getToolSchemas("openai", options);
1218
+ }
1219
+ async listContacts(request = {}) {
1220
+ assertCapability(this.capabilities, "contacts");
1221
+ if (!this.rest.listContacts) {
1222
+ throw new UnsupportedFeatureError("Contact listing is not available in current REST adapter");
1223
+ }
1224
+ const page = request.page ?? 1;
1225
+ const pageSize = request.pageSize ?? 50;
1226
+ return this.rest.listContacts(
1227
+ {
1228
+ page,
1229
+ pageSize
1230
+ },
1231
+ DEFAULT_REQUEST_OPTIONS
1232
+ );
1233
+ }
1234
+ async addContact(request) {
1235
+ assertCapability(this.capabilities, "contacts");
1236
+ if (!this.rest.addContact) {
1237
+ throw new UnsupportedFeatureError("Contact creation is not available in current REST adapter");
1238
+ }
1239
+ const normalizedHandle = request.handle.trim();
1240
+ if (normalizedHandle.length === 0) {
1241
+ throw new ValidationError("handle is required");
1242
+ }
1243
+ return this.rest.addContact(
1244
+ {
1245
+ handle: normalizedHandle,
1246
+ ...request.message ? { message: request.message } : {}
1247
+ },
1248
+ DEFAULT_REQUEST_OPTIONS
1249
+ );
1250
+ }
1251
+ async removeContact(request) {
1252
+ assertCapability(this.capabilities, "contacts");
1253
+ if (!this.rest.removeContact) {
1254
+ throw new UnsupportedFeatureError("Contact removal is not available in current REST adapter");
1255
+ }
1256
+ if (request.target === "handle") {
1257
+ const handle = request.handle.trim();
1258
+ if (handle.length === 0) {
1259
+ throw new ValidationError("handle is required");
1260
+ }
1261
+ return this.rest.removeContact(
1262
+ {
1263
+ target: "handle",
1264
+ handle
1265
+ },
1266
+ DEFAULT_REQUEST_OPTIONS
1267
+ );
1268
+ }
1269
+ const contactId = request.contactId.trim();
1270
+ if (contactId.length === 0) {
1271
+ throw new ValidationError("contactId is required");
1272
+ }
1273
+ return this.rest.removeContact(
1274
+ {
1275
+ target: "contactId",
1276
+ contactId
1277
+ },
1278
+ DEFAULT_REQUEST_OPTIONS
1279
+ );
1280
+ }
1281
+ async listContactRequests(request = {}) {
1282
+ assertCapability(this.capabilities, "contacts");
1283
+ if (!this.rest.listContactRequests) {
1284
+ throw new UnsupportedFeatureError("Contact request listing is not available in current REST adapter");
1285
+ }
1286
+ const page = request.page ?? 1;
1287
+ const pageSize = request.pageSize ?? 50;
1288
+ const sentStatus = request.sentStatus ?? "pending";
1289
+ return this.rest.listContactRequests(
1290
+ {
1291
+ page,
1292
+ pageSize,
1293
+ sentStatus
1294
+ },
1295
+ DEFAULT_REQUEST_OPTIONS
1296
+ );
1297
+ }
1298
+ async respondContactRequest(request) {
1299
+ assertCapability(this.capabilities, "contacts");
1300
+ if (!this.rest.respondContactRequest) {
1301
+ throw new UnsupportedFeatureError("Contact request responses are not available in current REST adapter");
1302
+ }
1303
+ if (request.target === "handle") {
1304
+ const handle = request.handle.trim();
1305
+ if (handle.length === 0) {
1306
+ throw new ValidationError("handle is required");
1307
+ }
1308
+ return this.rest.respondContactRequest(
1309
+ {
1310
+ action: request.action,
1311
+ target: "handle",
1312
+ handle
1313
+ },
1314
+ DEFAULT_REQUEST_OPTIONS
1315
+ );
1316
+ }
1317
+ const requestId = request.requestId.trim();
1318
+ if (requestId.length === 0) {
1319
+ throw new ValidationError("requestId is required");
1320
+ }
1321
+ return this.rest.respondContactRequest(
1322
+ {
1323
+ action: request.action,
1324
+ target: "requestId",
1325
+ requestId
1326
+ },
1327
+ DEFAULT_REQUEST_OPTIONS
1328
+ );
1329
+ }
1330
+ async listMemories(args = {}) {
1331
+ assertCapability(this.capabilities, "memory");
1332
+ if (!this.rest.listMemories) {
1333
+ throw new UnsupportedFeatureError("Memory listing is not available in current REST adapter");
1334
+ }
1335
+ return this.rest.listMemories(args, DEFAULT_REQUEST_OPTIONS);
1336
+ }
1337
+ async storeMemory(args) {
1338
+ assertCapability(this.capabilities, "memory");
1339
+ if (!this.rest.storeMemory) {
1340
+ throw new UnsupportedFeatureError("Memory creation is not available in current REST adapter");
1341
+ }
1342
+ return this.rest.storeMemory(args, DEFAULT_REQUEST_OPTIONS);
1343
+ }
1344
+ async getMemory(memoryId) {
1345
+ assertCapability(this.capabilities, "memory");
1346
+ if (!this.rest.getMemory) {
1347
+ throw new UnsupportedFeatureError("Memory lookup is not available in current REST adapter");
1348
+ }
1349
+ const normalizedMemoryId = memoryId.trim();
1350
+ if (normalizedMemoryId.length === 0) {
1351
+ throw new ValidationError("memoryId is required");
1352
+ }
1353
+ return this.rest.getMemory(normalizedMemoryId, DEFAULT_REQUEST_OPTIONS);
1354
+ }
1355
+ async supersedeMemory(memoryId) {
1356
+ assertCapability(this.capabilities, "memory");
1357
+ if (!this.rest.supersedeMemory) {
1358
+ throw new UnsupportedFeatureError("Memory supersede is not available in current REST adapter");
1359
+ }
1360
+ const normalizedMemoryId = memoryId.trim();
1361
+ if (normalizedMemoryId.length === 0) {
1362
+ throw new ValidationError("memoryId is required");
1363
+ }
1364
+ return this.rest.supersedeMemory(normalizedMemoryId, DEFAULT_REQUEST_OPTIONS);
1365
+ }
1366
+ async archiveMemory(memoryId) {
1367
+ assertCapability(this.capabilities, "memory");
1368
+ if (!this.rest.archiveMemory) {
1369
+ throw new UnsupportedFeatureError("Memory archive is not available in current REST adapter");
1370
+ }
1371
+ const normalizedMemoryId = memoryId.trim();
1372
+ if (normalizedMemoryId.length === 0) {
1373
+ throw new ValidationError("memoryId is required");
1374
+ }
1375
+ return this.rest.archiveMemory(normalizedMemoryId, DEFAULT_REQUEST_OPTIONS);
1376
+ }
1377
+ resolveMentions(mentions) {
1378
+ if (mentions.length === 0) {
1379
+ return [];
1380
+ }
1381
+ if (typeof mentions[0] !== "string") {
1382
+ return mentions.filter(
1383
+ (entry) => typeof entry === "object" && entry !== null && "id" in entry
1384
+ );
1385
+ }
1386
+ const stringMentions = mentions.filter((entry) => typeof entry === "string");
1387
+ const participantsByHandle = /* @__PURE__ */ new Map();
1388
+ const participantsById = /* @__PURE__ */ new Map();
1389
+ const participantsByName = /* @__PURE__ */ new Map();
1390
+ for (const participant of this.participants) {
1391
+ const ref = {
1392
+ id: String(participant.id),
1393
+ handle: typeof participant.handle === "string" ? participant.handle : void 0
1394
+ };
1395
+ participantsById.set(ref.id, ref);
1396
+ const handle = participant.handle;
1397
+ if (typeof handle === "string") {
1398
+ participantsByHandle.set(this.normalizeMentionHandle(handle), ref);
1399
+ }
1400
+ const name = participant.name;
1401
+ if (typeof name === "string" && name.trim().length > 0) {
1402
+ participantsByName.set(name.trim().toLowerCase(), ref);
1403
+ }
1404
+ }
1405
+ return stringMentions.map((mention) => {
1406
+ const byId = participantsById.get(mention);
1407
+ if (byId) {
1408
+ return byId;
1409
+ }
1410
+ const normalized = this.normalizeMentionHandle(mention);
1411
+ const found = participantsByHandle.get(normalized);
1412
+ if (found) {
1413
+ return found;
1414
+ }
1415
+ const byName = participantsByName.get(mention.trim().toLowerCase());
1416
+ if (byName) {
1417
+ return byName;
1418
+ }
1419
+ throw new ValidationError(`Mention '${mention}' not found in participants`);
1420
+ });
1421
+ }
1422
+ async lookupPeerByName(name) {
1423
+ const target = name.trim().toLowerCase();
1424
+ const pageSize = 100;
1425
+ const maxPages = 25;
1426
+ for (let page = 1; page <= maxPages; page += 1) {
1427
+ const peers = await this.lookupPeers(page, pageSize);
1428
+ const items = peers.data ?? [];
1429
+ const match = items.find((peer) => String(peer.name ?? "").toLowerCase() === target);
1430
+ if (match) {
1431
+ return match;
1432
+ }
1433
+ const totalPages = peers.metadata?.totalPages;
1434
+ if (typeof totalPages === "number" && totalPages > 0 && page >= totalPages) {
1435
+ break;
1436
+ }
1437
+ if ((typeof totalPages !== "number" || totalPages <= 0) && items.length < pageSize) {
1438
+ break;
1439
+ }
1440
+ }
1441
+ return null;
1442
+ }
1443
+ normalizeMentionHandle(handle) {
1444
+ return handle.trim().replace(/^@+/, "").toLowerCase();
1445
+ }
1446
+ replaceParticipants(participants) {
1447
+ this.participants.splice(0, this.participants.length, ...participants);
1448
+ }
1449
+ buildAdapterTools() {
1450
+ const tools = {
1451
+ capabilities: this.capabilities
1452
+ };
1453
+ for (const methodName of REQUIRED_ADAPTER_TOOL_METHODS) {
1454
+ tools[methodName] = this.bindAdapterToolMethod(methodName);
1455
+ }
1456
+ for (const [capabilityKey, methodNames] of Object.entries(OPTIONAL_ADAPTER_TOOL_METHODS)) {
1457
+ if (!this.capabilities[capabilityKey]) {
1458
+ continue;
1459
+ }
1460
+ for (const methodName of methodNames) {
1461
+ tools[methodName] = this.bindAdapterToolMethod(methodName);
1462
+ }
1463
+ }
1464
+ return Object.freeze(tools);
1465
+ }
1466
+ bindAdapterToolMethod(methodName) {
1467
+ const method = this[methodName];
1468
+ return method.bind(this);
1469
+ }
1470
+ buildToolHandlers() {
1471
+ return {
1472
+ ...this.buildMessagingToolHandlers(),
1473
+ ...this.buildContactToolHandlers(),
1474
+ ...this.buildMemoryToolHandlers()
1475
+ };
1476
+ }
1477
+ buildMessagingToolHandlers() {
1478
+ return {
1479
+ thenvoi_send_message: async (arguments_) => this.sendMessage(
1480
+ String(arguments_.content ?? ""),
1481
+ this.normalizeMentionInput(arguments_.mentions)
1482
+ ),
1483
+ thenvoi_send_event: async (arguments_) => this.sendEvent(
1484
+ String(arguments_.content ?? ""),
1485
+ String(arguments_.message_type ?? "task"),
1486
+ this.normalizeOptionalMetadata(arguments_.metadata)
1487
+ ),
1488
+ thenvoi_add_participant: async (arguments_) => this.addParticipant(String(arguments_.name ?? ""), String(arguments_.role ?? "member")),
1489
+ thenvoi_remove_participant: async (arguments_) => this.removeParticipant(String(arguments_.name ?? "")),
1490
+ thenvoi_lookup_peers: async (arguments_) => this.lookupPeers(
1491
+ coercePositiveInt(arguments_.page, 1),
1492
+ coercePositiveInt(arguments_.page_size, 50)
1493
+ ),
1494
+ thenvoi_get_participants: async () => this.getParticipants(),
1495
+ thenvoi_create_chatroom: async (arguments_) => this.createChatroom(this.normalizeOptionalString(arguments_.task_id))
1496
+ };
1497
+ }
1498
+ buildContactToolHandlers() {
1499
+ return {
1500
+ thenvoi_list_contacts: async (arguments_) => this.listContacts({
1501
+ page: coercePositiveInt(arguments_.page, 1),
1502
+ pageSize: coercePositiveInt(arguments_.page_size, 50)
1503
+ }),
1504
+ thenvoi_add_contact: async (arguments_) => this.addContact({
1505
+ handle: String(arguments_.handle ?? ""),
1506
+ ...typeof arguments_.message === "string" ? { message: arguments_.message } : {}
1507
+ }),
1508
+ thenvoi_remove_contact: async (arguments_) => this.removeContact(this.toRemoveContactArgs(arguments_)),
1509
+ thenvoi_list_contact_requests: async (arguments_) => this.listContactRequests({
1510
+ page: coercePositiveInt(arguments_.page, 1),
1511
+ pageSize: coercePositiveInt(arguments_.page_size, 50),
1512
+ sentStatus: String(arguments_.sent_status ?? "pending")
1513
+ }),
1514
+ thenvoi_respond_contact_request: async (arguments_) => this.respondContactRequest(this.toRespondContactRequestArgs(arguments_))
1515
+ };
1516
+ }
1517
+ buildMemoryToolHandlers() {
1518
+ return {
1519
+ thenvoi_list_memories: async (arguments_) => this.listMemories(this.toListMemoriesArgs(arguments_)),
1520
+ thenvoi_store_memory: async (arguments_) => this.storeMemory(this.toStoreMemoryArgs(arguments_)),
1521
+ thenvoi_get_memory: async (arguments_) => this.getMemory(String(arguments_.memory_id ?? "")),
1522
+ thenvoi_supersede_memory: async (arguments_) => this.supersedeMemory(String(arguments_.memory_id ?? "")),
1523
+ thenvoi_archive_memory: async (arguments_) => this.archiveMemory(String(arguments_.memory_id ?? ""))
1524
+ };
1525
+ }
1526
+ toRemoveContactArgs(arguments_) {
1527
+ const handle = this.normalizeOptionalString(arguments_.handle);
1528
+ const contactId = this.normalizeOptionalString(arguments_.contact_id);
1529
+ if (handle && contactId || !handle && !contactId) {
1530
+ throw new ValidationError("Provide exactly one of handle or contact_id");
1531
+ }
1532
+ if (handle) {
1533
+ return {
1534
+ target: "handle",
1535
+ handle
1536
+ };
1537
+ }
1538
+ return {
1539
+ target: "contactId",
1540
+ contactId
1541
+ };
1542
+ }
1543
+ toRespondContactRequestArgs(arguments_) {
1544
+ const actionValue = this.normalizeOptionalString(arguments_.action);
1545
+ if (!actionValue || !isContactRequestAction(actionValue)) {
1546
+ throw new ValidationError("action must be one of: approve, reject, cancel");
1547
+ }
1548
+ const handle = this.normalizeOptionalString(arguments_.handle);
1549
+ const requestId = this.normalizeOptionalString(arguments_.request_id);
1550
+ if (handle && requestId || !handle && !requestId) {
1551
+ throw new ValidationError("Provide exactly one of handle or request_id");
1552
+ }
1553
+ if (handle) {
1554
+ return {
1555
+ action: actionValue,
1556
+ target: "handle",
1557
+ handle
1558
+ };
1559
+ }
1560
+ return {
1561
+ action: actionValue,
1562
+ target: "requestId",
1563
+ requestId
1564
+ };
1565
+ }
1566
+ toListMemoriesArgs(arguments_) {
1567
+ const pageSize = this.normalizeOptionalNumber(arguments_.page_size);
1568
+ const scope = this.normalizeOptionalMemoryScope(arguments_.scope);
1569
+ const system = this.normalizeOptionalMemorySystem(arguments_.system);
1570
+ const type = this.normalizeOptionalMemoryType(arguments_.type);
1571
+ const segment = this.normalizeOptionalMemorySegment(arguments_.segment);
1572
+ const status = this.normalizeOptionalMemoryStatus(arguments_.status);
1573
+ const subjectId = this.normalizeOptionalString(arguments_.subject_id);
1574
+ const contentQuery = this.normalizeOptionalString(arguments_.content_query);
1575
+ return {
1576
+ ...subjectId ? { subject_id: subjectId } : {},
1577
+ ...scope ? { scope } : {},
1578
+ ...system ? { system } : {},
1579
+ ...type ? { type } : {},
1580
+ ...segment ? { segment } : {},
1581
+ ...contentQuery ? { content_query: contentQuery } : {},
1582
+ ...typeof pageSize === "number" ? { page_size: pageSize } : {},
1583
+ ...status ? { status } : {}
1584
+ };
1585
+ }
1586
+ toStoreMemoryArgs(arguments_) {
1587
+ const content = this.normalizeRequiredString(arguments_.content, "content");
1588
+ const thought = this.normalizeRequiredString(arguments_.thought, "thought");
1589
+ const system = this.normalizeOptionalMemorySystem(arguments_.system);
1590
+ const type = this.normalizeOptionalMemoryType(arguments_.type);
1591
+ const segment = this.normalizeOptionalMemorySegment(arguments_.segment);
1592
+ const scope = this.normalizeOptionalStoreMemoryScope(arguments_.scope);
1593
+ const subjectId = this.normalizeOptionalString(arguments_.subject_id);
1594
+ const metadata = this.normalizeOptionalMetadata(arguments_.metadata);
1595
+ if (!system) {
1596
+ throw new ValidationError("system must be one of: sensory, working, long_term");
1597
+ }
1598
+ if (!type) {
1599
+ throw new ValidationError("type must be one of: iconic, echoic, haptic, episodic, semantic, procedural");
1600
+ }
1601
+ if (!segment) {
1602
+ throw new ValidationError("segment must be one of: user, agent, tool, guideline");
1603
+ }
1604
+ return {
1605
+ content,
1606
+ thought,
1607
+ system,
1608
+ type,
1609
+ segment,
1610
+ ...scope ? { scope } : {},
1611
+ ...subjectId ? { subject_id: subjectId } : {},
1612
+ ...metadata ? { metadata } : {}
1613
+ };
1614
+ }
1615
+ normalizeOptionalString(value) {
1616
+ if (typeof value !== "string") {
1617
+ return void 0;
1618
+ }
1619
+ const normalized = value.trim();
1620
+ return normalized.length > 0 ? normalized : void 0;
1621
+ }
1622
+ normalizeRequiredString(value, fieldName) {
1623
+ const normalized = this.normalizeOptionalString(value);
1624
+ if (!normalized) {
1625
+ throw new ValidationError(`${fieldName} is required`);
1626
+ }
1627
+ return normalized;
1628
+ }
1629
+ normalizeOptionalNumber(value) {
1630
+ if (typeof value === "number" && Number.isFinite(value)) {
1631
+ return value;
1632
+ }
1633
+ if (typeof value === "string") {
1634
+ const parsed = Number(value);
1635
+ if (Number.isFinite(parsed)) {
1636
+ return parsed;
1637
+ }
1638
+ }
1639
+ return void 0;
1640
+ }
1641
+ normalizeOptionalMetadata(value) {
1642
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1643
+ return void 0;
1644
+ }
1645
+ return value;
1646
+ }
1647
+ normalizeMentionInput(value) {
1648
+ if (!Array.isArray(value) || value.length === 0) {
1649
+ return [];
1650
+ }
1651
+ if (typeof value[0] === "string") {
1652
+ return value.filter((entry) => typeof entry === "string");
1653
+ }
1654
+ const mentions = [];
1655
+ for (const entry of value) {
1656
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
1657
+ continue;
1658
+ }
1659
+ const mention = entry;
1660
+ if (typeof mention.id !== "string") {
1661
+ continue;
1662
+ }
1663
+ const normalized = { id: mention.id };
1664
+ if (typeof mention.handle === "string") {
1665
+ normalized.handle = mention.handle;
1666
+ }
1667
+ if (typeof mention.name === "string") {
1668
+ normalized.name = mention.name;
1669
+ }
1670
+ if (typeof mention.username === "string") {
1671
+ normalized.username = mention.username;
1672
+ }
1673
+ mentions.push(normalized);
1674
+ }
1675
+ return mentions;
1676
+ }
1677
+ normalizeOptionalMemoryScope(value) {
1678
+ const normalized = this.normalizeOptionalString(value);
1679
+ if (!normalized) {
1680
+ return void 0;
1681
+ }
1682
+ if (!isListMemoryScope(normalized)) {
1683
+ throw new ValidationError("scope must be one of: subject, organization, all");
1684
+ }
1685
+ return normalized;
1686
+ }
1687
+ normalizeOptionalMemorySystem(value) {
1688
+ const normalized = this.normalizeOptionalString(value);
1689
+ if (!normalized) {
1690
+ return void 0;
1691
+ }
1692
+ if (!isMemorySystem(normalized)) {
1693
+ throw new ValidationError("system must be one of: sensory, working, long_term");
1694
+ }
1695
+ return normalized;
1696
+ }
1697
+ normalizeOptionalMemoryType(value) {
1698
+ const normalized = this.normalizeOptionalString(value);
1699
+ if (!normalized) {
1700
+ return void 0;
1701
+ }
1702
+ if (!isMemoryType(normalized)) {
1703
+ throw new ValidationError("type must be one of: iconic, echoic, haptic, episodic, semantic, procedural");
1704
+ }
1705
+ return normalized;
1706
+ }
1707
+ normalizeOptionalMemorySegment(value) {
1708
+ const normalized = this.normalizeOptionalString(value);
1709
+ if (!normalized) {
1710
+ return void 0;
1711
+ }
1712
+ if (!isMemorySegment(normalized)) {
1713
+ throw new ValidationError("segment must be one of: user, agent, tool, guideline");
1714
+ }
1715
+ return normalized;
1716
+ }
1717
+ normalizeOptionalMemoryStatus(value) {
1718
+ const normalized = this.normalizeOptionalString(value);
1719
+ if (!normalized) {
1720
+ return void 0;
1721
+ }
1722
+ if (!isMemoryStatus(normalized)) {
1723
+ throw new ValidationError("status must be one of: active, superseded, archived, all");
1724
+ }
1725
+ return normalized;
1726
+ }
1727
+ normalizeOptionalStoreMemoryScope(value) {
1728
+ const normalized = this.normalizeOptionalString(value);
1729
+ if (!normalized) {
1730
+ return void 0;
1731
+ }
1732
+ if (!isStoreMemoryScope(normalized)) {
1733
+ throw new ValidationError("scope must be one of: subject, organization");
1734
+ }
1735
+ return normalized;
1736
+ }
1737
+ };
1738
+ function isContactRequestAction(value) {
1739
+ return CONTACT_REQUEST_ACTIONS.has(value);
1740
+ }
1741
+ function isMemorySystem(value) {
1742
+ return MEMORY_SYSTEMS.has(value);
1743
+ }
1744
+ function isMemoryType(value) {
1745
+ return MEMORY_TYPES.has(value);
1746
+ }
1747
+ function isMemorySegment(value) {
1748
+ return MEMORY_SEGMENTS.has(value);
1749
+ }
1750
+ function isStoreMemoryScope(value) {
1751
+ return MEMORY_SCOPES.has(value);
1752
+ }
1753
+ function isListMemoryScope(value) {
1754
+ return LIST_MEMORY_SCOPES.has(value);
1755
+ }
1756
+ function isMemoryStatus(value) {
1757
+ return LIST_MEMORY_STATUSES.has(value);
1758
+ }
1759
+ function coercePositiveInt(value, fallback) {
1760
+ const num = Number(value ?? fallback);
1761
+ return Number.isFinite(num) && num >= 1 ? Math.floor(num) : fallback;
1762
+ }
1763
+ function validateToolArgs(toolName, args) {
1764
+ const errors = [];
1765
+ const model = TOOL_MODELS[toolName];
1766
+ if (!model) {
1767
+ return null;
1768
+ }
1769
+ for (const field of model.required) {
1770
+ if (args[field] === void 0 || args[field] === null) {
1771
+ errors.push(`${field}: Field required`);
1772
+ }
1773
+ }
1774
+ if (toolName === "thenvoi_send_message") {
1775
+ const mentions = args.mentions;
1776
+ if (Array.isArray(mentions) && mentions.length === 0) {
1777
+ errors.push("mentions: At least one mention is required");
1778
+ }
1779
+ }
1780
+ if (toolName === "thenvoi_send_event") {
1781
+ const messageType = args.message_type;
1782
+ if (typeof messageType === "string" && !CHAT_EVENT_TYPES.includes(messageType)) {
1783
+ errors.push(
1784
+ `message_type: Invalid value '${messageType}'. Expected one of: ${[...CHAT_EVENT_TYPES].join(", ")}`
1785
+ );
1786
+ }
1787
+ }
1788
+ if (toolName === "thenvoi_respond_contact_request") {
1789
+ const action = args.action;
1790
+ const validActions = ["approve", "reject", "cancel"];
1791
+ if (typeof action === "string" && !validActions.includes(action)) {
1792
+ errors.push(
1793
+ `action: Invalid value '${action}'. Expected one of: ${validActions.join(", ")}`
1794
+ );
1795
+ }
1796
+ }
1797
+ if (toolName === "thenvoi_store_memory") {
1798
+ const validSystems = ["sensory", "working", "long_term"];
1799
+ const validTypes = ["iconic", "echoic", "haptic", "episodic", "semantic", "procedural"];
1800
+ const validSegments = ["user", "agent", "tool", "guideline"];
1801
+ if (typeof args.system === "string" && !validSystems.includes(args.system)) {
1802
+ errors.push(`system: Invalid value '${args.system}'. Expected one of: ${validSystems.join(", ")}`);
1803
+ }
1804
+ if (typeof args.type === "string" && !validTypes.includes(args.type)) {
1805
+ errors.push(`type: Invalid value '${args.type}'. Expected one of: ${validTypes.join(", ")}`);
1806
+ }
1807
+ if (typeof args.segment === "string" && !validSegments.includes(args.segment)) {
1808
+ errors.push(`segment: Invalid value '${args.segment}'. Expected one of: ${validSegments.join(", ")}`);
1809
+ }
1810
+ }
1811
+ if (errors.length > 0) {
1812
+ const message = `Invalid arguments for ${toolName}: ${errors.join("; ")}`;
1813
+ return createToolExecutorError({
1814
+ errorType: "ToolArgumentsValidationError",
1815
+ toolName,
1816
+ message,
1817
+ legacyMessage: message,
1818
+ details: { validationErrors: errors }
1819
+ });
1820
+ }
1821
+ return null;
1822
+ }
1823
+
1824
+ // src/runtime/retryTracker.ts
1825
+ var DEFAULT_MAX_TRACKED = 1e4;
1826
+ var MessageRetryTracker = class {
1827
+ maxRetriesValue;
1828
+ maxTracked;
1829
+ attempts = /* @__PURE__ */ new Map();
1830
+ failed = /* @__PURE__ */ new Set();
1831
+ constructor(maxRetries = 1, maxTracked = DEFAULT_MAX_TRACKED) {
1832
+ this.maxRetriesValue = maxRetries;
1833
+ this.maxTracked = maxTracked;
1834
+ }
1835
+ get maxRetries() {
1836
+ return this.maxRetriesValue;
1837
+ }
1838
+ isPermanentlyFailed(messageId) {
1839
+ return this.failed.has(messageId);
1840
+ }
1841
+ recordAttempt(messageId) {
1842
+ const attempts = (this.attempts.get(messageId) ?? 0) + 1;
1843
+ this.attempts.set(messageId, attempts);
1844
+ const exceeded = attempts > this.maxRetriesValue;
1845
+ if (exceeded) {
1846
+ this.evictSetIfNeeded(this.failed);
1847
+ this.failed.add(messageId);
1848
+ }
1849
+ this.evictMapIfNeeded(this.attempts);
1850
+ return [attempts, exceeded];
1851
+ }
1852
+ markSuccess(messageId) {
1853
+ this.attempts.delete(messageId);
1854
+ }
1855
+ markPermanentlyFailed(messageId) {
1856
+ this.evictSetIfNeeded(this.failed);
1857
+ this.failed.add(messageId);
1858
+ }
1859
+ evictMapIfNeeded(map) {
1860
+ while (map.size >= this.maxTracked) {
1861
+ const first = map.keys().next();
1862
+ if (first.done) break;
1863
+ map.delete(first.value);
1864
+ }
1865
+ }
1866
+ evictSetIfNeeded(set) {
1867
+ while (set.size >= this.maxTracked) {
1868
+ const first = set.values().next();
1869
+ if (first.done) break;
1870
+ set.delete(first.value);
1871
+ }
1872
+ }
1873
+ };
1874
+
1875
+ // src/runtime/ExecutionContext.ts
1876
+ var DEDUP_CACHE_MAX = 500;
1877
+ var ExecutionContext = class {
1878
+ roomId;
1879
+ link;
1880
+ maxContextMessages;
1881
+ enableContextCache;
1882
+ contextCacheTtlMs;
1883
+ enableContextHydration;
1884
+ history = [];
1885
+ messageIds = /* @__PURE__ */ new Set();
1886
+ dedupCache = /* @__PURE__ */ new Map();
1887
+ participants = [];
1888
+ tools;
1889
+ adapterTools;
1890
+ participantsMessage = null;
1891
+ lastSentParticipantIds = null;
1892
+ contactsMessage = null;
1893
+ contextCache = null;
1894
+ contextCacheExpiresAt = 0;
1895
+ _state = "starting";
1896
+ retryTrackerInstance;
1897
+ _llmInitialized = false;
1898
+ _pendingSystemMessages = [];
1899
+ constructor(options) {
1900
+ this.roomId = options.roomId;
1901
+ this.link = options.link;
1902
+ this.maxContextMessages = options.maxContextMessages;
1903
+ this.enableContextCache = options.enableContextCache ?? true;
1904
+ this.contextCacheTtlMs = Math.max(0, (options.contextCacheTtlSeconds ?? 300) * 1e3);
1905
+ this.enableContextHydration = options.enableContextHydration ?? true;
1906
+ this.retryTrackerInstance = new MessageRetryTracker(options.maxMessageRetries ?? 1);
1907
+ this.tools = new AgentTools({
1908
+ roomId: this.roomId,
1909
+ rest: this.link.rest,
1910
+ participants: this.participants,
1911
+ capabilities: this.link.capabilities
1912
+ });
1913
+ this.adapterTools = this.tools.getAdapterTools();
1914
+ }
1915
+ get state() {
1916
+ return this._state;
1917
+ }
1918
+ setState(state) {
1919
+ this._state = state;
1920
+ }
1921
+ getRetryTracker() {
1922
+ return this.retryTrackerInstance;
1923
+ }
1924
+ get isLlmInitialized() {
1925
+ return this._llmInitialized;
1926
+ }
1927
+ markLlmInitialized() {
1928
+ this._llmInitialized = true;
1929
+ }
1930
+ injectSystemMessage(message) {
1931
+ this._pendingSystemMessages.push(message);
1932
+ }
1933
+ consumeSystemMessages() {
1934
+ const copy = [...this._pendingSystemMessages];
1935
+ this._pendingSystemMessages.length = 0;
1936
+ return copy;
1937
+ }
1938
+ getTools() {
1939
+ return this.adapterTools;
1940
+ }
1941
+ hasMessage(messageId) {
1942
+ return this.messageIds.has(messageId) || this.dedupCache.has(messageId);
1943
+ }
1944
+ recordMessage(message) {
1945
+ if (this.hasMessage(message.id)) {
1946
+ return;
1947
+ }
1948
+ this.trackDedup(message.id);
1949
+ this.history.push(message);
1950
+ if (this.history.length > this.maxContextMessages) {
1951
+ this.history.splice(0, this.history.length - this.maxContextMessages);
1952
+ }
1953
+ this.rebuildMessageIds();
1954
+ if (this.contextCache) {
1955
+ this.contextCache.messages.push(this.toHistoryEntry(message));
1956
+ if (this.contextCache.messages.length > this.maxContextMessages) {
1957
+ this.contextCache.messages.splice(0, this.contextCache.messages.length - this.maxContextMessages);
1958
+ }
1959
+ this.contextCache.hydratedAt = /* @__PURE__ */ new Date();
1960
+ this.contextCacheExpiresAt = this.nextCacheExpiry();
1961
+ }
1962
+ }
1963
+ getRawHistory() {
1964
+ return this.history.map((entry) => ({
1965
+ id: entry.id,
1966
+ room_id: entry.roomId,
1967
+ content: entry.content,
1968
+ sender_id: entry.senderId,
1969
+ sender_type: entry.senderType,
1970
+ sender_name: entry.senderName,
1971
+ message_type: entry.messageType,
1972
+ metadata: entry.metadata,
1973
+ created_at: entry.createdAt.toISOString(),
1974
+ role: entry.senderType === "User" ? "user" : "assistant"
1975
+ }));
1976
+ }
1977
+ setParticipants(participants) {
1978
+ this.replaceParticipants(participants);
1979
+ this.updateCachedParticipants();
1980
+ }
1981
+ addParticipant(participant) {
1982
+ const existingIndex = this.participants.findIndex((entry) => entry.id === participant.id);
1983
+ if (existingIndex >= 0) {
1984
+ this.participants.splice(existingIndex, 1);
1985
+ }
1986
+ this.participants.push(participant);
1987
+ const name = String(participant.name ?? "unknown");
1988
+ this.participantsMessage = `${name} joined the room.`;
1989
+ this.updateCachedParticipants();
1990
+ }
1991
+ removeParticipant(participantId) {
1992
+ const removed = this.participants.find((entry) => String(entry.id) === participantId);
1993
+ const next = this.participants.filter((entry) => String(entry.id) !== participantId);
1994
+ this.replaceParticipants(next);
1995
+ if (removed) {
1996
+ this.participantsMessage = `${String(removed.name ?? participantId)} left the room.`;
1997
+ }
1998
+ this.updateCachedParticipants();
1999
+ }
2000
+ setContactsMessage(message) {
2001
+ this.contactsMessage = message;
2002
+ }
2003
+ consumeParticipantsMessage() {
2004
+ const currentIds = new Set(this.participants.map((p) => String(p.id)));
2005
+ let changed = !this.lastSentParticipantIds || currentIds.size !== this.lastSentParticipantIds.size;
2006
+ if (!changed) {
2007
+ for (const id of currentIds) {
2008
+ if (!this.lastSentParticipantIds.has(id)) {
2009
+ changed = true;
2010
+ break;
2011
+ }
2012
+ }
2013
+ }
2014
+ if (!changed && !this.participantsMessage) {
2015
+ return null;
2016
+ }
2017
+ const parts = [];
2018
+ if (this.participantsMessage) {
2019
+ parts.push(this.participantsMessage);
2020
+ this.participantsMessage = null;
2021
+ }
2022
+ if (changed) {
2023
+ const asRecords = this.participants.map((p) => ({
2024
+ id: p.id,
2025
+ name: p.name,
2026
+ type: p.type,
2027
+ handle: p.handle ?? null
2028
+ }));
2029
+ parts.push(buildParticipantsMessage(asRecords));
2030
+ }
2031
+ this.lastSentParticipantIds = currentIds;
2032
+ return parts.length > 0 ? parts.join("\n") : null;
2033
+ }
2034
+ consumeContactsMessage() {
2035
+ const value = this.contactsMessage;
2036
+ this.contactsMessage = null;
2037
+ return value;
2038
+ }
2039
+ async getHydratedHistory(excludeMessageId) {
2040
+ if (!this.enableContextHydration || !this.link.rest.getChatContext) {
2041
+ return this.getRawHistory().filter((entry) => entry.id !== excludeMessageId);
2042
+ }
2043
+ try {
2044
+ const context = await this.hydrateContext();
2045
+ return context.messages.filter((entry) => entry.id !== excludeMessageId);
2046
+ } catch (error) {
2047
+ if (error instanceof UnsupportedFeatureError) {
2048
+ return this.getRawHistory().filter((entry) => entry.id !== excludeMessageId);
2049
+ }
2050
+ throw error;
2051
+ }
2052
+ }
2053
+ async hydrateContext(forceRefresh = false) {
2054
+ if (!this.enableContextHydration || !this.link.rest.getChatContext) {
2055
+ return this.buildLocalContext();
2056
+ }
2057
+ if (!forceRefresh && this.enableContextCache && this.contextCache && !this.isCacheExpired()) {
2058
+ return this.contextCache;
2059
+ }
2060
+ try {
2061
+ const participants = await this.loadParticipants();
2062
+ const messages = await this.loadHydratedMessages();
2063
+ const context = {
2064
+ roomId: this.roomId,
2065
+ messages,
2066
+ participants,
2067
+ hydratedAt: /* @__PURE__ */ new Date()
2068
+ };
2069
+ this.contextCache = context;
2070
+ this.contextCacheExpiresAt = this.nextCacheExpiry();
2071
+ return context;
2072
+ } catch (error) {
2073
+ if (!(error instanceof UnsupportedFeatureError)) {
2074
+ throw error;
2075
+ }
2076
+ const fallback = this.buildLocalContext();
2077
+ this.contextCache = fallback;
2078
+ this.contextCacheExpiresAt = this.nextCacheExpiry();
2079
+ return fallback;
2080
+ }
2081
+ }
2082
+ replaceParticipants(participants) {
2083
+ this.participants.splice(0, this.participants.length, ...participants);
2084
+ }
2085
+ buildLocalContext() {
2086
+ return {
2087
+ roomId: this.roomId,
2088
+ messages: this.getRawHistory(),
2089
+ participants: [...this.participants],
2090
+ hydratedAt: /* @__PURE__ */ new Date()
2091
+ };
2092
+ }
2093
+ async loadParticipants() {
2094
+ const participants = await this.link.rest.listChatParticipants(this.roomId, DEFAULT_REQUEST_OPTIONS);
2095
+ const normalized = participants.map((participant) => ({
2096
+ id: participant.id,
2097
+ name: participant.name,
2098
+ type: participant.type,
2099
+ handle: participant.handle ?? null
2100
+ }));
2101
+ this.replaceParticipants(normalized);
2102
+ return [...this.participants];
2103
+ }
2104
+ async loadHydratedMessages() {
2105
+ const messages = [];
2106
+ const pageSize = Math.min(Math.max(this.maxContextMessages, 1), 100);
2107
+ const maxPages = 100;
2108
+ for (let page = 1; page <= maxPages; page += 1) {
2109
+ const response = await this.link.rest.getChatContext?.(
2110
+ {
2111
+ chatId: this.roomId,
2112
+ page,
2113
+ pageSize
2114
+ },
2115
+ DEFAULT_REQUEST_OPTIONS
2116
+ );
2117
+ const items = response?.data ?? [];
2118
+ messages.push(...items.map((item) => ({
2119
+ id: item.id,
2120
+ room_id: this.roomId,
2121
+ content: item.content,
2122
+ sender_id: item.sender_id,
2123
+ sender_type: item.sender_type,
2124
+ sender_name: item.sender_name ?? null,
2125
+ message_type: item.message_type,
2126
+ metadata: item.metadata ?? {},
2127
+ created_at: item.inserted_at,
2128
+ role: item.sender_type === "User" ? "user" : "assistant"
2129
+ })));
2130
+ const totalPages = response?.metadata?.totalPages;
2131
+ if (typeof totalPages === "number" && totalPages > 0 && page >= totalPages) {
2132
+ break;
2133
+ }
2134
+ if ((typeof totalPages !== "number" || totalPages <= 0) && items.length < pageSize) {
2135
+ break;
2136
+ }
2137
+ }
2138
+ return messages.slice(-this.maxContextMessages);
2139
+ }
2140
+ updateCachedParticipants() {
2141
+ if (!this.contextCache) {
2142
+ return;
2143
+ }
2144
+ this.contextCache.participants = [...this.participants];
2145
+ this.contextCache.hydratedAt = /* @__PURE__ */ new Date();
2146
+ this.contextCacheExpiresAt = this.nextCacheExpiry();
2147
+ }
2148
+ isCacheExpired() {
2149
+ return this.contextCacheExpiresAt > 0 && Date.now() >= this.contextCacheExpiresAt;
2150
+ }
2151
+ nextCacheExpiry() {
2152
+ if (!this.enableContextCache || this.contextCacheTtlMs === 0) {
2153
+ return 0;
2154
+ }
2155
+ return Date.now() + this.contextCacheTtlMs;
2156
+ }
2157
+ toHistoryEntry(message) {
2158
+ return {
2159
+ id: message.id,
2160
+ room_id: message.roomId,
2161
+ content: message.content,
2162
+ sender_id: message.senderId,
2163
+ sender_type: message.senderType,
2164
+ sender_name: message.senderName,
2165
+ message_type: message.messageType,
2166
+ metadata: message.metadata,
2167
+ created_at: message.createdAt.toISOString(),
2168
+ role: message.senderType === "User" ? "user" : "assistant"
2169
+ };
2170
+ }
2171
+ rebuildMessageIds() {
2172
+ this.messageIds = new Set(this.history.map((entry) => entry.id));
2173
+ }
2174
+ trackDedup(messageId) {
2175
+ this.dedupCache.set(messageId, true);
2176
+ if (this.dedupCache.size > DEDUP_CACHE_MAX) {
2177
+ const first = this.dedupCache.keys().next();
2178
+ if (!first.done) {
2179
+ this.dedupCache.delete(first.value);
2180
+ }
2181
+ }
2182
+ }
2183
+ };
2184
+
2185
+ // src/runtime/rooms/subscriptions.ts
2186
+ function hasRoomId(roomId) {
2187
+ return typeof roomId === "string" && roomId.length > 0;
2188
+ }
2189
+ async function trackRoomJoin(options) {
2190
+ if (!hasRoomId(options.roomId)) {
2191
+ return false;
2192
+ }
2193
+ if (options.roomFilter && !options.roomFilter(options.payload)) {
2194
+ return false;
2195
+ }
2196
+ if (options.trackedRooms.has(options.roomId)) {
2197
+ return false;
2198
+ }
2199
+ await options.link.subscribeRoom(options.roomId);
2200
+ options.trackedRooms.add(options.roomId);
2201
+ if (options.onJoined) {
2202
+ await options.onJoined(options.roomId, options.payload);
2203
+ }
2204
+ return true;
2205
+ }
2206
+ async function trackRoomLeave(options) {
2207
+ if (!hasRoomId(options.roomId) || !options.trackedRooms.has(options.roomId)) {
2208
+ return false;
2209
+ }
2210
+ await options.link.unsubscribeRoom(options.roomId);
2211
+ options.trackedRooms.delete(options.roomId);
2212
+ if (options.onLeft) {
2213
+ await options.onLeft(options.roomId);
2214
+ }
2215
+ return true;
2216
+ }
2217
+ async function hydrateTrackedRooms(options) {
2218
+ const pageSize = options.pageSize ?? 100;
2219
+ const maxPages = options.maxPages ?? 100;
2220
+ try {
2221
+ const rooms = await options.link.listAllChats(
2222
+ { pageSize, maxPages },
2223
+ options.requestOptions
2224
+ );
2225
+ for (const room of rooms) {
2226
+ await trackRoomJoin({
2227
+ link: options.link,
2228
+ roomId: typeof room.id === "string" ? room.id : null,
2229
+ payload: room,
2230
+ trackedRooms: options.trackedRooms,
2231
+ roomFilter: options.roomFilter,
2232
+ onJoined: options.onJoined
2233
+ });
2234
+ }
2235
+ } catch (error) {
2236
+ if (error instanceof UnsupportedFeatureError) {
2237
+ return;
2238
+ }
2239
+ if (options.onError) {
2240
+ await options.onError(error);
2241
+ return;
2242
+ }
2243
+ throw error;
2244
+ }
2245
+ }
2246
+
2247
+ // src/runtime/rooms/AgentRuntime.ts
2248
+ var AgentRuntime = class {
2249
+ link;
2250
+ agentId;
2251
+ onExecute;
2252
+ onSessionCleanup;
2253
+ onRoomJoined;
2254
+ onRoomLeft;
2255
+ onContactEvent;
2256
+ onParticipantAdded;
2257
+ onParticipantRemoved;
2258
+ onError;
2259
+ roomFilter;
2260
+ contextFactory;
2261
+ sessionConfig;
2262
+ autoSubscribeExistingRooms;
2263
+ subscribedRooms = /* @__PURE__ */ new Set();
2264
+ contexts = /* @__PURE__ */ new Map();
2265
+ executions = /* @__PURE__ */ new Map();
2266
+ executionWatchers = /* @__PURE__ */ new Map();
2267
+ logger;
2268
+ running = false;
2269
+ stopping = false;
2270
+ stopController = new AbortController();
2271
+ consumeTask = null;
2272
+ fatalError = null;
2273
+ constructor(options) {
2274
+ this.link = options.link;
2275
+ this.agentId = options.agentId;
2276
+ this.onExecute = options.onExecute;
2277
+ this.onSessionCleanup = options.onSessionCleanup ?? (async () => void 0);
2278
+ this.onRoomJoined = options.onRoomJoined;
2279
+ this.onRoomLeft = options.onRoomLeft;
2280
+ this.onError = options.onError;
2281
+ this.logger = options.logger ?? new NoopLogger();
2282
+ this.onContactEvent = options.onContactEvent;
2283
+ this.onParticipantAdded = options.onParticipantAdded;
2284
+ this.onParticipantRemoved = options.onParticipantRemoved;
2285
+ this.roomFilter = options.roomFilter;
2286
+ this.contextFactory = options.contextFactory;
2287
+ this.sessionConfig = {
2288
+ enableContextCache: options.sessionConfig?.enableContextCache ?? true,
2289
+ contextCacheTtlSeconds: options.sessionConfig?.contextCacheTtlSeconds ?? 300,
2290
+ maxContextMessages: options.sessionConfig?.maxContextMessages ?? 100,
2291
+ maxMessageRetries: options.sessionConfig?.maxMessageRetries ?? 1,
2292
+ enableContextHydration: options.sessionConfig?.enableContextHydration ?? true
2293
+ };
2294
+ this.autoSubscribeExistingRooms = options.agentConfig?.autoSubscribeExistingRooms ?? false;
2295
+ }
2296
+ async start() {
2297
+ if (this.running) {
2298
+ return;
2299
+ }
2300
+ this.running = true;
2301
+ this.stopping = false;
2302
+ this.fatalError = null;
2303
+ if (!this.stopController.signal.aborted) {
2304
+ this.stopController.abort();
2305
+ }
2306
+ this.stopController = new AbortController();
2307
+ try {
2308
+ await this.link.connect();
2309
+ } catch (error) {
2310
+ await this.handleStartFailure();
2311
+ throw error;
2312
+ }
2313
+ try {
2314
+ await this.link.subscribeAgentRooms();
2315
+ } catch {
2316
+ this.logger.warn("AgentRuntime failed to subscribe agent_rooms channel, continuing without it");
2317
+ }
2318
+ try {
2319
+ await this.subscribeExistingRooms();
2320
+ } catch (error) {
2321
+ await this.handleStartFailure();
2322
+ throw error;
2323
+ }
2324
+ this.consumeTask = this.consumeLoop(this.stopController.signal);
2325
+ if (!this.link.capabilities.contacts) {
2326
+ return;
2327
+ }
2328
+ try {
2329
+ await this.link.subscribeAgentContacts();
2330
+ } catch {
2331
+ this.logger.warn("AgentRuntime failed to subscribe agent_contacts channel, continuing without it");
2332
+ }
2333
+ }
2334
+ async handleStartFailure() {
2335
+ this.running = false;
2336
+ this.stopping = false;
2337
+ this.stopController.abort();
2338
+ if (this.consumeTask) {
2339
+ await this.consumeTask;
2340
+ this.consumeTask = null;
2341
+ }
2342
+ await this.link.disconnect();
2343
+ }
2344
+ async stop(timeoutMs) {
2345
+ if (!this.running || this.stopping) {
2346
+ return true;
2347
+ }
2348
+ this.stopping = true;
2349
+ this.running = false;
2350
+ this.stopController.abort();
2351
+ if (this.consumeTask) {
2352
+ await this.consumeTask;
2353
+ this.consumeTask = null;
2354
+ }
2355
+ const deadline = timeoutMs === void 0 ? void 0 : Date.now() + timeoutMs;
2356
+ let graceful = true;
2357
+ for (const execution of this.executions.values()) {
2358
+ const remaining = deadline === void 0 ? void 0 : Math.max(0, deadline - Date.now());
2359
+ const stopped = await execution.stop(remaining);
2360
+ if (!stopped) {
2361
+ graceful = false;
2362
+ }
2363
+ }
2364
+ for (const roomId of [...this.subscribedRooms]) {
2365
+ await this.leaveTrackedRoom(roomId);
2366
+ }
2367
+ for (const roomId of [...this.contexts.keys()]) {
2368
+ await this.onSessionCleanup(roomId);
2369
+ }
2370
+ this.subscribedRooms.clear();
2371
+ this.contexts.clear();
2372
+ this.executions.clear();
2373
+ this.executionWatchers.clear();
2374
+ if (this.link.capabilities.contacts) {
2375
+ await this.link.unsubscribeAgentContacts();
2376
+ }
2377
+ await this.link.disconnect();
2378
+ if (this.fatalError) {
2379
+ throw this.fatalError instanceof Error ? this.fatalError : new Error(String(this.fatalError));
2380
+ }
2381
+ return graceful;
2382
+ }
2383
+ getContext(roomId) {
2384
+ return this.contexts.get(roomId);
2385
+ }
2386
+ async waitUntilStopped() {
2387
+ if (this.consumeTask) {
2388
+ await this.consumeTask;
2389
+ } else {
2390
+ await this.link.runForever(this.stopController.signal);
2391
+ }
2392
+ if (this.fatalError) {
2393
+ throw this.fatalError instanceof Error ? this.fatalError : new Error(String(this.fatalError));
2394
+ }
2395
+ }
2396
+ getContexts() {
2397
+ return [...this.contexts.values()];
2398
+ }
2399
+ async consumeLoop(signal) {
2400
+ while (!signal.aborted) {
2401
+ const event = await this.link.nextEvent(signal);
2402
+ if (!event) {
2403
+ return;
2404
+ }
2405
+ try {
2406
+ await this.handleEvent(event);
2407
+ } catch (error) {
2408
+ await this.failRuntime(error, event);
2409
+ return;
2410
+ }
2411
+ }
2412
+ }
2413
+ async handleEvent(event) {
2414
+ switch (event.type) {
2415
+ case "room_added":
2416
+ await trackRoomJoin({
2417
+ link: this.link,
2418
+ roomId: event.roomId,
2419
+ payload: event.payload,
2420
+ trackedRooms: this.subscribedRooms,
2421
+ roomFilter: this.roomFilter,
2422
+ onJoined: async (roomId) => {
2423
+ this.getOrCreateExecution(roomId);
2424
+ await this.onRoomJoined?.(roomId, event.payload);
2425
+ }
2426
+ });
2427
+ return;
2428
+ case "room_removed":
2429
+ case "room_deleted":
2430
+ if (event.roomId) {
2431
+ await this.onRoomLeft?.(event.roomId);
2432
+ await this.leaveTrackedRoom(event.roomId);
2433
+ }
2434
+ return;
2435
+ case "participant_added":
2436
+ if (event.roomId) {
2437
+ const context = this.getOrCreateContext(event.roomId);
2438
+ const participant = {
2439
+ id: event.payload.id,
2440
+ name: event.payload.name,
2441
+ type: event.payload.type,
2442
+ handle: event.payload.handle
2443
+ };
2444
+ context.addParticipant(participant);
2445
+ await this.onParticipantAdded?.(event.roomId, participant);
2446
+ }
2447
+ return;
2448
+ case "participant_removed":
2449
+ if (event.roomId) {
2450
+ const context = this.getOrCreateContext(event.roomId);
2451
+ context.removeParticipant(event.payload.id);
2452
+ await this.onParticipantRemoved?.(event.roomId, event.payload.id);
2453
+ }
2454
+ return;
2455
+ case "contact_request_received":
2456
+ case "contact_request_updated":
2457
+ case "contact_added":
2458
+ case "contact_removed":
2459
+ await this.onContactEvent?.(event);
2460
+ return;
2461
+ case "message_created":
2462
+ if (!event.roomId) {
2463
+ return;
2464
+ }
2465
+ await this.getOrCreateExecution(event.roomId).enqueue(event);
2466
+ return;
2467
+ }
2468
+ return assertNever(event);
2469
+ }
2470
+ async enqueueEvent(roomId, event) {
2471
+ await this.getOrCreateExecution(roomId).enqueue(event);
2472
+ }
2473
+ async bootstrapRoomMessage(roomId, message) {
2474
+ await this.link.subscribeRoom(roomId);
2475
+ this.subscribedRooms.add(roomId);
2476
+ await this.getOrCreateExecution(roomId).bootstrapMessage(message);
2477
+ }
2478
+ async resetRoomSession(roomId, timeoutMs) {
2479
+ const execution = this.executions.get(roomId);
2480
+ let graceful = true;
2481
+ if (execution) {
2482
+ graceful = await execution.stop(timeoutMs);
2483
+ }
2484
+ this.executions.delete(roomId);
2485
+ this.contexts.delete(roomId);
2486
+ await this.onSessionCleanup(roomId);
2487
+ return graceful;
2488
+ }
2489
+ getOrCreateExecution(roomId) {
2490
+ const existing = this.executions.get(roomId);
2491
+ if (existing) {
2492
+ return existing;
2493
+ }
2494
+ const execution = new Execution({
2495
+ roomId,
2496
+ link: this.link,
2497
+ context: this.getOrCreateContext(roomId),
2498
+ onExecute: this.onExecute,
2499
+ onFailure: async (error, event) => {
2500
+ await this.failRuntime(error, event);
2501
+ },
2502
+ logger: this.logger
2503
+ });
2504
+ this.executions.set(roomId, execution);
2505
+ const watcher = execution.waitUntilStopped().catch(async (error) => {
2506
+ await this.failRuntime(error, {
2507
+ type: "message_created",
2508
+ roomId,
2509
+ payload: {
2510
+ id: "execution-failed",
2511
+ content: "",
2512
+ sender_id: this.agentId,
2513
+ sender_type: "Agent",
2514
+ sender_name: null,
2515
+ message_type: "text",
2516
+ metadata: {},
2517
+ inserted_at: (/* @__PURE__ */ new Date(0)).toISOString(),
2518
+ updated_at: (/* @__PURE__ */ new Date(0)).toISOString()
2519
+ }
2520
+ });
2521
+ }).finally(() => {
2522
+ this.executionWatchers.delete(roomId);
2523
+ });
2524
+ this.executionWatchers.set(roomId, watcher);
2525
+ return execution;
2526
+ }
2527
+ getOrCreateContext(roomId) {
2528
+ const existing = this.contexts.get(roomId);
2529
+ if (existing) {
2530
+ return existing;
2531
+ }
2532
+ const defaults = {
2533
+ roomId,
2534
+ link: this.link,
2535
+ maxContextMessages: this.sessionConfig.maxContextMessages,
2536
+ maxMessageRetries: this.sessionConfig.maxMessageRetries,
2537
+ enableContextCache: this.sessionConfig.enableContextCache,
2538
+ contextCacheTtlSeconds: this.sessionConfig.contextCacheTtlSeconds,
2539
+ enableContextHydration: this.sessionConfig.enableContextHydration
2540
+ };
2541
+ const context = this.contextFactory ? this.contextFactory(roomId, defaults) : new ExecutionContext(defaults);
2542
+ this.contexts.set(roomId, context);
2543
+ return context;
2544
+ }
2545
+ async subscribeExistingRooms() {
2546
+ if (!this.autoSubscribeExistingRooms) {
2547
+ return;
2548
+ }
2549
+ await hydrateTrackedRooms({
2550
+ link: this.link,
2551
+ trackedRooms: this.subscribedRooms,
2552
+ roomFilter: this.roomFilter,
2553
+ onJoined: async (roomId, payload) => {
2554
+ this.getOrCreateExecution(roomId);
2555
+ await this.onRoomJoined?.(roomId, payload);
2556
+ },
2557
+ onError: async (error) => {
2558
+ this.logger.warn("AgentRuntime failed to subscribe existing rooms", {
2559
+ error
2560
+ });
2561
+ }
2562
+ });
2563
+ }
2564
+ async leaveTrackedRoom(roomId) {
2565
+ await trackRoomLeave({
2566
+ link: this.link,
2567
+ roomId,
2568
+ trackedRooms: this.subscribedRooms,
2569
+ onLeft: async (leftRoomId) => {
2570
+ await this.executions.get(leftRoomId)?.stop();
2571
+ this.contexts.delete(leftRoomId);
2572
+ this.executions.delete(leftRoomId);
2573
+ await this.onSessionCleanup(leftRoomId);
2574
+ }
2575
+ });
2576
+ }
2577
+ async failRuntime(error, event) {
2578
+ if (!this.fatalError) {
2579
+ this.fatalError = error;
2580
+ this.running = false;
2581
+ this.logger.error("Fatal runtime error handling platform event", {
2582
+ eventType: event.type,
2583
+ roomId: event.roomId,
2584
+ error
2585
+ });
2586
+ this.notifyOnError(error, event);
2587
+ }
2588
+ if (!this.stopController.signal.aborted) {
2589
+ this.stopController.abort();
2590
+ }
2591
+ }
2592
+ notifyOnError(error, event) {
2593
+ if (!this.onError) {
2594
+ return;
2595
+ }
2596
+ try {
2597
+ this.onError(error, event);
2598
+ } catch (observerError) {
2599
+ this.logger.error("Error in runtime onError callback", {
2600
+ eventType: event.type,
2601
+ roomId: event.roomId,
2602
+ error: observerError
2603
+ });
2604
+ }
2605
+ }
2606
+ };
2607
+ function assertNever(value) {
2608
+ throw new Error(`Unhandled platform event: ${JSON.stringify(value)}`);
2609
+ }
2610
+
2611
+ // src/runtime/preprocessing/DefaultPreprocessor.ts
2612
+ function toPlatformMessage2(roomId, payload) {
2613
+ return {
2614
+ id: payload.id,
2615
+ roomId,
2616
+ content: payload.content,
2617
+ senderId: payload.sender_id,
2618
+ senderType: payload.sender_type,
2619
+ senderName: payload.sender_name ?? null,
2620
+ messageType: payload.message_type,
2621
+ metadata: payload.metadata ?? {},
2622
+ createdAt: new Date(payload.inserted_at)
2623
+ };
2624
+ }
2625
+ var DefaultPreprocessor = class {
2626
+ async process(context, event, agentId) {
2627
+ if (event.type !== "message_created" || !event.roomId) {
2628
+ return null;
2629
+ }
2630
+ const message = toPlatformMessage2(event.roomId, event.payload);
2631
+ if (message.senderId === agentId) {
2632
+ return null;
2633
+ }
2634
+ if (context.hasMessage(message.id)) {
2635
+ return null;
2636
+ }
2637
+ const isSessionBootstrap = !context.isLlmInitialized;
2638
+ if (isSessionBootstrap) {
2639
+ context.markLlmInitialized();
2640
+ }
2641
+ context.recordMessage(message);
2642
+ const systemMessages = context.consumeSystemMessages();
2643
+ let contactsMessage;
2644
+ if (systemMessages.length > 0) {
2645
+ contactsMessage = systemMessages.join("\n");
2646
+ } else {
2647
+ contactsMessage = context.consumeContactsMessage();
2648
+ }
2649
+ const history = isSessionBootstrap ? await context.getHydratedHistory(message.id) : context.getRawHistory();
2650
+ return {
2651
+ message,
2652
+ tools: context.getTools(),
2653
+ history: new HistoryProvider(history),
2654
+ participantsMessage: context.consumeParticipantsMessage(),
2655
+ contactsMessage,
2656
+ isSessionBootstrap,
2657
+ roomId: context.roomId
2658
+ };
2659
+ }
2660
+ };
2661
+
2662
+ // src/runtime/tools/ContactToolsImpl.ts
2663
+ var ContactToolsImpl = class {
2664
+ rest;
2665
+ constructor(rest) {
2666
+ this.rest = rest;
2667
+ }
2668
+ async listContacts(request = {}) {
2669
+ if (!this.rest.listContacts) {
2670
+ throw new UnsupportedFeatureError("Contact listing is not available in current REST adapter");
2671
+ }
2672
+ return this.rest.listContacts(
2673
+ {
2674
+ page: request.page ?? 1,
2675
+ pageSize: request.pageSize ?? 50
2676
+ },
2677
+ DEFAULT_REQUEST_OPTIONS
2678
+ );
2679
+ }
2680
+ async addContact(request) {
2681
+ if (!this.rest.addContact) {
2682
+ throw new UnsupportedFeatureError("Contact creation is not available in current REST adapter");
2683
+ }
2684
+ const normalizedHandle = request.handle.trim();
2685
+ if (normalizedHandle.length === 0) {
2686
+ throw new ValidationError("handle is required");
2687
+ }
2688
+ return this.rest.addContact(
2689
+ {
2690
+ handle: normalizedHandle,
2691
+ ...request.message ? { message: request.message } : {}
2692
+ },
2693
+ DEFAULT_REQUEST_OPTIONS
2694
+ );
2695
+ }
2696
+ async removeContact(request) {
2697
+ if (!this.rest.removeContact) {
2698
+ throw new UnsupportedFeatureError("Contact removal is not available in current REST adapter");
2699
+ }
2700
+ if (request.target === "handle") {
2701
+ const handle = request.handle.trim();
2702
+ if (handle.length === 0) {
2703
+ throw new ValidationError("handle is required");
2704
+ }
2705
+ return this.rest.removeContact(
2706
+ { target: "handle", handle },
2707
+ DEFAULT_REQUEST_OPTIONS
2708
+ );
2709
+ }
2710
+ const contactId = request.contactId.trim();
2711
+ if (contactId.length === 0) {
2712
+ throw new ValidationError("contactId is required");
2713
+ }
2714
+ return this.rest.removeContact(
2715
+ { target: "contactId", contactId },
2716
+ DEFAULT_REQUEST_OPTIONS
2717
+ );
2718
+ }
2719
+ async listContactRequests(request = {}) {
2720
+ if (!this.rest.listContactRequests) {
2721
+ throw new UnsupportedFeatureError("Contact request listing is not available in current REST adapter");
2722
+ }
2723
+ return this.rest.listContactRequests(
2724
+ {
2725
+ page: request.page ?? 1,
2726
+ pageSize: request.pageSize ?? 50,
2727
+ sentStatus: request.sentStatus ?? "pending"
2728
+ },
2729
+ DEFAULT_REQUEST_OPTIONS
2730
+ );
2731
+ }
2732
+ async respondContactRequest(request) {
2733
+ if (!this.rest.respondContactRequest) {
2734
+ throw new UnsupportedFeatureError("Contact request responses are not available in current REST adapter");
2735
+ }
2736
+ if (request.target === "handle") {
2737
+ const handle = request.handle.trim();
2738
+ if (handle.length === 0) {
2739
+ throw new ValidationError("handle is required");
2740
+ }
2741
+ return this.rest.respondContactRequest(
2742
+ { action: request.action, target: "handle", handle },
2743
+ DEFAULT_REQUEST_OPTIONS
2744
+ );
2745
+ }
2746
+ const requestId = request.requestId.trim();
2747
+ if (requestId.length === 0) {
2748
+ throw new ValidationError("requestId is required");
2749
+ }
2750
+ return this.rest.respondContactRequest(
2751
+ { action: request.action, target: "requestId", requestId },
2752
+ DEFAULT_REQUEST_OPTIONS
2753
+ );
2754
+ }
2755
+ };
2756
+
2757
+ // src/runtime/tools/ContactCallbackTools.ts
2758
+ function toParticipantRecord(participant) {
2759
+ return {
2760
+ id: participant.id,
2761
+ name: participant.name,
2762
+ type: participant.type,
2763
+ handle: participant.handle ?? null
2764
+ };
2765
+ }
2766
+ function normalizePage(value, fallback) {
2767
+ return Number.isInteger(value) && value && value > 0 ? value : fallback;
2768
+ }
2769
+ function requireRoomId(roomId, methodName) {
2770
+ if (roomId) {
2771
+ return roomId;
2772
+ }
2773
+ throw new UnsupportedFeatureError(`${methodName} is unavailable for contact callbacks without a room context`);
2774
+ }
2775
+ var ContactCallbackTools = class {
2776
+ capabilities;
2777
+ rest;
2778
+ roomId;
2779
+ contactTools;
2780
+ constructor(rest, roomId) {
2781
+ this.rest = rest;
2782
+ this.roomId = roomId;
2783
+ const hasContactMethods = Boolean(
2784
+ rest.listContacts || rest.addContact || rest.removeContact || rest.listContactRequests || rest.respondContactRequest
2785
+ );
2786
+ this.contactTools = hasContactMethods ? new ContactToolsImpl(rest) : null;
2787
+ this.capabilities = Object.freeze({
2788
+ peers: Boolean(rest.listPeers),
2789
+ contacts: hasContactMethods,
2790
+ memory: Boolean(
2791
+ rest.listMemories || rest.storeMemory || rest.getMemory || rest.supersedeMemory || rest.archiveMemory
2792
+ )
2793
+ });
2794
+ }
2795
+ async sendMessage(content, mentions) {
2796
+ const roomId = requireRoomId(this.roomId, "sendMessage");
2797
+ if (!this.rest.createChatMessage) {
2798
+ throw new UnsupportedFeatureError("Message sending is not available in current REST adapter");
2799
+ }
2800
+ if (Array.isArray(mentions) && mentions.some((mention) => typeof mention === "string")) {
2801
+ throw new UnsupportedFeatureError("sendMessage string mentions are unavailable for contact callbacks");
2802
+ }
2803
+ const normalizedMentions = mentions && mentions.every((mention) => typeof mention !== "string") ? mentions : void 0;
2804
+ return this.rest.createChatMessage(
2805
+ roomId,
2806
+ {
2807
+ content,
2808
+ ...normalizedMentions ? { mentions: normalizedMentions } : {}
2809
+ },
2810
+ DEFAULT_REQUEST_OPTIONS
2811
+ );
2812
+ }
2813
+ async sendEvent(content, messageType, metadata) {
2814
+ const roomId = requireRoomId(this.roomId, "sendEvent");
2815
+ if (!this.rest.createChatEvent) {
2816
+ throw new UnsupportedFeatureError("Event sending is not available in current REST adapter");
2817
+ }
2818
+ return this.rest.createChatEvent(
2819
+ roomId,
2820
+ {
2821
+ content,
2822
+ messageType,
2823
+ ...metadata ? { metadata } : {}
2824
+ },
2825
+ DEFAULT_REQUEST_OPTIONS
2826
+ );
2827
+ }
2828
+ async addParticipant() {
2829
+ throw new UnsupportedFeatureError("addParticipant is unavailable for contact callbacks");
2830
+ }
2831
+ async removeParticipant() {
2832
+ throw new UnsupportedFeatureError("removeParticipant is unavailable for contact callbacks");
2833
+ }
2834
+ async getParticipants() {
2835
+ const roomId = requireRoomId(this.roomId, "getParticipants");
2836
+ if (!this.rest.listChatParticipants) {
2837
+ throw new UnsupportedFeatureError("Participant listing is not available in current REST adapter");
2838
+ }
2839
+ return (await this.rest.listChatParticipants(roomId, DEFAULT_REQUEST_OPTIONS)).map(toParticipantRecord);
2840
+ }
2841
+ async createChatroom(taskId) {
2842
+ const chat = await this.rest.createChat(taskId, DEFAULT_REQUEST_OPTIONS);
2843
+ return chat.id;
2844
+ }
2845
+ async lookupPeers(page = 1, pageSize = 50) {
2846
+ if (!this.rest.listPeers) {
2847
+ throw new UnsupportedFeatureError("Peer lookup is not available in current REST adapter");
2848
+ }
2849
+ return this.rest.listPeers(
2850
+ {
2851
+ page: normalizePage(page, 1),
2852
+ pageSize: normalizePage(pageSize, 50),
2853
+ notInChat: this.roomId ?? ""
2854
+ },
2855
+ DEFAULT_REQUEST_OPTIONS
2856
+ );
2857
+ }
2858
+ getToolSchemas() {
2859
+ return [];
2860
+ }
2861
+ getAnthropicToolSchemas() {
2862
+ return [];
2863
+ }
2864
+ getOpenAIToolSchemas() {
2865
+ return [];
2866
+ }
2867
+ async listContacts(request = {}) {
2868
+ if (!this.contactTools) {
2869
+ throw new UnsupportedFeatureError("Contact listing is not available in current REST adapter");
2870
+ }
2871
+ return this.contactTools.listContacts(request);
2872
+ }
2873
+ async addContact(request) {
2874
+ if (!this.contactTools) {
2875
+ throw new UnsupportedFeatureError("Contact creation is not available in current REST adapter");
2876
+ }
2877
+ return this.contactTools.addContact(request);
2878
+ }
2879
+ async removeContact(request) {
2880
+ if (!this.contactTools) {
2881
+ throw new UnsupportedFeatureError("Contact removal is not available in current REST adapter");
2882
+ }
2883
+ return this.contactTools.removeContact(request);
2884
+ }
2885
+ async listContactRequests(request = {}) {
2886
+ if (!this.contactTools) {
2887
+ throw new UnsupportedFeatureError("Contact request listing is not available in current REST adapter");
2888
+ }
2889
+ return this.contactTools.listContactRequests(request);
2890
+ }
2891
+ async respondContactRequest(request) {
2892
+ if (!this.contactTools) {
2893
+ throw new UnsupportedFeatureError("Contact request responses are not available in current REST adapter");
2894
+ }
2895
+ return this.contactTools.respondContactRequest(request);
2896
+ }
2897
+ async listMemories(args = {}) {
2898
+ if (!this.rest.listMemories) {
2899
+ throw new UnsupportedFeatureError("Memory listing is not available in current REST adapter");
2900
+ }
2901
+ return this.rest.listMemories(args, DEFAULT_REQUEST_OPTIONS);
2902
+ }
2903
+ async storeMemory(args) {
2904
+ if (!this.rest.storeMemory) {
2905
+ throw new UnsupportedFeatureError("Memory storage is not available in current REST adapter");
2906
+ }
2907
+ return this.rest.storeMemory(args, DEFAULT_REQUEST_OPTIONS);
2908
+ }
2909
+ async getMemory(memoryId) {
2910
+ if (!this.rest.getMemory) {
2911
+ throw new UnsupportedFeatureError("Memory lookup is not available in current REST adapter");
2912
+ }
2913
+ return this.rest.getMemory(memoryId, DEFAULT_REQUEST_OPTIONS);
2914
+ }
2915
+ async supersedeMemory(memoryId) {
2916
+ if (!this.rest.supersedeMemory) {
2917
+ throw new UnsupportedFeatureError("Memory supersede is not available in current REST adapter");
2918
+ }
2919
+ return this.rest.supersedeMemory(memoryId, DEFAULT_REQUEST_OPTIONS);
2920
+ }
2921
+ async archiveMemory(memoryId) {
2922
+ if (!this.rest.archiveMemory) {
2923
+ throw new UnsupportedFeatureError("Memory archival is not available in current REST adapter");
2924
+ }
2925
+ return this.rest.archiveMemory(memoryId, DEFAULT_REQUEST_OPTIONS);
2926
+ }
2927
+ async executeToolCall(toolName, toolArgs) {
2928
+ switch (toolName) {
2929
+ case "thenvoi_send_message":
2930
+ return this.sendMessage(
2931
+ String(toolArgs.content ?? ""),
2932
+ toolArgs.mentions
2933
+ );
2934
+ case "thenvoi_send_event":
2935
+ return this.sendEvent(
2936
+ String(toolArgs.content ?? ""),
2937
+ String(toolArgs.message_type ?? "task"),
2938
+ toolArgs.metadata
2939
+ );
2940
+ case "thenvoi_get_participants":
2941
+ return this.getParticipants();
2942
+ case "thenvoi_create_chatroom":
2943
+ return this.createChatroom(typeof toolArgs.task_id === "string" ? toolArgs.task_id : void 0);
2944
+ case "thenvoi_lookup_peers":
2945
+ return this.lookupPeers(
2946
+ typeof toolArgs.page === "number" ? toolArgs.page : void 0,
2947
+ typeof toolArgs.page_size === "number" ? toolArgs.page_size : void 0
2948
+ );
2949
+ case "thenvoi_list_contacts":
2950
+ return this.listContacts({
2951
+ page: typeof toolArgs.page === "number" ? toolArgs.page : void 0,
2952
+ pageSize: typeof toolArgs.page_size === "number" ? toolArgs.page_size : void 0
2953
+ });
2954
+ case "thenvoi_add_contact":
2955
+ return this.addContact({
2956
+ handle: String(toolArgs.handle ?? ""),
2957
+ ...typeof toolArgs.message === "string" ? { message: toolArgs.message } : {}
2958
+ });
2959
+ case "thenvoi_remove_contact":
2960
+ if (typeof toolArgs.contact_id === "string") {
2961
+ return this.removeContact({ target: "contactId", contactId: toolArgs.contact_id });
2962
+ }
2963
+ return this.removeContact({ target: "handle", handle: String(toolArgs.handle ?? "") });
2964
+ case "thenvoi_list_contact_requests":
2965
+ return this.listContactRequests({
2966
+ page: typeof toolArgs.page === "number" ? toolArgs.page : void 0,
2967
+ pageSize: typeof toolArgs.page_size === "number" ? toolArgs.page_size : void 0,
2968
+ sentStatus: typeof toolArgs.sent_status === "string" ? toolArgs.sent_status : void 0
2969
+ });
2970
+ case "thenvoi_respond_contact_request":
2971
+ if (typeof toolArgs.request_id === "string") {
2972
+ return this.respondContactRequest({
2973
+ action: String(toolArgs.action ?? "approve"),
2974
+ target: "requestId",
2975
+ requestId: toolArgs.request_id
2976
+ });
2977
+ }
2978
+ return this.respondContactRequest({
2979
+ action: String(toolArgs.action ?? "approve"),
2980
+ target: "handle",
2981
+ handle: String(toolArgs.handle ?? "")
2982
+ });
2983
+ case "thenvoi_list_memories":
2984
+ return this.listMemories(toolArgs);
2985
+ case "thenvoi_store_memory":
2986
+ return this.storeMemory(toolArgs);
2987
+ case "thenvoi_get_memory":
2988
+ return this.getMemory(String(toolArgs.memory_id ?? ""));
2989
+ case "thenvoi_supersede_memory":
2990
+ return this.supersedeMemory(String(toolArgs.memory_id ?? ""));
2991
+ case "thenvoi_archive_memory":
2992
+ return this.archiveMemory(String(toolArgs.memory_id ?? ""));
2993
+ default:
2994
+ throw new UnsupportedFeatureError(`Unsupported tool call for contact callback: ${toolName}`);
2995
+ }
2996
+ }
2997
+ };
2998
+
2999
+ // src/runtime/ContactEventHandler.ts
3000
+ var LRU_MAX_SIZE = 1e3;
3001
+ var HUB_ROOM_SYSTEM_PROMPT = `## OVERRIDE: Contact Management Mode
3002
+
3003
+ This is your CONTACTS HUB - a dedicated room for managing contact requests.
3004
+
3005
+ **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.
3006
+
3007
+ ## Your Role
3008
+
3009
+ 1. **Review incoming contact requests** - When you see a [Contact Request] message, evaluate it
3010
+ 2. **Take action** - Use the contact tools to respond:
3011
+ - \`thenvoi_respond_contact_request(action="approve", request_id="...")\` to accept
3012
+ - \`thenvoi_respond_contact_request(action="reject", request_id="...")\` to decline
3013
+ 3. **Report your decision** - Send a thought event explaining what you did
3014
+
3015
+ ## Example
3016
+
3017
+ [Contact Events]: [Contact Request] Alice (@alice) wants to connect.
3018
+ Request ID: abc-123
3019
+
3020
+ Your response:
3021
+ 1. thenvoi_send_event("Received contact request from Alice. Approving.", message_type="thought")
3022
+ 2. thenvoi_respond_contact_request(action="approve", request_id="abc-123")
3023
+ 3. thenvoi_send_event("Approved contact request from Alice (@alice)", message_type="thought")
3024
+
3025
+ ## Contact Tools (use these, NOT participant tools)
3026
+ - \`thenvoi_respond_contact_request(action, request_id)\` - Approve/reject requests
3027
+ - \`thenvoi_list_contact_requests()\` - List pending requests
3028
+ - \`thenvoi_list_contacts()\` - List current contacts`;
3029
+ var ContactEventHandlerError = class extends Error {
3030
+ retryable;
3031
+ stage;
3032
+ eventType;
3033
+ constructor(input) {
3034
+ super(`Contact event ${input.eventType} failed during ${input.stage}`, { cause: input.cause });
3035
+ this.name = "ContactEventHandlerError";
3036
+ this.retryable = input.retryable;
3037
+ this.stage = input.stage;
3038
+ this.eventType = input.eventType;
3039
+ }
3040
+ };
3041
+ var ContactEventHandler = class {
3042
+ config;
3043
+ rest;
3044
+ logger;
3045
+ onBroadcast;
3046
+ onHubEvent;
3047
+ onHubInit;
3048
+ dedup = /* @__PURE__ */ new Set();
3049
+ dedupOrder = [];
3050
+ syntheticIdCounter = 0;
3051
+ requestCache = /* @__PURE__ */ new Map();
3052
+ hubRoomId = null;
3053
+ hubRoomInitPromise = null;
3054
+ constructor(options) {
3055
+ this.config = options.config;
3056
+ this.rest = options.rest;
3057
+ this.logger = options.logger ?? new NoopLogger();
3058
+ this.onBroadcast = options.onBroadcast;
3059
+ this.onHubEvent = options.onHubEvent;
3060
+ this.onHubInit = options.onHubInit;
3061
+ }
3062
+ async handle(event, tools) {
3063
+ const strategy = this.config.strategy ?? "disabled";
3064
+ const hasBroadcast = this.config.broadcastChanges && this.onBroadcast;
3065
+ if (strategy === "disabled" && !hasBroadcast) {
3066
+ return;
3067
+ }
3068
+ const dedupKey = this.buildDedupKey(event);
3069
+ if (this.dedup.has(dedupKey)) {
3070
+ this.logger.debug("Duplicate contact event skipped", { key: dedupKey });
3071
+ return;
3072
+ }
3073
+ this.recordDedup(dedupKey);
3074
+ try {
3075
+ if (event.type === "contact_request_received") {
3076
+ this.cacheRequestInfo(event.payload.id, {
3077
+ fromHandle: event.payload.from_handle,
3078
+ fromName: event.payload.from_name,
3079
+ message: event.payload.message ?? null
3080
+ });
3081
+ }
3082
+ if (hasBroadcast) {
3083
+ const broadcastMsg = this.formatBroadcast(event);
3084
+ if (broadcastMsg) {
3085
+ this.onBroadcast(broadcastMsg);
3086
+ }
3087
+ }
3088
+ if (strategy === "disabled") {
3089
+ return;
3090
+ }
3091
+ if (strategy === "callback") {
3092
+ await this.handleCallback(event, tools);
3093
+ } else if (strategy === "hub_room") {
3094
+ await this.handleHubRoom(event);
3095
+ }
3096
+ } catch (error) {
3097
+ this.rollbackDedup(dedupKey);
3098
+ throw error;
3099
+ }
3100
+ }
3101
+ async handleCallback(event, tools) {
3102
+ const callback = this.config.onEvent;
3103
+ if (!callback) {
3104
+ this.logger.warn("Contact event callback strategy configured but no onEvent callback provided");
3105
+ return;
3106
+ }
3107
+ const callbackTools = tools ?? new ContactCallbackTools(this.rest, event.roomId);
3108
+ try {
3109
+ await callback(event, callbackTools);
3110
+ } catch (error) {
3111
+ const failure = this.buildHandlerError(event, "callback", error, false);
3112
+ this.logFailure(event, failure.stage, failure.retryable, error);
3113
+ throw failure;
3114
+ }
3115
+ }
3116
+ async handleHubRoom(event) {
3117
+ const hubTaskId = this.config.hubTaskId;
3118
+ if (!hubTaskId) {
3119
+ this.logger.warn("hub_room strategy requires hubTaskId");
3120
+ return;
3121
+ }
3122
+ if (!this.hubRoomId) {
3123
+ if (!this.hubRoomInitPromise) {
3124
+ this.hubRoomInitPromise = this.initHubRoom(hubTaskId);
3125
+ }
3126
+ try {
3127
+ this.hubRoomId = await this.hubRoomInitPromise;
3128
+ } catch (error) {
3129
+ this.hubRoomInitPromise = null;
3130
+ const failure = this.buildHandlerError(event, "hub_room_init", error, true);
3131
+ this.logFailure(event, failure.stage, failure.retryable, error);
3132
+ throw failure;
3133
+ }
3134
+ }
3135
+ const formattedMessage = await this.formatEventMessage(event);
3136
+ let persistFailure = null;
3137
+ try {
3138
+ await this.rest.createChatEvent(this.hubRoomId, {
3139
+ content: formattedMessage,
3140
+ messageType: "contact_event",
3141
+ metadata: { contactEventType: event.type, payload: event.payload }
3142
+ });
3143
+ } catch (error) {
3144
+ persistFailure = this.buildHandlerError(event, "hub_room_persist", error, true);
3145
+ this.logFailure(event, persistFailure.stage, persistFailure.retryable, error);
3146
+ }
3147
+ let dispatchFailure = null;
3148
+ if (this.onHubEvent) {
3149
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3150
+ const syntheticEvent = {
3151
+ type: "message_created",
3152
+ roomId: this.hubRoomId,
3153
+ payload: {
3154
+ id: `contact-evt-${Date.now()}-${this.syntheticIdCounter++}`,
3155
+ content: formattedMessage,
3156
+ message_type: "contact_event",
3157
+ sender_id: SYNTHETIC_CONTACT_EVENTS_SENDER_ID,
3158
+ sender_type: SYNTHETIC_SENDER_TYPE,
3159
+ sender_name: SYNTHETIC_CONTACT_EVENTS_SENDER_NAME,
3160
+ inserted_at: now,
3161
+ updated_at: now
3162
+ }
3163
+ };
3164
+ try {
3165
+ await this.onHubEvent(this.hubRoomId, syntheticEvent);
3166
+ } catch (error) {
3167
+ dispatchFailure = this.buildHandlerError(event, "hub_room_dispatch", error, true);
3168
+ this.logFailure(event, dispatchFailure.stage, dispatchFailure.retryable, error);
3169
+ }
3170
+ }
3171
+ if (persistFailure) {
3172
+ throw persistFailure;
3173
+ }
3174
+ if (dispatchFailure) {
3175
+ throw dispatchFailure;
3176
+ }
3177
+ }
3178
+ async initHubRoom(hubTaskId) {
3179
+ const result = await this.rest.createChat(hubTaskId);
3180
+ this.logger.info("Hub room created for contact events", { roomId: result.id });
3181
+ if (this.onHubInit) {
3182
+ await this.onHubInit(result.id, HUB_ROOM_SYSTEM_PROMPT);
3183
+ }
3184
+ return result.id;
3185
+ }
3186
+ async formatEventMessage(event) {
3187
+ switch (event.type) {
3188
+ case "contact_request_received": {
3189
+ const msg = event.payload.message ? `
3190
+ Message: "${event.payload.message}"` : "";
3191
+ const handle = normalizeHandle(event.payload.from_handle);
3192
+ return `[Contact Request] ${event.payload.from_name} (${handle}) wants to connect.${msg}
3193
+ Request ID: ${event.payload.id}`;
3194
+ }
3195
+ case "contact_request_updated": {
3196
+ const info = await this.enrichUpdateEvent(event.payload.id);
3197
+ if (info) {
3198
+ const name = info.fromName ?? info.toName;
3199
+ const rawHandle = info.fromHandle ?? info.toHandle ?? "";
3200
+ const handle = normalizeHandle(rawHandle);
3201
+ if (name) {
3202
+ const direction = info.fromName ? "from" : "to";
3203
+ return `[Contact Request Update] Request ${direction} ${name} (${handle}) status changed to: ${event.payload.status}
3204
+ Request ID: ${event.payload.id}`;
3205
+ }
3206
+ }
3207
+ return `[Contact Request Update] Request ${event.payload.id} status changed to: ${event.payload.status}`;
3208
+ }
3209
+ case "contact_added": {
3210
+ const handle = normalizeHandle(event.payload.handle);
3211
+ return `[Contact Added] ${event.payload.name} (${handle}) is now a contact.
3212
+ Type: ${event.payload.type}, ID: ${event.payload.id}`;
3213
+ }
3214
+ case "contact_removed":
3215
+ return `[Contact Removed] Contact ${event.payload.id} was removed.`;
3216
+ }
3217
+ return assertNever2(event);
3218
+ }
3219
+ formatBroadcast(event) {
3220
+ switch (event.type) {
3221
+ case "contact_added": {
3222
+ const handle = normalizeHandle(event.payload.handle);
3223
+ return `[Contacts]: ${handle} (${event.payload.name}) is now a contact`;
3224
+ }
3225
+ case "contact_removed":
3226
+ return `[Contacts]: Contact ${event.payload.id} was removed`;
3227
+ default:
3228
+ return null;
3229
+ }
3230
+ }
3231
+ async enrichUpdateEvent(requestId) {
3232
+ const cached = this.requestCache.get(requestId);
3233
+ if (cached) {
3234
+ return cached;
3235
+ }
3236
+ this.logger.debug("Cache miss for request, fetching from API", { requestId });
3237
+ return await this.fetchRequestDetails(requestId);
3238
+ }
3239
+ async fetchRequestDetails(requestId) {
3240
+ if (!this.rest.listContactRequests) {
3241
+ return null;
3242
+ }
3243
+ try {
3244
+ const response = await this.rest.listContactRequests({ page: 1, pageSize: 100 });
3245
+ for (const req of response.received ?? []) {
3246
+ if (req.id === requestId) {
3247
+ const info = {
3248
+ fromHandle: req.from_handle,
3249
+ fromName: req.from_name,
3250
+ message: req.message ?? null
3251
+ };
3252
+ this.cacheRequestInfo(requestId, info);
3253
+ this.logger.debug("Fetched request details from API (received)", { requestId });
3254
+ return info;
3255
+ }
3256
+ }
3257
+ for (const req of response.sent ?? []) {
3258
+ if (req.id === requestId) {
3259
+ const info = {
3260
+ toHandle: req.to_handle,
3261
+ toName: req.to_name,
3262
+ message: req.message ?? null
3263
+ };
3264
+ this.cacheRequestInfo(requestId, info);
3265
+ this.logger.debug("Fetched request details from API (sent)", { requestId });
3266
+ return info;
3267
+ }
3268
+ }
3269
+ this.logger.debug("Request not found in API", { requestId });
3270
+ return null;
3271
+ } catch (error) {
3272
+ this.logger.warn("Failed to fetch request details from API", { requestId, error });
3273
+ return null;
3274
+ }
3275
+ }
3276
+ cacheRequestInfo(id, info) {
3277
+ this.requestCache.set(id, info);
3278
+ while (this.requestCache.size > LRU_MAX_SIZE) {
3279
+ const first = this.requestCache.keys().next();
3280
+ if (first.done) break;
3281
+ this.requestCache.delete(first.value);
3282
+ }
3283
+ }
3284
+ buildDedupKey(event) {
3285
+ const id = event.payload.id;
3286
+ if (event.type === "contact_request_updated") {
3287
+ return `${event.type}:${id}:${event.payload.status}`;
3288
+ }
3289
+ return `${event.type}:${id}`;
3290
+ }
3291
+ recordDedup(key) {
3292
+ this.dedup.add(key);
3293
+ this.dedupOrder.push(key);
3294
+ while (this.dedupOrder.length > LRU_MAX_SIZE) {
3295
+ const evicted = this.dedupOrder.shift();
3296
+ if (evicted) {
3297
+ this.dedup.delete(evicted);
3298
+ }
3299
+ }
3300
+ }
3301
+ rollbackDedup(key) {
3302
+ if (!this.dedup.delete(key)) {
3303
+ return;
3304
+ }
3305
+ const index = this.dedupOrder.lastIndexOf(key);
3306
+ if (index >= 0) {
3307
+ this.dedupOrder.splice(index, 1);
3308
+ }
3309
+ }
3310
+ buildHandlerError(event, stage, cause, defaultRetryable) {
3311
+ return new ContactEventHandlerError({
3312
+ eventType: event.type,
3313
+ stage,
3314
+ retryable: this.resolveRetryable(cause, defaultRetryable),
3315
+ cause
3316
+ });
3317
+ }
3318
+ resolveRetryable(error, defaultRetryable) {
3319
+ if (typeof error === "object" && error !== null && "retryable" in error) {
3320
+ return error.retryable === true;
3321
+ }
3322
+ return defaultRetryable;
3323
+ }
3324
+ logFailure(event, stage, retryable, error) {
3325
+ this.logger.error("contact_event.failure", {
3326
+ type: event.type,
3327
+ stage,
3328
+ retryable,
3329
+ error: this.serializeError(error)
3330
+ });
3331
+ }
3332
+ serializeError(error) {
3333
+ if (error instanceof Error) {
3334
+ const payload = {
3335
+ name: error.name,
3336
+ message: error.message,
3337
+ stack: error.stack
3338
+ };
3339
+ if (typeof error.retryable !== "undefined") {
3340
+ payload.retryable = error.retryable;
3341
+ }
3342
+ return payload;
3343
+ }
3344
+ return { message: String(error) };
3345
+ }
3346
+ };
3347
+ function assertNever2(value) {
3348
+ throw new Error(`Unhandled contact event: ${JSON.stringify(value)}`);
3349
+ }
3350
+ function normalizeHandle(handle) {
3351
+ if (!handle) return "@unknown";
3352
+ return handle.startsWith("@") ? handle : `@${handle}`;
3353
+ }
3354
+
3355
+ // src/runtime/PlatformRuntime.ts
3356
+ var PlatformRuntime = class {
3357
+ _agentId;
3358
+ _apiKey;
3359
+ _wsUrl;
3360
+ _restUrl;
3361
+ preprocessor;
3362
+ sessionConfig;
3363
+ contactConfig;
3364
+ agentConfig;
3365
+ linkOptions;
3366
+ configuredIdentity;
3367
+ logger;
3368
+ _onParticipantAdded;
3369
+ _onParticipantRemoved;
3370
+ _roomFilter;
3371
+ _contextFactory;
3372
+ linkInstance;
3373
+ initPromise = null;
3374
+ runtime;
3375
+ contactHandler;
3376
+ activeAdapter;
3377
+ stopping = false;
3378
+ _agentName = "";
3379
+ _agentDescription = "";
3380
+ contactsSubscribed = false;
3381
+ constructor(options) {
3382
+ if (!options.agentId || options.agentId.trim() === "") {
3383
+ throw new ValidationError(
3384
+ "agentId is required and must be a non-empty string. Use loadAgentConfig() to load credentials from agent_config.yaml."
3385
+ );
3386
+ }
3387
+ if (!options.apiKey || options.apiKey.trim() === "") {
3388
+ throw new ValidationError(
3389
+ "apiKey is required and must be a non-empty string. Use loadAgentConfig() to load credentials from agent_config.yaml."
3390
+ );
3391
+ }
3392
+ this._agentId = options.agentId;
3393
+ this._apiKey = options.apiKey;
3394
+ this._wsUrl = options.wsUrl;
3395
+ this._restUrl = options.restUrl;
3396
+ this.linkInstance = options.link;
3397
+ this.linkOptions = options.linkOptions;
3398
+ this.preprocessor = options.preprocessor ?? new DefaultPreprocessor();
3399
+ this.sessionConfig = options.sessionConfig;
3400
+ this.contactConfig = options.contactConfig;
3401
+ this.agentConfig = options.agentConfig;
3402
+ this.logger = options.logger ?? new NoopLogger();
3403
+ this.configuredIdentity = options.identity;
3404
+ this._onParticipantAdded = options.onParticipantAdded;
3405
+ this._onParticipantRemoved = options.onParticipantRemoved;
3406
+ this._roomFilter = options.roomFilter;
3407
+ this._contextFactory = options.contextFactory;
3408
+ }
3409
+ get link() {
3410
+ if (!this.linkInstance) {
3411
+ throw new RuntimeStateError("Runtime is not initialized");
3412
+ }
3413
+ return this.linkInstance;
3414
+ }
3415
+ get name() {
3416
+ return this._agentName;
3417
+ }
3418
+ get agentId() {
3419
+ return this._agentId;
3420
+ }
3421
+ get description() {
3422
+ return this._agentDescription;
3423
+ }
3424
+ get contactConfiguration() {
3425
+ return this.contactConfig;
3426
+ }
3427
+ get isContactsSubscribed() {
3428
+ return this.contactsSubscribed;
3429
+ }
3430
+ async initialize() {
3431
+ if (this.initPromise) {
3432
+ return this.initPromise;
3433
+ }
3434
+ this.initPromise = this.doInitialize();
3435
+ try {
3436
+ await this.initPromise;
3437
+ } catch (error) {
3438
+ this.initPromise = null;
3439
+ throw error;
3440
+ }
3441
+ }
3442
+ async doInitialize() {
3443
+ if (!this.linkInstance) {
3444
+ this.linkInstance = new ThenvoiLink({
3445
+ ...this.linkOptions,
3446
+ agentId: this._agentId,
3447
+ apiKey: this._apiKey,
3448
+ wsUrl: this._wsUrl,
3449
+ restUrl: this._restUrl
3450
+ });
3451
+ }
3452
+ if (this.configuredIdentity) {
3453
+ this._agentName = this.configuredIdentity.name;
3454
+ this._agentDescription = this.configuredIdentity.description ?? "";
3455
+ return;
3456
+ }
3457
+ const me = await this.link.rest.getAgentMe();
3458
+ this._agentName = me.name;
3459
+ this._agentDescription = me.description ?? "";
3460
+ }
3461
+ async start(adapter) {
3462
+ await this.initialize();
3463
+ await adapter.onStarted(this._agentName, this._agentDescription);
3464
+ this.activeAdapter = adapter;
3465
+ try {
3466
+ this.contactHandler = new ContactEventHandler({
3467
+ config: this.contactConfig ?? { strategy: "disabled" },
3468
+ rest: this.link.rest,
3469
+ onBroadcast: (message) => {
3470
+ const runtime = this.runtime;
3471
+ if (!runtime) return;
3472
+ for (const context of runtime.getContexts()) {
3473
+ context.injectSystemMessage(message);
3474
+ }
3475
+ },
3476
+ onHubEvent: async (roomId, event) => {
3477
+ const runtime = this.runtime;
3478
+ if (!runtime) return;
3479
+ await runtime.enqueueEvent(roomId, event);
3480
+ },
3481
+ onHubInit: async (roomId, systemPrompt) => {
3482
+ const runtime = this.runtime;
3483
+ if (!runtime) return;
3484
+ runtime.getOrCreateContext(roomId).injectSystemMessage(systemPrompt);
3485
+ }
3486
+ });
3487
+ this.runtime = new AgentRuntime({
3488
+ link: this.link,
3489
+ agentId: this._agentId,
3490
+ sessionConfig: this.sessionConfig,
3491
+ agentConfig: this.agentConfig,
3492
+ logger: this.logger,
3493
+ onExecute: (context, event) => this.executeAdapter(context, event, adapter),
3494
+ onSessionCleanup: (roomId) => adapter.onCleanup(roomId),
3495
+ onContactEvent: (event) => this.handleContactEvent(event),
3496
+ onParticipantAdded: this._onParticipantAdded,
3497
+ onParticipantRemoved: this._onParticipantRemoved,
3498
+ roomFilter: this._roomFilter,
3499
+ contextFactory: this._contextFactory
3500
+ });
3501
+ await this.runtime.start();
3502
+ this.contactsSubscribed = Boolean(this.link.capabilities.contacts);
3503
+ } catch (error) {
3504
+ try {
3505
+ await this.stop();
3506
+ } catch (stopError) {
3507
+ throw new AggregateError(
3508
+ [error, stopError],
3509
+ "PlatformRuntime failed to start and cleanup also failed"
3510
+ );
3511
+ }
3512
+ throw error;
3513
+ }
3514
+ }
3515
+ async stop(timeoutMs) {
3516
+ if (this.stopping) {
3517
+ return true;
3518
+ }
3519
+ const runtime = this.runtime;
3520
+ const adapter = this.activeAdapter;
3521
+ if (!runtime && !adapter) {
3522
+ return true;
3523
+ }
3524
+ this.stopping = true;
3525
+ this.runtime = void 0;
3526
+ this.contactHandler = void 0;
3527
+ this.contactsSubscribed = false;
3528
+ this.activeAdapter = void 0;
3529
+ let graceful = true;
3530
+ let runtimeError = null;
3531
+ if (runtime) {
3532
+ try {
3533
+ graceful = await runtime.stop(timeoutMs);
3534
+ } catch (error) {
3535
+ runtimeError = error;
3536
+ }
3537
+ }
3538
+ try {
3539
+ await adapter?.onRuntimeStop?.();
3540
+ } catch (error) {
3541
+ if (runtimeError) {
3542
+ throw new AggregateError(
3543
+ [runtimeError, error],
3544
+ "PlatformRuntime stop failed and adapter cleanup also failed"
3545
+ );
3546
+ }
3547
+ throw error;
3548
+ }
3549
+ if (runtimeError) {
3550
+ throw runtimeError instanceof Error ? runtimeError : new Error(String(runtimeError));
3551
+ }
3552
+ this.stopping = false;
3553
+ return graceful;
3554
+ }
3555
+ async runForever() {
3556
+ if (!this.runtime) {
3557
+ throw new RuntimeStateError("Runtime not started");
3558
+ }
3559
+ await this.runtime.waitUntilStopped();
3560
+ }
3561
+ async bootstrapRoomMessage(roomId, message) {
3562
+ if (!this.runtime) {
3563
+ throw new RuntimeStateError("Runtime not started");
3564
+ }
3565
+ await this.runtime.bootstrapRoomMessage(roomId, message);
3566
+ }
3567
+ async resetRoomSession(roomId, timeoutMs) {
3568
+ if (!this.runtime) {
3569
+ throw new RuntimeStateError("Runtime not started");
3570
+ }
3571
+ return await this.runtime.resetRoomSession(roomId, timeoutMs);
3572
+ }
3573
+ async executeAdapter(context, event, adapter) {
3574
+ const input = await this.preprocessor.process(context, event, this._agentId);
3575
+ if (!input) {
3576
+ return;
3577
+ }
3578
+ const messageId = String(input.message.id ?? "");
3579
+ const roomId = input.roomId;
3580
+ const isSynthetic = input.message.senderType === SYNTHETIC_SENDER_TYPE && input.message.senderId === SYNTHETIC_CONTACT_EVENTS_SENDER_ID;
3581
+ const messageMarkOptions = { bestEffort: true };
3582
+ if (messageId && !isSynthetic) {
3583
+ await this.link.markProcessing(roomId, messageId, messageMarkOptions);
3584
+ }
3585
+ try {
3586
+ await adapter.onEvent(input);
3587
+ if (messageId && !isSynthetic) {
3588
+ await this.link.markProcessed(roomId, messageId, messageMarkOptions);
3589
+ }
3590
+ } catch (error) {
3591
+ const label = error instanceof Error ? error.message : String(error);
3592
+ if (messageId && !isSynthetic) {
3593
+ await this.link.markFailed(roomId, messageId, label, messageMarkOptions);
3594
+ }
3595
+ throw error;
3596
+ }
3597
+ }
3598
+ async handleContactEvent(event) {
3599
+ if (this.contactHandler) {
3600
+ await this.contactHandler.handle(event);
3601
+ }
3602
+ }
3603
+ };
3604
+
3605
+ // src/runtime/shutdown.ts
3606
+ var DEFAULT_SHUTDOWN_TIMEOUT_MS = 3e4;
3607
+ var GracefulShutdown = class {
3608
+ agent;
3609
+ timeoutMs;
3610
+ onSignal;
3611
+ handlers = /* @__PURE__ */ new Map();
3612
+ shuttingDown = false;
3613
+ constructor(agent, options) {
3614
+ this.agent = agent;
3615
+ this.timeoutMs = options?.timeoutMs === void 0 ? DEFAULT_SHUTDOWN_TIMEOUT_MS : options.timeoutMs;
3616
+ this.onSignal = options?.onSignal;
3617
+ }
3618
+ registerSignals() {
3619
+ const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
3620
+ for (const signal of signals) {
3621
+ const handler = () => {
3622
+ this.onSignal?.(signal);
3623
+ void this.shutdown(signal);
3624
+ };
3625
+ process.on(signal, handler);
3626
+ this.handlers.set(signal, handler);
3627
+ }
3628
+ }
3629
+ unregisterSignals() {
3630
+ for (const [signal, handler] of this.handlers.entries()) {
3631
+ process.off(signal, handler);
3632
+ }
3633
+ this.handlers.clear();
3634
+ }
3635
+ async withSignals(fn) {
3636
+ this.registerSignals();
3637
+ try {
3638
+ return await fn();
3639
+ } finally {
3640
+ this.unregisterSignals();
3641
+ }
3642
+ }
3643
+ async shutdown(_signal) {
3644
+ if (this.shuttingDown) {
3645
+ process.exit(1);
3646
+ }
3647
+ this.shuttingDown = true;
3648
+ try {
3649
+ await this.agent.stop(this.timeoutMs);
3650
+ } catch {
3651
+ process.exit(1);
3652
+ }
3653
+ }
3654
+ };
3655
+ async function runWithGracefulShutdown(agent, options) {
3656
+ const shutdown = new GracefulShutdown(agent, options);
3657
+ await shutdown.withSignals(async () => {
3658
+ await agent.run({
3659
+ shutdownTimeoutMs: options?.timeoutMs === void 0 ? DEFAULT_SHUTDOWN_TIMEOUT_MS : options.timeoutMs,
3660
+ signals: false
3661
+ });
3662
+ });
3663
+ }
3664
+
3665
+ export {
3666
+ ThenvoiLink,
3667
+ Execution,
3668
+ AgentTools,
3669
+ MessageRetryTracker,
3670
+ ExecutionContext,
3671
+ trackRoomJoin,
3672
+ trackRoomLeave,
3673
+ hydrateTrackedRooms,
3674
+ AgentRuntime,
3675
+ DefaultPreprocessor,
3676
+ ContactToolsImpl,
3677
+ HUB_ROOM_SYSTEM_PROMPT,
3678
+ ContactEventHandler,
3679
+ PlatformRuntime,
3680
+ GracefulShutdown,
3681
+ runWithGracefulShutdown
3682
+ };