macro-agent 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/README.md +67 -57
  3. package/dist/acp/index.d.ts +3 -3
  4. package/dist/acp/index.js +1 -1
  5. package/dist/acp/index.js.map +1 -1
  6. package/dist/cli/acp.d.ts +22 -20
  7. package/dist/cli/acp.d.ts.map +1 -1
  8. package/dist/cli/acp.js +134 -44
  9. package/dist/cli/acp.js.map +1 -1
  10. package/dist/cli/index.d.ts +5 -1
  11. package/dist/cli/index.d.ts.map +1 -1
  12. package/dist/cli/index.js +6 -2
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/map/adapter/acp-over-map.d.ts +77 -0
  15. package/dist/map/adapter/acp-over-map.d.ts.map +1 -0
  16. package/dist/map/adapter/acp-over-map.js +372 -0
  17. package/dist/map/adapter/acp-over-map.js.map +1 -0
  18. package/dist/map/adapter/index.d.ts +1 -0
  19. package/dist/map/adapter/index.d.ts.map +1 -1
  20. package/dist/map/adapter/index.js +2 -0
  21. package/dist/map/adapter/index.js.map +1 -1
  22. package/dist/map/adapter/interface.d.ts +22 -0
  23. package/dist/map/adapter/interface.d.ts.map +1 -1
  24. package/dist/map/adapter/map-adapter.d.ts +25 -1
  25. package/dist/map/adapter/map-adapter.d.ts.map +1 -1
  26. package/dist/map/adapter/map-adapter.js +147 -21
  27. package/dist/map/adapter/map-adapter.js.map +1 -1
  28. package/dist/map/adapter/rpc-handler.d.ts.map +1 -1
  29. package/dist/map/adapter/rpc-handler.js +4 -0
  30. package/dist/map/adapter/rpc-handler.js.map +1 -1
  31. package/dist/map/adapter/subscription-manager.js.map +1 -1
  32. package/dist/server/combined-server.d.ts.map +1 -1
  33. package/dist/server/combined-server.js +8 -1
  34. package/dist/server/combined-server.js.map +1 -1
  35. package/dist/store/event-store.js +7 -1
  36. package/dist/store/event-store.js.map +1 -1
  37. package/docs/configuration.md +27 -18
  38. package/docs/sudocode-integration.md +1 -1
  39. package/docs/troubleshooting.md +3 -3
  40. package/package.json +4 -4
  41. package/src/acp/index.ts +4 -4
  42. package/src/cli/__tests__/acp.test.ts +48 -40
  43. package/src/cli/acp.ts +149 -55
  44. package/src/cli/index.ts +6 -2
  45. package/src/map/adapter/acp-over-map.ts +516 -0
  46. package/src/map/adapter/index.ts +8 -0
  47. package/src/map/adapter/interface.ts +30 -6
  48. package/src/map/adapter/map-adapter.ts +325 -76
  49. package/src/map/adapter/rpc-handler.ts +4 -0
  50. package/src/map/adapter/subscription-manager.ts +10 -10
  51. package/src/server/combined-server.ts +8 -1
  52. package/src/store/event-store.ts +8 -2
  53. package/src/trigger/__tests__/wake-manager.test.ts +26 -9
@@ -38,11 +38,18 @@ import type {
38
38
  SubscriptionId,
39
39
  SubscriptionFilter,
40
40
  EventNotification,
41
+ MAPEventType,
41
42
  } from "./types.js";
42
- import type { Address, SendOptions, ScopeId } from "../types.js";
43
+ import { isAgentAddress, type Address, type SendOptions, type ScopeId } from "../types.js";
43
44
  import type { AgentId } from "../../store/types/index.js";
