@multi-agent-protocol/sdk 0.0.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.
@@ -0,0 +1,4000 @@
1
+ import { ulid } from 'ulid';
2
+
3
+ // src/jsonrpc/index.ts
4
+ function isRequest(message) {
5
+ return typeof message === "object" && message !== null && "jsonrpc" in message && message.jsonrpc === "2.0" && "id" in message && "method" in message;
6
+ }
7
+ function isNotification(message) {
8
+ return typeof message === "object" && message !== null && "jsonrpc" in message && message.jsonrpc === "2.0" && "method" in message && !("id" in message);
9
+ }
10
+ function isResponse(message) {
11
+ return typeof message === "object" && message !== null && "jsonrpc" in message && message.jsonrpc === "2.0" && "id" in message && !("method" in message);
12
+ }
13
+ function isErrorResponse(response) {
14
+ return "error" in response;
15
+ }
16
+ function createRequest(id, method, params) {
17
+ const request = {
18
+ jsonrpc: "2.0",
19
+ id,
20
+ method
21
+ };
22
+ if (params !== void 0) {
23
+ request.params = params;
24
+ }
25
+ return request;
26
+ }
27
+ function createNotification(method, params) {
28
+ const notification = {
29
+ jsonrpc: "2.0",
30
+ method
31
+ };
32
+ if (params !== void 0) {
33
+ notification.params = params;
34
+ }
35
+ return notification;
36
+ }
37
+ function createSuccessResponse(id, result) {
38
+ return {
39
+ jsonrpc: "2.0",
40
+ id,
41
+ result
42
+ };
43
+ }
44
+ function createErrorResponse(id, error) {
45
+ return {
46
+ jsonrpc: "2.0",
47
+ id,
48
+ error
49
+ };
50
+ }
51
+
52
+ // src/types/index.ts
53
+ var EVENT_TYPES = {
54
+ // Agent lifecycle events
55
+ AGENT_REGISTERED: "agent_registered",
56
+ AGENT_UNREGISTERED: "agent_unregistered",
57
+ AGENT_STATE_CHANGED: "agent_state_changed",
58
+ AGENT_ORPHANED: "agent_orphaned",
59
+ // Participant lifecycle events
60
+ PARTICIPANT_CONNECTED: "participant_connected",
61
+ PARTICIPANT_DISCONNECTED: "participant_disconnected",
62
+ // Message events
63
+ MESSAGE_SENT: "message_sent",
64
+ // Scope events
65
+ SCOPE_CREATED: "scope_created",
66
+ SCOPE_MEMBER_JOINED: "scope_member_joined",
67
+ SCOPE_MEMBER_LEFT: "scope_member_left",
68
+ // Permission events
69
+ PERMISSIONS_CLIENT_UPDATED: "permissions_client_updated",
70
+ PERMISSIONS_AGENT_UPDATED: "permissions_agent_updated"};
71
+ var CORE_METHODS = {
72
+ CONNECT: "map/connect",
73
+ DISCONNECT: "map/disconnect",
74
+ SEND: "map/send",
75
+ SUBSCRIBE: "map/subscribe",
76
+ UNSUBSCRIBE: "map/unsubscribe",
77
+ REPLAY: "map/replay"
78
+ };
79
+ var OBSERVATION_METHODS = {
80
+ AGENTS_LIST: "map/agents/list",
81
+ AGENTS_GET: "map/agents/get",
82
+ SCOPES_LIST: "map/scopes/list",
83
+ SCOPES_GET: "map/scopes/get",
84
+ SCOPES_MEMBERS: "map/scopes/members",
85
+ STRUCTURE_GRAPH: "map/structure/graph"
86
+ };
87
+ var LIFECYCLE_METHODS = {
88
+ AGENTS_REGISTER: "map/agents/register",
89
+ AGENTS_UNREGISTER: "map/agents/unregister",
90
+ AGENTS_SPAWN: "map/agents/spawn"
91
+ };
92
+ var STATE_METHODS = {
93
+ AGENTS_UPDATE: "map/agents/update",
94
+ AGENTS_SUSPEND: "map/agents/suspend",
95
+ AGENTS_RESUME: "map/agents/resume",
96
+ AGENTS_STOP: "map/agents/stop"
97
+ };
98
+ var STEERING_METHODS = {
99
+ INJECT: "map/inject"
100
+ };
101
+ var SCOPE_METHODS = {
102
+ SCOPES_CREATE: "map/scopes/create",
103
+ SCOPES_JOIN: "map/scopes/join",
104
+ SCOPES_LEAVE: "map/scopes/leave"
105
+ };
106
+ var SESSION_METHODS = {
107
+ SESSION_LIST: "map/session/list",
108
+ SESSION_LOAD: "map/session/load",
109
+ SESSION_CLOSE: "map/session/close"
110
+ };
111
+ var PERMISSION_METHODS = {
112
+ PERMISSIONS_UPDATE: "map/permissions/update"
113
+ };
114
+ var FEDERATION_METHODS = {
115
+ FEDERATION_ROUTE: "map/federation/route"
116
+ };
117
+ var NOTIFICATION_METHODS = {
118
+ EVENT: "map/event",
119
+ MESSAGE: "map/message",
120
+ /** Client acknowledges received events (for backpressure) */
121
+ SUBSCRIBE_ACK: "map/subscribe.ack"
122
+ };
123
+ var PROTOCOL_ERROR_CODES = {
124
+ PARSE_ERROR: -32700,
125
+ INVALID_REQUEST: -32600,
126
+ METHOD_NOT_FOUND: -32601,
127
+ INVALID_PARAMS: -32602,
128
+ INTERNAL_ERROR: -32603
129
+ };
130
+ var AUTH_ERROR_CODES = {
131
+ AUTH_REQUIRED: 1e3,
132
+ AUTH_FAILED: 1001,
133
+ TOKEN_EXPIRED: 1002,
134
+ PERMISSION_DENIED: 1003
135
+ };
136
+ var ROUTING_ERROR_CODES = {
137
+ ADDRESS_NOT_FOUND: 2e3,
138
+ AGENT_NOT_FOUND: 2001,
139
+ SCOPE_NOT_FOUND: 2002,
140
+ DELIVERY_FAILED: 2003,
141
+ ADDRESS_AMBIGUOUS: 2004
142
+ };
143
+ var AGENT_ERROR_CODES = {
144
+ AGENT_EXISTS: 3e3,
145
+ STATE_INVALID: 3001,
146
+ NOT_RESPONDING: 3002,
147
+ TERMINATED: 3003,
148
+ SPAWN_FAILED: 3004
149
+ };
150
+ var RESOURCE_ERROR_CODES = {
151
+ EXHAUSTED: 4e3,
152
+ RATE_LIMITED: 4001,
153
+ QUOTA_EXCEEDED: 4002
154
+ };
155
+ var FEDERATION_ERROR_CODES = {
156
+ FEDERATION_UNAVAILABLE: 5e3,
157
+ FEDERATION_SYSTEM_NOT_FOUND: 5001,
158
+ FEDERATION_AUTH_FAILED: 5002,
159
+ FEDERATION_ROUTE_REJECTED: 5003,
160
+ /** Message has already visited this system (loop detected) */
161
+ FEDERATION_LOOP_DETECTED: 5010,
162
+ /** Message exceeded maximum hop count */
163
+ FEDERATION_MAX_HOPS_EXCEEDED: 5011
164
+ };
165
+ var ERROR_CODES = {
166
+ ...PROTOCOL_ERROR_CODES,
167
+ ...AUTH_ERROR_CODES,
168
+ ...ROUTING_ERROR_CODES,
169
+ ...AGENT_ERROR_CODES,
170
+ ...RESOURCE_ERROR_CODES,
171
+ ...FEDERATION_ERROR_CODES
172
+ };
173
+ var PROTOCOL_VERSION = 1;
174
+
175
+ // src/errors/index.ts
176
+ var MAPRequestError = class _MAPRequestError extends Error {
177
+ code;
178
+ data;
179
+ constructor(code, message, data) {
180
+ super(message);
181
+ this.name = "MAPRequestError";
182
+ this.code = code;
183
+ this.data = data;
184
+ }
185
+ /**
186
+ * Convert to MAP error object
187
+ */
188
+ toError() {
189
+ const error = {
190
+ code: this.code,
191
+ message: this.message
192
+ };
193
+ if (this.data) {
194
+ error.data = this.data;
195
+ }
196
+ return error;
197
+ }
198
+ /**
199
+ * Convert to JSON-RPC error response
200
+ */
201
+ toResponse(id) {
202
+ return createErrorResponse(id, this.toError());
203
+ }
204
+ // ==========================================================================
205
+ // Protocol Errors (-32xxx)
206
+ // ==========================================================================
207
+ static parseError(details) {
208
+ return new _MAPRequestError(
209
+ PROTOCOL_ERROR_CODES.PARSE_ERROR,
210
+ details ?? "Parse error",
211
+ { category: "protocol" }
212
+ );
213
+ }
214
+ static invalidRequest(details) {
215
+ return new _MAPRequestError(
216
+ PROTOCOL_ERROR_CODES.INVALID_REQUEST,
217
+ details ?? "Invalid request",
218
+ { category: "protocol" }
219
+ );
220
+ }
221
+ static methodNotFound(method) {
222
+ return new _MAPRequestError(
223
+ PROTOCOL_ERROR_CODES.METHOD_NOT_FOUND,
224
+ `Method not found: ${method}`,
225
+ { category: "protocol" }
226
+ );
227
+ }
228
+ static invalidParams(details) {
229
+ return new _MAPRequestError(
230
+ PROTOCOL_ERROR_CODES.INVALID_PARAMS,
231
+ "Invalid params",
232
+ { category: "protocol", details }
233
+ );
234
+ }
235
+ static internalError(details) {
236
+ return new _MAPRequestError(
237
+ PROTOCOL_ERROR_CODES.INTERNAL_ERROR,
238
+ details ?? "Internal error",
239
+ { category: "internal" }
240
+ );
241
+ }
242
+ // ==========================================================================
243
+ // Auth Errors (1xxx)
244
+ // ==========================================================================
245
+ static authRequired() {
246
+ return new _MAPRequestError(
247
+ AUTH_ERROR_CODES.AUTH_REQUIRED,
248
+ "Authentication required",
249
+ { category: "auth" }
250
+ );
251
+ }
252
+ static authFailed(details) {
253
+ return new _MAPRequestError(
254
+ AUTH_ERROR_CODES.AUTH_FAILED,
255
+ details ?? "Authentication failed",
256
+ { category: "auth" }
257
+ );
258
+ }
259
+ static tokenExpired() {
260
+ return new _MAPRequestError(
261
+ AUTH_ERROR_CODES.TOKEN_EXPIRED,
262
+ "Token expired",
263
+ { category: "auth", retryable: true }
264
+ );
265
+ }
266
+ static permissionDenied(required) {
267
+ return new _MAPRequestError(
268
+ AUTH_ERROR_CODES.PERMISSION_DENIED,
269
+ required ? `Permission denied: ${required}` : "Permission denied",
270
+ { category: "auth" }
271
+ );
272
+ }
273
+ // ==========================================================================
274
+ // Routing Errors (2xxx)
275
+ // ==========================================================================
276
+ static addressNotFound(address) {
277
+ return new _MAPRequestError(
278
+ ROUTING_ERROR_CODES.ADDRESS_NOT_FOUND,
279
+ `Address not found: ${address}`,
280
+ { category: "routing" }
281
+ );
282
+ }
283
+ static agentNotFound(agentId) {
284
+ return new _MAPRequestError(
285
+ ROUTING_ERROR_CODES.AGENT_NOT_FOUND,
286
+ `Agent not found: ${agentId}`,
287
+ { category: "routing" }
288
+ );
289
+ }
290
+ static scopeNotFound(scopeId) {
291
+ return new _MAPRequestError(
292
+ ROUTING_ERROR_CODES.SCOPE_NOT_FOUND,
293
+ `Scope not found: ${scopeId}`,
294
+ { category: "routing" }
295
+ );
296
+ }
297
+ static deliveryFailed(details) {
298
+ return new _MAPRequestError(
299
+ ROUTING_ERROR_CODES.DELIVERY_FAILED,
300
+ details ?? "Message delivery failed",
301
+ { category: "routing", retryable: true }
302
+ );
303
+ }
304
+ static addressAmbiguous(address) {
305
+ return new _MAPRequestError(
306
+ ROUTING_ERROR_CODES.ADDRESS_AMBIGUOUS,
307
+ `Address is ambiguous: ${address}`,
308
+ { category: "routing" }
309
+ );
310
+ }
311
+ // ==========================================================================
312
+ // Agent Errors (3xxx)
313
+ // ==========================================================================
314
+ static agentExists(agentId) {
315
+ return new _MAPRequestError(
316
+ AGENT_ERROR_CODES.AGENT_EXISTS,
317
+ `Agent already exists: ${agentId}`,
318
+ { category: "agent" }
319
+ );
320
+ }
321
+ static stateInvalid(currentState, requestedAction) {
322
+ return new _MAPRequestError(
323
+ AGENT_ERROR_CODES.STATE_INVALID,
324
+ `Cannot ${requestedAction} agent in state: ${currentState}`,
325
+ { category: "agent" }
326
+ );
327
+ }
328
+ static agentNotResponding(agentId) {
329
+ return new _MAPRequestError(
330
+ AGENT_ERROR_CODES.NOT_RESPONDING,
331
+ `Agent not responding: ${agentId}`,
332
+ { category: "agent", retryable: true }
333
+ );
334
+ }
335
+ static agentTerminated(agentId) {
336
+ return new _MAPRequestError(
337
+ AGENT_ERROR_CODES.TERMINATED,
338
+ `Agent terminated: ${agentId}`,
339
+ { category: "agent" }
340
+ );
341
+ }
342
+ static spawnFailed(details) {
343
+ return new _MAPRequestError(
344
+ AGENT_ERROR_CODES.SPAWN_FAILED,
345
+ details ?? "Failed to spawn agent",
346
+ { category: "agent" }
347
+ );
348
+ }
349
+ // ==========================================================================
350
+ // Resource Errors (4xxx)
351
+ // ==========================================================================
352
+ static resourceExhausted(resource) {
353
+ return new _MAPRequestError(
354
+ RESOURCE_ERROR_CODES.EXHAUSTED,
355
+ resource ? `Resource exhausted: ${resource}` : "Resource exhausted",
356
+ { category: "resource", retryable: true }
357
+ );
358
+ }
359
+ static rateLimited(retryAfterMs) {
360
+ return new _MAPRequestError(
361
+ RESOURCE_ERROR_CODES.RATE_LIMITED,
362
+ "Rate limited",
363
+ { category: "resource", retryable: true, retryAfterMs }
364
+ );
365
+ }
366
+ static quotaExceeded(quota) {
367
+ return new _MAPRequestError(
368
+ RESOURCE_ERROR_CODES.QUOTA_EXCEEDED,
369
+ quota ? `Quota exceeded: ${quota}` : "Quota exceeded",
370
+ { category: "resource" }
371
+ );
372
+ }
373
+ // ==========================================================================
374
+ // Federation Errors (5xxx)
375
+ // ==========================================================================
376
+ static federationUnavailable(systemId) {
377
+ return new _MAPRequestError(
378
+ FEDERATION_ERROR_CODES.FEDERATION_UNAVAILABLE,
379
+ systemId ? `Federation unavailable: ${systemId}` : "Federation unavailable",
380
+ { category: "federation", retryable: true }
381
+ );
382
+ }
383
+ static federationSystemNotFound(systemId) {
384
+ return new _MAPRequestError(
385
+ FEDERATION_ERROR_CODES.FEDERATION_SYSTEM_NOT_FOUND,
386
+ `System not found: ${systemId}`,
387
+ { category: "federation" }
388
+ );
389
+ }
390
+ static federationAuthFailed(systemId) {
391
+ return new _MAPRequestError(
392
+ FEDERATION_ERROR_CODES.FEDERATION_AUTH_FAILED,
393
+ `Federation authentication failed: ${systemId}`,
394
+ { category: "federation" }
395
+ );
396
+ }
397
+ static federationRouteRejected(systemId, reason) {
398
+ return new _MAPRequestError(
399
+ FEDERATION_ERROR_CODES.FEDERATION_ROUTE_REJECTED,
400
+ reason ? `Route rejected by ${systemId}: ${reason}` : `Route rejected by ${systemId}`,
401
+ { category: "federation" }
402
+ );
403
+ }
404
+ // ==========================================================================
405
+ // Utility
406
+ // ==========================================================================
407
+ /**
408
+ * Create from a MAP error object
409
+ */
410
+ static fromError(error) {
411
+ return new _MAPRequestError(error.code, error.message, error.data);
412
+ }
413
+ /**
414
+ * Check if this error is retryable
415
+ */
416
+ get retryable() {
417
+ return this.data?.retryable ?? false;
418
+ }
419
+ /**
420
+ * Get retry delay in milliseconds, if specified
421
+ */
422
+ get retryAfterMs() {
423
+ return this.data?.retryAfterMs;
424
+ }
425
+ /**
426
+ * Get error category
427
+ */
428
+ get category() {
429
+ return this.data?.category;
430
+ }
431
+ };
432
+ var MAPConnectionError = class _MAPConnectionError extends Error {
433
+ constructor(message) {
434
+ super(message);
435
+ this.name = "MAPConnectionError";
436
+ }
437
+ static closed() {
438
+ return new _MAPConnectionError("Connection closed");
439
+ }
440
+ static timeout() {
441
+ return new _MAPConnectionError("Connection timeout");
442
+ }
443
+ };
444
+ var MAPTimeoutError = class extends Error {
445
+ timeoutMs;
446
+ constructor(operation, timeoutMs) {
447
+ super(`Operation timed out after ${timeoutMs}ms: ${operation}`);
448
+ this.name = "MAPTimeoutError";
449
+ this.timeoutMs = timeoutMs;
450
+ }
451
+ };
452
+
453
+ // src/connection/base.ts
454
+ var BaseConnection = class {
455
+ #stream;
456
+ #pendingResponses = /* @__PURE__ */ new Map();
457
+ #abortController = new AbortController();
458
+ #closedPromise;
459
+ #defaultTimeout;
460
+ #stateChangeHandlers = /* @__PURE__ */ new Set();
461
+ #nextRequestId = 1;
462
+ #writeQueue = Promise.resolve();
463
+ #requestHandler = null;
464
+ #notificationHandler = null;
465
+ #closed = false;
466
+ #closeResolver;
467
+ #state = "initial";
468
+ constructor(stream, options = {}) {
469
+ this.#stream = stream;
470
+ this.#defaultTimeout = options.defaultTimeout ?? 3e4;
471
+ this.#closedPromise = new Promise((resolve) => {
472
+ this.#closeResolver = resolve;
473
+ });
474
+ void this.#startReceiving();
475
+ }
476
+ /**
477
+ * AbortSignal that triggers when the connection closes.
478
+ * Useful for cancelling operations tied to this connection.
479
+ */
480
+ get signal() {
481
+ return this.#abortController.signal;
482
+ }
483
+ /**
484
+ * Promise that resolves when the connection is closed.
485
+ */
486
+ get closed() {
487
+ return this.#closedPromise;
488
+ }
489
+ /**
490
+ * Whether the connection is closed
491
+ */
492
+ get isClosed() {
493
+ return this.#closed;
494
+ }
495
+ /**
496
+ * Set the handler for incoming requests
497
+ */
498
+ setRequestHandler(handler) {
499
+ this.#requestHandler = handler;
500
+ }
501
+ /**
502
+ * Set the handler for incoming notifications
503
+ */
504
+ setNotificationHandler(handler) {
505
+ this.#notificationHandler = handler;
506
+ }
507
+ /**
508
+ * Current connection state
509
+ */
510
+ get state() {
511
+ return this.#state;
512
+ }
513
+ /**
514
+ * Register a handler for state changes.
515
+ *
516
+ * @param handler - Function called when state changes
517
+ * @returns Unsubscribe function to remove the handler
518
+ */
519
+ onStateChange(handler) {
520
+ this.#stateChangeHandlers.add(handler);
521
+ return () => this.#stateChangeHandlers.delete(handler);
522
+ }
523
+ /**
524
+ * Transition to a new state and notify handlers.
525
+ * @internal
526
+ */
527
+ _transitionTo(newState) {
528
+ if (this.#state === newState) return;
529
+ const oldState = this.#state;
530
+ this.#state = newState;
531
+ for (const handler of this.#stateChangeHandlers) {
532
+ try {
533
+ handler(newState, oldState);
534
+ } catch (error) {
535
+ console.error("MAP: State change handler error:", error);
536
+ }
537
+ }
538
+ }
539
+ /**
540
+ * Reconnect with a new stream.
541
+ *
542
+ * This method is used by role-specific connections to replace the
543
+ * underlying transport after a disconnect.
544
+ *
545
+ * @param newStream - The new stream to use
546
+ * @throws If the connection is permanently closed
547
+ */
548
+ async reconnect(newStream) {
549
+ if (this.#state === "closed") {
550
+ throw new Error("Cannot reconnect a permanently closed connection");
551
+ }
552
+ this.#stream = newStream;
553
+ this.#closed = false;
554
+ this.#writeQueue = Promise.resolve();
555
+ void this.#startReceiving();
556
+ this._transitionTo("connected");
557
+ }
558
+ /**
559
+ * Send a request and wait for response
560
+ */
561
+ async sendRequest(method, params, options) {
562
+ if (this.#closed) {
563
+ throw MAPConnectionError.closed();
564
+ }
565
+ const id = this.#nextRequestId++;
566
+ const request = createRequest(id, method, params);
567
+ const responsePromise = new Promise((resolve, reject) => {
568
+ const pending = { resolve, reject };
569
+ const timeout = options?.timeout ?? this.#defaultTimeout;
570
+ if (timeout > 0) {
571
+ pending.timeoutId = setTimeout(() => {
572
+ this.#pendingResponses.delete(id);
573
+ reject(new MAPTimeoutError(method, timeout));
574
+ }, timeout);
575
+ }
576
+ this.#pendingResponses.set(id, pending);
577
+ });
578
+ await this.#sendMessage(request);
579
+ return responsePromise;
580
+ }
581
+ /**
582
+ * Send a notification (no response expected)
583
+ */
584
+ async sendNotification(method, params) {
585
+ if (this.#closed) {
586
+ throw MAPConnectionError.closed();
587
+ }
588
+ const notification = createNotification(method, params);
589
+ await this.#sendMessage(notification);
590
+ }
591
+ /**
592
+ * Send a response to a request
593
+ */
594
+ async sendResponse(id, result) {
595
+ if (this.#closed) {
596
+ throw MAPConnectionError.closed();
597
+ }
598
+ const response = createSuccessResponse(id, result);
599
+ await this.#sendMessage(response);
600
+ }
601
+ /**
602
+ * Send an error response to a request
603
+ */
604
+ async sendErrorResponse(id, error) {
605
+ if (this.#closed) {
606
+ throw MAPConnectionError.closed();
607
+ }
608
+ const response = createErrorResponse(id, error);
609
+ await this.#sendMessage(response);
610
+ }
611
+ /**
612
+ * Close the connection
613
+ */
614
+ async close() {
615
+ if (this.#closed) return;
616
+ this.#closed = true;
617
+ this._transitionTo("closed");
618
+ this.#abortController.abort();
619
+ for (const [, pending] of this.#pendingResponses) {
620
+ if (pending.timeoutId) {
621
+ clearTimeout(pending.timeoutId);
622
+ }
623
+ pending.reject(MAPConnectionError.closed());
624
+ }
625
+ this.#pendingResponses.clear();
626
+ try {
627
+ const writer = this.#stream.writable.getWriter();
628
+ await writer.close();
629
+ writer.releaseLock();
630
+ } catch {
631
+ }
632
+ this.#closeResolver();
633
+ }
634
+ /**
635
+ * Start receiving messages from the stream
636
+ */
637
+ async #startReceiving() {
638
+ const reader = this.#stream.readable.getReader();
639
+ try {
640
+ while (!this.#closed) {
641
+ const { done, value } = await reader.read();
642
+ if (done) {
643
+ break;
644
+ }
645
+ await this.#handleMessage(value);
646
+ }
647
+ } catch (error) {
648
+ if (!this.#closed) {
649
+ console.error("MAP: Error receiving message:", error);
650
+ }
651
+ } finally {
652
+ reader.releaseLock();
653
+ await this.close();
654
+ }
655
+ }
656
+ /**
657
+ * Handle an incoming message
658
+ */
659
+ async #handleMessage(message) {
660
+ try {
661
+ if (isRequest(message)) {
662
+ await this.#handleRequest(message);
663
+ } else if (isNotification(message)) {
664
+ await this.#handleNotification(message);
665
+ } else if (isResponse(message)) {
666
+ this.#handleResponse(message);
667
+ } else {
668
+ console.error("MAP: Unknown message type:", message);
669
+ }
670
+ } catch (error) {
671
+ console.error("MAP: Error handling message:", error);
672
+ }
673
+ }
674
+ /**
675
+ * Handle an incoming request
676
+ */
677
+ async #handleRequest(request) {
678
+ const { id, method, params } = request;
679
+ if (!this.#requestHandler) {
680
+ await this.sendErrorResponse(
681
+ id,
682
+ MAPRequestError.methodNotFound(method).toError()
683
+ );
684
+ return;
685
+ }
686
+ try {
687
+ const result = await this.#requestHandler(method, params);
688
+ await this.sendResponse(id, result ?? null);
689
+ } catch (error) {
690
+ if (error instanceof MAPRequestError) {
691
+ await this.sendErrorResponse(id, error.toError());
692
+ } else {
693
+ const message = error instanceof Error ? error.message : "Unknown error";
694
+ await this.sendErrorResponse(
695
+ id,
696
+ MAPRequestError.internalError(message).toError()
697
+ );
698
+ }
699
+ }
700
+ }
701
+ /**
702
+ * Handle an incoming notification
703
+ */
704
+ async #handleNotification(notification) {
705
+ const { method, params } = notification;
706
+ if (!this.#notificationHandler) {
707
+ console.warn("MAP: No notification handler for:", method);
708
+ return;
709
+ }
710
+ try {
711
+ await this.#notificationHandler(method, params);
712
+ } catch (error) {
713
+ console.error("MAP: Error handling notification:", method, error);
714
+ }
715
+ }
716
+ /**
717
+ * Handle an incoming response
718
+ */
719
+ #handleResponse(response) {
720
+ const { id } = response;
721
+ const pending = this.#pendingResponses.get(id);
722
+ if (!pending) {
723
+ console.warn("MAP: Received response for unknown request:", id);
724
+ return;
725
+ }
726
+ this.#pendingResponses.delete(id);
727
+ if (pending.timeoutId) {
728
+ clearTimeout(pending.timeoutId);
729
+ }
730
+ if (isErrorResponse(response)) {
731
+ pending.reject(MAPRequestError.fromError(response.error));
732
+ } else {
733
+ pending.resolve(response.result);
734
+ }
735
+ }
736
+ /**
737
+ * Send a message through the stream with write queue serialization
738
+ */
739
+ async #sendMessage(message) {
740
+ this.#writeQueue = this.#writeQueue.then(async () => {
741
+ if (this.#closed) return;
742
+ const writer = this.#stream.writable.getWriter();
743
+ try {
744
+ await writer.write(message);
745
+ } finally {
746
+ writer.releaseLock();
747
+ }
748
+ }).catch((error) => {
749
+ console.error("MAP: Write error:", error);
750
+ });
751
+ return this.#writeQueue;
752
+ }
753
+ };
754
+
755
+ // src/federation/envelope.ts
756
+ function processFederationEnvelope(envelope, config) {
757
+ const { federation } = envelope;
758
+ const maxHops = federation.maxHops ?? config.maxHops ?? 10;
759
+ if (federation.hopCount >= maxHops) {
760
+ return {
761
+ success: false,
762
+ errorCode: ERROR_CODES.FEDERATION_MAX_HOPS_EXCEEDED,
763
+ errorMessage: `Message exceeded maximum hop count of ${maxHops}`
764
+ };
765
+ }
766
+ if (federation.path?.includes(config.systemId)) {
767
+ return {
768
+ success: false,
769
+ errorCode: ERROR_CODES.FEDERATION_LOOP_DETECTED,
770
+ errorMessage: `Loop detected: message already visited ${config.systemId}`
771
+ };
772
+ }
773
+ if (config.allowedSources && !config.allowedSources.includes(federation.sourceSystem)) {
774
+ return {
775
+ success: false,
776
+ errorCode: ERROR_CODES.FEDERATION_ROUTE_REJECTED,
777
+ errorMessage: `Source system ${federation.sourceSystem} not in allowed sources`
778
+ };
779
+ }
780
+ if (config.allowedTargets && !config.allowedTargets.includes(federation.targetSystem)) {
781
+ return {
782
+ success: false,
783
+ errorCode: ERROR_CODES.FEDERATION_ROUTE_REJECTED,
784
+ errorMessage: `Target system ${federation.targetSystem} not in allowed targets`
785
+ };
786
+ }
787
+ return {
788
+ success: true,
789
+ envelope: {
790
+ payload: envelope.payload,
791
+ federation: {
792
+ ...federation,
793
+ hopCount: federation.hopCount + 1,
794
+ path: config.trackPath ? [...federation.path ?? [], config.systemId] : federation.path
795
+ }
796
+ }
797
+ };
798
+ }
799
+ function unwrapEnvelope(envelope) {
800
+ return envelope.payload;
801
+ }
802
+ function isValidEnvelope(obj) {
803
+ if (!obj || typeof obj !== "object") return false;
804
+ const envelope = obj;
805
+ if (!("payload" in envelope) || !("federation" in envelope)) return false;
806
+ const federation = envelope.federation;
807
+ if (!federation || typeof federation !== "object") return false;
808
+ return typeof federation.sourceSystem === "string" && typeof federation.targetSystem === "string" && typeof federation.hopCount === "number" && typeof federation.originTimestamp === "number";
809
+ }
810
+
811
+ // src/protocol/index.ts
812
+ function buildConnectResponse(params) {
813
+ return {
814
+ protocolVersion: params.protocolVersion,
815
+ sessionId: params.sessionId,
816
+ participantId: params.participantId,
817
+ capabilities: params.capabilities,
818
+ systemInfo: params.systemInfo,
819
+ reconnected: params.reconnected,
820
+ reclaimedAgents: params.reclaimedAgents,
821
+ ownedAgents: params.ownedAgents
822
+ };
823
+ }
824
+ function buildSendResponse(messageId, delivered) {
825
+ return { messageId, delivered };
826
+ }
827
+ function buildAgentsRegisterResponse(agent) {
828
+ return { agent };
829
+ }
830
+ function buildAgentsUnregisterResponse(agent) {
831
+ return { agent };
832
+ }
833
+ function buildAgentsListResponse(agents) {
834
+ return { agents };
835
+ }
836
+ function buildAgentsGetResponse(agent, children, descendants) {
837
+ const result = { agent };
838
+ if (children) result.children = children;
839
+ if (descendants) result.descendants = descendants;
840
+ return result;
841
+ }
842
+ function buildAgentsUpdateResponse(agent) {
843
+ return { agent };
844
+ }
845
+ function buildScopesCreateResponse(scope) {
846
+ return { scope };
847
+ }
848
+ function buildScopesListResponse(scopes) {
849
+ return { scopes };
850
+ }
851
+ function buildScopesJoinResponse(scope, agent) {
852
+ return { scope, agent };
853
+ }
854
+ function buildScopesLeaveResponse(scope, agent) {
855
+ return { scope, agent };
856
+ }
857
+ function buildSubscribeResponse(subscriptionId) {
858
+ return { subscriptionId };
859
+ }
860
+ function buildUnsubscribeResponse(subscriptionId, closedAt = Date.now()) {
861
+ return {
862
+ subscription: {
863
+ id: subscriptionId,
864
+ closedAt
865
+ }
866
+ };
867
+ }
868
+
869
+ // src/permissions/index.ts
870
+ function isAgentExposed(exposure, agentId) {
871
+ if (!exposure?.agents) return true;
872
+ const {
873
+ publicByDefault = true,
874
+ publicAgents = [],
875
+ hiddenAgents = []
876
+ } = exposure.agents;
877
+ if (matchesPatterns(agentId, hiddenAgents)) return false;
878
+ if (matchesPatterns(agentId, publicAgents)) return true;
879
+ return publicByDefault;
880
+ }
881
+ function isEventTypeExposed(exposure, eventType) {
882
+ if (!exposure?.events) return true;
883
+ const { exposedTypes, hiddenTypes = [] } = exposure.events;
884
+ if (hiddenTypes.includes(eventType)) return false;
885
+ if (exposedTypes && !exposedTypes.includes(eventType)) return false;
886
+ return true;
887
+ }
888
+ function isScopeExposed(exposure, scopeId) {
889
+ if (!exposure?.scopes) return true;
890
+ const {
891
+ publicByDefault = true,
892
+ publicScopes = [],
893
+ hiddenScopes = []
894
+ } = exposure.scopes;
895
+ if (matchesPatterns(scopeId, hiddenScopes)) return false;
896
+ if (matchesPatterns(scopeId, publicScopes)) return true;
897
+ return publicByDefault;
898
+ }
899
+ function canSeeScope(scope, participant, memberAgentIds = []) {
900
+ const visibility = scope.visibility ?? "public";
901
+ switch (visibility) {
902
+ case "public":
903
+ return true;
904
+ case "members":
905
+ return memberAgentIds.length > 0;
906
+ case "system":
907
+ return participant.type === "system";
908
+ default:
909
+ return false;
910
+ }
911
+ }
912
+ function canSeeAgent(agent, participant, ownedAgentIds = []) {
913
+ const visibility = agent.visibility ?? "public";
914
+ switch (visibility) {
915
+ case "public":
916
+ return true;
917
+ case "parent-only":
918
+ if (ownedAgentIds.includes(agent.id)) return true;
919
+ return agent.parent ? ownedAgentIds.includes(agent.parent) : false;
920
+ case "scope":
921
+ return true;
922
+ case "system":
923
+ return participant.type === "system";
924
+ default:
925
+ return false;
926
+ }
927
+ }
928
+ function filterVisibleAgents(agents, context) {
929
+ const ownedAgentIds = context.ownedAgentIds ?? [];
930
+ return agents.filter((agent) => {
931
+ if (!isAgentExposed(context.system.exposure, agent.id)) {
932
+ return false;
933
+ }
934
+ if (!canSeeAgent(agent, context.participant, ownedAgentIds)) {
935
+ return false;
936
+ }
937
+ return true;
938
+ });
939
+ }
940
+ function filterVisibleScopes(scopes, context) {
941
+ const scopeMembership = context.scopeMembership ?? /* @__PURE__ */ new Map();
942
+ return scopes.filter((scope) => {
943
+ if (!isScopeExposed(context.system.exposure, scope.id)) {
944
+ return false;
945
+ }
946
+ const memberAgentIds = scopeMembership.get(scope.id) ?? [];
947
+ if (!canSeeScope(scope, context.participant, memberAgentIds)) {
948
+ return false;
949
+ }
950
+ return true;
951
+ });
952
+ }
953
+ function matchesPatterns(value, patterns) {
954
+ return patterns.some((pattern) => matchGlob(value, pattern));
955
+ }
956
+ function matchGlob(value, pattern) {
957
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
958
+ const regex = new RegExp(`^${escaped}$`);
959
+ return regex.test(value);
960
+ }
961
+
962
+ // src/utils/retry.ts
963
+ var DEFAULT_RETRY_POLICY = {
964
+ maxRetries: 10,
965
+ baseDelayMs: 1e3,
966
+ maxDelayMs: 3e4,
967
+ jitter: true
968
+ };
969
+ function calculateDelay(attempt, policy) {
970
+ let delay = Math.min(
971
+ policy.baseDelayMs * Math.pow(2, attempt - 1),
972
+ policy.maxDelayMs
973
+ );
974
+ if (policy.jitter) {
975
+ delay = delay * (0.5 + Math.random());
976
+ }
977
+ return Math.floor(delay);
978
+ }
979
+ async function withRetry(operation, policy = DEFAULT_RETRY_POLICY, callbacks) {
980
+ let lastError;
981
+ for (let attempt = 1; attempt <= policy.maxRetries + 1; attempt++) {
982
+ try {
983
+ const result = await operation();
984
+ callbacks?.onSuccess?.(result, attempt);
985
+ return result;
986
+ } catch (error) {
987
+ lastError = error;
988
+ const isRetryable = policy.isRetryable?.(lastError) ?? true;
989
+ const hasMoreAttempts = attempt <= policy.maxRetries;
990
+ if (!isRetryable || !hasMoreAttempts) {
991
+ break;
992
+ }
993
+ const delay = calculateDelay(attempt, policy);
994
+ callbacks?.onRetry?.({
995
+ attempt,
996
+ nextDelayMs: delay,
997
+ lastError
998
+ });
999
+ await sleep(delay);
1000
+ }
1001
+ }
1002
+ callbacks?.onFailure?.(lastError, policy.maxRetries + 1);
1003
+ throw lastError;
1004
+ }
1005
+ function sleep(ms) {
1006
+ return new Promise((resolve) => setTimeout(resolve, ms));
1007
+ }
1008
+
1009
+ // src/testing/server.ts
1010
+ var TestServer = class {
1011
+ #options;
1012
+ #participants = /* @__PURE__ */ new Map();
1013
+ #agents = /* @__PURE__ */ new Map();
1014
+ #scopes = /* @__PURE__ */ new Map();
1015
+ #subscriptions = /* @__PURE__ */ new Map();
1016
+ #messages = [];
1017
+ #eventHistory = [];
1018
+ #maxEventHistory;
1019
+ /**
1020
+ * Tracks recent event IDs for causal linking.
1021
+ * Key format: "type:source" or "type:messageId" for message events
1022
+ */
1023
+ #recentEventIds = /* @__PURE__ */ new Map();
1024
+ #sessionId;
1025
+ #nextParticipantId = 1;
1026
+ #nextAgentId = 1;
1027
+ #nextScopeId = 1;
1028
+ #nextSubscriptionId = 1;
1029
+ #nextMessageId = 1;
1030
+ constructor(options = {}) {
1031
+ this.#options = options;
1032
+ this.#sessionId = `session-${Date.now()}`;
1033
+ this.#maxEventHistory = options.maxEventHistory ?? 1e4;
1034
+ }
1035
+ /**
1036
+ * Accept a new connection
1037
+ */
1038
+ acceptConnection(stream) {
1039
+ const connection = new BaseConnection(stream);
1040
+ connection.setRequestHandler(this.#handleRequest.bind(this, connection));
1041
+ return connection;
1042
+ }
1043
+ /**
1044
+ * Get current session ID
1045
+ */
1046
+ get sessionId() {
1047
+ return this.#sessionId;
1048
+ }
1049
+ /**
1050
+ * Get all registered agents
1051
+ */
1052
+ get agents() {
1053
+ return this.#agents;
1054
+ }
1055
+ /**
1056
+ * Get all scopes
1057
+ */
1058
+ get scopes() {
1059
+ return this.#scopes;
1060
+ }
1061
+ /**
1062
+ * Get all messages sent through the server
1063
+ */
1064
+ get messages() {
1065
+ return this.#messages;
1066
+ }
1067
+ /**
1068
+ * Get connected participant count
1069
+ */
1070
+ get participantCount() {
1071
+ return this.#participants.size;
1072
+ }
1073
+ /**
1074
+ * Get event history for replay testing
1075
+ */
1076
+ get eventHistory() {
1077
+ return this.#eventHistory;
1078
+ }
1079
+ /**
1080
+ * Emit an event to subscribers
1081
+ * @param event - The event to emit (without id/timestamp)
1082
+ * @param causedBy - Optional array of event IDs that caused this event
1083
+ * @returns The generated event ID
1084
+ */
1085
+ emitEvent(event, causedBy) {
1086
+ const eventId = ulid();
1087
+ const timestamp = Date.now();
1088
+ const fullEvent = {
1089
+ id: `evt-${timestamp}-${Math.random().toString(36).slice(2)}`,
1090
+ timestamp,
1091
+ ...event
1092
+ };
1093
+ if (!isEventTypeExposed(this.#options.exposure, event.type)) {
1094
+ return eventId;
1095
+ }
1096
+ this.#eventHistory.push({ eventId, timestamp, event: fullEvent, causedBy });
1097
+ const trackingKey = event.source ? `${event.type}:${event.source}` : event.type;
1098
+ this.#recentEventIds.set(trackingKey, eventId);
1099
+ const eventData = event.data;
1100
+ if (eventData?.messageId) {
1101
+ this.#recentEventIds.set(`message:${eventData.messageId}`, eventId);
1102
+ }
1103
+ if (eventData?.agentId) {
1104
+ this.#recentEventIds.set(`${event.type}:agent:${eventData.agentId}`, eventId);
1105
+ }
1106
+ while (this.#eventHistory.length > this.#maxEventHistory) {
1107
+ this.#eventHistory.shift();
1108
+ }
1109
+ for (const subscription of this.#subscriptions.values()) {
1110
+ if (this.#matchesFilter(fullEvent, subscription.filter)) {
1111
+ const participant = this.#participants.get(subscription.participantId);
1112
+ if (participant) {
1113
+ subscription.sequenceNumber++;
1114
+ participant.connection.sendNotification(NOTIFICATION_METHODS.EVENT, {
1115
+ subscriptionId: subscription.id,
1116
+ sequenceNumber: subscription.sequenceNumber,
1117
+ eventId,
1118
+ timestamp,
1119
+ event: fullEvent,
1120
+ causedBy
1121
+ });
1122
+ }
1123
+ }
1124
+ }
1125
+ return eventId;
1126
+ }
1127
+ /**
1128
+ * Deliver a message notification to a participant
1129
+ */
1130
+ deliverMessage(participantId, message) {
1131
+ const participant = this.#participants.get(participantId);
1132
+ if (participant) {
1133
+ participant.connection.sendNotification(NOTIFICATION_METHODS.MESSAGE, {
1134
+ message
1135
+ });
1136
+ }
1137
+ }
1138
+ /**
1139
+ * Handle incoming requests
1140
+ */
1141
+ async #handleRequest(connection, method, params) {
1142
+ switch (method) {
1143
+ // =======================================================================
1144
+ // Core Methods
1145
+ // =======================================================================
1146
+ case CORE_METHODS.CONNECT:
1147
+ return this.#handleConnect(connection, params);
1148
+ case CORE_METHODS.DISCONNECT:
1149
+ return this.#handleDisconnect(connection, params);
1150
+ case CORE_METHODS.SEND:
1151
+ return this.#handleSend(connection, params);
1152
+ case CORE_METHODS.SUBSCRIBE:
1153
+ return this.#handleSubscribe(connection, params);
1154
+ case CORE_METHODS.UNSUBSCRIBE:
1155
+ return this.#handleUnsubscribe(connection, params);
1156
+ case CORE_METHODS.REPLAY:
1157
+ return this.#handleReplay(params);
1158
+ // =======================================================================
1159
+ // Observation Methods
1160
+ // =======================================================================
1161
+ case OBSERVATION_METHODS.AGENTS_LIST:
1162
+ return this.#handleAgentsList(connection, params);
1163
+ case OBSERVATION_METHODS.AGENTS_GET:
1164
+ return this.#handleAgentsGet(params);
1165
+ case OBSERVATION_METHODS.SCOPES_LIST:
1166
+ return this.#handleScopesList(connection);
1167
+ // =======================================================================
1168
+ // Lifecycle Methods
1169
+ // =======================================================================
1170
+ case LIFECYCLE_METHODS.AGENTS_REGISTER:
1171
+ return this.#handleAgentsRegister(connection, params);
1172
+ case LIFECYCLE_METHODS.AGENTS_UNREGISTER:
1173
+ return this.#handleAgentsUnregister(params);
1174
+ case LIFECYCLE_METHODS.AGENTS_SPAWN:
1175
+ return this.#handleAgentsSpawn(
1176
+ connection,
1177
+ params
1178
+ );
1179
+ // =======================================================================
1180
+ // State Methods
1181
+ // =======================================================================
1182
+ case STATE_METHODS.AGENTS_UPDATE:
1183
+ return this.#handleAgentsUpdate(connection, params);
1184
+ case STATE_METHODS.AGENTS_STOP:
1185
+ return this.#handleAgentsStop(params);
1186
+ // =======================================================================
1187
+ // Scope Methods
1188
+ // =======================================================================
1189
+ case SCOPE_METHODS.SCOPES_CREATE:
1190
+ return this.#handleScopesCreate(params);
1191
+ case SCOPE_METHODS.SCOPES_JOIN:
1192
+ return this.#handleScopesJoin(params);
1193
+ case SCOPE_METHODS.SCOPES_LEAVE:
1194
+ return this.#handleScopesLeave(params);
1195
+ // =======================================================================
1196
+ // Steering Methods
1197
+ // =======================================================================
1198
+ case STEERING_METHODS.INJECT:
1199
+ return this.#handleInject(params);
1200
+ // =======================================================================
1201
+ // Federation Methods
1202
+ // =======================================================================
1203
+ case FEDERATION_METHODS.FEDERATION_ROUTE:
1204
+ return this.#handleFederationRoute(params);
1205
+ // =======================================================================
1206
+ // Notification Methods (client → server)
1207
+ // =======================================================================
1208
+ case NOTIFICATION_METHODS.SUBSCRIBE_ACK:
1209
+ return this.#handleSubscriptionAck(params);
1210
+ // =======================================================================
1211
+ // Permission Methods
1212
+ // =======================================================================
1213
+ case PERMISSION_METHODS.PERMISSIONS_UPDATE:
1214
+ return this.#handlePermissionsUpdate(
1215
+ connection,
1216
+ params
1217
+ );
1218
+ default:
1219
+ throw MAPRequestError.methodNotFound(method);
1220
+ }
1221
+ }
1222
+ // ===========================================================================
1223
+ // Core Method Handlers
1224
+ // ===========================================================================
1225
+ #handleConnect(connection, params) {
1226
+ const participantId = params.participantId || `participant-${this.#nextParticipantId++}`;
1227
+ const participant = {
1228
+ id: participantId,
1229
+ type: params.participantType,
1230
+ name: params.name,
1231
+ connection,
1232
+ subscriptions: /* @__PURE__ */ new Set(),
1233
+ capabilities: params.capabilities
1234
+ };
1235
+ this.#participants.set(participantId, participant);
1236
+ connection.closed.then(() => {
1237
+ this.#handleParticipantDisconnect(participantId);
1238
+ });
1239
+ this.emitEvent({
1240
+ type: EVENT_TYPES.PARTICIPANT_CONNECTED,
1241
+ source: participantId,
1242
+ data: { participantId, participantType: params.participantType, name: params.name }
1243
+ });
1244
+ return buildConnectResponse({
1245
+ protocolVersion: PROTOCOL_VERSION,
1246
+ sessionId: this.#sessionId,
1247
+ participantId,
1248
+ capabilities: this.#options.capabilities ?? {
1249
+ observation: { canObserve: true, canQuery: true },
1250
+ messaging: { canSend: true, canReceive: true, canBroadcast: true },
1251
+ lifecycle: { canSpawn: true, canRegister: true, canUnregister: true, canStop: true, canSteer: true },
1252
+ scopes: { canCreateScopes: true, canManageScopes: true },
1253
+ streaming: { supportsAck: true, supportsPause: true }
1254
+ },
1255
+ systemInfo: {
1256
+ name: this.#options.name ?? "Test MAP Server",
1257
+ version: this.#options.version ?? "1.0.0"
1258
+ }
1259
+ });
1260
+ }
1261
+ #handleDisconnect(connection, params) {
1262
+ for (const [id, participant] of this.#participants) {
1263
+ if (participant.connection === connection) {
1264
+ this.#handleParticipantDisconnect(id, params?.policy);
1265
+ break;
1266
+ }
1267
+ }
1268
+ return { acknowledged: true };
1269
+ }
1270
+ #handleParticipantDisconnect(participantId, policy) {
1271
+ const participant = this.#participants.get(participantId);
1272
+ if (!participant) return;
1273
+ for (const subId of participant.subscriptions) {
1274
+ this.#subscriptions.delete(subId);
1275
+ }
1276
+ const disconnectEventId = this.emitEvent({
1277
+ type: EVENT_TYPES.PARTICIPANT_DISCONNECTED,
1278
+ source: participantId,
1279
+ data: { participantId, participantType: participant.type }
1280
+ });
1281
+ const ownedAgents = Array.from(this.#agents.values()).filter((a) => a.ownerId === participantId);
1282
+ for (const agent of ownedAgents) {
1283
+ const agentBehavior = policy?.agentBehavior ?? "unregister";
1284
+ if (agentBehavior === "unregister") {
1285
+ this.#terminateAgentTree(agent.id, [disconnectEventId]);
1286
+ } else if (agentBehavior === "orphan") {
1287
+ const previousOwner = agent.ownerId;
1288
+ agent.ownerId = null;
1289
+ this.emitEvent(
1290
+ {
1291
+ type: EVENT_TYPES.AGENT_ORPHANED,
1292
+ source: agent.id,
1293
+ data: { agentId: agent.id, previousOwner }
1294
+ },
1295
+ [disconnectEventId]
1296
+ // Caused by the disconnect event
1297
+ );
1298
+ }
1299
+ }
1300
+ this.#participants.delete(participantId);
1301
+ }
1302
+ /**
1303
+ * Terminate an agent and all its descendants
1304
+ * @param agentId - Agent to terminate
1305
+ * @param causedBy - Event IDs that caused this termination (e.g., disconnect event)
1306
+ */
1307
+ #terminateAgentTree(agentId, causedBy) {
1308
+ const children = Array.from(this.#agents.values()).filter((a) => a.parent === agentId);
1309
+ for (const child of children) {
1310
+ this.#terminateAgentTree(child.id, causedBy);
1311
+ }
1312
+ const agent = this.#agents.get(agentId);
1313
+ if (agent) {
1314
+ this.#agents.delete(agentId);
1315
+ for (const scope of this.#scopes.values()) {
1316
+ scope.members.delete(agentId);
1317
+ }
1318
+ this.emitEvent(
1319
+ {
1320
+ type: EVENT_TYPES.AGENT_UNREGISTERED,
1321
+ source: agentId,
1322
+ data: { agentId, reason: "owner_disconnected" }
1323
+ },
1324
+ causedBy
1325
+ );
1326
+ }
1327
+ }
1328
+ #handleAgentsList(connection, params) {
1329
+ let agents = Array.from(this.#agents.values());
1330
+ const permissionContext = this.#buildPermissionContext(connection);
1331
+ if (permissionContext) {
1332
+ agents = filterVisibleAgents(agents, permissionContext);
1333
+ }
1334
+ if (params?.filter) {
1335
+ const { states, roles, scopes, parent } = params.filter;
1336
+ if (states?.length) {
1337
+ agents = agents.filter((a) => states.includes(a.state));
1338
+ }
1339
+ if (roles?.length) {
1340
+ agents = agents.filter((a) => a.role && roles.includes(a.role));
1341
+ }
1342
+ if (scopes?.length) {
1343
+ agents = agents.filter((a) => a.scopes?.some((s) => scopes.includes(s)));
1344
+ }
1345
+ if (parent) {
1346
+ agents = agents.filter((a) => a.parent === parent);
1347
+ }
1348
+ }
1349
+ return buildAgentsListResponse(agents);
1350
+ }
1351
+ #handleAgentsGet(params) {
1352
+ const agent = this.#agents.get(params.agentId);
1353
+ if (!agent) {
1354
+ throw MAPRequestError.agentNotFound(params.agentId);
1355
+ }
1356
+ const children = params.include?.children ? Array.from(this.#agents.values()).filter((a) => a.parent === params.agentId) : void 0;
1357
+ const descendants = params.include?.descendants ? this.#getDescendants(params.agentId) : void 0;
1358
+ return buildAgentsGetResponse(agent, children, descendants);
1359
+ }
1360
+ /**
1361
+ * Get all descendants of an agent recursively
1362
+ */
1363
+ #getDescendants(agentId) {
1364
+ const descendants = [];
1365
+ const children = Array.from(this.#agents.values()).filter((a) => a.parent === agentId);
1366
+ for (const child of children) {
1367
+ descendants.push(child);
1368
+ descendants.push(...this.#getDescendants(child.id));
1369
+ }
1370
+ return descendants;
1371
+ }
1372
+ #handleSend(connection, params) {
1373
+ const messageId = `msg-${this.#nextMessageId++}`;
1374
+ const delivered = [];
1375
+ let senderId;
1376
+ let senderAgentId;
1377
+ for (const [id, participant] of this.#participants) {
1378
+ if (participant.connection === connection) {
1379
+ senderId = id;
1380
+ senderAgentId = participant.agentId;
1381
+ break;
1382
+ }
1383
+ }
1384
+ const message = {
1385
+ id: messageId,
1386
+ from: senderId ?? "unknown",
1387
+ to: params.to,
1388
+ timestamp: Date.now(),
1389
+ payload: params.payload,
1390
+ meta: params.meta
1391
+ };
1392
+ this.#messages.push(message);
1393
+ const recipients = this.#resolveAddress(params.to, senderAgentId);
1394
+ for (const recipientId of recipients) {
1395
+ const participant = this.#participants.get(recipientId);
1396
+ if (participant) {
1397
+ this.deliverMessage(recipientId, message);
1398
+ delivered.push(recipientId);
1399
+ }
1400
+ }
1401
+ this.emitEvent({
1402
+ type: EVENT_TYPES.MESSAGE_SENT,
1403
+ source: senderId,
1404
+ data: { messageId, to: params.to }
1405
+ });
1406
+ return buildSendResponse(messageId, delivered);
1407
+ }
1408
+ #handleSubscribe(connection, params) {
1409
+ let participantId;
1410
+ for (const [id, participant] of this.#participants) {
1411
+ if (participant.connection === connection) {
1412
+ participantId = id;
1413
+ break;
1414
+ }
1415
+ }
1416
+ if (!participantId) {
1417
+ throw MAPRequestError.authRequired();
1418
+ }
1419
+ const subscriptionId = `sub-${this.#nextSubscriptionId++}`;
1420
+ const subscription = {
1421
+ id: subscriptionId,
1422
+ participantId,
1423
+ filter: params?.filter,
1424
+ sequenceNumber: 0
1425
+ };
1426
+ this.#subscriptions.set(subscriptionId, subscription);
1427
+ this.#participants.get(participantId).subscriptions.add(subscriptionId);
1428
+ return buildSubscribeResponse(subscriptionId);
1429
+ }
1430
+ #handleUnsubscribe(_connection, params) {
1431
+ const subscription = this.#subscriptions.get(params.subscriptionId);
1432
+ if (subscription) {
1433
+ this.#subscriptions.delete(params.subscriptionId);
1434
+ const participant = this.#participants.get(subscription.participantId);
1435
+ if (participant) {
1436
+ participant.subscriptions.delete(params.subscriptionId);
1437
+ }
1438
+ }
1439
+ return buildUnsubscribeResponse(params.subscriptionId);
1440
+ }
1441
+ #handleReplay(params) {
1442
+ const {
1443
+ afterEventId,
1444
+ fromTimestamp,
1445
+ toTimestamp,
1446
+ filter,
1447
+ limit = 100
1448
+ } = params ?? {};
1449
+ let events = [...this.#eventHistory];
1450
+ if (afterEventId) {
1451
+ const idx = events.findIndex((e) => e.eventId === afterEventId);
1452
+ if (idx >= 0) {
1453
+ events = events.slice(idx + 1);
1454
+ }
1455
+ }
1456
+ if (fromTimestamp !== void 0) {
1457
+ events = events.filter((e) => e.timestamp >= fromTimestamp);
1458
+ }
1459
+ if (toTimestamp !== void 0) {
1460
+ events = events.filter((e) => e.timestamp <= toTimestamp);
1461
+ }
1462
+ if (filter) {
1463
+ events = events.filter((e) => this.#matchesFilter(e.event, filter));
1464
+ }
1465
+ const hasMore = events.length > limit;
1466
+ events = events.slice(0, Math.min(limit, 1e3));
1467
+ return {
1468
+ events: events.map((e) => ({
1469
+ eventId: e.eventId,
1470
+ timestamp: e.timestamp,
1471
+ event: e.event,
1472
+ causedBy: e.causedBy
1473
+ })),
1474
+ hasMore,
1475
+ totalCount: this.#eventHistory.length
1476
+ };
1477
+ }
1478
+ // ===========================================================================
1479
+ // Structure Method Handlers
1480
+ // ===========================================================================
1481
+ #handleAgentsRegister(connection, params) {
1482
+ const agentId = params.agentId || `agent-${this.#nextAgentId++}`;
1483
+ if (this.#agents.has(agentId)) {
1484
+ throw MAPRequestError.agentExists(agentId);
1485
+ }
1486
+ let ownerId = "unknown";
1487
+ for (const [participantId, participant] of this.#participants) {
1488
+ if (participant.connection === connection) {
1489
+ ownerId = participantId;
1490
+ participant.agentId = agentId;
1491
+ break;
1492
+ }
1493
+ }
1494
+ const agent = {
1495
+ id: agentId,
1496
+ name: params.name,
1497
+ description: params.description,
1498
+ role: params.role,
1499
+ parent: params.parent,
1500
+ ownerId,
1501
+ // v2: Track which participant owns this agent
1502
+ state: "registered",
1503
+ scopes: params.scopes,
1504
+ visibility: params.visibility,
1505
+ capabilities: params.capabilities,
1506
+ metadata: params.metadata,
1507
+ permissionOverrides: params.permissionOverrides,
1508
+ lifecycle: {
1509
+ createdAt: Date.now()
1510
+ }
1511
+ };
1512
+ this.#agents.set(agentId, agent);
1513
+ this.emitEvent({
1514
+ type: EVENT_TYPES.AGENT_REGISTERED,
1515
+ source: agentId,
1516
+ data: { agentId, name: agent.name, role: agent.role, ownerId }
1517
+ });
1518
+ return buildAgentsRegisterResponse(agent);
1519
+ }
1520
+ #handleAgentsUnregister(params) {
1521
+ const agent = this.#agents.get(params.agentId);
1522
+ if (!agent) {
1523
+ throw MAPRequestError.agentNotFound(params.agentId);
1524
+ }
1525
+ const agentCopy = { ...agent };
1526
+ this.#agents.delete(params.agentId);
1527
+ for (const scope of this.#scopes.values()) {
1528
+ scope.members.delete(params.agentId);
1529
+ }
1530
+ this.emitEvent({
1531
+ type: EVENT_TYPES.AGENT_UNREGISTERED,
1532
+ source: params.agentId,
1533
+ data: { agentId: params.agentId, reason: params.reason }
1534
+ });
1535
+ return buildAgentsUnregisterResponse(agentCopy);
1536
+ }
1537
+ #handleAgentsUpdate(connection, params) {
1538
+ const agent = this.#agents.get(params.agentId);
1539
+ if (!agent) {
1540
+ throw MAPRequestError.agentNotFound(params.agentId);
1541
+ }
1542
+ let requestingParticipantId;
1543
+ for (const [id, p] of this.#participants) {
1544
+ if (p.connection === connection) {
1545
+ requestingParticipantId = id;
1546
+ break;
1547
+ }
1548
+ }
1549
+ const previousState = agent.state;
1550
+ if (params.state) {
1551
+ agent.state = params.state;
1552
+ }
1553
+ if (params.metadata) {
1554
+ agent.metadata = { ...agent.metadata, ...params.metadata };
1555
+ }
1556
+ if (params.permissionOverrides) {
1557
+ const existingOverrides = agent.permissionOverrides ?? {};
1558
+ const newOverrides = {};
1559
+ if (existingOverrides.canSee || params.permissionOverrides.canSee) {
1560
+ newOverrides.canSee = {
1561
+ ...existingOverrides.canSee,
1562
+ ...params.permissionOverrides.canSee
1563
+ };
1564
+ }
1565
+ if (existingOverrides.canMessage || params.permissionOverrides.canMessage) {
1566
+ newOverrides.canMessage = {
1567
+ ...existingOverrides.canMessage,
1568
+ ...params.permissionOverrides.canMessage
1569
+ };
1570
+ }
1571
+ if (existingOverrides.acceptsFrom || params.permissionOverrides.acceptsFrom) {
1572
+ newOverrides.acceptsFrom = {
1573
+ ...existingOverrides.acceptsFrom,
1574
+ ...params.permissionOverrides.acceptsFrom
1575
+ };
1576
+ }
1577
+ agent.permissionOverrides = newOverrides;
1578
+ this.emitEvent({
1579
+ type: EVENT_TYPES.PERMISSIONS_AGENT_UPDATED,
1580
+ source: params.agentId,
1581
+ data: {
1582
+ agentId: params.agentId,
1583
+ changes: params.permissionOverrides,
1584
+ effectivePermissions: newOverrides,
1585
+ updatedBy: requestingParticipantId
1586
+ }
1587
+ });
1588
+ }
1589
+ if (params.state && params.state !== previousState) {
1590
+ this.emitEvent({
1591
+ type: EVENT_TYPES.AGENT_STATE_CHANGED,
1592
+ source: params.agentId,
1593
+ data: { agentId: params.agentId, previousState, newState: params.state }
1594
+ });
1595
+ }
1596
+ return buildAgentsUpdateResponse(agent);
1597
+ }
1598
+ #handleAgentsSpawn(connection, params) {
1599
+ if (!this.#agents.has(params.parent)) {
1600
+ throw MAPRequestError.agentNotFound(params.parent);
1601
+ }
1602
+ return this.#handleAgentsRegister(connection, {
1603
+ name: params.name,
1604
+ role: params.role,
1605
+ parent: params.parent
1606
+ });
1607
+ }
1608
+ #handleAgentsStop(params) {
1609
+ const agent = this.#agents.get(params.agentId);
1610
+ if (!agent) {
1611
+ throw MAPRequestError.agentNotFound(params.agentId);
1612
+ }
1613
+ agent.state = "stopping";
1614
+ this.emitEvent({
1615
+ type: EVENT_TYPES.AGENT_STATE_CHANGED,
1616
+ source: params.agentId,
1617
+ data: { agentId: params.agentId, previousState: agent.state, newState: "stopping" }
1618
+ });
1619
+ return { stopping: true, agent };
1620
+ }
1621
+ #handleScopesList(connection) {
1622
+ let scopes = Array.from(this.#scopes.values()).map(({ members, ...scope }) => scope);
1623
+ const permissionContext = this.#buildPermissionContext(connection);
1624
+ if (permissionContext) {
1625
+ scopes = filterVisibleScopes(scopes, permissionContext);
1626
+ }
1627
+ return buildScopesListResponse(scopes);
1628
+ }
1629
+ #handleScopesCreate(params) {
1630
+ const scopeId = params.scopeId || `scope-${this.#nextScopeId++}`;
1631
+ const scope = {
1632
+ id: scopeId,
1633
+ name: params.name,
1634
+ description: params.description,
1635
+ parent: params.parent,
1636
+ joinPolicy: params.joinPolicy,
1637
+ visibility: params.visibility,
1638
+ members: /* @__PURE__ */ new Set()
1639
+ };
1640
+ this.#scopes.set(scopeId, scope);
1641
+ this.emitEvent({
1642
+ type: EVENT_TYPES.SCOPE_CREATED,
1643
+ data: { scopeId, name: scope.name }
1644
+ });
1645
+ const { members, ...scopeWithoutMembers } = scope;
1646
+ return buildScopesCreateResponse(scopeWithoutMembers);
1647
+ }
1648
+ #handleScopesJoin(params) {
1649
+ const scope = this.#scopes.get(params.scopeId);
1650
+ if (!scope) {
1651
+ throw MAPRequestError.scopeNotFound(params.scopeId);
1652
+ }
1653
+ const agent = this.#agents.get(params.agentId);
1654
+ if (!agent) {
1655
+ throw MAPRequestError.agentNotFound(params.agentId);
1656
+ }
1657
+ scope.members.add(params.agentId);
1658
+ if (!agent.scopes) {
1659
+ agent.scopes = [];
1660
+ }
1661
+ if (!agent.scopes.includes(params.scopeId)) {
1662
+ agent.scopes.push(params.scopeId);
1663
+ }
1664
+ this.emitEvent({
1665
+ type: EVENT_TYPES.SCOPE_MEMBER_JOINED,
1666
+ source: params.agentId,
1667
+ data: { scopeId: params.scopeId, agentId: params.agentId }
1668
+ });
1669
+ const { members, ...scopeWithoutMembers } = scope;
1670
+ return buildScopesJoinResponse(scopeWithoutMembers, agent);
1671
+ }
1672
+ #handleScopesLeave(params) {
1673
+ const scope = this.#scopes.get(params.scopeId);
1674
+ if (!scope) {
1675
+ throw MAPRequestError.scopeNotFound(params.scopeId);
1676
+ }
1677
+ scope.members.delete(params.agentId);
1678
+ const agent = this.#agents.get(params.agentId);
1679
+ if (!agent) {
1680
+ throw MAPRequestError.agentNotFound(params.agentId);
1681
+ }
1682
+ if (agent.scopes) {
1683
+ agent.scopes = agent.scopes.filter((s) => s !== params.scopeId);
1684
+ }
1685
+ this.emitEvent({
1686
+ type: EVENT_TYPES.SCOPE_MEMBER_LEFT,
1687
+ source: params.agentId,
1688
+ data: { scopeId: params.scopeId, agentId: params.agentId }
1689
+ });
1690
+ const { members, ...scopeWithoutMembers } = scope;
1691
+ return buildScopesLeaveResponse(scopeWithoutMembers, agent);
1692
+ }
1693
+ // ===========================================================================
1694
+ // Extension Method Handlers
1695
+ // ===========================================================================
1696
+ #handleInject(params) {
1697
+ const agent = this.#agents.get(params.agentId);
1698
+ if (!agent) {
1699
+ throw MAPRequestError.agentNotFound(params.agentId);
1700
+ }
1701
+ return { injected: true, delivery: params.delivery ?? "queue" };
1702
+ }
1703
+ // ===========================================================================
1704
+ // Helpers
1705
+ // ===========================================================================
1706
+ /**
1707
+ * Build a permission context for a connection.
1708
+ * Returns undefined if no exposure configuration is set.
1709
+ */
1710
+ #buildPermissionContext(connection) {
1711
+ if (!this.#options.exposure) {
1712
+ return void 0;
1713
+ }
1714
+ let participant;
1715
+ for (const p of this.#participants.values()) {
1716
+ if (p.connection === connection) {
1717
+ participant = p;
1718
+ break;
1719
+ }
1720
+ }
1721
+ if (!participant) {
1722
+ return void 0;
1723
+ }
1724
+ const ownedAgentIds = [];
1725
+ for (const agent of this.#agents.values()) {
1726
+ if (agent.ownerId === participant.id) {
1727
+ ownedAgentIds.push(agent.id);
1728
+ }
1729
+ }
1730
+ const scopeMembership = /* @__PURE__ */ new Map();
1731
+ for (const [scopeId, scope] of this.#scopes) {
1732
+ const memberAgentIds = Array.from(scope.members).filter(
1733
+ (agentId) => ownedAgentIds.includes(agentId)
1734
+ );
1735
+ if (memberAgentIds.length > 0) {
1736
+ scopeMembership.set(scopeId, memberAgentIds);
1737
+ }
1738
+ }
1739
+ return {
1740
+ system: { exposure: this.#options.exposure },
1741
+ participant: {
1742
+ id: participant.id,
1743
+ type: participant.type,
1744
+ capabilities: this.#options.capabilities ?? {
1745
+ observation: { canObserve: true, canQuery: true },
1746
+ messaging: { canSend: true, canReceive: true, canBroadcast: true },
1747
+ lifecycle: { canSpawn: true, canRegister: true, canUnregister: true, canStop: true, canSteer: true },
1748
+ scopes: { canCreateScopes: true, canManageScopes: true }
1749
+ }
1750
+ },
1751
+ ownedAgentIds,
1752
+ scopeMembership
1753
+ };
1754
+ }
1755
+ #resolveAddress(address, senderAgentId) {
1756
+ if (typeof address === "string") {
1757
+ return [this.#findParticipantForAgent(address) ?? address];
1758
+ }
1759
+ if ("agent" in address && !("system" in address)) {
1760
+ const participantId = this.#findParticipantForAgent(address.agent);
1761
+ return participantId ? [participantId] : [];
1762
+ }
1763
+ if ("agents" in address) {
1764
+ return address.agents.map((agentId) => this.#findParticipantForAgent(agentId)).filter((id) => id !== void 0);
1765
+ }
1766
+ if ("scope" in address) {
1767
+ const scope = this.#scopes.get(address.scope);
1768
+ if (!scope) return [];
1769
+ return Array.from(scope.members).map((agentId) => this.#findParticipantForAgent(agentId)).filter((id) => id !== void 0);
1770
+ }
1771
+ if ("broadcast" in address) {
1772
+ return Array.from(this.#participants.keys());
1773
+ }
1774
+ if ("role" in address) {
1775
+ const agents = Array.from(this.#agents.values()).filter((a) => a.role === address.role);
1776
+ return agents.map((a) => this.#findParticipantForAgent(a.id)).filter((id) => id !== void 0);
1777
+ }
1778
+ if ("parent" in address || "children" in address || "siblings" in address || "ancestors" in address || "descendants" in address) {
1779
+ return this.#resolveHierarchicalAddress(address, senderAgentId);
1780
+ }
1781
+ return [];
1782
+ }
1783
+ /**
1784
+ * Resolve hierarchical address relative to sender agent
1785
+ */
1786
+ #resolveHierarchicalAddress(address, senderAgentId) {
1787
+ if (!senderAgentId) return [];
1788
+ const senderAgent = this.#agents.get(senderAgentId);
1789
+ if (!senderAgent) return [];
1790
+ const targetAgentIds = [];
1791
+ const depth = address.depth;
1792
+ if (address.parent && senderAgent.parent) {
1793
+ targetAgentIds.push(senderAgent.parent);
1794
+ }
1795
+ if (address.children) {
1796
+ const children = Array.from(this.#agents.values()).filter((a) => a.parent === senderAgentId);
1797
+ targetAgentIds.push(...children.map((a) => a.id));
1798
+ }
1799
+ if (address.siblings && senderAgent.parent) {
1800
+ const siblings = Array.from(this.#agents.values()).filter((a) => a.parent === senderAgent.parent && a.id !== senderAgentId);
1801
+ targetAgentIds.push(...siblings.map((a) => a.id));
1802
+ }
1803
+ if (address.ancestors) {
1804
+ let currentAgent = senderAgent;
1805
+ let currentDepth = 0;
1806
+ while (currentAgent.parent && (depth === void 0 || currentDepth < depth)) {
1807
+ targetAgentIds.push(currentAgent.parent);
1808
+ currentAgent = this.#agents.get(currentAgent.parent);
1809
+ if (!currentAgent) break;
1810
+ currentDepth++;
1811
+ }
1812
+ }
1813
+ if (address.descendants) {
1814
+ const collectDescendants = (agentId, currentDepth) => {
1815
+ if (depth !== void 0 && currentDepth >= depth) return;
1816
+ const children = Array.from(this.#agents.values()).filter((a) => a.parent === agentId);
1817
+ for (const child of children) {
1818
+ targetAgentIds.push(child.id);
1819
+ collectDescendants(child.id, currentDepth + 1);
1820
+ }
1821
+ };
1822
+ collectDescendants(senderAgentId, 0);
1823
+ }
1824
+ return targetAgentIds.map((agentId) => this.#findParticipantForAgent(agentId)).filter((id) => id !== void 0);
1825
+ }
1826
+ /**
1827
+ * Find the participant ID that registered a given agent
1828
+ */
1829
+ #findParticipantForAgent(agentId) {
1830
+ for (const [participantId, participant] of this.#participants) {
1831
+ if (participant.agentId === agentId) {
1832
+ return participantId;
1833
+ }
1834
+ }
1835
+ return void 0;
1836
+ }
1837
+ #matchesFilter(event, filter) {
1838
+ if (!filter) return true;
1839
+ if (filter.eventTypes?.length) {
1840
+ if (!filter.eventTypes.includes(event.type)) {
1841
+ return false;
1842
+ }
1843
+ }
1844
+ if (filter.agents?.length && event.source) {
1845
+ if (!filter.agents.includes(event.source)) {
1846
+ return false;
1847
+ }
1848
+ }
1849
+ if (filter.fromAgents?.length && event.source) {
1850
+ if (!filter.fromAgents.includes(event.source)) {
1851
+ return false;
1852
+ }
1853
+ }
1854
+ if (filter.scopes?.length) {
1855
+ const eventData = event.data;
1856
+ const scopeId = eventData?.scopeId;
1857
+ if (!scopeId || !filter.scopes.includes(scopeId)) {
1858
+ return false;
1859
+ }
1860
+ }
1861
+ if (filter.fromRoles?.length && event.source) {
1862
+ const agent = this.#agents.get(event.source);
1863
+ if (!agent?.role || !filter.fromRoles.includes(agent.role)) {
1864
+ return false;
1865
+ }
1866
+ }
1867
+ if (filter.correlationIds?.length) {
1868
+ const eventData = event.data;
1869
+ const correlationId = eventData?.correlationId;
1870
+ if (!correlationId || !filter.correlationIds.includes(correlationId)) {
1871
+ return false;
1872
+ }
1873
+ }
1874
+ if (filter.metadataMatch) {
1875
+ const eventData = event.data;
1876
+ const metadata = eventData?.metadata;
1877
+ if (!metadata) return false;
1878
+ for (const [key, value] of Object.entries(filter.metadataMatch)) {
1879
+ if (metadata[key] !== value) {
1880
+ return false;
1881
+ }
1882
+ }
1883
+ }
1884
+ return true;
1885
+ }
1886
+ // ===========================================================================
1887
+ // Federation Method Handlers
1888
+ // ===========================================================================
1889
+ #handleFederationRoute(params) {
1890
+ const { systemId, envelope, message } = params;
1891
+ let actualMessage;
1892
+ if (envelope && isValidEnvelope(envelope)) {
1893
+ if (this.#options.federationRouting) {
1894
+ const result = processFederationEnvelope(
1895
+ envelope,
1896
+ this.#options.federationRouting
1897
+ );
1898
+ if (!result.success) {
1899
+ throw new MAPRequestError(result.errorCode, result.errorMessage);
1900
+ }
1901
+ }
1902
+ actualMessage = unwrapEnvelope(envelope);
1903
+ } else if (message) {
1904
+ actualMessage = message;
1905
+ } else {
1906
+ throw MAPRequestError.invalidParams("Missing envelope or message");
1907
+ }
1908
+ this.#messages.push(actualMessage);
1909
+ this.emitEvent({
1910
+ type: EVENT_TYPES.MESSAGE_SENT,
1911
+ source: actualMessage.from,
1912
+ data: {
1913
+ messageId: actualMessage.id,
1914
+ from: actualMessage.from,
1915
+ to: actualMessage.to,
1916
+ federatedTo: systemId
1917
+ }
1918
+ });
1919
+ return { routed: true, messageId: actualMessage.id };
1920
+ }
1921
+ // ===========================================================================
1922
+ // Notification Handlers
1923
+ // ===========================================================================
1924
+ #handleSubscriptionAck(params) {
1925
+ const { subscriptionId, upToSequence } = params;
1926
+ const subscription = this.#subscriptions.get(subscriptionId);
1927
+ if (subscription) {
1928
+ subscription.lastAckedSequence = upToSequence;
1929
+ }
1930
+ }
1931
+ /**
1932
+ * Get ack state for a subscription (for testing assertions)
1933
+ */
1934
+ getSubscriptionAckState(subscriptionId) {
1935
+ const subscription = this.#subscriptions.get(subscriptionId);
1936
+ if (!subscription) return void 0;
1937
+ return {
1938
+ sequenceNumber: subscription.sequenceNumber,
1939
+ lastAckedSequence: subscription.lastAckedSequence
1940
+ };
1941
+ }
1942
+ // ===========================================================================
1943
+ // Permission Handlers
1944
+ // ===========================================================================
1945
+ /**
1946
+ * Handle permissions update request.
1947
+ * Updates client capabilities and emits permission update event.
1948
+ */
1949
+ #handlePermissionsUpdate(connection, params) {
1950
+ const { clientId, permissions } = params;
1951
+ const targetParticipant = this.#participants.get(clientId);
1952
+ if (!targetParticipant) {
1953
+ throw MAPRequestError.invalidParams(`Client ${clientId} not found`);
1954
+ }
1955
+ let requestingParticipantId;
1956
+ for (const [id, p] of this.#participants) {
1957
+ if (p.connection === connection) {
1958
+ requestingParticipantId = id;
1959
+ break;
1960
+ }
1961
+ }
1962
+ const existingCapabilities = targetParticipant.capabilities ?? {};
1963
+ const effectivePermissions = {};
1964
+ if (existingCapabilities.observation) {
1965
+ effectivePermissions.observation = { ...existingCapabilities.observation };
1966
+ }
1967
+ if (existingCapabilities.messaging) {
1968
+ effectivePermissions.messaging = { ...existingCapabilities.messaging };
1969
+ }
1970
+ if (existingCapabilities.lifecycle) {
1971
+ effectivePermissions.lifecycle = { ...existingCapabilities.lifecycle };
1972
+ }
1973
+ if (existingCapabilities.scopes) {
1974
+ effectivePermissions.scopes = { ...existingCapabilities.scopes };
1975
+ }
1976
+ if (existingCapabilities.federation) {
1977
+ effectivePermissions.federation = { ...existingCapabilities.federation };
1978
+ }
1979
+ if (existingCapabilities.streaming) {
1980
+ effectivePermissions.streaming = { ...existingCapabilities.streaming };
1981
+ }
1982
+ if (permissions.observation) {
1983
+ effectivePermissions.observation = {
1984
+ ...effectivePermissions.observation,
1985
+ ...permissions.observation
1986
+ };
1987
+ }
1988
+ if (permissions.messaging) {
1989
+ effectivePermissions.messaging = {
1990
+ ...effectivePermissions.messaging,
1991
+ ...permissions.messaging
1992
+ };
1993
+ }
1994
+ if (permissions.lifecycle) {
1995
+ effectivePermissions.lifecycle = {
1996
+ ...effectivePermissions.lifecycle,
1997
+ ...permissions.lifecycle
1998
+ };
1999
+ }
2000
+ if (permissions.scopes) {
2001
+ effectivePermissions.scopes = {
2002
+ ...effectivePermissions.scopes,
2003
+ ...permissions.scopes
2004
+ };
2005
+ }
2006
+ if (permissions.federation) {
2007
+ effectivePermissions.federation = {
2008
+ ...effectivePermissions.federation,
2009
+ ...permissions.federation
2010
+ };
2011
+ }
2012
+ if (permissions.streaming) {
2013
+ effectivePermissions.streaming = {
2014
+ ...effectivePermissions.streaming,
2015
+ ...permissions.streaming
2016
+ };
2017
+ }
2018
+ targetParticipant.capabilities = effectivePermissions;
2019
+ this.emitEvent({
2020
+ type: EVENT_TYPES.PERMISSIONS_CLIENT_UPDATED,
2021
+ data: {
2022
+ clientId,
2023
+ changes: permissions,
2024
+ effectivePermissions,
2025
+ changedBy: requestingParticipantId
2026
+ }
2027
+ });
2028
+ return {
2029
+ success: true,
2030
+ effectivePermissions
2031
+ };
2032
+ }
2033
+ };
2034
+
2035
+ // src/subscription/index.ts
2036
+ var Subscription = class {
2037
+ id;
2038
+ filter;
2039
+ #eventHandlers = /* @__PURE__ */ new Set();
2040
+ #overflowHandlers = /* @__PURE__ */ new Set();
2041
+ #eventQueue = [];
2042
+ #bufferSize;
2043
+ #unsubscribe;
2044
+ #sendAck;
2045
+ // Deduplication tracking
2046
+ #seenEventIds = /* @__PURE__ */ new Set();
2047
+ #seenEventIdOrder = [];
2048
+ // For LRU eviction
2049
+ #maxSeenEventIds;
2050
+ #eventResolver = null;
2051
+ #pauseResolver = null;
2052
+ #state = "active";
2053
+ #lastSequenceNumber = -1;
2054
+ #lastEventId;
2055
+ #lastTimestamp;
2056
+ // Overflow tracking
2057
+ #totalDropped = 0;
2058
+ #oldestDroppedId;
2059
+ #newestDroppedId;
2060
+ // Ack support
2061
+ #serverSupportsAck = false;
2062
+ constructor(id, unsubscribe, options = {}, sendAck) {
2063
+ this.id = id;
2064
+ this.filter = options.filter;
2065
+ this.#bufferSize = options.bufferSize ?? 1e3;
2066
+ this.#maxSeenEventIds = options.maxSeenEventIds ?? 1e4;
2067
+ this.#unsubscribe = unsubscribe;
2068
+ this.#sendAck = sendAck;
2069
+ }
2070
+ /**
2071
+ * Current subscription state
2072
+ */
2073
+ get state() {
2074
+ return this.#state;
2075
+ }
2076
+ /**
2077
+ * Whether the subscription is closed
2078
+ */
2079
+ get isClosed() {
2080
+ return this.#state === "closed";
2081
+ }
2082
+ /**
2083
+ * Whether the subscription is paused
2084
+ */
2085
+ get isPaused() {
2086
+ return this.#state === "paused";
2087
+ }
2088
+ /**
2089
+ * Last received sequence number (for ordering verification)
2090
+ */
2091
+ get lastSequenceNumber() {
2092
+ return this.#lastSequenceNumber;
2093
+ }
2094
+ /**
2095
+ * Last received eventId (for replay positioning)
2096
+ */
2097
+ get lastEventId() {
2098
+ return this.#lastEventId;
2099
+ }
2100
+ /**
2101
+ * Last received server timestamp
2102
+ */
2103
+ get lastTimestamp() {
2104
+ return this.#lastTimestamp;
2105
+ }
2106
+ /**
2107
+ * Number of events currently buffered
2108
+ */
2109
+ get bufferedCount() {
2110
+ return this.#eventQueue.length;
2111
+ }
2112
+ /**
2113
+ * Number of eventIds being tracked for deduplication
2114
+ */
2115
+ get trackedEventIdCount() {
2116
+ return this.#seenEventIds.size;
2117
+ }
2118
+ /**
2119
+ * Total number of events dropped due to buffer overflow
2120
+ */
2121
+ get totalDropped() {
2122
+ return this.#totalDropped;
2123
+ }
2124
+ /**
2125
+ * Whether the server supports acknowledgments
2126
+ */
2127
+ get supportsAck() {
2128
+ return this.#serverSupportsAck && !!this.#sendAck;
2129
+ }
2130
+ /**
2131
+ * Pause event delivery from the async iterator.
2132
+ * Events are still buffered but not yielded until resume() is called.
2133
+ * Event handlers (on('event', ...)) still receive events while paused.
2134
+ */
2135
+ pause() {
2136
+ if (this.#state === "closed") return;
2137
+ this.#state = "paused";
2138
+ }
2139
+ /**
2140
+ * Resume event delivery from the async iterator.
2141
+ * Any events buffered during pause will be yielded.
2142
+ */
2143
+ resume() {
2144
+ if (this.#state === "closed") return;
2145
+ this.#state = "active";
2146
+ if (this.#pauseResolver) {
2147
+ this.#pauseResolver();
2148
+ this.#pauseResolver = null;
2149
+ }
2150
+ if (this.#eventResolver && this.#eventQueue.length > 0) {
2151
+ const event = this.#eventQueue.shift();
2152
+ this.#eventResolver(event);
2153
+ this.#eventResolver = null;
2154
+ }
2155
+ }
2156
+ /**
2157
+ * Acknowledge events up to a sequence number.
2158
+ * No-op if server doesn't support acks.
2159
+ *
2160
+ * @param upToSequence - Acknowledge all events up to and including this sequence.
2161
+ * If omitted, acknowledges up to lastSequenceNumber.
2162
+ */
2163
+ ack(upToSequence) {
2164
+ if (!this.supportsAck) return;
2165
+ const seq = upToSequence ?? this.#lastSequenceNumber;
2166
+ if (seq < 0) return;
2167
+ this.#sendAck({
2168
+ subscriptionId: this.id,
2169
+ upToSequence: seq
2170
+ });
2171
+ }
2172
+ on(type, handler) {
2173
+ if (type === "event") {
2174
+ this.#eventHandlers.add(handler);
2175
+ } else if (type === "overflow") {
2176
+ this.#overflowHandlers.add(handler);
2177
+ }
2178
+ return this;
2179
+ }
2180
+ off(type, handler) {
2181
+ if (type === "event") {
2182
+ this.#eventHandlers.delete(handler);
2183
+ } else if (type === "overflow") {
2184
+ this.#overflowHandlers.delete(handler);
2185
+ }
2186
+ return this;
2187
+ }
2188
+ /**
2189
+ * Register a one-time event handler
2190
+ */
2191
+ once(type, handler) {
2192
+ if (type === "event") {
2193
+ const wrapper = (event) => {
2194
+ this.off("event", wrapper);
2195
+ handler(event);
2196
+ };
2197
+ this.on("event", wrapper);
2198
+ }
2199
+ return this;
2200
+ }
2201
+ /**
2202
+ * Unsubscribe and close the subscription
2203
+ */
2204
+ async unsubscribe() {
2205
+ if (this.#state === "closed") return;
2206
+ this.#state = "closed";
2207
+ if (this.#eventResolver) {
2208
+ this.#eventResolver(null);
2209
+ this.#eventResolver = null;
2210
+ }
2211
+ if (this.#pauseResolver) {
2212
+ this.#pauseResolver();
2213
+ this.#pauseResolver = null;
2214
+ }
2215
+ this.#eventHandlers.clear();
2216
+ this.#overflowHandlers.clear();
2217
+ this.#seenEventIds.clear();
2218
+ this.#seenEventIdOrder.length = 0;
2219
+ await this.#unsubscribe();
2220
+ }
2221
+ /**
2222
+ * Set whether server supports acknowledgments.
2223
+ * Called by connection after capability negotiation.
2224
+ * @internal
2225
+ */
2226
+ _setServerSupportsAck(supports) {
2227
+ this.#serverSupportsAck = supports;
2228
+ }
2229
+ /**
2230
+ * Push an event to the subscription (called by connection)
2231
+ * @internal
2232
+ */
2233
+ _pushEvent(params) {
2234
+ if (this.#state === "closed") return;
2235
+ const { sequenceNumber, eventId, timestamp, event } = params;
2236
+ if (eventId) {
2237
+ if (this.#seenEventIds.has(eventId)) {
2238
+ return;
2239
+ }
2240
+ this.#seenEventIds.add(eventId);
2241
+ this.#seenEventIdOrder.push(eventId);
2242
+ while (this.#seenEventIds.size > this.#maxSeenEventIds) {
2243
+ const oldestId = this.#seenEventIdOrder.shift();
2244
+ if (oldestId) {
2245
+ this.#seenEventIds.delete(oldestId);
2246
+ }
2247
+ }
2248
+ this.#lastEventId = eventId;
2249
+ }
2250
+ if (timestamp !== void 0) {
2251
+ this.#lastTimestamp = timestamp;
2252
+ }
2253
+ if (this.#lastSequenceNumber >= 0 && sequenceNumber !== this.#lastSequenceNumber + 1) {
2254
+ console.warn(
2255
+ `MAP: Subscription ${this.id} sequence gap: expected ${this.#lastSequenceNumber + 1}, got ${sequenceNumber}`
2256
+ );
2257
+ }
2258
+ this.#lastSequenceNumber = sequenceNumber;
2259
+ for (const handler of this.#eventHandlers) {
2260
+ try {
2261
+ handler(event);
2262
+ } catch (error) {
2263
+ console.error("MAP: Event handler error:", error);
2264
+ }
2265
+ }
2266
+ if (this.#eventResolver && this.#state === "active") {
2267
+ this.#eventResolver(event);
2268
+ this.#eventResolver = null;
2269
+ return;
2270
+ }
2271
+ if (this.#eventQueue.length < this.#bufferSize) {
2272
+ this.#eventQueue.push(event);
2273
+ } else {
2274
+ this.#totalDropped++;
2275
+ if (eventId) {
2276
+ if (this.#oldestDroppedId === void 0) {
2277
+ this.#oldestDroppedId = eventId;
2278
+ }
2279
+ this.#newestDroppedId = eventId;
2280
+ }
2281
+ const info = {
2282
+ eventsDropped: 1,
2283
+ oldestDroppedId: this.#oldestDroppedId,
2284
+ newestDroppedId: this.#newestDroppedId,
2285
+ timestamp: Date.now(),
2286
+ totalDropped: this.#totalDropped
2287
+ };
2288
+ for (const handler of this.#overflowHandlers) {
2289
+ try {
2290
+ handler(info);
2291
+ } catch (error) {
2292
+ console.error("MAP: Overflow handler error:", error);
2293
+ }
2294
+ }
2295
+ console.warn(`MAP: Subscription ${this.id} buffer full, dropping event`);
2296
+ }
2297
+ }
2298
+ /**
2299
+ * Mark the subscription as closed (called by connection)
2300
+ * @internal
2301
+ */
2302
+ _close() {
2303
+ this.#state = "closed";
2304
+ if (this.#eventResolver) {
2305
+ this.#eventResolver(null);
2306
+ this.#eventResolver = null;
2307
+ }
2308
+ if (this.#pauseResolver) {
2309
+ this.#pauseResolver();
2310
+ this.#pauseResolver = null;
2311
+ }
2312
+ }
2313
+ /**
2314
+ * Async iterator implementation
2315
+ */
2316
+ async *[Symbol.asyncIterator]() {
2317
+ while (!this.isClosed) {
2318
+ while (this.isPaused) {
2319
+ await new Promise((resolve) => {
2320
+ this.#pauseResolver = resolve;
2321
+ });
2322
+ if (this.isClosed) {
2323
+ while (this.#eventQueue.length > 0) {
2324
+ yield this.#eventQueue.shift();
2325
+ }
2326
+ return;
2327
+ }
2328
+ }
2329
+ if (this.#eventQueue.length > 0) {
2330
+ yield this.#eventQueue.shift();
2331
+ continue;
2332
+ }
2333
+ const event = await new Promise((resolve) => {
2334
+ this.#eventResolver = resolve;
2335
+ });
2336
+ if (event === null) {
2337
+ break;
2338
+ }
2339
+ yield event;
2340
+ }
2341
+ while (this.#eventQueue.length > 0) {
2342
+ yield this.#eventQueue.shift();
2343
+ }
2344
+ }
2345
+ };
2346
+ function createSubscription(id, unsubscribe, options, sendAck) {
2347
+ return new Subscription(id, unsubscribe, options, sendAck);
2348
+ }
2349
+
2350
+ // src/connection/client.ts
2351
+ var ClientConnection = class {
2352
+ #connection;
2353
+ #subscriptions = /* @__PURE__ */ new Map();
2354
+ #subscriptionStates = /* @__PURE__ */ new Map();
2355
+ #reconnectionHandlers = /* @__PURE__ */ new Set();
2356
+ #options;
2357
+ #sessionId = null;
2358
+ #serverCapabilities = null;
2359
+ #connected = false;
2360
+ #lastConnectOptions;
2361
+ #isReconnecting = false;
2362
+ constructor(stream, options = {}) {
2363
+ this.#connection = new BaseConnection(stream, options);
2364
+ this.#options = options;
2365
+ this.#connection.setNotificationHandler(this.#handleNotification.bind(this));
2366
+ if (options.reconnection?.enabled && options.createStream) {
2367
+ this.#connection.onStateChange((newState) => {
2368
+ if (newState === "closed" && this.#connected && !this.#isReconnecting) {
2369
+ void this.#handleDisconnect();
2370
+ }
2371
+ });
2372
+ }
2373
+ }
2374
+ // ===========================================================================
2375
+ // Connection Lifecycle
2376
+ // ===========================================================================
2377
+ /**
2378
+ * Connect to the MAP system
2379
+ */
2380
+ async connect(options) {
2381
+ const params = {
2382
+ protocolVersion: PROTOCOL_VERSION,
2383
+ participantType: "client",
2384
+ name: this.#options.name,
2385
+ capabilities: this.#options.capabilities,
2386
+ sessionId: options?.sessionId,
2387
+ auth: options?.auth
2388
+ };
2389
+ const result = await this.#connection.sendRequest(CORE_METHODS.CONNECT, params);
2390
+ this.#sessionId = result.sessionId;
2391
+ this.#serverCapabilities = result.capabilities;
2392
+ this.#connected = true;
2393
+ this.#connection._transitionTo("connected");
2394
+ this.#lastConnectOptions = options;
2395
+ return result;
2396
+ }
2397
+ /**
2398
+ * Disconnect from the MAP system
2399
+ */
2400
+ async disconnect(reason) {
2401
+ if (!this.#connected) return;
2402
+ try {
2403
+ await this.#connection.sendRequest(
2404
+ CORE_METHODS.DISCONNECT,
2405
+ reason ? { reason } : void 0
2406
+ );
2407
+ } finally {
2408
+ for (const subscription of this.#subscriptions.values()) {
2409
+ subscription._close();
2410
+ }
2411
+ this.#subscriptions.clear();
2412
+ await this.#connection.close();
2413
+ this.#connected = false;
2414
+ }
2415
+ }
2416
+ /**
2417
+ * Whether the client is connected
2418
+ */
2419
+ get isConnected() {
2420
+ return this.#connected && !this.#connection.isClosed;
2421
+ }
2422
+ /**
2423
+ * Current session ID
2424
+ */
2425
+ get sessionId() {
2426
+ return this.#sessionId;
2427
+ }
2428
+ /**
2429
+ * Server capabilities
2430
+ */
2431
+ get serverCapabilities() {
2432
+ return this.#serverCapabilities;
2433
+ }
2434
+ /**
2435
+ * AbortSignal that triggers when the connection closes
2436
+ */
2437
+ get signal() {
2438
+ return this.#connection.signal;
2439
+ }
2440
+ /**
2441
+ * Promise that resolves when the connection closes
2442
+ */
2443
+ get closed() {
2444
+ return this.#connection.closed;
2445
+ }
2446
+ // ===========================================================================
2447
+ // Session Management
2448
+ // ===========================================================================
2449
+ /**
2450
+ * List available sessions
2451
+ */
2452
+ async listSessions() {
2453
+ return this.#connection.sendRequest(SESSION_METHODS.SESSION_LIST);
2454
+ }
2455
+ /**
2456
+ * Load an existing session
2457
+ */
2458
+ async loadSession(sessionId) {
2459
+ return this.#connection.sendRequest(SESSION_METHODS.SESSION_LOAD, { sessionId });
2460
+ }
2461
+ /**
2462
+ * Close the current session
2463
+ */
2464
+ async closeSession(sessionId) {
2465
+ return this.#connection.sendRequest(SESSION_METHODS.SESSION_CLOSE, { sessionId });
2466
+ }
2467
+ // ===========================================================================
2468
+ // Agent Queries
2469
+ // ===========================================================================
2470
+ /**
2471
+ * List agents with optional filters
2472
+ */
2473
+ async listAgents(options) {
2474
+ return this.#connection.sendRequest(OBSERVATION_METHODS.AGENTS_LIST, options);
2475
+ }
2476
+ /**
2477
+ * Get a single agent by ID
2478
+ */
2479
+ async getAgent(agentId, options) {
2480
+ const params = { agentId, ...options };
2481
+ return this.#connection.sendRequest(
2482
+ OBSERVATION_METHODS.AGENTS_GET,
2483
+ params
2484
+ );
2485
+ }
2486
+ /**
2487
+ * Get the agent structure/hierarchy graph
2488
+ */
2489
+ async getStructureGraph(options) {
2490
+ return this.#connection.sendRequest(OBSERVATION_METHODS.STRUCTURE_GRAPH, options);
2491
+ }
2492
+ // ===========================================================================
2493
+ // Scope Queries
2494
+ // ===========================================================================
2495
+ /**
2496
+ * List scopes
2497
+ */
2498
+ async listScopes(options) {
2499
+ return this.#connection.sendRequest(OBSERVATION_METHODS.SCOPES_LIST, options);
2500
+ }
2501
+ /**
2502
+ * Get a single scope by ID
2503
+ */
2504
+ async getScope(scopeId) {
2505
+ const result = await this.#connection.sendRequest(OBSERVATION_METHODS.SCOPES_GET, { scopeId });
2506
+ return result.scope;
2507
+ }
2508
+ /**
2509
+ * List members of a scope
2510
+ */
2511
+ async getScopeMembers(scopeId, options) {
2512
+ return this.#connection.sendRequest(OBSERVATION_METHODS.SCOPES_MEMBERS, {
2513
+ scopeId,
2514
+ ...options
2515
+ });
2516
+ }
2517
+ // ===========================================================================
2518
+ // Messaging
2519
+ // ===========================================================================
2520
+ /**
2521
+ * Send a message to an address
2522
+ */
2523
+ async send(to, payload, meta) {
2524
+ const params = { to };
2525
+ if (payload !== void 0) params.payload = payload;
2526
+ if (meta) params.meta = meta;
2527
+ return this.#connection.sendRequest(CORE_METHODS.SEND, params);
2528
+ }
2529
+ /**
2530
+ * Send a message to a specific agent
2531
+ */
2532
+ async sendToAgent(agentId, payload, meta) {
2533
+ return this.send({ agent: agentId }, payload, meta);
2534
+ }
2535
+ /**
2536
+ * Send a message to all agents in a scope
2537
+ */
2538
+ async sendToScope(scopeId, payload, meta) {
2539
+ return this.send({ scope: scopeId }, payload, meta);
2540
+ }
2541
+ /**
2542
+ * Send a message to agents with a specific role
2543
+ */
2544
+ async sendToRole(role, payload, meta, withinScope) {
2545
+ return this.send({ role, within: withinScope }, payload, meta);
2546
+ }
2547
+ /**
2548
+ * Broadcast a message to all agents
2549
+ */
2550
+ async broadcast(payload, meta) {
2551
+ return this.send({ broadcast: true }, payload, meta);
2552
+ }
2553
+ /**
2554
+ * Send a request and wait for a correlated response
2555
+ *
2556
+ * This is a higher-level pattern for request/response messaging.
2557
+ * A correlationId is automatically generated.
2558
+ */
2559
+ async request(to, payload, options) {
2560
+ const correlationId = `req-${Date.now()}-${Math.random().toString(36).slice(2)}`;
2561
+ const responseSub = await this.subscribe({
2562
+ // We'll filter in the handler since subscription filters don't support correlationId
2563
+ });
2564
+ try {
2565
+ await this.send(to, payload, {
2566
+ ...options?.meta,
2567
+ expectsResponse: true,
2568
+ correlationId
2569
+ });
2570
+ const timeout = options?.timeout ?? 3e4;
2571
+ const timeoutPromise = new Promise((_, reject) => {
2572
+ setTimeout(() => reject(new Error(`Request timed out after ${timeout}ms`)), timeout);
2573
+ });
2574
+ const responsePromise = (async () => {
2575
+ for await (const event of responseSub) {
2576
+ if (event.type === "message_delivered" && event.data && event.data.correlationId === correlationId) {
2577
+ return event.data.message;
2578
+ }
2579
+ }
2580
+ throw new Error("Subscription closed before response received");
2581
+ })();
2582
+ return await Promise.race([responsePromise, timeoutPromise]);
2583
+ } finally {
2584
+ await responseSub.unsubscribe();
2585
+ }
2586
+ }
2587
+ // ===========================================================================
2588
+ // Subscriptions
2589
+ // ===========================================================================
2590
+ /**
2591
+ * Subscribe to events
2592
+ */
2593
+ async subscribe(filter) {
2594
+ const params = {};
2595
+ if (filter) params.filter = filter;
2596
+ const result = await this.#connection.sendRequest(CORE_METHODS.SUBSCRIBE, params);
2597
+ const serverSupportsAck = this.#serverCapabilities?.streaming?.supportsAck === true;
2598
+ const sendAck = serverSupportsAck ? (ackParams) => {
2599
+ this.#connection.sendNotification(NOTIFICATION_METHODS.SUBSCRIBE_ACK, ackParams);
2600
+ } : void 0;
2601
+ const subscription = createSubscription(
2602
+ result.subscriptionId,
2603
+ () => this.unsubscribe(result.subscriptionId),
2604
+ { filter },
2605
+ sendAck
2606
+ );
2607
+ if (serverSupportsAck) {
2608
+ subscription._setServerSupportsAck(true);
2609
+ }
2610
+ this.#subscriptions.set(result.subscriptionId, subscription);
2611
+ if (this.#options.reconnection?.restoreSubscriptions !== false) {
2612
+ this.#subscriptionStates.set(result.subscriptionId, {
2613
+ filter,
2614
+ handlers: /* @__PURE__ */ new Set()
2615
+ });
2616
+ const originalPushEvent = subscription._pushEvent.bind(subscription);
2617
+ subscription._pushEvent = (event) => {
2618
+ const state = this.#subscriptionStates.get(result.subscriptionId);
2619
+ if (state && event.eventId) {
2620
+ state.lastEventId = event.eventId;
2621
+ }
2622
+ originalPushEvent(event);
2623
+ };
2624
+ }
2625
+ return subscription;
2626
+ }
2627
+ /**
2628
+ * Unsubscribe from events
2629
+ */
2630
+ async unsubscribe(subscriptionId) {
2631
+ const subscription = this.#subscriptions.get(subscriptionId);
2632
+ if (subscription) {
2633
+ subscription._close();
2634
+ this.#subscriptions.delete(subscriptionId);
2635
+ }
2636
+ this.#subscriptionStates.delete(subscriptionId);
2637
+ await this.#connection.sendRequest(CORE_METHODS.UNSUBSCRIBE, { subscriptionId });
2638
+ }
2639
+ // ===========================================================================
2640
+ // Event Replay
2641
+ // ===========================================================================
2642
+ /**
2643
+ * Replay historical events.
2644
+ *
2645
+ * Uses keyset pagination - pass the last eventId from the previous
2646
+ * response to get the next page.
2647
+ *
2648
+ * @example
2649
+ * ```typescript
2650
+ * // Replay all events from the last hour
2651
+ * const result = await client.replay({
2652
+ * fromTimestamp: Date.now() - 3600000,
2653
+ * filter: { eventTypes: ['agent.registered'] },
2654
+ * limit: 100
2655
+ * });
2656
+ *
2657
+ * // Paginate through results
2658
+ * let afterEventId: string | undefined;
2659
+ * do {
2660
+ * const page = await client.replay({ afterEventId, limit: 100 });
2661
+ * for (const item of page.events) {
2662
+ * console.log(item.eventId, item.event);
2663
+ * }
2664
+ * afterEventId = page.events.at(-1)?.eventId;
2665
+ * } while (page.hasMore);
2666
+ * ```
2667
+ */
2668
+ async replay(params = {}) {
2669
+ const limit = Math.min(params.limit ?? 100, 1e3);
2670
+ return this.#connection.sendRequest(
2671
+ CORE_METHODS.REPLAY,
2672
+ { ...params, limit }
2673
+ );
2674
+ }
2675
+ /**
2676
+ * Replay all events matching filter, handling pagination automatically.
2677
+ *
2678
+ * Returns an async generator for streaming through all results.
2679
+ *
2680
+ * @example
2681
+ * ```typescript
2682
+ * for await (const item of client.replayAll({
2683
+ * filter: { eventTypes: ['agent.registered'] }
2684
+ * })) {
2685
+ * console.log(item.eventId, item.event);
2686
+ * }
2687
+ * ```
2688
+ */
2689
+ async *replayAll(params = {}) {
2690
+ let afterEventId;
2691
+ let hasMore = true;
2692
+ while (hasMore) {
2693
+ const result = await this.replay({ ...params, afterEventId });
2694
+ for (const item of result.events) {
2695
+ yield item;
2696
+ }
2697
+ hasMore = result.hasMore;
2698
+ afterEventId = result.events.at(-1)?.eventId;
2699
+ if (result.events.length === 0) {
2700
+ break;
2701
+ }
2702
+ }
2703
+ }
2704
+ // ===========================================================================
2705
+ // Steering (requires canSteer capability)
2706
+ // ===========================================================================
2707
+ /**
2708
+ * Inject context into a running agent
2709
+ */
2710
+ async inject(agentId, content, delivery) {
2711
+ const params = { agentId, content };
2712
+ if (delivery) params.delivery = delivery;
2713
+ return this.#connection.sendRequest(STEERING_METHODS.INJECT, params);
2714
+ }
2715
+ // ===========================================================================
2716
+ // Lifecycle Control (requires canStop capability)
2717
+ // ===========================================================================
2718
+ /**
2719
+ * Request an agent to stop
2720
+ */
2721
+ async stopAgent(agentId, options) {
2722
+ return this.#connection.sendRequest(STATE_METHODS.AGENTS_STOP, {
2723
+ agentId,
2724
+ ...options
2725
+ });
2726
+ }
2727
+ /**
2728
+ * Suspend an agent
2729
+ */
2730
+ async suspendAgent(agentId, reason) {
2731
+ return this.#connection.sendRequest(STATE_METHODS.AGENTS_SUSPEND, {
2732
+ agentId,
2733
+ reason
2734
+ });
2735
+ }
2736
+ /**
2737
+ * Resume a suspended agent
2738
+ */
2739
+ async resumeAgent(agentId) {
2740
+ return this.#connection.sendRequest(STATE_METHODS.AGENTS_RESUME, { agentId });
2741
+ }
2742
+ // ===========================================================================
2743
+ // Reconnection
2744
+ // ===========================================================================
2745
+ /**
2746
+ * Current connection state
2747
+ */
2748
+ get state() {
2749
+ return this.#connection.state;
2750
+ }
2751
+ /**
2752
+ * Whether the connection is currently reconnecting
2753
+ */
2754
+ get isReconnecting() {
2755
+ return this.#isReconnecting;
2756
+ }
2757
+ /**
2758
+ * Register a handler for reconnection events.
2759
+ *
2760
+ * @param handler - Function called when reconnection events occur
2761
+ * @returns Unsubscribe function to remove the handler
2762
+ *
2763
+ * @example
2764
+ * ```typescript
2765
+ * const unsubscribe = client.onReconnection((event) => {
2766
+ * switch (event.type) {
2767
+ * case 'disconnected':
2768
+ * console.log('Connection lost');
2769
+ * break;
2770
+ * case 'reconnecting':
2771
+ * console.log(`Reconnecting, attempt ${event.attempt}`);
2772
+ * break;
2773
+ * case 'reconnected':
2774
+ * console.log('Reconnected successfully');
2775
+ * break;
2776
+ * case 'reconnectFailed':
2777
+ * console.log('Failed to reconnect:', event.error);
2778
+ * break;
2779
+ * }
2780
+ * });
2781
+ * ```
2782
+ */
2783
+ onReconnection(handler) {
2784
+ this.#reconnectionHandlers.add(handler);
2785
+ return () => this.#reconnectionHandlers.delete(handler);
2786
+ }
2787
+ /**
2788
+ * Register a handler for connection state changes.
2789
+ *
2790
+ * @param handler - Function called when state changes
2791
+ * @returns Unsubscribe function to remove the handler
2792
+ */
2793
+ onStateChange(handler) {
2794
+ return this.#connection.onStateChange(handler);
2795
+ }
2796
+ // ===========================================================================
2797
+ // Internal
2798
+ // ===========================================================================
2799
+ /**
2800
+ * Handle incoming notifications
2801
+ */
2802
+ async #handleNotification(method, params) {
2803
+ switch (method) {
2804
+ case NOTIFICATION_METHODS.EVENT: {
2805
+ const eventParams = params;
2806
+ const subscription = this.#subscriptions.get(eventParams.subscriptionId);
2807
+ if (subscription) {
2808
+ subscription._pushEvent(eventParams);
2809
+ } else {
2810
+ console.warn("MAP: Event for unknown subscription:", eventParams.subscriptionId);
2811
+ }
2812
+ break;
2813
+ }
2814
+ case NOTIFICATION_METHODS.MESSAGE: {
2815
+ break;
2816
+ }
2817
+ default:
2818
+ console.warn("MAP: Unknown notification:", method);
2819
+ }
2820
+ }
2821
+ /**
2822
+ * Emit a reconnection event to all registered handlers
2823
+ */
2824
+ #emitReconnectionEvent(event) {
2825
+ for (const handler of this.#reconnectionHandlers) {
2826
+ try {
2827
+ handler(event);
2828
+ } catch (error) {
2829
+ console.error("MAP: Reconnection event handler error:", error);
2830
+ }
2831
+ }
2832
+ }
2833
+ /**
2834
+ * Handle disconnect when auto-reconnect is enabled
2835
+ */
2836
+ async #handleDisconnect() {
2837
+ this.#isReconnecting = true;
2838
+ this.#connected = false;
2839
+ this.#emitReconnectionEvent({ type: "disconnected" });
2840
+ try {
2841
+ await this.#attemptReconnect();
2842
+ } catch (error) {
2843
+ this.#isReconnecting = false;
2844
+ this.#emitReconnectionEvent({
2845
+ type: "reconnectFailed",
2846
+ error: error instanceof Error ? error : new Error(String(error))
2847
+ });
2848
+ }
2849
+ }
2850
+ /**
2851
+ * Attempt to reconnect with retry logic
2852
+ */
2853
+ async #attemptReconnect() {
2854
+ const options = this.#options.reconnection;
2855
+ const createStream = this.#options.createStream;
2856
+ const retryPolicy = {
2857
+ maxRetries: options.maxRetries ?? DEFAULT_RETRY_POLICY.maxRetries,
2858
+ baseDelayMs: options.baseDelayMs ?? DEFAULT_RETRY_POLICY.baseDelayMs,
2859
+ maxDelayMs: options.maxDelayMs ?? DEFAULT_RETRY_POLICY.maxDelayMs,
2860
+ jitter: options.jitter ?? DEFAULT_RETRY_POLICY.jitter
2861
+ };
2862
+ await withRetry(
2863
+ async () => {
2864
+ const newStream = await createStream();
2865
+ await this.#connection.reconnect(newStream);
2866
+ const connectResult = await this.connect(this.#lastConnectOptions);
2867
+ this.#sessionId = connectResult.sessionId;
2868
+ this.#serverCapabilities = connectResult.capabilities;
2869
+ },
2870
+ retryPolicy,
2871
+ {
2872
+ onRetry: (state) => {
2873
+ this.#emitReconnectionEvent({
2874
+ type: "reconnecting",
2875
+ attempt: state.attempt,
2876
+ delay: state.nextDelayMs,
2877
+ error: state.lastError
2878
+ });
2879
+ }
2880
+ }
2881
+ );
2882
+ this.#isReconnecting = false;
2883
+ this.#emitReconnectionEvent({ type: "reconnected" });
2884
+ if (options.restoreSubscriptions !== false) {
2885
+ await this.#restoreSubscriptions();
2886
+ }
2887
+ }
2888
+ /**
2889
+ * Restore subscriptions after reconnection
2890
+ */
2891
+ async #restoreSubscriptions() {
2892
+ const options = this.#options.reconnection;
2893
+ const subscriptionEntries = Array.from(this.#subscriptionStates.entries());
2894
+ this.#subscriptions.clear();
2895
+ this.#subscriptionStates.clear();
2896
+ for (const [oldId, state] of subscriptionEntries) {
2897
+ try {
2898
+ const newSubscription = await this.subscribe(state.filter);
2899
+ const newId = newSubscription.id;
2900
+ if (options.replayOnRestore !== false && state.lastEventId) {
2901
+ const maxEvents = options.maxReplayEventsPerSubscription ?? 1e3;
2902
+ try {
2903
+ let replayedCount = 0;
2904
+ let afterEventId = state.lastEventId;
2905
+ let hasMore = true;
2906
+ while (hasMore && replayedCount < maxEvents) {
2907
+ const result = await this.replay({
2908
+ afterEventId,
2909
+ filter: state.filter,
2910
+ limit: Math.min(100, maxEvents - replayedCount)
2911
+ });
2912
+ for (const replayedEvent of result.events) {
2913
+ if (replayedCount >= maxEvents) break;
2914
+ newSubscription._pushEvent({
2915
+ subscriptionId: newId,
2916
+ sequenceNumber: replayedCount + 1,
2917
+ eventId: replayedEvent.eventId,
2918
+ timestamp: replayedEvent.timestamp,
2919
+ event: replayedEvent.event
2920
+ });
2921
+ replayedCount++;
2922
+ }
2923
+ hasMore = result.hasMore;
2924
+ afterEventId = result.events.at(-1)?.eventId;
2925
+ if (result.events.length === 0) {
2926
+ break;
2927
+ }
2928
+ }
2929
+ } catch (replayError) {
2930
+ console.warn("MAP: Failed to replay events for subscription:", oldId, replayError);
2931
+ }
2932
+ }
2933
+ this.#emitReconnectionEvent({
2934
+ type: "subscriptionRestored",
2935
+ subscriptionId: oldId,
2936
+ newSubscriptionId: newId
2937
+ });
2938
+ } catch (error) {
2939
+ this.#emitReconnectionEvent({
2940
+ type: "subscriptionRestoreFailed",
2941
+ subscriptionId: oldId,
2942
+ error: error instanceof Error ? error : new Error(String(error))
2943
+ });
2944
+ }
2945
+ }
2946
+ }
2947
+ };
2948
+
2949
+ // src/stream/index.ts
2950
+ function createStreamPair() {
2951
+ const clientToServer = [];
2952
+ const serverToClient = [];
2953
+ let clientToServerResolver = null;
2954
+ let serverToClientResolver = null;
2955
+ let clientToServerClosed = false;
2956
+ let serverToClientClosed = false;
2957
+ function createReadable(queue, _getResolver, setResolver, isClosed) {
2958
+ return new ReadableStream({
2959
+ async pull(controller) {
2960
+ if (queue.length > 0) {
2961
+ controller.enqueue(queue.shift());
2962
+ return;
2963
+ }
2964
+ if (isClosed()) {
2965
+ controller.close();
2966
+ return;
2967
+ }
2968
+ const message = await new Promise((resolve) => {
2969
+ setResolver((msg) => {
2970
+ setResolver(null);
2971
+ resolve(msg);
2972
+ });
2973
+ });
2974
+ if (message === null) {
2975
+ controller.close();
2976
+ } else {
2977
+ controller.enqueue(message);
2978
+ }
2979
+ }
2980
+ });
2981
+ }
2982
+ function createWritable(queue, getResolver, setClosed) {
2983
+ return new WritableStream({
2984
+ write(message) {
2985
+ const resolver = getResolver();
2986
+ if (resolver) {
2987
+ resolver(message);
2988
+ } else {
2989
+ queue.push(message);
2990
+ }
2991
+ },
2992
+ close() {
2993
+ setClosed();
2994
+ const resolver = getResolver();
2995
+ if (resolver) {
2996
+ resolver(null);
2997
+ }
2998
+ }
2999
+ });
3000
+ }
3001
+ const clientStream = {
3002
+ // Client writes to server
3003
+ writable: createWritable(
3004
+ clientToServer,
3005
+ () => clientToServerResolver,
3006
+ () => {
3007
+ clientToServerClosed = true;
3008
+ }
3009
+ ),
3010
+ // Client reads from server
3011
+ readable: createReadable(
3012
+ serverToClient,
3013
+ () => serverToClientResolver,
3014
+ (r) => {
3015
+ serverToClientResolver = r;
3016
+ },
3017
+ () => serverToClientClosed
3018
+ )
3019
+ };
3020
+ const serverStream = {
3021
+ // Server writes to client
3022
+ writable: createWritable(
3023
+ serverToClient,
3024
+ () => serverToClientResolver,
3025
+ () => {
3026
+ serverToClientClosed = true;
3027
+ }
3028
+ ),
3029
+ // Server reads from client
3030
+ readable: createReadable(
3031
+ clientToServer,
3032
+ () => clientToServerResolver,
3033
+ (r) => {
3034
+ clientToServerResolver = r;
3035
+ },
3036
+ () => clientToServerClosed
3037
+ )
3038
+ };
3039
+ return [clientStream, serverStream];
3040
+ }
3041
+
3042
+ // src/testing/client.ts
3043
+ var TestClient = class _TestClient {
3044
+ #connection;
3045
+ #subscriptions = /* @__PURE__ */ new Map();
3046
+ constructor(connection) {
3047
+ this.#connection = connection;
3048
+ }
3049
+ /**
3050
+ * Create and connect a test client
3051
+ */
3052
+ static async create(server, options = {}) {
3053
+ const [clientStream, serverStream] = createStreamPair();
3054
+ const connection = new ClientConnection(clientStream, options);
3055
+ server.acceptConnection(serverStream);
3056
+ const client = new _TestClient(connection);
3057
+ if (options.autoConnect !== false) {
3058
+ await connection.connect();
3059
+ }
3060
+ return client;
3061
+ }
3062
+ /**
3063
+ * Get the underlying connection
3064
+ */
3065
+ get connection() {
3066
+ return this.#connection;
3067
+ }
3068
+ /**
3069
+ * Whether the client is connected
3070
+ */
3071
+ get isConnected() {
3072
+ return this.#connection.isConnected;
3073
+ }
3074
+ /**
3075
+ * Current session ID
3076
+ */
3077
+ get sessionId() {
3078
+ return this.#connection.sessionId;
3079
+ }
3080
+ /**
3081
+ * Connect to the server
3082
+ */
3083
+ async connect() {
3084
+ await this.#connection.connect();
3085
+ }
3086
+ /**
3087
+ * Disconnect from the server
3088
+ */
3089
+ async disconnect() {
3090
+ for (const sub of this.#subscriptions.values()) {
3091
+ await sub.unsubscribe();
3092
+ }
3093
+ this.#subscriptions.clear();
3094
+ await this.#connection.disconnect();
3095
+ }
3096
+ // ===========================================================================
3097
+ // Agent Queries
3098
+ // ===========================================================================
3099
+ /**
3100
+ * List all agents
3101
+ */
3102
+ async listAgents(options) {
3103
+ const result = await this.#connection.listAgents(options);
3104
+ return result.agents;
3105
+ }
3106
+ /**
3107
+ * Get agent by ID with optional hierarchy expansion
3108
+ */
3109
+ async getAgent(agentId, options) {
3110
+ return this.#connection.getAgent(agentId, options);
3111
+ }
3112
+ /**
3113
+ * Find agent by name
3114
+ */
3115
+ async findAgentByName(name) {
3116
+ const agents = await this.listAgents();
3117
+ return agents.find((a) => a.name === name);
3118
+ }
3119
+ /**
3120
+ * Find agents by role
3121
+ */
3122
+ async findAgentsByRole(role) {
3123
+ const result = await this.#connection.listAgents({ filter: { roles: [role] } });
3124
+ return result.agents;
3125
+ }
3126
+ // ===========================================================================
3127
+ // Scope Queries
3128
+ // ===========================================================================
3129
+ /**
3130
+ * List all scopes
3131
+ */
3132
+ async listScopes(options) {
3133
+ const result = await this.#connection.listScopes(options);
3134
+ return result.scopes;
3135
+ }
3136
+ /**
3137
+ * Get scope by ID
3138
+ */
3139
+ async getScope(scopeId) {
3140
+ return this.#connection.getScope(scopeId);
3141
+ }
3142
+ // ===========================================================================
3143
+ // Messaging
3144
+ // ===========================================================================
3145
+ /**
3146
+ * Send message to agent
3147
+ */
3148
+ async sendTo(agentId, payload) {
3149
+ const result = await this.#connection.sendToAgent(agentId, payload);
3150
+ return result.messageId;
3151
+ }
3152
+ /**
3153
+ * Broadcast to all agents
3154
+ */
3155
+ async broadcast(payload) {
3156
+ const result = await this.#connection.broadcast(payload);
3157
+ return result.messageId;
3158
+ }
3159
+ /**
3160
+ * Send to all agents with a specific role
3161
+ */
3162
+ async sendToRole(role, payload) {
3163
+ const result = await this.#connection.sendToRole(role, payload);
3164
+ return result.messageId;
3165
+ }
3166
+ /**
3167
+ * Send to all agents in a scope
3168
+ */
3169
+ async sendToScope(scopeId, payload) {
3170
+ const result = await this.#connection.sendToScope(scopeId, payload);
3171
+ return result.messageId;
3172
+ }
3173
+ // ===========================================================================
3174
+ // Subscriptions
3175
+ // ===========================================================================
3176
+ /**
3177
+ * Subscribe to events with automatic tracking
3178
+ */
3179
+ async subscribe(filter) {
3180
+ const subscription = await this.#connection.subscribe(filter);
3181
+ this.#subscriptions.set(subscription.id, subscription);
3182
+ return subscription;
3183
+ }
3184
+ /**
3185
+ * Collect events matching filter for a duration
3186
+ */
3187
+ async collectEvents(filter, durationMs) {
3188
+ const events = [];
3189
+ const subscription = await this.subscribe(filter);
3190
+ subscription.on("event", (event) => events.push(event));
3191
+ await new Promise((resolve) => setTimeout(resolve, durationMs));
3192
+ await subscription.unsubscribe();
3193
+ this.#subscriptions.delete(subscription.id);
3194
+ return events;
3195
+ }
3196
+ /**
3197
+ * Wait for a specific event type
3198
+ */
3199
+ async waitForEvent(eventType, timeoutMs = 5e3) {
3200
+ const subscription = await this.subscribe({ eventTypes: [eventType] });
3201
+ try {
3202
+ return await new Promise((resolve, reject) => {
3203
+ const timeout = setTimeout(() => {
3204
+ reject(new Error(`Timeout waiting for event: ${eventType}`));
3205
+ }, timeoutMs);
3206
+ subscription.on("event", (event) => {
3207
+ clearTimeout(timeout);
3208
+ resolve(event);
3209
+ });
3210
+ });
3211
+ } finally {
3212
+ await subscription.unsubscribe();
3213
+ this.#subscriptions.delete(subscription.id);
3214
+ }
3215
+ }
3216
+ // ===========================================================================
3217
+ // Control
3218
+ // ===========================================================================
3219
+ /**
3220
+ * Stop an agent
3221
+ */
3222
+ async stopAgent(agentId, reason) {
3223
+ await this.#connection.stopAgent(agentId, { reason });
3224
+ }
3225
+ /**
3226
+ * Inject context into an agent
3227
+ */
3228
+ async inject(agentId, content, delivery) {
3229
+ await this.#connection.inject(agentId, content, delivery);
3230
+ }
3231
+ };
3232
+
3233
+ // src/connection/agent.ts
3234
+ var AgentConnection = class {
3235
+ #connection;
3236
+ #subscriptions = /* @__PURE__ */ new Map();
3237
+ #options;
3238
+ #messageHandlers = /* @__PURE__ */ new Set();
3239
+ #reconnectionHandlers = /* @__PURE__ */ new Set();
3240
+ #scopeMemberships = /* @__PURE__ */ new Set();
3241
+ #agentId = null;
3242
+ #sessionId = null;
3243
+ #serverCapabilities = null;
3244
+ #currentState = "registered";
3245
+ #connected = false;
3246
+ #lastConnectOptions;
3247
+ #isReconnecting = false;
3248
+ constructor(stream, options = {}) {
3249
+ this.#connection = new BaseConnection(stream, options);
3250
+ this.#options = options;
3251
+ this.#connection.setNotificationHandler(this.#handleNotification.bind(this));
3252
+ if (options.reconnection?.enabled && options.createStream) {
3253
+ this.#connection.onStateChange((newState) => {
3254
+ if (newState === "closed" && this.#connected && !this.#isReconnecting) {
3255
+ void this.#handleDisconnect();
3256
+ }
3257
+ });
3258
+ }
3259
+ }
3260
+ // ===========================================================================
3261
+ // Connection Lifecycle
3262
+ // ===========================================================================
3263
+ /**
3264
+ * Connect and register with the MAP system
3265
+ */
3266
+ async connect(options) {
3267
+ const connectParams = {
3268
+ protocolVersion: PROTOCOL_VERSION,
3269
+ participantType: "agent",
3270
+ participantId: options?.agentId,
3271
+ name: this.#options.name,
3272
+ capabilities: this.#options.capabilities,
3273
+ auth: options?.auth
3274
+ };
3275
+ const connectResult = await this.#connection.sendRequest(CORE_METHODS.CONNECT, connectParams);
3276
+ this.#sessionId = connectResult.sessionId;
3277
+ this.#serverCapabilities = connectResult.capabilities;
3278
+ this.#connected = true;
3279
+ this.#lastConnectOptions = options;
3280
+ const registerParams = {
3281
+ agentId: options?.agentId,
3282
+ name: this.#options.name,
3283
+ role: this.#options.role,
3284
+ parent: this.#options.parent,
3285
+ scopes: this.#options.scopes,
3286
+ visibility: this.#options.visibility,
3287
+ capabilities: this.#options.capabilities
3288
+ };
3289
+ const registerResult = await this.#connection.sendRequest(LIFECYCLE_METHODS.AGENTS_REGISTER, registerParams);
3290
+ this.#agentId = registerResult.agent.id;
3291
+ this.#currentState = registerResult.agent.state;
3292
+ this.#connection._transitionTo("connected");
3293
+ return { connection: connectResult, agent: registerResult.agent };
3294
+ }
3295
+ /**
3296
+ * Disconnect from the MAP system
3297
+ */
3298
+ async disconnect(reason) {
3299
+ if (!this.#connected) return;
3300
+ try {
3301
+ if (this.#agentId) {
3302
+ await this.#connection.sendRequest(LIFECYCLE_METHODS.AGENTS_UNREGISTER, {
3303
+ agentId: this.#agentId,
3304
+ reason
3305
+ });
3306
+ }
3307
+ await this.#connection.sendRequest(
3308
+ CORE_METHODS.DISCONNECT,
3309
+ reason ? { reason } : void 0
3310
+ );
3311
+ } finally {
3312
+ for (const subscription of this.#subscriptions.values()) {
3313
+ subscription._close();
3314
+ }
3315
+ this.#subscriptions.clear();
3316
+ await this.#connection.close();
3317
+ this.#connected = false;
3318
+ }
3319
+ }
3320
+ /**
3321
+ * Whether the agent is connected
3322
+ */
3323
+ get isConnected() {
3324
+ return this.#connected && !this.#connection.isClosed;
3325
+ }
3326
+ /**
3327
+ * This agent's ID
3328
+ */
3329
+ get agentId() {
3330
+ return this.#agentId;
3331
+ }
3332
+ /**
3333
+ * Current session ID
3334
+ */
3335
+ get sessionId() {
3336
+ return this.#sessionId;
3337
+ }
3338
+ /**
3339
+ * Server capabilities
3340
+ */
3341
+ get serverCapabilities() {
3342
+ return this.#serverCapabilities;
3343
+ }
3344
+ /**
3345
+ * Current agent state
3346
+ */
3347
+ get state() {
3348
+ return this.#currentState;
3349
+ }
3350
+ /**
3351
+ * AbortSignal that triggers when the connection closes
3352
+ */
3353
+ get signal() {
3354
+ return this.#connection.signal;
3355
+ }
3356
+ /**
3357
+ * Promise that resolves when the connection closes
3358
+ */
3359
+ get closed() {
3360
+ return this.#connection.closed;
3361
+ }
3362
+ // ===========================================================================
3363
+ // Message Handling
3364
+ // ===========================================================================
3365
+ /**
3366
+ * Register a handler for incoming messages
3367
+ */
3368
+ onMessage(handler) {
3369
+ this.#messageHandlers.add(handler);
3370
+ return this;
3371
+ }
3372
+ /**
3373
+ * Remove a message handler
3374
+ */
3375
+ offMessage(handler) {
3376
+ this.#messageHandlers.delete(handler);
3377
+ return this;
3378
+ }
3379
+ // ===========================================================================
3380
+ // State Management
3381
+ // ===========================================================================
3382
+ /**
3383
+ * Update this agent's state
3384
+ */
3385
+ async updateState(state) {
3386
+ if (!this.#agentId) {
3387
+ throw new Error("Agent not registered");
3388
+ }
3389
+ const result = await this.#connection.sendRequest(STATE_METHODS.AGENTS_UPDATE, {
3390
+ agentId: this.#agentId,
3391
+ state
3392
+ });
3393
+ this.#currentState = result.agent.state;
3394
+ return result.agent;
3395
+ }
3396
+ /**
3397
+ * Update this agent's metadata
3398
+ */
3399
+ async updateMetadata(metadata) {
3400
+ if (!this.#agentId) {
3401
+ throw new Error("Agent not registered");
3402
+ }
3403
+ const result = await this.#connection.sendRequest(STATE_METHODS.AGENTS_UPDATE, {
3404
+ agentId: this.#agentId,
3405
+ metadata
3406
+ });
3407
+ return result.agent;
3408
+ }
3409
+ /**
3410
+ * Mark this agent as busy
3411
+ */
3412
+ async busy() {
3413
+ return this.updateState("busy");
3414
+ }
3415
+ /**
3416
+ * Mark this agent as idle
3417
+ */
3418
+ async idle() {
3419
+ return this.updateState("idle");
3420
+ }
3421
+ /**
3422
+ * Mark this agent as done/stopped
3423
+ */
3424
+ async done(result) {
3425
+ if (!this.#agentId) {
3426
+ throw new Error("Agent not registered");
3427
+ }
3428
+ await this.updateState("stopped");
3429
+ if (result) {
3430
+ await this.updateMetadata({
3431
+ exitCode: result.exitCode,
3432
+ exitReason: result.exitReason
3433
+ });
3434
+ }
3435
+ }
3436
+ // ===========================================================================
3437
+ // Child Agent Management
3438
+ // ===========================================================================
3439
+ /**
3440
+ * Spawn a child agent
3441
+ */
3442
+ async spawn(options) {
3443
+ if (!this.#agentId) {
3444
+ throw new Error("Agent not registered");
3445
+ }
3446
+ const params = {
3447
+ ...options,
3448
+ parent: this.#agentId
3449
+ };
3450
+ return this.#connection.sendRequest(LIFECYCLE_METHODS.AGENTS_SPAWN, params);
3451
+ }
3452
+ // ===========================================================================
3453
+ // Messaging
3454
+ // ===========================================================================
3455
+ /**
3456
+ * Send a message to an address
3457
+ */
3458
+ async send(to, payload, meta) {
3459
+ const params = { to };
3460
+ if (payload !== void 0) params.payload = payload;
3461
+ if (meta) params.meta = meta;
3462
+ return this.#connection.sendRequest(CORE_METHODS.SEND, params);
3463
+ }
3464
+ /**
3465
+ * Send a message to the parent agent
3466
+ */
3467
+ async sendToParent(payload, meta) {
3468
+ return this.send({ parent: true }, payload, {
3469
+ ...meta,
3470
+ relationship: "child-to-parent"
3471
+ });
3472
+ }
3473
+ /**
3474
+ * Send a message to child agents
3475
+ */
3476
+ async sendToChildren(payload, meta) {
3477
+ return this.send({ children: true }, payload, {
3478
+ ...meta,
3479
+ relationship: "parent-to-child"
3480
+ });
3481
+ }
3482
+ /**
3483
+ * Send a message to a specific agent
3484
+ */
3485
+ async sendToAgent(agentId, payload, meta) {
3486
+ return this.send({ agent: agentId }, payload, meta);
3487
+ }
3488
+ /**
3489
+ * Send a message to all agents in a scope
3490
+ */
3491
+ async sendToScope(scopeId, payload, meta) {
3492
+ return this.send({ scope: scopeId }, payload, meta);
3493
+ }
3494
+ /**
3495
+ * Send a message to sibling agents
3496
+ */
3497
+ async sendToSiblings(payload, meta) {
3498
+ return this.send({ siblings: true }, payload, {
3499
+ ...meta,
3500
+ relationship: "peer"
3501
+ });
3502
+ }
3503
+ /**
3504
+ * Reply to a message (uses correlationId from original)
3505
+ */
3506
+ async reply(originalMessage, payload, meta) {
3507
+ return this.send({ agent: originalMessage.from }, payload, {
3508
+ ...meta,
3509
+ correlationId: originalMessage.meta?.correlationId ?? originalMessage.id,
3510
+ isResult: true
3511
+ });
3512
+ }
3513
+ // ===========================================================================
3514
+ // Scope Management
3515
+ // ===========================================================================
3516
+ /**
3517
+ * Create a new scope
3518
+ */
3519
+ async createScope(options) {
3520
+ const result = await this.#connection.sendRequest(SCOPE_METHODS.SCOPES_CREATE, options);
3521
+ return result.scope;
3522
+ }
3523
+ /**
3524
+ * Join a scope
3525
+ */
3526
+ async joinScope(scopeId) {
3527
+ if (!this.#agentId) {
3528
+ throw new Error("Agent not registered");
3529
+ }
3530
+ const result = await this.#connection.sendRequest(SCOPE_METHODS.SCOPES_JOIN, {
3531
+ scopeId,
3532
+ agentId: this.#agentId
3533
+ });
3534
+ this.#scopeMemberships.add(scopeId);
3535
+ return result;
3536
+ }
3537
+ /**
3538
+ * Leave a scope
3539
+ */
3540
+ async leaveScope(scopeId) {
3541
+ if (!this.#agentId) {
3542
+ throw new Error("Agent not registered");
3543
+ }
3544
+ const result = await this.#connection.sendRequest(SCOPE_METHODS.SCOPES_LEAVE, {
3545
+ scopeId,
3546
+ agentId: this.#agentId
3547
+ });
3548
+ this.#scopeMemberships.delete(scopeId);
3549
+ return result;
3550
+ }
3551
+ // ===========================================================================
3552
+ // Subscriptions
3553
+ // ===========================================================================
3554
+ /**
3555
+ * Subscribe to events
3556
+ */
3557
+ async subscribe(filter) {
3558
+ const params = {};
3559
+ if (filter) params.filter = filter;
3560
+ const result = await this.#connection.sendRequest(CORE_METHODS.SUBSCRIBE, params);
3561
+ const subscription = createSubscription(
3562
+ result.subscriptionId,
3563
+ () => this.unsubscribe(result.subscriptionId),
3564
+ { filter }
3565
+ );
3566
+ this.#subscriptions.set(result.subscriptionId, subscription);
3567
+ return subscription;
3568
+ }
3569
+ /**
3570
+ * Unsubscribe from events
3571
+ */
3572
+ async unsubscribe(subscriptionId) {
3573
+ const subscription = this.#subscriptions.get(subscriptionId);
3574
+ if (subscription) {
3575
+ subscription._close();
3576
+ this.#subscriptions.delete(subscriptionId);
3577
+ }
3578
+ await this.#connection.sendRequest(CORE_METHODS.UNSUBSCRIBE, { subscriptionId });
3579
+ }
3580
+ // ===========================================================================
3581
+ // Reconnection
3582
+ // ===========================================================================
3583
+ /**
3584
+ * Current connection state
3585
+ */
3586
+ get connectionState() {
3587
+ return this.#connection.state;
3588
+ }
3589
+ /**
3590
+ * Whether the connection is currently reconnecting
3591
+ */
3592
+ get isReconnecting() {
3593
+ return this.#isReconnecting;
3594
+ }
3595
+ /**
3596
+ * Register a handler for reconnection events.
3597
+ *
3598
+ * @param handler - Function called when reconnection events occur
3599
+ * @returns Unsubscribe function to remove the handler
3600
+ */
3601
+ onReconnection(handler) {
3602
+ this.#reconnectionHandlers.add(handler);
3603
+ return () => this.#reconnectionHandlers.delete(handler);
3604
+ }
3605
+ /**
3606
+ * Register a handler for connection state changes.
3607
+ *
3608
+ * @param handler - Function called when state changes
3609
+ * @returns Unsubscribe function to remove the handler
3610
+ */
3611
+ onStateChange(handler) {
3612
+ return this.#connection.onStateChange(handler);
3613
+ }
3614
+ // ===========================================================================
3615
+ // Internal
3616
+ // ===========================================================================
3617
+ /**
3618
+ * Handle incoming notifications
3619
+ */
3620
+ async #handleNotification(method, params) {
3621
+ switch (method) {
3622
+ case NOTIFICATION_METHODS.EVENT: {
3623
+ const eventParams = params;
3624
+ const subscription = this.#subscriptions.get(eventParams.subscriptionId);
3625
+ if (subscription) {
3626
+ subscription._pushEvent(eventParams);
3627
+ }
3628
+ break;
3629
+ }
3630
+ case NOTIFICATION_METHODS.MESSAGE: {
3631
+ const messageParams = params;
3632
+ for (const handler of this.#messageHandlers) {
3633
+ try {
3634
+ await handler(messageParams.message);
3635
+ } catch (error) {
3636
+ console.error("MAP: Message handler error:", error);
3637
+ }
3638
+ }
3639
+ break;
3640
+ }
3641
+ default:
3642
+ console.warn("MAP: Unknown notification:", method);
3643
+ }
3644
+ }
3645
+ /**
3646
+ * Emit a reconnection event to all registered handlers
3647
+ */
3648
+ #emitReconnectionEvent(event) {
3649
+ for (const handler of this.#reconnectionHandlers) {
3650
+ try {
3651
+ handler(event);
3652
+ } catch (error) {
3653
+ console.error("MAP: Reconnection event handler error:", error);
3654
+ }
3655
+ }
3656
+ }
3657
+ /**
3658
+ * Handle disconnect when auto-reconnect is enabled
3659
+ */
3660
+ async #handleDisconnect() {
3661
+ this.#isReconnecting = true;
3662
+ this.#connected = false;
3663
+ this.#emitReconnectionEvent({ type: "disconnected" });
3664
+ try {
3665
+ await this.#attemptReconnect();
3666
+ } catch (error) {
3667
+ this.#isReconnecting = false;
3668
+ this.#emitReconnectionEvent({
3669
+ type: "reconnectFailed",
3670
+ error: error instanceof Error ? error : new Error(String(error))
3671
+ });
3672
+ }
3673
+ }
3674
+ /**
3675
+ * Attempt to reconnect with retry logic
3676
+ */
3677
+ async #attemptReconnect() {
3678
+ const options = this.#options.reconnection;
3679
+ const createStream = this.#options.createStream;
3680
+ const retryPolicy = {
3681
+ maxRetries: options.maxRetries ?? DEFAULT_RETRY_POLICY.maxRetries,
3682
+ baseDelayMs: options.baseDelayMs ?? DEFAULT_RETRY_POLICY.baseDelayMs,
3683
+ maxDelayMs: options.maxDelayMs ?? DEFAULT_RETRY_POLICY.maxDelayMs,
3684
+ jitter: options.jitter ?? DEFAULT_RETRY_POLICY.jitter
3685
+ };
3686
+ const scopesToRestore = Array.from(this.#scopeMemberships);
3687
+ await withRetry(
3688
+ async () => {
3689
+ const newStream = await createStream();
3690
+ await this.#connection.reconnect(newStream);
3691
+ const result = await this.connect({
3692
+ agentId: this.#agentId ?? this.#lastConnectOptions?.agentId,
3693
+ auth: this.#lastConnectOptions?.auth
3694
+ });
3695
+ this.#agentId = result.agent.id;
3696
+ this.#sessionId = result.connection.sessionId;
3697
+ this.#serverCapabilities = result.connection.capabilities;
3698
+ this.#currentState = result.agent.state;
3699
+ },
3700
+ retryPolicy,
3701
+ {
3702
+ onRetry: (state) => {
3703
+ this.#emitReconnectionEvent({
3704
+ type: "reconnecting",
3705
+ attempt: state.attempt,
3706
+ delay: state.nextDelayMs,
3707
+ error: state.lastError
3708
+ });
3709
+ }
3710
+ }
3711
+ );
3712
+ this.#isReconnecting = false;
3713
+ this.#emitReconnectionEvent({ type: "reconnected" });
3714
+ if (options.restoreScopeMemberships !== false) {
3715
+ await this.#restoreScopeMemberships(scopesToRestore);
3716
+ }
3717
+ }
3718
+ /**
3719
+ * Restore scope memberships after reconnection
3720
+ */
3721
+ async #restoreScopeMemberships(scopes) {
3722
+ this.#scopeMemberships.clear();
3723
+ for (const scopeId of scopes) {
3724
+ try {
3725
+ await this.joinScope(scopeId);
3726
+ } catch (error) {
3727
+ console.warn("MAP: Failed to restore scope membership:", scopeId, error);
3728
+ }
3729
+ }
3730
+ }
3731
+ };
3732
+
3733
+ // src/testing/agent.ts
3734
+ var TestAgent = class _TestAgent {
3735
+ #connection;
3736
+ #receivedMessages = [];
3737
+ #subscriptions = /* @__PURE__ */ new Map();
3738
+ constructor(connection) {
3739
+ this.#connection = connection;
3740
+ connection.onMessage((msg) => {
3741
+ this.#receivedMessages.push(msg);
3742
+ });
3743
+ }
3744
+ /**
3745
+ * Create and connect a test agent
3746
+ */
3747
+ static async create(server, options = {}) {
3748
+ const [clientStream, serverStream] = createStreamPair();
3749
+ const connection = new AgentConnection(clientStream, options);
3750
+ server.acceptConnection(serverStream);
3751
+ const agent = new _TestAgent(connection);
3752
+ if (options.autoConnect !== false) {
3753
+ await connection.connect({ agentId: options.agentId });
3754
+ }
3755
+ return agent;
3756
+ }
3757
+ /**
3758
+ * Get the underlying connection
3759
+ */
3760
+ get connection() {
3761
+ return this.#connection;
3762
+ }
3763
+ /**
3764
+ * Whether the agent is connected
3765
+ */
3766
+ get isConnected() {
3767
+ return this.#connection.isConnected;
3768
+ }
3769
+ /**
3770
+ * Agent ID
3771
+ */
3772
+ get id() {
3773
+ return this.#connection.agentId;
3774
+ }
3775
+ /**
3776
+ * Current state
3777
+ */
3778
+ get state() {
3779
+ return this.#connection.state;
3780
+ }
3781
+ /**
3782
+ * Session ID
3783
+ */
3784
+ get sessionId() {
3785
+ return this.#connection.sessionId;
3786
+ }
3787
+ /**
3788
+ * All messages received by this agent
3789
+ */
3790
+ get receivedMessages() {
3791
+ return this.#receivedMessages;
3792
+ }
3793
+ /**
3794
+ * Get the last received message
3795
+ */
3796
+ get lastMessage() {
3797
+ return this.#receivedMessages[this.#receivedMessages.length - 1];
3798
+ }
3799
+ /**
3800
+ * Clear received messages
3801
+ */
3802
+ clearMessages() {
3803
+ this.#receivedMessages.length = 0;
3804
+ }
3805
+ /**
3806
+ * Connect to the server
3807
+ */
3808
+ async connect(agentId) {
3809
+ const result = await this.#connection.connect({ agentId });
3810
+ return result.agent;
3811
+ }
3812
+ /**
3813
+ * Disconnect from the server
3814
+ */
3815
+ async disconnect(reason) {
3816
+ for (const sub of this.#subscriptions.values()) {
3817
+ await sub.unsubscribe();
3818
+ }
3819
+ this.#subscriptions.clear();
3820
+ await this.#connection.disconnect(reason);
3821
+ }
3822
+ // ===========================================================================
3823
+ // State Management
3824
+ // ===========================================================================
3825
+ /**
3826
+ * Update state
3827
+ */
3828
+ async setState(state) {
3829
+ return this.#connection.updateState(state);
3830
+ }
3831
+ /**
3832
+ * Mark as busy
3833
+ */
3834
+ async busy() {
3835
+ return this.#connection.busy();
3836
+ }
3837
+ /**
3838
+ * Mark as idle
3839
+ */
3840
+ async idle() {
3841
+ return this.#connection.idle();
3842
+ }
3843
+ /**
3844
+ * Mark as done
3845
+ */
3846
+ async done(exitCode, exitReason) {
3847
+ await this.#connection.done({ exitCode, exitReason });
3848
+ }
3849
+ /**
3850
+ * Update metadata
3851
+ */
3852
+ async setMetadata(metadata) {
3853
+ return this.#connection.updateMetadata(metadata);
3854
+ }
3855
+ // ===========================================================================
3856
+ // Child Agent Management
3857
+ // ===========================================================================
3858
+ /**
3859
+ * Spawn a child agent
3860
+ */
3861
+ async spawn(options = {}) {
3862
+ const result = await this.#connection.spawn(options);
3863
+ return result.agent;
3864
+ }
3865
+ /**
3866
+ * Spawn and return a connected TestAgent for the child
3867
+ */
3868
+ async spawnTestAgent(server, options = {}) {
3869
+ await this.#connection.spawn({
3870
+ name: options.name,
3871
+ role: options.role,
3872
+ agentId: options.agentId
3873
+ });
3874
+ const child = await _TestAgent.create(server, {
3875
+ ...options,
3876
+ parent: this.id,
3877
+ autoConnect: false
3878
+ });
3879
+ return child;
3880
+ }
3881
+ // ===========================================================================
3882
+ // Scope Management
3883
+ // ===========================================================================
3884
+ /**
3885
+ * Create a scope
3886
+ */
3887
+ async createScope(name, options) {
3888
+ return this.#connection.createScope({ name, ...options });
3889
+ }
3890
+ /**
3891
+ * Join a scope
3892
+ */
3893
+ async joinScope(scopeId) {
3894
+ return this.#connection.joinScope(scopeId);
3895
+ }
3896
+ /**
3897
+ * Leave a scope
3898
+ */
3899
+ async leaveScope(scopeId) {
3900
+ return this.#connection.leaveScope(scopeId);
3901
+ }
3902
+ // ===========================================================================
3903
+ // Messaging
3904
+ // ===========================================================================
3905
+ /**
3906
+ * Send to another agent
3907
+ */
3908
+ async sendTo(agentId, payload) {
3909
+ const result = await this.#connection.sendToAgent(agentId, payload);
3910
+ return result.messageId;
3911
+ }
3912
+ /**
3913
+ * Send to parent
3914
+ */
3915
+ async sendToParent(payload) {
3916
+ const result = await this.#connection.sendToParent(payload);
3917
+ return result.messageId;
3918
+ }
3919
+ /**
3920
+ * Send to children
3921
+ */
3922
+ async sendToChildren(payload) {
3923
+ const result = await this.#connection.sendToChildren(payload);
3924
+ return result.messageId;
3925
+ }
3926
+ /**
3927
+ * Send to siblings
3928
+ */
3929
+ async sendToSiblings(payload) {
3930
+ const result = await this.#connection.sendToSiblings(payload);
3931
+ return result.messageId;
3932
+ }
3933
+ /**
3934
+ * Send to scope
3935
+ */
3936
+ async sendToScope(scopeId, payload) {
3937
+ const result = await this.#connection.sendToScope(scopeId, payload);
3938
+ return result.messageId;
3939
+ }
3940
+ /**
3941
+ * Reply to a message
3942
+ */
3943
+ async reply(message, payload) {
3944
+ const result = await this.#connection.reply(message, payload);
3945
+ return result.messageId;
3946
+ }
3947
+ /**
3948
+ * Wait for a message
3949
+ */
3950
+ async waitForMessage(timeoutMs = 5e3) {
3951
+ const startLen = this.#receivedMessages.length;
3952
+ return new Promise((resolve, reject) => {
3953
+ const timeout = setTimeout(() => {
3954
+ reject(new Error("Timeout waiting for message"));
3955
+ }, timeoutMs);
3956
+ const checkInterval = setInterval(() => {
3957
+ if (this.#receivedMessages.length > startLen) {
3958
+ clearTimeout(timeout);
3959
+ clearInterval(checkInterval);
3960
+ resolve(this.#receivedMessages[this.#receivedMessages.length - 1]);
3961
+ }
3962
+ }, 10);
3963
+ });
3964
+ }
3965
+ // ===========================================================================
3966
+ // Subscriptions
3967
+ // ===========================================================================
3968
+ /**
3969
+ * Subscribe to events
3970
+ */
3971
+ async subscribe(filter) {
3972
+ const subscription = await this.#connection.subscribe(filter);
3973
+ this.#subscriptions.set(subscription.id, subscription);
3974
+ return subscription;
3975
+ }
3976
+ /**
3977
+ * Wait for a specific event
3978
+ */
3979
+ async waitForEvent(eventType, timeoutMs = 5e3) {
3980
+ const subscription = await this.subscribe({ eventTypes: [eventType] });
3981
+ try {
3982
+ return await new Promise((resolve, reject) => {
3983
+ const timeout = setTimeout(() => {
3984
+ reject(new Error(`Timeout waiting for event: ${eventType}`));
3985
+ }, timeoutMs);
3986
+ subscription.on("event", (event) => {
3987
+ clearTimeout(timeout);
3988
+ resolve(event);
3989
+ });
3990
+ });
3991
+ } finally {
3992
+ await subscription.unsubscribe();
3993
+ this.#subscriptions.delete(subscription.id);
3994
+ }
3995
+ }
3996
+ };
3997
+
3998
+ export { TestAgent, TestClient, TestServer };
3999
+ //# sourceMappingURL=testing.js.map
4000
+ //# sourceMappingURL=testing.js.map