agent-inbox 0.1.8 → 0.2.0

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 (75) hide show
  1. package/CLAUDE.md +44 -7
  2. package/README.md +67 -24
  3. package/dist/cli.d.ts +20 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +89 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/federation/connection-manager.d.ts +13 -2
  8. package/dist/federation/connection-manager.d.ts.map +1 -1
  9. package/dist/federation/connection-manager.js +109 -10
  10. package/dist/federation/connection-manager.js.map +1 -1
  11. package/dist/index.d.mts +2 -0
  12. package/dist/index.d.ts +25 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +58 -5
  15. package/dist/index.js.map +1 -1
  16. package/dist/index.mjs +1 -0
  17. package/dist/index.mjs.map +1 -0
  18. package/dist/ipc/ipc-server.d.ts +2 -0
  19. package/dist/ipc/ipc-server.d.ts.map +1 -1
  20. package/dist/ipc/ipc-server.js +48 -0
  21. package/dist/ipc/ipc-server.js.map +1 -1
  22. package/dist/map/map-client.d.ts +100 -0
  23. package/dist/map/map-client.d.ts.map +1 -1
  24. package/dist/map/map-client.js +61 -0
  25. package/dist/map/map-client.js.map +1 -1
  26. package/dist/mcp/mcp-proxy.d.ts +28 -0
  27. package/dist/mcp/mcp-proxy.d.ts.map +1 -0
  28. package/dist/mcp/mcp-proxy.js +280 -0
  29. package/dist/mcp/mcp-proxy.js.map +1 -0
  30. package/dist/mesh/delivery-bridge.d.ts +47 -0
  31. package/dist/mesh/delivery-bridge.d.ts.map +1 -0
  32. package/dist/mesh/delivery-bridge.js +73 -0
  33. package/dist/mesh/delivery-bridge.js.map +1 -0
  34. package/dist/mesh/mesh-connector.d.ts +29 -0
  35. package/dist/mesh/mesh-connector.d.ts.map +1 -0
  36. package/dist/mesh/mesh-connector.js +36 -0
  37. package/dist/mesh/mesh-connector.js.map +1 -0
  38. package/dist/mesh/mesh-transport.d.ts +70 -0
  39. package/dist/mesh/mesh-transport.d.ts.map +1 -0
  40. package/dist/mesh/mesh-transport.js +92 -0
  41. package/dist/mesh/mesh-transport.js.map +1 -0
  42. package/dist/mesh/type-mapper.d.ts +67 -0
  43. package/dist/mesh/type-mapper.d.ts.map +1 -0
  44. package/dist/mesh/type-mapper.js +165 -0
  45. package/dist/mesh/type-mapper.js.map +1 -0
  46. package/dist/types.d.ts +29 -2
  47. package/dist/types.d.ts.map +1 -1
  48. package/docs/CLAUDE-CODE-SWARM-PROPOSAL.md +137 -0
  49. package/package.json +10 -2
  50. package/src/cli.ts +94 -0
  51. package/src/federation/connection-manager.ts +125 -10
  52. package/src/index.ts +96 -5
  53. package/src/ipc/ipc-server.ts +58 -0
  54. package/src/map/map-client.ts +152 -0
  55. package/src/mcp/mcp-proxy.ts +326 -0
  56. package/src/mesh/delivery-bridge.ts +110 -0
  57. package/src/mesh/mesh-connector.ts +41 -0
  58. package/src/mesh/mesh-transport.ts +157 -0
  59. package/src/mesh/type-mapper.ts +239 -0
  60. package/src/types.ts +33 -1
  61. package/test/federation/integration.test.ts +37 -3
  62. package/test/federation/sdk-integration.test.ts +4 -8
  63. package/test/ipc-new-commands.test.ts +200 -0
  64. package/test/mcp-proxy.test.ts +191 -0
  65. package/test/mesh/delivery-bridge.test.ts +178 -0
  66. package/test/mesh/e2e-mesh.test.ts +527 -0
  67. package/test/mesh/e2e-real-meshpeer.test.ts +629 -0
  68. package/test/mesh/federation-mesh.test.ts +269 -0
  69. package/test/mesh/mesh-connector.test.ts +66 -0
  70. package/test/mesh/mesh-transport.test.ts +191 -0
  71. package/test/mesh/meshpeer-integration.test.ts +442 -0
  72. package/test/mesh/mock-mesh.ts +125 -0
  73. package/test/mesh/mock-meshpeer.ts +266 -0
  74. package/test/mesh/type-mapper.test.ts +226 -0
  75. package/docs/PLAN.md +0 -545