44
- import { createConnectionManager, type ConnectionManager } from "./connection-manager.js";
45
- import { createSubscriptionManager, type SubscriptionManager } from "./subscription-manager.js";
45
+ import {
46
+ createConnectionManager,
47
+ type ConnectionManager,
48
+ } from "./connection-manager.js";
49
+ import {
50
+ createSubscriptionManager,
51
+ type SubscriptionManager,
52
+ } from "./subscription-manager.js";
46
53
  import {
47
54
  RPCHandler,
48
55
  createRPCHandler,
@@ -54,7 +61,13 @@ import {
54
61
  type JsonRpcMessage,
55
62
  } from "./rpc-handler.js";
56
63
  import { EXTENSION_CAPABILITIES } from "./extensions/index.js";
57
- import type { FederationHandler, FederationCapabilities, ConnectedPeer, MAPPeerConfig } from "../federation/types.js";
64
+ import type {
65
+ FederationHandler,
66
+ FederationCapabilities,
67
+ ConnectedPeer,
68
+ MAPPeerConfig,
69
+ } from "../federation/types.js";
70
+ import { ACPOverMAPHandler, type ACPEnvelope } from "./acp-over-map.js";
58
71
 
59
72
  // =============================================================================
60
73
  // Connection Session
@@ -97,7 +110,7 @@ export interface MAPAdapterServices {
97
110
  from: AgentId,
98
111
  to: Address,
99
112
  content: string,
100
- options?: SendOptions
113
+ options?: SendOptions,
101
114
  ) => Promise<{ delivered: AgentId[] }>;
102
115
 
103
116
  /**
@@ -114,6 +127,26 @@ export interface MAPAdapterServices {
114
127
  * Optional federation handler for cross-system communication.
115
128
  */
116
129
  federationHandler?: FederationHandler;
130
+
131
+ /**
132
+ * Full AgentManager reference for ACP-over-MAP support.
133
+ */
134
+ agentManager?: import("../../agent/agent-manager.js").AgentManager;
135
+
136
+ /**
137
+ * Full EventStore reference for ACP-over-MAP support.
138
+ */
139
+ eventStore?: import("../../store/event-store.js").EventStore;
140
+
141
+ /**
142
+ * Full TaskManager reference for ACP-over-MAP support.
143
+ */
144
+ taskManager?: import("../../task/task-manager.js").TaskManager;
145
+
146
+ /**
147
+ * Default working directory for ACP sessions.
148
+ */
149
+ defaultCwd?: string;
117
150
  }
118
151
 
119
152
  /**
@@ -161,10 +194,16 @@ export class MAPAdapterImpl implements MAPAdapter {
161
194
  private readonly scopes: Map<ScopeId, ScopeState> = new Map();
162
195
  private readonly eventHandlers: Set<AdapterEventHandler> = new Set();
163
196
  private readonly services: MAPAdapterServices;
197
+ private readonly acpOverMapHandler: ACPOverMAPHandler | null = null;
198
+ /** Sequence numbers per subscription for proper event ordering */
199
+ private readonly subscriptionSequences: Map<SubscriptionId, number> = new Map();
164
200
 
165
201
  private running = false;
166
202
 
