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