@@ -0,0 +1,157 @@
1
+ import type {
2
+ MapConnection,
3
+ IncomingMapMessage,
4
+ } from "../map/map-client.js";
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Minimal type stubs for agentic-mesh.
8
+ // We only reference the subset of MeshContext / IMessageChannel that
9
+ // MeshTransport actually uses, so agent-inbox compiles without importing
10
+ // the full agentic-mesh package (it's an optional peer dep).
11
+ // ---------------------------------------------------------------------------
12
+
13
+ /** Subset of agentic-mesh PeerInfo used by MeshTransport. */
14
+ export interface MeshPeerInfo {
15
+ id: string;
16
+ name?: string;
17
+ groups: string[];
18
+ }
19
+
20
+ /** Subset of agentic-mesh IMessageChannel<T>. */
21
+ export interface MeshChannel<T> {
22
+ readonly name: string;
23
+ readonly isOpen: boolean;
24
+ open(): Promise<void>;
25
+ close(): Promise<void>;
26
+ send(peerId: string, message: T): boolean;
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ on(event: string | symbol, listener: (...args: any[]) => void): this;
29
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
+ off(event: string | symbol, listener: (...args: any[]) => void): this;
31
+ }
32
+
33
+ /** Subset of agentic-mesh MeshContext used by MeshTransport. */
34
+ export interface MeshContextLike {
35
+ createChannel<T>(name: string): MeshChannel<T>;
36
+ _getPeerId(): string;
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Wire format
41
+ // ---------------------------------------------------------------------------
42
+
43
+ /**
44
+ * Payload exchanged over the mesh channel between agent-inbox instances.
45
+ * Flat structure that maps 1-to-1 with IncomingMapMessage.
46
+ */
47
+ export interface InboxWireMessage {
48
+ to?: { agentId?: string; scope?: string };
49
+ payload: unknown;
50
+ from: string;
51
+ timestamp?: string;
52
+ meta?: Record<string, unknown>;
53
+ }
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // MeshTransport
57
+ // ---------------------------------------------------------------------------
58
+
59
+ /** Default channel name used for agent-inbox federation traffic.
60
+ * Uses the "proto:" prefix to distinguish protocol channels from
61
+ * application channels on the same mesh. */
62
+ export const DEFAULT_CHANNEL_NAME = "proto:agent-inbox";
63
+
64
+ /**
65
+ * Implements the MapConnection interface over an agentic-mesh MessageChannel.
66
+ *
67
+ * Each instance represents a connection to a single remote peer. Multiple
68
+ * MeshTransport instances share the same underlying channel (one channel
69
+ * per mesh, not per peer) and filter incoming messages by remotePeerId.
70
+ */
71
+ export class MeshTransport implements MapConnection {
72
+ private channel: MeshChannel<InboxWireMessage>;
73
+ private handlers: ((msg: IncomingMapMessage) => void)[] = [];
74
+ private messageListener: ((message: InboxWireMessage, from: MeshPeerInfo) => void) | null = null;
75
+ private closed = false;
76
+ readonly systemName: string;
77
+
78
+ constructor(
79
+ private mesh: MeshContextLike,
80
+ private localPeerId: string,
81
+ private remotePeerId: string,
82
+ channelName: string = DEFAULT_CHANNEL_NAME
83
+ ) {
84
+ this.systemName = remotePeerId;
85
+ this.channel = mesh.createChannel<InboxWireMessage>(channelName);
86
+ }
87
+
88
+ /**
89
+ * Open the underlying channel and start listening for messages.
90
+ * Must be called before send() or onMessage().
91
+ */
92
+ async open(): Promise<void> {
93
+ if (!this.channel.isOpen) {
94
+ await this.channel.open();
95
+ }
96
+ this.messageListener = (message: InboxWireMessage, from: MeshPeerInfo) => {
97
+ if (this.closed) return;
98
+ // Only process messages from our remote peer
99
+ if (from.id !== this.remotePeerId) return;
100
+ const incoming = wireToIncoming(message);
101
+ for (const handler of this.handlers) {
102
+ handler(incoming);
103
+ }
104
+ };
105
+ this.channel.on("message", this.messageListener);
106
+ }
107
+
108
+ async send(
109
+ to: { agentId?: string; scope?: string },
110
+ payload: unknown,
111
+ meta?: Record<string, unknown>
112
+ ): Promise<void> {
113
+ if (this.closed) {
114
+ throw new Error("MeshTransport is closed");
115
+ }
116
+ const wire: InboxWireMessage = {
117
+ to,
118
+ payload,
119
+ from: this.localPeerId,
120
+ timestamp: new Date().toISOString(),
121
+ meta,
122
+ };
123
+ const sent = this.channel.send(this.remotePeerId, wire);
124
+ if (!sent) {
125
+ throw new Error(
126
+ `Failed to send to peer "${this.remotePeerId}" — peer may be offline`
127
+ );
128
+ }
129
+ }
130
+
131
+ onMessage(handler: (msg: IncomingMapMessage) => void): void {
132
+ this.handlers.push(handler);
133
+ }
134
+
135
+ async disconnect(): Promise<void> {
136
+ this.closed = true;
137
+ if (this.messageListener) {
138
+ this.channel.off("message", this.messageListener);
139
+ this.messageListener = null;
140
+ }
141
+ this.handlers = [];
142
+ }
143
+ }
144
+
145
+ // ---------------------------------------------------------------------------
146
+ // Helpers
147
+ // ---------------------------------------------------------------------------
148
+
149
+ function wireToIncoming(wire: InboxWireMessage): IncomingMapMessage {
150
+ return {
151
+ from: wire.from,
152
+ to: wire.to,
153
+ payload: wire.payload,
154
+ timestamp: wire.timestamp,
155
+ meta: wire.meta,
156
+ };
157
+ }
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Type mapping between agentic-mesh MAP messages and agent-inbox messages.
3
+ *
4
+ * Translates MAP Message (from agentic-mesh's MapServer/FederationGateway)
5
+ * into agent-inbox's internal Message format and vice versa.
6
+ *
7
+ * Inbox-specific fields (subject, thread_tag, importance, recipientKind)
8
+ * are encoded in the MAP message's `_meta` vendor extension field.
9
+ */
10
+
11
+ import { ulid } from "ulid";
12
+ import { normalizeContent } from "../router/message-router.js";
13
+ import type {
14
+ Message,
15
+ Recipient,
16
+ RecipientKind,
17
+ Importance,
18
+ } from "../types.js";
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Minimal MAP message type stubs (matches agentic-mesh v0.2.0)
22
+ // ---------------------------------------------------------------------------
23
+
24
+ /** MAP Message as delivered by agentic-mesh's MapServer. */
25
+ export interface MapMessage {
26
+ id: string;
27
+ from: string;
28
+ to: MapAddress;
29
+ timestamp: number;
30
+ payload?: unknown;
31
+ meta?: MapMessageMeta;
32
+ _meta?: Record<string, unknown>;
33
+ }
34
+
35
+ export type MapAddress =
36
+ | string
37
+ | { agent: string }
38
+ | { agents: string[] }
39
+ | { scope: string }
40
+ | { role: string; within?: string }
41
+ | { broadcast: true }
42
+ | { system: string; agent: string }
43
+ | Record<string, unknown>;
44
+
45
+ export interface MapMessageMeta {
46
+ priority?: "urgent" | "high" | "normal" | "low";
47
+ correlationId?: string;
48
+ delivery?: "fire-and-forget" | "acknowledged" | "guaranteed";
49
+ _meta?: Record<string, unknown>;
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // MAP → Inbox
54
+ // ---------------------------------------------------------------------------
55
+
56
+ /**
57
+ * Convert a MAP Message from agentic-mesh into an agent-inbox Message.
58
+ *
59
+ * Inbox-specific fields are read from `_meta` (or `meta._meta`):
60
+ * - `_meta.subject` → `subject`
61
+ * - `_meta.threadTag` / `meta.correlationId` → `thread_tag`
62
+ * - `_meta.inReplyTo` → `in_reply_to`
63
+ * - `_meta.recipientKind` → per-recipient `kind`
64
+ * - `_meta.inboxMessageId` → used as `id` if present
65
+ * - `meta.priority` → `importance`
66
+ */
67
+ export function mapMessageToInbox(
68
+ mapMsg: MapMessage,
69
+ scope: string = "default"
70
+ ): Message {
71
+ const vendorMeta = mapMsg._meta ?? mapMsg.meta?._meta ?? {};
72
+ const recipients = resolveMapAddress(mapMsg.to, vendorMeta);
73
+
74
+ const priority = mapMsg.meta?.priority;
75
+ const importance: Importance =
76
+ priority === "urgent"
77
+ ? "urgent"
78
+ : priority === "high"
79
+ ? "high"
80
+ : priority === "low"
81
+ ? "low"
82
+ : "normal";
83
+
84
+ return {
85
+ id: (vendorMeta.inboxMessageId as string) ?? ulid(),
86
+ scope,
87
+ sender_id: mapMsg.from,
88
+ recipients,
89
+ content: normalizeContent(mapMsg.payload),
90
+ importance,
91
+ subject: vendorMeta.subject as string | undefined,
92
+ thread_tag:
93
+ (vendorMeta.threadTag as string) ?? mapMsg.meta?.correlationId,
94
+ in_reply_to: vendorMeta.inReplyTo as string | undefined,
95
+ conversation_id: vendorMeta.conversationId as string | undefined,
96
+ metadata: stripInboxKeys(vendorMeta),
97
+ created_at: new Date(mapMsg.timestamp).toISOString(),
98
+ };
99
+ }
100
+
101
+ /**
102
+ * Resolve a MAP Address into agent-inbox Recipients.
103
+ */
104
+ function resolveMapAddress(
105
+ addr: MapAddress,
106
+ vendorMeta: Record<string, unknown>
107
+ ): Recipient[] {
108
+ const kind = (vendorMeta.recipientKind as RecipientKind) ?? "to";
109
+ const now = new Date().toISOString();
110
+
111
+ if (typeof addr === "string") {
112
+ return [{ agent_id: addr, kind, delivered_at: now }];
113
+ }
114
+
115
+ // Federated address must be checked before simple agent address
116
+ // since { system: "x", agent: "y" } also matches "agent" in addr
117
+ if (
118
+ "system" in addr &&
119
+ "agent" in addr &&
120
+ typeof addr.system === "string" &&
121
+ typeof addr.agent === "string"
122
+ ) {
123
+ return [
124
+ { agent_id: `${addr.agent}@${addr.system}`, kind, delivered_at: now },
125
+ ];
126
+ }
127
+
128
+ if ("agent" in addr && typeof addr.agent === "string") {
129
+ return [{ agent_id: addr.agent, kind, delivered_at: now }];
130
+ }
131
+
132
+ if ("agents" in addr && Array.isArray(addr.agents)) {
133
+ return addr.agents.map((agentId: string) => ({
134
+ agent_id: agentId,
135
+ kind,
136
+ delivered_at: now,
137
+ }));
138
+ }
139
+
140
+ if ("scope" in addr && typeof addr.scope === "string") {
141
+ return [{ agent_id: addr.scope, kind, delivered_at: now }];
142
+ }
143
+
144
+ if ("broadcast" in addr) {
145
+ return [{ agent_id: "*", kind, delivered_at: now }];
146
+ }
147
+
148
+ return [];
149
+ }
150
+
151
+ // Keys that belong to the inbox layer, not stored in metadata
152
+ const INBOX_META_KEYS = new Set([
153
+ "subject",
154
+ "threadTag",
155
+ "inReplyTo",
156
+ "recipientKind",
157
+ "inboxMessageId",
158
+ "conversationId",
159
+ "importance",
160
+ ]);
161
+
162
+ function stripInboxKeys(
163
+ meta: Record<string, unknown>
164
+ ): Record<string, unknown> {
165
+ const result: Record<string, unknown> = {};
166
+ for (const [k, v] of Object.entries(meta)) {
167
+ if (!INBOX_META_KEYS.has(k)) {
168
+ result[k] = v;
169
+ }
170
+ }
171
+ return result;
172
+ }
173
+
174
+ // ---------------------------------------------------------------------------
175
+ // Inbox → MAP
176
+ // ---------------------------------------------------------------------------
177
+
178
+ /**
179
+ * Convert an agent-inbox Message into partial MAP message fields
180
+ * suitable for sending via `AgentConnection.send()` or `FederationGateway.route()`.
181
+ *
182
+ * Returns `to`, `payload`, and `meta` (including `_meta` for inbox fields).
183
+ */
184
+ export function inboxMessageToMap(msg: Message): {
185
+ to: MapAddress;
186
+ payload: unknown;
187
+ meta: MapMessageMeta & { _meta: Record<string, unknown> };
188
+ } {
189
+ // Build the MAP address from recipients
190
+ const primaryRecipients = msg.recipients.filter((r) => r.kind === "to");
191
+ const to = resolveRecipientsToAddress(primaryRecipients);
192
+
193
+ const priority: MapMessageMeta["priority"] =
194
+ msg.importance === "urgent"
195
+ ? "urgent"
196
+ : msg.importance === "high"
197
+ ? "high"
198
+ : msg.importance === "low"
199
+ ? "low"
200
+ : "normal";
201
+
202
+ return {
203
+ to,
204
+ payload: msg.content,
205
+ meta: {
206
+ priority,
207
+ correlationId: msg.thread_tag,
208
+ _meta: {
209
+ inboxMessageId: msg.id,
210
+ ...(msg.subject ? { subject: msg.subject } : {}),
211
+ ...(msg.thread_tag ? { threadTag: msg.thread_tag } : {}),
212
+ ...(msg.in_reply_to ? { inReplyTo: msg.in_reply_to } : {}),
213
+ ...(msg.conversation_id
214
+ ? { conversationId: msg.conversation_id }
215
+ : {}),
216
+ ...msg.metadata,
217
+ },
218
+ },
219
+ };
220
+ }
221
+
222
+ function resolveRecipientsToAddress(recipients: Recipient[]): MapAddress {
223
+ if (recipients.length === 0) {
224
+ return { broadcast: true };
225
+ }
226
+ if (recipients.length === 1) {
227
+ const agentId = recipients[0].agent_id;
228
+ // Check for federated address (agent@system)
229
+ const atIdx = agentId.indexOf("@");
230
+ if (atIdx > 0) {
231
+ return {
232
+ system: agentId.slice(atIdx + 1),
233
+ agent: agentId.slice(0, atIdx),
234
+ };
235
+ }
236
+ return { agent: agentId };
237
+ }
238
+ return { agents: recipients.map((r) => r.agent_id) };
239
+ }
package/src/types.ts CHANGED
@@ -140,6 +140,18 @@ export interface IpcCheckInboxCommand {
140
140
  clear?: boolean;
141
141
  }
142
142
 
143
+ export interface IpcReadThreadCommand {
144
+ action: "read_thread";
145
+ threadTag: string;
146
+ scope?: string;
147
+ }
148
+
149
+ export interface IpcListAgentsCommand {
150
+ action: "list_agents";
151
+ scope?: string;
152
+ includeFederated?: boolean;
153
+ }
154
+
143
155
  export interface IpcPingCommand {
144
156
  action: "ping";
145
157
  }
@@ -149,12 +161,26 @@ export type IpcCommand =
149
161
  | IpcEmitCommand
150
162
  | IpcNotifyCommand
151
163
  | IpcCheckInboxCommand
164
+ | IpcReadThreadCommand
165
+ | IpcListAgentsCommand
152
166
  | IpcPingCommand;
153
167
 
154
168
  export interface IpcResponse {
155
169
  ok: boolean;
156
170
  messageId?: string;
157
171
  messages?: Message[];
172
+ agents?: Array<{
173
+ agentId: string;
174
+ name?: string;
175
+ scope: string;
176
+ status: string;
177
+ program?: string;
178
+ lastActive?: string;
179
+ location: "local" | "federated";
180
+ peerId?: string;
181
+ }>;
182
+ count?: number;
183
+ threadTag?: string;
158
184
  error?: string;
159
185
  pid?: number;
160
186
  }
@@ -180,7 +206,10 @@ export interface FederatedAddress {
180
206
 
181
207
  export interface FederationPeerConfig {
182
208
  systemId: string;
183
- url: string;
209
+ /** WebSocket URL for MAP SDK connections. */
210
+ url?: string;
211
+ /** Mesh peer ID for agentic-mesh transport connections. */
212
+ meshPeerId?: string;
184
213
  auth?: FederationAuth;
185
214
  exposure?: ExposurePolicy;
186
215
  }
@@ -199,12 +228,15 @@ export interface ExposurePolicy {
199
228
  events?: ExposureLevel;
200
229
  }
201
230
 
231
+ export type FederationTransport = "websocket" | "mesh";
232
+
202
233
  export interface FederationLink {
203
234
  peerId: string;
204
235
  sessionId: string;
205
236
  status: "connected" | "disconnected" | "authenticating";
206
237
  exposure: ExposurePolicy;
207
238
  url: string;
239
+ transport: FederationTransport;
208
240
  connectedAt?: string;
209
241
  }
210
242
 
@@ -185,7 +185,7 @@ describe("Federation Integration", () => {
185
185
  await hubFederation.destroy();
186
186
  });
187
187
 
188
- it("should queue when system-qualified address has no peer link", async () => {
188
+ it("should relay via upstream hub when system-qualified address has no direct peer link", async () => {
189
189
  const hubFederation = new ConnectionManager(events, {
190
190
  systemId: "system-1",
191
191
  trust: {
@@ -204,7 +204,8 @@ describe("Federation Integration", () => {
204
204
  url: "ws://hub:3000",
205
205
  });
206
206
 
207
- // Route to far-system which has no peer link — gets queued for far-system
207
+ // Route to far-system which has no direct peer link.
208
+ // With hierarchical strategy, falls through to upstream hub relay.
208
209
  const msg: Message = {
209
210
  id: "msg-hub-2",
210
211
  scope: "default",
@@ -217,11 +218,44 @@ describe("Federation Integration", () => {
217
218
  };
218
219
 
219
220
  const result = await hubFederation.route(msg);
221
+ // Hierarchical strategy delegates to upstream hub instead of queuing
222
+ expect(result.delivered).toBe(true);
223
+ expect(result.peerId).toBe("hub-system");
224
+
225
+ await hubFederation.destroy();
226
+ });
227
+
228
+ it("should queue when system-qualified address has no peer link (table strategy)", async () => {
229
+ const tableFederation = new ConnectionManager(events, {
230
+ systemId: "system-1",
231
+ trust: {
232
+ allowedServers: [],
233
+ scopePermissions: {},
234
+ requireAuth: false,
235
+ },
236
+ routing: {
237
+ strategy: "table",
238
+ },
239
+ });
240
+
241
+ // No peers connected — route to far-system queues immediately
242
+ const msg: Message = {
243
+ id: "msg-table-1",
244
+ scope: "default",
245
+ sender_id: "agent-a",
246
+ recipients: [{ agent_id: "agent-x@far-system", kind: "to" }],
247
+ content: { type: "text", text: "no direct link" },
248
+ importance: "normal",
249
+ metadata: {},
250
+ created_at: new Date().toISOString(),
251
+ };
252
+
253
+ const result = await tableFederation.route(msg);
220
254
  expect(result.delivered).toBe(false);
221
255
  expect(result.queued).toBe(true);
222
256
  expect(result.peerId).toBe("far-system");
223
257
 
224
- await hubFederation.destroy();
258
+ await tableFederation.destroy();
225
259
  });
226
260
  });
227
261
 
@@ -477,14 +477,10 @@ describe("Federation SDK Integration (two-system)", () => {
477
477
  });
478
478
 
479
479
  describe("broadcast strategy with real transport", () => {
480
- // NOTE: In the current routing implementation, broadcast/hierarchical strategies
481
- // are only reachable via handleUnknownRoute(), which requires resolveRoute()
482
- // to return null. This only happens for agent-only addresses (no @system),
483
- // but those are filtered out by isRemoteAddress() in route().
484
- //
485
- // System-qualified addresses like "bob@system-2" always resolve via addr.system,
486
- // bypassing the strategy entirely. This is a known design gap documented in
487
- // integration.test.ts.
480
+ // For system-qualified addresses (bob@system-2) where the target system IS a
481
+ // connected peer, delivery goes directly via transport (no broadcast needed).
482
+ // Broadcast/hierarchical strategies activate when the resolved peer has no
483
+ // active link e.g., targeting a system we're not directly connected to.
488
484
  //
489
485
  // These tests verify broadcast transport by:
490
486
  // 1. Testing system-qualified delivery to multiple peers (direct transport)