167
- constructor(config: MAPAdapterConfig = {}, services: MAPAdapterServices = {}) {
203
+ constructor(
204
+ config: MAPAdapterConfig = {},
205
+ services: MAPAdapterServices = {},
206
+ ) {
168
207
  this.config = {
169
208
  name: config.name ?? "macro-agent",
170
209
  version: config.version ?? "1.0.0",
@@ -172,6 +211,17 @@ export class MAPAdapterImpl implements MAPAdapter {
172
211
  };
173
212
  this.services = services;
174
213
 
214
+ // Initialize ACP-over-MAP handler if services are available
215
+ if (services.agentManager && services.eventStore && services.taskManager) {
216
+ this.acpOverMapHandler = new ACPOverMAPHandler({
217
+ agentManager: services.agentManager,
218
+ eventStore: services.eventStore,
219
+ taskManager: services.taskManager,
220
+ defaultCwd: services.defaultCwd,
221
+ });
222
+ console.error("[MAPAdapter] ACP-over-MAP handler initialized");
223
+ }
224
+
175
225
  // Initialize connection manager
176
226
  this.connections = createConnectionManager({
177
227
  limits: config.limits,
@@ -264,7 +314,10 @@ export class MAPAdapterImpl implements MAPAdapter {
264
314
  return participant;
265
315
  }
266
316
 
267
- async disconnectParticipant(id: ParticipantId, reason?: string): Promise<void> {
317
+ async disconnectParticipant(
318
+ id: ParticipantId,
319
+ reason?: string,
320
+ ): Promise<void> {
268
321
  const session = this.sessions.get(id);
269
322
  if (!session) {
270
323
  return;
@@ -308,7 +361,7 @@ export class MAPAdapterImpl implements MAPAdapter {
308
361
  participantId: ParticipantId,
309
362
  to: Address,
310
363
  payload: MessagePayload,
311
- options?: SendOptions
364
+ options?: SendOptions,
312
365
  ): Promise<SendResult> {
313
366
  const participant = this.connections.getParticipant(participantId);
314
367
  if (!participant) {
@@ -329,8 +382,10 @@ export class MAPAdapterImpl implements MAPAdapter {
329
382
  const result = await this.services.sendMessage(
330
383
  senderId,
331
384
  to,
332
- typeof payload.content === "string" ? payload.content : JSON.stringify(payload.content),
333
- options
385
+ typeof payload.content === "string"
386
+ ? payload.content
387
+ : JSON.stringify(payload.content),
388
+ options,
334
389
  );
335
390
 
336
391
  return {
@@ -345,7 +400,7 @@ export class MAPAdapterImpl implements MAPAdapter {
345
400
 
346
401
  async createSubscription(
347
402
  participantId: ParticipantId,
348
- filter?: SubscriptionFilter
403
+ filter?: SubscriptionFilter,
349
404
  ): Promise<SubscriptionId> {
350
405
  const participant = this.connections.getParticipant(participantId);
351
406
  if (!participant) {
@@ -361,6 +416,7 @@ export class MAPAdapterImpl implements MAPAdapter {
361
416
 
362
417
  async removeSubscription(subscriptionId: SubscriptionId): Promise<void> {
363
418
  this.subscriptions.unsubscribe(subscriptionId);
419
+ this.subscriptionSequences.delete(subscriptionId);
364
420
  }
365
421
 
366
422
  getSubscriptions(participantId: ParticipantId): SubscriptionId[] {
@@ -397,7 +453,10 @@ export class MAPAdapterImpl implements MAPAdapter {
397
453
  return agents.map((agent) => this.toAgentInfo(agent));
398
454
  }
399
455
 
400
- getAgent(participantId: ParticipantId, agentId: AgentId): AgentInfo | undefined {
456
+ getAgent(
457
+ participantId: ParticipantId,
458
+ agentId: AgentId,
459
+ ): AgentInfo | undefined {
401
460
  const participant = this.connections.getParticipant(participantId);
402
461
  if (!participant) {
403
462
  return undefined;
@@ -425,10 +484,15 @@ export class MAPAdapterImpl implements MAPAdapter {
425
484
  return [];
426
485
  }
427
486
 
428
- return Array.from(this.scopes.values()).map((scope) => this.toScopeInfo(scope));
487
+ return Array.from(this.scopes.values()).map((scope) =>
488
+ this.toScopeInfo(scope),
489
+ );
429
490
  }
430
491
 
431
- getScope(participantId: ParticipantId, scopeId: ScopeId): ScopeInfo | undefined {
492
+ getScope(
493
+ participantId: ParticipantId,
494
+ scopeId: ScopeId,
495
+ ): ScopeInfo | undefined {
432
496
  const participant = this.connections.getParticipant(participantId);
433
497
  if (!participant) {
434
498
  return undefined;
@@ -448,13 +512,33 @@ export class MAPAdapterImpl implements MAPAdapter {
448
512
 
449
513
  emitEvent(event: EventNotification): void {
450
514
  // Match event against subscriptions
451
- const { subscriptions: matchingSubs, participantIds } = this.subscriptions.match(event);
515
+ const { subscriptions: matchingSubs } =
516
+ this.subscriptions.match(event);
452
517
 
453
- // Send to each matched participant
454
- for (const participantId of participantIds) {
455
- const session = this.sessions.get(participantId);
518
+ // Send to each matched subscription with proper SDK format
519
+ // SDK EventNotificationParams: { subscriptionId, sequenceNumber, eventId?, timestamp?, event, causedBy? }
520
+ for (const subscription of matchingSubs) {
521
+ const session = this.sessions.get(subscription.participantId);
456
522
  if (session) {
457
- const notification = createNotification("map/event", event);
523
+ // Get and increment sequence number for this subscription
524
+ const currentSeq = this.subscriptionSequences.get(subscription.id) ?? 0;
525
+ this.subscriptionSequences.set(subscription.id, currentSeq + 1);
526
+
527
+ // Build params in SDK EventNotificationParams format
528
+ const params = {
529
+ subscriptionId: subscription.id,
530
+ sequenceNumber: currentSeq,
531
+ eventId: event.eventId, // For deduplication
532
+ timestamp: event.timestamp, // Envelope timestamp
533
+ event: { // The actual event object
534
+ id: event.eventId,
535
+ type: event.type,
536
+ timestamp: event.timestamp,
537
+ data: event.data,
538
+ },
539
+ ...(event.causedBy && { causedBy: event.causedBy }),
540
+ };
541
+ const notification = createNotification("map/event", params);
458
542
  this.sendToSession(session, notification).catch((error) => {
459
543
  console.error("[MAPAdapter] Failed to send event:", error);
460
544
  });
@@ -578,32 +662,46 @@ export class MAPAdapterImpl implements MAPAdapter {
578
662
  private createRPCHandler(participantId: ParticipantId): RPCHandler {
579
663
  const handlers: HandlerRegistry = {
580
664
  // Connection
581
- "map/connect": async (params) => this.handleConnect(participantId, params),
665
+ "map/connect": async (params) =>
666
+ this.handleConnect(participantId, params),
582
667
  "map/disconnect": async () => this.handleDisconnect(participantId),
583
668
 
584
669
  // Subscriptions
585
- "map/subscribe": async (params) => this.handleSubscribe(participantId, params),
586
- "map/unsubscribe": async (params) => this.handleUnsubscribe(participantId, params),
670
+ "map/subscribe": async (params) =>
671
+ this.handleSubscribe(participantId, params),
672
+ "map/unsubscribe": async (params) =>
673
+ this.handleUnsubscribe(participantId, params),
587
674
 
588
675
  // Messaging
589
- "map/send": async (params, ctx) => this.handleSend(participantId, params, ctx),
676
+ "map/send": async (params, ctx) =>
677
+ this.handleSend(participantId, params, ctx),
590
678
 
591
679
  // Agent queries
592
- "map/agents/list": async (params) => this.handleListAgents(participantId, params),
593
- "map/agents/get": async (params) => this.handleGetAgent(participantId, params),
680
+ "map/agents/list": async (params) =>
681
+ this.handleListAgents(participantId, params),
682
+ "map/agents/get": async (params) =>
683
+ this.handleGetAgent(participantId, params),
594
684
 
595
685
  // Scope queries
596
686
  "map/scopes/list": async () => this.handleListScopes(participantId),
597
- "map/scopes/get": async (params) => this.handleGetScope(participantId, params),
598
- "map/scopes/create": async (params, ctx) => this.handleCreateScope(participantId, params, ctx),
599
- "map/scopes/join": async (params) => this.handleJoinScope(participantId, params),
600
- "map/scopes/leave": async (params) => this.handleLeaveScope(participantId, params),
687
+ "map/scopes/get": async (params) =>
688
+ this.handleGetScope(participantId, params),
689
+ "map/scopes/create": async (params, ctx) =>
690
+ this.handleCreateScope(participantId, params, ctx),
691
+ "map/scopes/join": async (params) =>
692
+ this.handleJoinScope(participantId, params),
693
+ "map/scopes/leave": async (params) =>
694
+ this.handleLeaveScope(participantId, params),
601
695
 
602
696
  // Federation methods
603
- "map/federation/connect": async (params) => this.handleFederationConnect(participantId, params),
604
- "map/federation/disconnect": async (params) => this.handleFederationDisconnect(participantId, params),
605
- "map/federation/list": async () => this.handleFederationList(participantId),
606
- "map/federation/capabilities": async (params) => this.handleFederationCapabilities(participantId, params),
697
+ "map/federation/connect": async (params) =>
698
+ this.handleFederationConnect(participantId, params),
699
+ "map/federation/disconnect": async (params) =>
700
+ this.handleFederationDisconnect(participantId, params),
701
+ "map/federation/list": async () =>
702
+ this.handleFederationList(participantId),
703
+ "map/federation/capabilities": async (params) =>
704
+ this.handleFederationCapabilities(participantId, params),
607
705
  };
608
706
 
609
707
  // Add extension method handlers
@@ -612,14 +710,18 @@ export class MAPAdapterImpl implements MAPAdapter {
612
710
  const extCtx: ExtensionContext = {
613
711
  participantId: ctx.participantId,
614
712
  capabilities: ctx.capabilities,
615
- sessionId: this.sessions.get(ctx.participantId)?.participant.sessionId ?? "",
713
+ sessionId:
714
+ this.sessions.get(ctx.participantId)?.participant.sessionId ?? "",
616
715
  };
617
716
  return handler(extCtx, params);
618
717
  };
619
718
  }
620
719
 
621
720
  // Capability requirements for methods
622
- const capabilityRequirements: Record<string, keyof ConnectedParticipant["capabilities"]> = {
721
+ const capabilityRequirements: Record<
722
+ string,
723
+ keyof ConnectedParticipant["capabilities"]
724
+ > = {
623
725
  "map/subscribe": "canSubscribe",
624
726
  "map/send": "canMessage",
625
727
  "map/agents/list": "canQuery",
@@ -638,7 +740,8 @@ export class MAPAdapterImpl implements MAPAdapter {
638
740
  for (const method of this.extensions.keys()) {
639
741
  const capability = EXTENSION_CAPABILITIES[method];
640
742
  if (capability) {
641
- capabilityRequirements[method] = capability as keyof ConnectedParticipant["capabilities"];
743
+ capabilityRequirements[method] =
744
+ capability as keyof ConnectedParticipant["capabilities"];
642
745
  }
643
746
  }
644
747
 
@@ -660,7 +763,9 @@ export class MAPAdapterImpl implements MAPAdapter {
660
763
  }
661
764
 
662
765
  // Process the message
663
- const participant = this.connections.getParticipant(session.participantId);
766
+ const participant = this.connections.getParticipant(
767
+ session.participantId,
768
+ );
664
769
  if (!participant) {
665
770
  break;
666
771
  }
@@ -685,15 +790,13 @@ export class MAPAdapterImpl implements MAPAdapter {
685
790
  }
686
791
  }
687
792
 
688
- private async sendToSession(session: ConnectionSession, message: JsonRpcMessage): Promise<void> {
793
+ private async sendToSession(
794
+ session: ConnectionSession,
795
+ message: JsonRpcMessage,
796
+ ): Promise<void> {
689
797
  try {
690
- // Reacquire writer lock
691
- const writer = session.stream.writable.getWriter();
692
- try {
693
- await writer.write(message);
694
- } finally {
695
- writer.releaseLock();
696
- }
798
+ // Use the existing writer from the session (acquired in acceptConnection)
799
+ await session.writer.write(message);
697
800
  } catch (error) {
698
801
  console.error("[MAPAdapter] Failed to send message:", error);
699
802
  throw error;
@@ -716,8 +819,11 @@ export class MAPAdapterImpl implements MAPAdapter {
716
819
 
717
820
  private async handleConnect(
718
821
  participantId: ParticipantId,
719
- params: unknown
720
- ): Promise<{ participantId: ParticipantId; capabilities: ConnectedParticipant["capabilities"] }> {
822
+ params: unknown,
823
+ ): Promise<{
824
+ participantId: ParticipantId;
825
+ capabilities: ConnectedParticipant["capabilities"];
826
+ }> {
721
827
  const participant = this.connections.getParticipant(participantId);
722
828
  if (!participant) {
723
829
  throw RPCError.notFound("participant", participantId);
@@ -732,25 +838,44 @@ export class MAPAdapterImpl implements MAPAdapter {
732
838
  };
733
839
  }
734
840
 
735
- private async handleDisconnect(participantId: ParticipantId): Promise<{ success: boolean }> {
841
+ private async handleDisconnect(
842
+ participantId: ParticipantId,
843
+ ): Promise<{ success: boolean }> {
736
844
  await this.disconnectParticipant(participantId, "client_disconnect");
737
845
  return { success: true };
738
846
  }
739
847
 
740
848
  private async handleSubscribe(
741
849
  participantId: ParticipantId,
742
- params: unknown
850
+ params: unknown,
743
851
  ): Promise<{ subscriptionId: SubscriptionId }> {
744
- const filter = (params as { filter?: SubscriptionFilter })?.filter;
852
+ // Extract filter and translate SDK field names to internal format
853
+ // SDK uses 'fromAgents', macro-agent uses 'agents'
854
+ const rawFilter = (params as { filter?: Record<string, unknown> })?.filter;
855
+ let filter: SubscriptionFilter | undefined;
856
+
857
+ if (rawFilter) {
858
+ filter = {
859
+ eventTypes: rawFilter.eventTypes as MAPEventType[] | undefined,
860
+ agents: (rawFilter.fromAgents ?? rawFilter.agents) as
861
+ | AgentId[]
862
+ | undefined,
863
+ scopes: rawFilter.scopes as ScopeId[] | undefined,
864
+ subtree: rawFilter.subtree as AgentId | undefined,
865
+ lineage: rawFilter.lineage as AgentId | undefined,
866
+ };
867
+ }
868
+
745
869
  const subscriptionId = await this.createSubscription(participantId, filter);
746
870
  return { subscriptionId };
747
871
  }
748
872
 
749
873
  private async handleUnsubscribe(
750
874
  participantId: ParticipantId,
751
- params: unknown
875
+ params: unknown,
752
876
  ): Promise<{ success: boolean }> {
753
- const subscriptionId = (params as { subscriptionId: SubscriptionId })?.subscriptionId;
877
+ const subscriptionId = (params as { subscriptionId: SubscriptionId })
878
+ ?.subscriptionId;
754
879
  if (!subscriptionId) {
755
880
  throw RPCError.invalidParams("subscriptionId required");
756
881
  }
@@ -761,7 +886,7 @@ export class MAPAdapterImpl implements MAPAdapter {
761
886
  private async handleSend(
762
887
  participantId: ParticipantId,
763
888
  params: unknown,
764
- ctx: HandlerContext
889
+ ctx: HandlerContext,
765
890
  ): Promise<SendResult> {
766
891
  const { to, payload, options } = params as {
767
892
  to: Address;
@@ -776,12 +901,120 @@ export class MAPAdapterImpl implements MAPAdapter {
776
901
  throw RPCError.invalidParams("payload required");
777
902
  }
778
903
 
904
+ // Check if this is an ACP-over-MAP message
905
+ // SDK sends ACP envelope directly as payload (not wrapped in content)
906
+ const rawPayload = payload as unknown as Record<string, unknown> | undefined;
907
+ if (rawPayload && typeof rawPayload === 'object' && 'acp' in rawPayload && 'acpContext' in rawPayload) {
908
+ // This is an ACP envelope - route through ACP-over-MAP handler
909
+ return this.handleACPOverMAP(participantId, to, rawPayload, ctx);
910
+ }
911
+
779
912
  return this.sendMessage(participantId, to, payload, options);
780
913
  }
781
914
 
915
+ /**
916
+ * Handle ACP-over-MAP messages.
917
+ * These are ACP protocol messages sent via MAP to an agent.
918
+ */
919
+ private async handleACPOverMAP(
920
+ participantId: ParticipantId,
921
+ to: Address,
922
+ envelope: Record<string, unknown>,
923
+ ctx: HandlerContext,
924
+ ): Promise<SendResult> {
925
+ if (!this.acpOverMapHandler) {
926
+ throw RPCError.internalError("ACP-over-MAP not available - missing services");
927
+ }
928
+
929
+ const acp = envelope.acp as {
930
+ jsonrpc: string;
931
+ id?: string | number;
932
+ method?: string;
933
+ params?: unknown;
934
+ result?: unknown;
935
+ error?: unknown;
936
+ };
937
+ const acpContext = envelope.acpContext as {
938
+ streamId: string;
939
+ sessionId?: string;
940
+ direction: string;
941
+ };
942
+
943
+ console.error(`[ACP-over-MAP] Received - method=${acp.method} to=${JSON.stringify(to)}`);
944
+
945
+ // For now, we only support messages to specific agents
946
+ if (!isAgentAddress(to)) {
947
+ throw RPCError.invalidParams("ACP-over-MAP requires agent target");
948
+ }
949
+
950
+ const targetAgentId = to.agent;
951
+
952
+ // Check if this is a request (has method) or response (has result/error)
953
+ if (!acp.method) {
954
+ // This is a response - route it normally
955
+ console.error(`[ACP-over-MAP] Routing response back`);
956
+ return this.sendMessage(participantId, to, { content: envelope }, undefined);
957
+ }
958
+
959
+ // Create notification emitter to stream session updates
960
+ const emitNotification = (notification: ACPEnvelope) => {
961
+ this.emitEvent({
962
+ eventId: ulid(),
963
+ type: "message_delivered" as MAPEventType,
964
+ timestamp: Date.now(),
965
+ agentId: targetAgentId,
966
+ data: {
967
+ message: {
968
+ id: `acp-notif-${Date.now()}`,
969
+ from: targetAgentId,
970
+ payload: notification,
971
+ },
972
+ },
973
+ });
974
+ };
975
+
976
+ // Process the ACP request through the handler
977
+ const acpEnvelope: ACPEnvelope = {
978
+ acp: acp as ACPEnvelope["acp"],
979
+ acpContext: acpContext as ACPEnvelope["acpContext"],
980
+ };
981
+ const responseEnvelope = await this.acpOverMapHandler.processRequest(
982
+ targetAgentId,
983
+ acpEnvelope,
984
+ emitNotification
985
+ );
986
+
987
+ console.error(`[ACP-over-MAP] Request processed - method=${acp.method}`);
988
+
989
+ // Emit response event to the participant's subscriptions
990
+ // SDK's stream.ts checks for "message_delivered" (underscore) not "message.delivered" (dot)
991
+ // Use underscore format for ACP-over-MAP compatibility
992
+ const participant = this.connections.getParticipant(participantId);
993
+ if (participant) {
994
+ this.emitEvent({
995
+ eventId: ulid(),
996
+ type: "message_delivered" as MAPEventType, // Cast needed - SDK expects underscore format
997
+ timestamp: Date.now(),
998
+ agentId: targetAgentId, // Must be at top level for subscription matching
999
+ data: {
1000
+ message: {
1001
+ id: `acp-resp-${Date.now()}`,
1002
+ from: targetAgentId,
1003
+ payload: responseEnvelope,
1004
+ },
1005
+ },
1006
+ });
1007
+ }
1008
+
1009
+ return {
1010
+ messageId: `acp-${acp.id}`,
1011
+ delivered: [targetAgentId],
1012
+ };
1013
+ }
1014
+
782
1015
  private async handleListAgents(
783
1016
  participantId: ParticipantId,
784
- params: unknown
1017
+ params: unknown,
785
1018
  ): Promise<{ agents: AgentInfo[] }> {
786
1019
  const filter = (params as { filter?: AgentFilter })?.filter;
787
1020
  const agents = this.listAgents(participantId, filter);
@@ -790,7 +1023,7 @@ export class MAPAdapterImpl implements MAPAdapter {
790
1023
 
791
1024
  private async handleGetAgent(
792
1025
  participantId: ParticipantId,
793
- params: unknown
1026
+ params: unknown,
794
1027
  ): Promise<{ agent: AgentInfo | null }> {
795
1028
  const agentId = (params as { agentId: AgentId })?.agentId;
796
1029
  if (!agentId) {
@@ -800,14 +1033,16 @@ export class MAPAdapterImpl implements MAPAdapter {
800
1033
  return { agent: agent ?? null };
801
1034
  }
802
1035
 
803
- private async handleListScopes(participantId: ParticipantId): Promise<{ scopes: ScopeInfo[] }> {
1036
+ private async handleListScopes(
1037
+ participantId: ParticipantId,
1038
+ ): Promise<{ scopes: ScopeInfo[] }> {
804
1039
  const scopes = this.listScopes(participantId);
805
1040
  return { scopes };
806
1041
  }
807
1042
 
808
1043
  private async handleGetScope(
809
1044
  participantId: ParticipantId,
810
- params: unknown
1045
+ params: unknown,
811
1046
  ): Promise<{ scope: ScopeInfo | null }> {
812
1047
  const scopeId = (params as { scopeId: ScopeId })?.scopeId;
813
1048
  if (!scopeId) {
@@ -820,18 +1055,22 @@ export class MAPAdapterImpl implements MAPAdapter {
820
1055
  private async handleCreateScope(
821
1056
  participantId: ParticipantId,
822
1057
  params: unknown,
823
- ctx: HandlerContext
1058
+ ctx: HandlerContext,
824
1059
  ): Promise<{ scopeId: ScopeId }> {
825
- const { name, metadata } = (params as { name?: string; metadata?: Record<string, unknown> }) ?? {};
1060
+ const { name, metadata } =
1061
+ (params as { name?: string; metadata?: Record<string, unknown> }) ?? {};
826
1062
  const scopeId = this.createScope(name, metadata);
827
1063
  return { scopeId };
828
1064
  }
829
1065
 
830
1066
  private async handleJoinScope(
831
1067
  participantId: ParticipantId,
832
- params: unknown
1068
+ params: unknown,
833
1069
  ): Promise<{ success: boolean }> {
834
- const { scopeId, agentId } = params as { scopeId: ScopeId; agentId: AgentId };
1070
+ const { scopeId, agentId } = params as {
1071
+ scopeId: ScopeId;
1072
+ agentId: AgentId;
1073
+ };
835
1074
  if (!scopeId) {
836
1075
  throw RPCError.invalidParams("scopeId required");
837
1076
  }
@@ -848,9 +1087,12 @@ export class MAPAdapterImpl implements MAPAdapter {
848
1087
 
849
1088
  private async handleLeaveScope(
850
1089
  participantId: ParticipantId,
851
- params: unknown
1090
+ params: unknown,
852
1091
  ): Promise<{ success: boolean }> {
853
- const { scopeId, agentId } = params as { scopeId: ScopeId; agentId: AgentId };
1092
+ const { scopeId, agentId } = params as {
1093
+ scopeId: ScopeId;
1094
+ agentId: AgentId;
1095
+ };
854
1096
  if (!scopeId) {
855
1097
  throw RPCError.invalidParams("scopeId required");
856
1098
  }
@@ -871,7 +1113,7 @@ export class MAPAdapterImpl implements MAPAdapter {
871
1113
 
872
1114
  private async handleFederationConnect(
873
1115
  participantId: ParticipantId,
874
- params: unknown
1116
+ params: unknown,
875
1117
  ): Promise<{ capabilities: FederationCapabilities }> {
876
1118
  if (!this.services.federationHandler) {
877
1119
  throw RPCError.internalError("Federation not available");
@@ -894,14 +1136,14 @@ export class MAPAdapterImpl implements MAPAdapter {
894
1136
  return { capabilities };
895
1137
  } catch (err) {
896
1138
  throw RPCError.internalError(
897
- err instanceof Error ? err.message : "Failed to connect"
1139
+ err instanceof Error ? err.message : "Failed to connect",
898
1140
  );
899
1141
  }
900
1142
  }
901
1143
 
902
1144
  private async handleFederationDisconnect(
903
1145
  participantId: ParticipantId,
904
- params: unknown
1146
+ params: unknown,
905
1147
  ): Promise<{ success: boolean }> {
906
1148
  if (!this.services.federationHandler) {
907
1149
  throw RPCError.internalError("Federation not available");
@@ -916,21 +1158,20 @@ export class MAPAdapterImpl implements MAPAdapter {
916
1158
  await this.services.federationHandler.disconnect(systemId);
917
1159
  return { success: true };
918
1160
  } catch (err) {
919
- if (
920
- err instanceof Error &&
921
- err.message.includes("Not connected")
922
- ) {
1161
+ if (err instanceof Error && err.message.includes("Not connected")) {
923
1162
  throw RPCError.notFound("peer", systemId);
924
1163
  }
925
1164
  throw RPCError.internalError(
926
- err instanceof Error ? err.message : "Failed to disconnect"
1165
+ err instanceof Error ? err.message : "Failed to disconnect",
927
1166
  );
928
1167
  }
929
1168
  }
930
1169
 
931
1170
  private async handleFederationList(
932
- participantId: ParticipantId
933
- ): Promise<{ peers: Array<{ systemId: string; status: string; connectedAt: number }> }> {
1171
+ participantId: ParticipantId,
1172
+ ): Promise<{
1173
+ peers: Array<{ systemId: string; status: string; connectedAt: number }>;
1174
+ }> {
934
1175
  if (!this.services.federationHandler) {
935
1176
  return { peers: [] };
936
1177
  }
@@ -947,7 +1188,7 @@ export class MAPAdapterImpl implements MAPAdapter {
947
1188
 
948
1189
  private async handleFederationCapabilities(
949
1190
  participantId: ParticipantId,
950
- params: unknown
1191
+ params: unknown,
951
1192
  ): Promise<{ capabilities: FederationCapabilities | null }> {
952
1193
  if (!this.services.federationHandler) {
953
1194
  throw RPCError.internalError("Federation not available");
@@ -958,7 +1199,8 @@ export class MAPAdapterImpl implements MAPAdapter {
958
1199
  throw RPCError.invalidParams("systemId is required");
959
1200
  }
960
1201
 
961
- const capabilities = this.services.federationHandler.getCapabilities(systemId);
1202
+ const capabilities =
1203
+ this.services.federationHandler.getCapabilities(systemId);
962
1204
  if (!capabilities) {
963
1205
  throw RPCError.notFound("peer", systemId);
964
1206
  }
@@ -980,6 +1222,13 @@ export class MAPAdapterImpl implements MAPAdapter {
980
1222
  scopes: this.getAgentScopes(agent.id),
981
1223
  metadata: agent.metadata,
982
1224
  createdAt: agent.createdAt,
1225
+ // All macro-agent agents support ACP (they're Claude Code sessions)
1226
+ capabilities: {
1227
+ protocols: ["acp"],
1228
+ acp: {
1229
+ features: ["streaming"],
1230
+ },
1231
+ },
983
1232
  };
984
1233
  }
985
1234
 
@@ -1013,7 +1262,7 @@ export class MAPAdapterImpl implements MAPAdapter {
1013
1262
  */
1014
1263
  export function createMAPAdapter(
1015
1264
  config?: MAPAdapterConfig,
1016
- services?: MAPAdapterServices
1265
+ services?: MAPAdapterServices,
1017
1266
  ): MAPAdapter {
1018
1267
  return new MAPAdapterImpl(config, services);
1019
1268
  }