claude-code-swarm 0.3.3 → 0.3.5

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 (273) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +22 -1
  3. package/.claude-plugin/run-agent-inbox-mcp.sh +76 -0
  4. package/.claude-plugin/run-minimem-mcp.sh +98 -0
  5. package/.claude-plugin/run-opentasks-mcp.sh +65 -0
  6. package/CLAUDE.md +200 -36
  7. package/README.md +65 -0
  8. package/e2e/helpers/cleanup.mjs +17 -3
  9. package/e2e/helpers/map-mock-server.mjs +201 -25
  10. package/e2e/helpers/sidecar.mjs +222 -0
  11. package/e2e/helpers/workspace.mjs +2 -1
  12. package/e2e/tier5-sidecar-inbox.test.mjs +900 -0
  13. package/e2e/tier6-inbox-mcp.test.mjs +173 -0
  14. package/e2e/tier6-live-agent.test.mjs +759 -0
  15. package/e2e/vitest.config.e2e.mjs +1 -1
  16. package/hooks/hooks.json +15 -8
  17. package/package.json +13 -1
  18. package/references/agent-inbox/CLAUDE.md +151 -0
  19. package/references/agent-inbox/README.md +238 -0
  20. package/references/agent-inbox/docs/CLAUDE-CODE-SWARM-PROPOSAL.md +137 -0
  21. package/references/agent-inbox/docs/DESIGN.md +1156 -0
  22. package/references/agent-inbox/hooks/inbox-hook.mjs +119 -0
  23. package/references/agent-inbox/hooks/register-hook.mjs +69 -0
  24. package/references/agent-inbox/package-lock.json +3347 -0
  25. package/references/agent-inbox/package.json +58 -0
  26. package/references/agent-inbox/rules/agent-inbox.md +78 -0
  27. package/references/agent-inbox/src/federation/address.ts +61 -0
  28. package/references/agent-inbox/src/federation/connection-manager.ts +573 -0
  29. package/references/agent-inbox/src/federation/delivery-queue.ts +222 -0
  30. package/references/agent-inbox/src/federation/index.ts +6 -0
  31. package/references/agent-inbox/src/federation/routing-engine.ts +188 -0
  32. package/references/agent-inbox/src/federation/trust.ts +71 -0
  33. package/references/agent-inbox/src/index.ts +390 -0
  34. package/references/agent-inbox/src/ipc/ipc-server.ts +207 -0
  35. package/references/agent-inbox/src/jsonrpc/mail-server.ts +382 -0
  36. package/references/agent-inbox/src/map/map-client.ts +414 -0
  37. package/references/agent-inbox/src/mcp/mcp-server.ts +272 -0
  38. package/references/agent-inbox/src/mesh/delivery-bridge.ts +110 -0
  39. package/references/agent-inbox/src/mesh/mesh-connector.ts +41 -0
  40. package/references/agent-inbox/src/mesh/mesh-transport.ts +157 -0
  41. package/references/agent-inbox/src/mesh/type-mapper.ts +239 -0
  42. package/references/agent-inbox/src/push/notifier.ts +233 -0
  43. package/references/agent-inbox/src/registry/warm-registry.ts +255 -0
  44. package/references/agent-inbox/src/router/message-router.ts +175 -0
  45. package/references/agent-inbox/src/storage/interface.ts +48 -0
  46. package/references/agent-inbox/src/storage/memory.ts +145 -0
  47. package/references/agent-inbox/src/storage/sqlite.ts +671 -0
  48. package/references/agent-inbox/src/traceability/traceability.ts +183 -0
  49. package/references/agent-inbox/src/types.ts +303 -0
  50. package/references/agent-inbox/test/federation/address.test.ts +101 -0
  51. package/references/agent-inbox/test/federation/connection-manager.test.ts +546 -0
  52. package/references/agent-inbox/test/federation/delivery-queue.test.ts +159 -0
  53. package/references/agent-inbox/test/federation/integration.test.ts +857 -0
  54. package/references/agent-inbox/test/federation/routing-engine.test.ts +117 -0
  55. package/references/agent-inbox/test/federation/sdk-integration.test.ts +744 -0
  56. package/references/agent-inbox/test/federation/trust.test.ts +89 -0
  57. package/references/agent-inbox/test/ipc-jsonrpc.test.ts +113 -0
  58. package/references/agent-inbox/test/ipc-server.test.ts +197 -0
  59. package/references/agent-inbox/test/mail-server.test.ts +285 -0
  60. package/references/agent-inbox/test/map-client.test.ts +408 -0
  61. package/references/agent-inbox/test/mesh/delivery-bridge.test.ts +178 -0
  62. package/references/agent-inbox/test/mesh/e2e-mesh.test.ts +527 -0
  63. package/references/agent-inbox/test/mesh/e2e-real-meshpeer.test.ts +629 -0
  64. package/references/agent-inbox/test/mesh/federation-mesh.test.ts +269 -0
  65. package/references/agent-inbox/test/mesh/mesh-connector.test.ts +66 -0
  66. package/references/agent-inbox/test/mesh/mesh-transport.test.ts +191 -0
  67. package/references/agent-inbox/test/mesh/meshpeer-integration.test.ts +442 -0
  68. package/references/agent-inbox/test/mesh/mock-mesh.ts +125 -0
  69. package/references/agent-inbox/test/mesh/mock-meshpeer.ts +266 -0
  70. package/references/agent-inbox/test/mesh/type-mapper.test.ts +226 -0
  71. package/references/agent-inbox/test/message-router.test.ts +184 -0
  72. package/references/agent-inbox/test/push-notifier.test.ts +139 -0
  73. package/references/agent-inbox/test/registry/warm-registry.test.ts +171 -0
  74. package/references/agent-inbox/test/sqlite-prefix.test.ts +192 -0
  75. package/references/agent-inbox/test/sqlite-storage.test.ts +243 -0
  76. package/references/agent-inbox/test/storage.test.ts +196 -0
  77. package/references/agent-inbox/test/traceability.test.ts +123 -0
  78. package/references/agent-inbox/test/wake.test.ts +330 -0
  79. package/references/agent-inbox/tsconfig.json +20 -0
  80. package/references/agent-inbox/tsup.config.ts +10 -0
  81. package/references/agent-inbox/vitest.config.ts +8 -0
  82. package/references/minimem/.claude/settings.json +7 -0
  83. package/references/minimem/.sudocode/issues.jsonl +18 -0
  84. package/references/minimem/.sudocode/specs.jsonl +1 -0
  85. package/references/minimem/CLAUDE.md +329 -0
  86. package/references/minimem/README.md +565 -0
  87. package/references/minimem/claude-plugin/.claude-plugin/plugin.json +10 -0
  88. package/references/minimem/claude-plugin/.mcp.json +7 -0
  89. package/references/minimem/claude-plugin/README.md +158 -0
  90. package/references/minimem/claude-plugin/commands/recall.md +47 -0
  91. package/references/minimem/claude-plugin/commands/remember.md +41 -0
  92. package/references/minimem/claude-plugin/hooks/__tests__/hooks.test.ts +272 -0
  93. package/references/minimem/claude-plugin/hooks/hooks.json +27 -0
  94. package/references/minimem/claude-plugin/hooks/session-end.sh +86 -0
  95. package/references/minimem/claude-plugin/hooks/session-start.sh +85 -0
  96. package/references/minimem/claude-plugin/skills/memory/SKILL.md +108 -0
  97. package/references/minimem/media/banner.png +0 -0
  98. package/references/minimem/package-lock.json +5373 -0
  99. package/references/minimem/package.json +76 -0
  100. package/references/minimem/scripts/postbuild.js +49 -0
  101. package/references/minimem/src/__tests__/edge-cases.test.ts +371 -0
  102. package/references/minimem/src/__tests__/errors.test.ts +265 -0
  103. package/references/minimem/src/__tests__/helpers.ts +199 -0
  104. package/references/minimem/src/__tests__/internal.test.ts +407 -0
  105. package/references/minimem/src/__tests__/knowledge-frontmatter.test.ts +148 -0
  106. package/references/minimem/src/__tests__/knowledge.test.ts +148 -0
  107. package/references/minimem/src/__tests__/minimem.integration.test.ts +1127 -0
  108. package/references/minimem/src/__tests__/session.test.ts +190 -0
  109. package/references/minimem/src/cli/__tests__/commands.test.ts +760 -0
  110. package/references/minimem/src/cli/__tests__/contained-layout.test.ts +286 -0
  111. package/references/minimem/src/cli/commands/__tests__/conflicts.test.ts +141 -0
  112. package/references/minimem/src/cli/commands/append.ts +76 -0
  113. package/references/minimem/src/cli/commands/config.ts +262 -0
  114. package/references/minimem/src/cli/commands/conflicts.ts +415 -0
  115. package/references/minimem/src/cli/commands/daemon.ts +169 -0
  116. package/references/minimem/src/cli/commands/index.ts +12 -0
  117. package/references/minimem/src/cli/commands/init.ts +166 -0
  118. package/references/minimem/src/cli/commands/mcp.ts +221 -0
  119. package/references/minimem/src/cli/commands/push-pull.ts +213 -0
  120. package/references/minimem/src/cli/commands/search.ts +223 -0
  121. package/references/minimem/src/cli/commands/status.ts +84 -0
  122. package/references/minimem/src/cli/commands/store.ts +189 -0
  123. package/references/minimem/src/cli/commands/sync-init.ts +290 -0
  124. package/references/minimem/src/cli/commands/sync.ts +70 -0
  125. package/references/minimem/src/cli/commands/upsert.ts +197 -0
  126. package/references/minimem/src/cli/config.ts +611 -0
  127. package/references/minimem/src/cli/index.ts +299 -0
  128. package/references/minimem/src/cli/shared.ts +189 -0
  129. package/references/minimem/src/cli/sync/__tests__/central.test.ts +152 -0
  130. package/references/minimem/src/cli/sync/__tests__/conflicts.test.ts +209 -0
  131. package/references/minimem/src/cli/sync/__tests__/daemon.test.ts +118 -0
  132. package/references/minimem/src/cli/sync/__tests__/detection.test.ts +207 -0
  133. package/references/minimem/src/cli/sync/__tests__/integration.test.ts +476 -0
  134. package/references/minimem/src/cli/sync/__tests__/registry.test.ts +363 -0
  135. package/references/minimem/src/cli/sync/__tests__/state.test.ts +255 -0
  136. package/references/minimem/src/cli/sync/__tests__/validation.test.ts +193 -0
  137. package/references/minimem/src/cli/sync/__tests__/watcher.test.ts +178 -0
  138. package/references/minimem/src/cli/sync/central.ts +292 -0
  139. package/references/minimem/src/cli/sync/conflicts.ts +205 -0
  140. package/references/minimem/src/cli/sync/daemon.ts +407 -0
  141. package/references/minimem/src/cli/sync/detection.ts +138 -0
  142. package/references/minimem/src/cli/sync/index.ts +107 -0
  143. package/references/minimem/src/cli/sync/operations.ts +373 -0
  144. package/references/minimem/src/cli/sync/registry.ts +279 -0
  145. package/references/minimem/src/cli/sync/state.ts +358 -0
  146. package/references/minimem/src/cli/sync/validation.ts +206 -0
  147. package/references/minimem/src/cli/sync/watcher.ts +237 -0
  148. package/references/minimem/src/cli/version.ts +34 -0
  149. package/references/minimem/src/core/index.ts +9 -0
  150. package/references/minimem/src/core/indexer.ts +628 -0
  151. package/references/minimem/src/core/searcher.ts +221 -0
  152. package/references/minimem/src/db/schema.ts +183 -0
  153. package/references/minimem/src/db/sqlite-vec.ts +24 -0
  154. package/references/minimem/src/embeddings/__tests__/embeddings.test.ts +431 -0
  155. package/references/minimem/src/embeddings/batch-gemini.ts +392 -0
  156. package/references/minimem/src/embeddings/batch-openai.ts +409 -0
  157. package/references/minimem/src/embeddings/embeddings.ts +434 -0
  158. package/references/minimem/src/index.ts +132 -0
  159. package/references/minimem/src/internal.ts +299 -0
  160. package/references/minimem/src/minimem.ts +1291 -0
  161. package/references/minimem/src/search/__tests__/hybrid.test.ts +247 -0
  162. package/references/minimem/src/search/graph.ts +234 -0
  163. package/references/minimem/src/search/hybrid.ts +151 -0
  164. package/references/minimem/src/search/search.ts +256 -0
  165. package/references/minimem/src/server/__tests__/mcp.test.ts +347 -0
  166. package/references/minimem/src/server/__tests__/tools.test.ts +364 -0
  167. package/references/minimem/src/server/mcp.ts +326 -0
  168. package/references/minimem/src/server/tools.ts +720 -0
  169. package/references/minimem/src/session.ts +460 -0
  170. package/references/minimem/src/store/__tests__/manifest.test.ts +177 -0
  171. package/references/minimem/src/store/__tests__/materialize.test.ts +52 -0
  172. package/references/minimem/src/store/__tests__/store-graph.test.ts +228 -0
  173. package/references/minimem/src/store/index.ts +27 -0
  174. package/references/minimem/src/store/manifest.ts +203 -0
  175. package/references/minimem/src/store/materialize.ts +185 -0
  176. package/references/minimem/src/store/store-graph.ts +252 -0
  177. package/references/minimem/tsconfig.json +19 -0
  178. package/references/minimem/tsup.config.ts +26 -0
  179. package/references/minimem/vitest.config.ts +29 -0
  180. package/references/openteams/src/cli/generate.ts +23 -1
  181. package/references/openteams/src/generators/agent-prompt-generator.test.ts +94 -0
  182. package/references/openteams/src/generators/agent-prompt-generator.ts +42 -13
  183. package/references/openteams/src/generators/package-generator.ts +9 -1
  184. package/references/openteams/src/generators/skill-generator.test.ts +28 -0
  185. package/references/openteams/src/generators/skill-generator.ts +10 -4
  186. package/references/skill-tree/.claude/settings.json +6 -0
  187. package/references/skill-tree/.sudocode/issues.jsonl +19 -0
  188. package/references/skill-tree/.sudocode/specs.jsonl +3 -0
  189. package/references/skill-tree/CLAUDE.md +132 -0
  190. package/references/skill-tree/README.md +396 -0
  191. package/references/skill-tree/docs/GAPS_v1.md +221 -0
  192. package/references/skill-tree/docs/INTEGRATION_PLAN.md +467 -0
  193. package/references/skill-tree/docs/TODOS.md +91 -0
  194. package/references/skill-tree/docs/anthropic_skill_guide.md +1364 -0
  195. package/references/skill-tree/docs/design/federated-skill-trees.md +524 -0
  196. package/references/skill-tree/docs/design/multi-agent-sync.md +759 -0
  197. package/references/skill-tree/docs/scraper/BRAINSTORM.md +583 -0
  198. package/references/skill-tree/docs/scraper/POC_PLAN.md +420 -0
  199. package/references/skill-tree/docs/scraper/README.md +170 -0
  200. package/references/skill-tree/examples/basic-usage.ts +157 -0
  201. package/references/skill-tree/package-lock.json +1852 -0
  202. package/references/skill-tree/package.json +66 -0
  203. package/references/skill-tree/plan.md +78 -0
  204. package/references/skill-tree/scraper/README.md +123 -0
  205. package/references/skill-tree/scraper/docs/DESIGN.md +683 -0
  206. package/references/skill-tree/scraper/docs/PLAN.md +336 -0
  207. package/references/skill-tree/scraper/drizzle.config.ts +10 -0
  208. package/references/skill-tree/scraper/package-lock.json +6329 -0
  209. package/references/skill-tree/scraper/package.json +68 -0
  210. package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-description.md +7 -0
  211. package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-name.md +7 -0
  212. package/references/skill-tree/scraper/test/fixtures/minimal-skill/SKILL.md +27 -0
  213. package/references/skill-tree/scraper/test/fixtures/skill-json/SKILL.json +21 -0
  214. package/references/skill-tree/scraper/test/fixtures/skill-with-meta/SKILL.md +54 -0
  215. package/references/skill-tree/scraper/test/fixtures/skill-with-meta/_meta.json +24 -0
  216. package/references/skill-tree/scraper/test/fixtures/valid-skill/SKILL.md +93 -0
  217. package/references/skill-tree/scraper/test/fixtures/valid-skill/_meta.json +22 -0
  218. package/references/skill-tree/scraper/tsup.config.ts +14 -0
  219. package/references/skill-tree/scraper/vitest.config.ts +17 -0
  220. package/references/skill-tree/scripts/convert-to-vitest.ts +166 -0
  221. package/references/skill-tree/skills/skill-writer/SKILL.md +339 -0
  222. package/references/skill-tree/skills/skill-writer/references/examples.md +326 -0
  223. package/references/skill-tree/skills/skill-writer/references/patterns.md +210 -0
  224. package/references/skill-tree/skills/skill-writer/references/quality-checklist.md +123 -0
  225. package/references/skill-tree/test/run-all.ts +106 -0
  226. package/references/skill-tree/test/utils.ts +128 -0
  227. package/references/skill-tree/vitest.config.ts +16 -0
  228. package/references/swarmkit/src/commands/init/phases/configure.ts +0 -22
  229. package/references/swarmkit/src/commands/init/phases/global-setup.ts +5 -3
  230. package/references/swarmkit/src/commands/init/wizard.ts +2 -2
  231. package/references/swarmkit/src/packages/setup.test.ts +53 -7
  232. package/references/swarmkit/src/packages/setup.ts +37 -1
  233. package/scripts/bootstrap.mjs +26 -1
  234. package/scripts/generate-agents.mjs +5 -1
  235. package/scripts/map-hook.mjs +97 -64
  236. package/scripts/map-sidecar.mjs +179 -25
  237. package/scripts/team-loader.mjs +12 -41
  238. package/skills/swarm/SKILL.md +89 -25
  239. package/src/__tests__/agent-generator.test.mjs +6 -13
  240. package/src/__tests__/bootstrap.test.mjs +124 -1
  241. package/src/__tests__/config.test.mjs +200 -27
  242. package/src/__tests__/e2e-live-map.test.mjs +536 -0
  243. package/src/__tests__/e2e-mesh-sidecar.test.mjs +570 -0
  244. package/src/__tests__/e2e-native-task-hooks.test.mjs +376 -0
  245. package/src/__tests__/e2e-sidecar-bridge.test.mjs +477 -0
  246. package/src/__tests__/helpers.mjs +13 -0
  247. package/src/__tests__/inbox.test.mjs +22 -89
  248. package/src/__tests__/index.test.mjs +35 -9
  249. package/src/__tests__/integration.test.mjs +513 -0
  250. package/src/__tests__/map-events.test.mjs +514 -150
  251. package/src/__tests__/mesh-connection.test.mjs +308 -0
  252. package/src/__tests__/opentasks-client.test.mjs +517 -0
  253. package/src/__tests__/paths.test.mjs +185 -41
  254. package/src/__tests__/sidecar-client.test.mjs +35 -0
  255. package/src/__tests__/sidecar-server.test.mjs +124 -0
  256. package/src/__tests__/skilltree-client.test.mjs +80 -0
  257. package/src/agent-generator.mjs +104 -33
  258. package/src/bootstrap.mjs +150 -10
  259. package/src/config.mjs +81 -17
  260. package/src/context-output.mjs +58 -8
  261. package/src/inbox.mjs +9 -54
  262. package/src/index.mjs +39 -8
  263. package/src/map-connection.mjs +4 -3
  264. package/src/map-events.mjs +350 -80
  265. package/src/mesh-connection.mjs +148 -0
  266. package/src/opentasks-client.mjs +269 -0
  267. package/src/paths.mjs +182 -27
  268. package/src/sessionlog.mjs +14 -9
  269. package/src/sidecar-client.mjs +81 -27
  270. package/src/sidecar-server.mjs +175 -16
  271. package/src/skilltree-client.mjs +173 -0
  272. package/src/template.mjs +68 -4
  273. package/vitest.config.mjs +1 -0
@@ -0,0 +1,573 @@
1
+ import { EventEmitter } from "node:events";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import * as os from "node:os";
5
+ import * as crypto from "node:crypto";
6
+ import type {
7
+ FederatedAddress,
8
+ FederationConfig,
9
+ FederationLink,
10
+ FederationPeerConfig,
11
+ Message,
12
+ SystemId,
13
+ } from "../types.js";
14
+ import type {
15
+ MapConnection,
16
+ MapAgentConnectionClass,
17
+ IncomingMapMessage,
18
+ MeshPeerLike,
19
+ MeshFederationGateway,
20
+ } from "../map/map-client.js";
21
+ import type { MeshConnector } from "../mesh/mesh-connector.js";
22
+ import { inboxMessageToMap } from "../mesh/type-mapper.js";
23
+ import { RoutingEngine } from "./routing-engine.js";
24
+ import { DeliveryQueue } from "./delivery-queue.js";
25
+ import { TrustManager } from "./trust.js";
26
+ import { parseAddress, isRemoteAddress } from "./address.js";
27
+
28
+ export interface DeliveryResult {
29
+ delivered: boolean;
30
+ peerId?: string;
31
+ queued?: boolean;
32
+ error?: string;
33
+ }
34
+
35
+ /**
36
+ * Callback for handling incoming federation messages.
37
+ * Wired to router.routeMessage() by index.ts.
38
+ */
39
+ export type IncomingMessageHandler = (incoming: {
40
+ from: string;
41
+ peerId: string;
42
+ payload: unknown;
43
+ meta?: Record<string, unknown>;
44
+ }) => void;
45
+
46
+ /**
47
+ * Manages MAP connections and federation peer links.
48
+ *
49
+ * Coordinates routing, delivery queuing, and trust enforcement
50
+ * for cross-system messaging. When an SDK connect function is provided,
51
+ * opens real MAP connections to federation peers for actual message transport.
52
+ */
53
+ export class ConnectionManager {
54
+ private peers = new Map<string, FederationLink>();
55
+ private connections = new Map<string, MapConnection>();
56
+ private gateways = new Map<string, MeshFederationGateway>();
57
+ private systemId: SystemId;
58
+ private sdkClass: MapAgentConnectionClass | null;
59
+ private meshConnector: MeshConnector | null;
60
+ private meshPeer: MeshPeerLike | null;
61
+ private onIncoming: IncomingMessageHandler | null;
62
+ readonly routing: RoutingEngine;
63
+ readonly queue: DeliveryQueue;
64
+ readonly trust: TrustManager;
65
+
66
+ constructor(
67
+ private events: EventEmitter,
68
+ private config: FederationConfig = {},
69
+ opts?: {
70
+ sdkClass?: MapAgentConnectionClass;
71
+ meshConnector?: MeshConnector;
72
+ /** Full MeshPeer for Phase 2 FederationGateway delegation */
73
+ meshPeer?: MeshPeerLike;
74
+ onIncomingMessage?: IncomingMessageHandler;
75
+ }
76
+ ) {
77
+ this.systemId = this.resolveSystemId();
78
+ this.routing = new RoutingEngine(events, config.routing);
79
+ this.queue = new DeliveryQueue(events, config.deliveryQueue);
80
+ this.trust = new TrustManager(config.trust);
81
+ this.sdkClass = opts?.sdkClass ?? null;
82
+ this.meshConnector = opts?.meshConnector ?? null;
83
+ this.meshPeer = opts?.meshPeer ?? null;
84
+ this.onIncoming = opts?.onIncomingMessage ?? null;
85
+
86
+ // Wire up flush-on-reconnect
87
+ this.events.on("federation.connected", (peerId: string) => {
88
+ if (config.deliveryQueue?.flushOnReconnect !== false) {
89
+ const queued = this.queue.flush(peerId);
90
+ if (queued.length > 0) {
91
+ this.events.emit("federation.flushing", {
92
+ peerId,
93
+ count: queued.length,
94
+ });
95
+ for (const entry of queued) {
96
+ this.route(entry.message).catch(() => {
97
+ // Re-queue if delivery still fails
98
+ this.queue.enqueue(peerId, entry.message);
99
+ });
100
+ }
101
+ }
102
+ }
103
+ });
104
+ }
105
+
106
+ /**
107
+ * Get the resolved system ID for this Agent Inbox instance.
108
+ */
109
+ getSystemId(): SystemId {
110
+ return this.systemId;
111
+ }
112
+
113
+ /**
114
+ * Establish federation with a peer. Uses MAP federation/connect protocol.
115
+ * If an SDK class was injected, opens a real MAP connection to the peer.
116
+ * Returns the federation link, or throws if trust check fails.
117
+ */
118
+ async federate(peer: FederationPeerConfig): Promise<FederationLink> {
119
+ if (!peer.url && !peer.meshPeerId) {
120
+ throw new Error(
121
+ `Federation peer "${peer.systemId}" must have either url or meshPeerId`
122
+ );
123
+ }
124
+
125
+ // Trust check
126
+ if (!this.trust.canConnect(peer.systemId)) {
127
+ throw new Error(
128
+ `Federation denied: system "${peer.systemId}" not in allowed servers list`
129
+ );
130
+ }
131
+
132
+ let conn: MapConnection | undefined;
133
+ let transport: "websocket" | "mesh" = "websocket";
134
+
135
+ if (peer.meshPeerId && this.meshPeer) {
136
+ // Phase 2: Full MeshPeer integration with FederationGateway (v0.2.0+)
137
+ transport = "mesh";
138
+ try {
139
+ // Try to get an existing gateway first (e.g. pre-connected in test setup),
140
+ // otherwise create one via federateWith()
141
+ let gateway: MeshFederationGateway | undefined =
142
+ this.meshPeer.getFederationGateway(peer.systemId);
143
+ if (!gateway) {
144
+ gateway = await this.meshPeer.federateWith(peer.systemId, {
145
+ localSystemId: this.meshPeer.peerId,
146
+ remoteSystemId: peer.systemId,
147
+ buffer: { enabled: true, maxMessages: 1000 },
148
+ routing: { maxHops: 5, trackPath: true },
149
+ });
150
+ }
151
+
152
+ this.gateways.set(peer.systemId, gateway);
153
+
154
+ // Listen for incoming federated messages
155
+ gateway.on("message:received", (envelope: unknown) => {
156
+ const env = envelope as { payload?: { from?: string; payload?: unknown; meta?: Record<string, unknown> } };
157
+ if (env.payload) {
158
+ this.handlePeerMessage(peer.systemId, {
159
+ from: env.payload.from ?? peer.systemId,
160
+ payload: env.payload.payload,
161
+ meta: env.payload.meta,
162
+ });
163
+ }
164
+ });
165
+ } catch (err) {
166
+ throw new Error(
167
+ `Mesh federation connection failed for "${peer.systemId}": ${err instanceof Error ? err.message : err}`
168
+ );
169
+ }
170
+ } else if (peer.meshPeerId && this.meshConnector) {
171
+ // Phase 1 fallback: Mesh transport via raw channel (no MeshPeer)
172
+ transport = "mesh";
173
+ try {
174
+ conn = await this.meshConnector.connect(peer.meshPeerId);
175
+
176
+ conn.onMessage((msg: IncomingMapMessage) => {
177
+ this.handlePeerMessage(peer.systemId, msg);
178
+ });
179
+
180
+ this.connections.set(peer.systemId, conn);
181
+ } catch (err) {
182
+ throw new Error(
183
+ `Mesh federation connection failed for "${peer.systemId}": ${err instanceof Error ? err.message : err}`
184
+ );
185
+ }
186
+ } else if (peer.url && this.sdkClass) {
187
+ // WebSocket transport — connect via MAP SDK
188
+ try {
189
+ conn = await this.sdkClass.connect(peer.url, {
190
+ name: this.systemId.id,
191
+ role: "gateway",
192
+ scopes: ["federation"],
193
+ capabilities: { trajectory: { canReport: false } },
194
+ metadata: {
195
+ systemId: this.systemId.id,
196
+ type: "federation-gateway",
197
+ peerSystemId: peer.systemId,
198
+ },
199
+ reconnection: {
200
+ enabled: true,
201
+ maxRetries: 5,
202
+ baseDelayMs: 1000,
203
+ maxDelayMs: 30000,
204
+ },
205
+ });
206
+
207
+ conn.onMessage((msg: IncomingMapMessage) => {
208
+ this.handlePeerMessage(peer.systemId, msg);
209
+ });
210
+
211
+ this.connections.set(peer.systemId, conn);
212
+ } catch (err) {
213
+ throw new Error(
214
+ `Federation connection failed for "${peer.systemId}": ${err instanceof Error ? err.message : err}`
215
+ );
216
+ }
217
+ }
218
+
219
+ const link: FederationLink = {
220
+ peerId: peer.systemId,
221
+ sessionId: crypto.randomUUID(),
222
+ status: "connected",
223
+ exposure: peer.exposure ?? { agents: "all" },
224
+ url: peer.url ?? "",
225
+ transport,
226
+ connectedAt: new Date().toISOString(),
227
+ };
228
+
229
+ this.peers.set(peer.systemId, link);
230
+ this.events.emit("federation.connected", peer.systemId);
231
+ return link;
232
+ }
233
+
234
+ /**
235
+ * Disconnect from a federation peer.
236
+ * Closes the real MAP connection if one exists.
237
+ */
238
+ async disconnect(peerId: string): Promise<void> {
239
+ const link = this.peers.get(peerId);
240
+ if (!link) return;
241
+
242
+ // Close real connection
243
+ const conn = this.connections.get(peerId);
244
+ if (conn) {
245
+ try {
246
+ await conn.disconnect();
247
+ } catch {
248
+ // Best-effort disconnect
249
+ }
250
+ this.connections.delete(peerId);
251
+ }
252
+
253
+ link.status = "disconnected";
254
+ this.routing.removePeer(peerId);
255
+ this.peers.delete(peerId);
256
+ this.events.emit("federation.disconnected", peerId);
257
+ }
258
+
259
+ /**
260
+ * Route a message to the correct federation peer.
261
+ * Resolves the address, checks trust, and delivers or queues.
262
+ * If a real connection exists, sends via conn.send(); otherwise emits event.
263
+ */
264
+ async route(message: Message): Promise<DeliveryResult> {
265
+ // Determine target for each recipient
266
+ for (const recipient of message.recipients) {
267
+ const addr = parseAddress(recipient.agent_id);
268
+ if (!isRemoteAddress(addr)) continue;
269
+
270
+ const peerId = this.routing.resolveRoute(addr);
271
+ if (!peerId) {
272
+ // Route unknown — try strategies
273
+ return this.handleUnknownRoute(addr, message);
274
+ }
275
+
276
+ // Check trust
277
+ if (!this.trust.canRoute(peerId, message.scope)) {
278
+ return {
279
+ delivered: false,
280
+ peerId,
281
+ error: `Trust policy denies routing to scope "${message.scope}" from system "${peerId}"`,
282
+ };
283
+ }
284
+
285
+ const link = this.peers.get(peerId);
286
+ if (!link || link.status !== "connected") {
287
+ // No direct peer link — try broadcast/hierarchical strategies before queuing.
288
+ // This handles cases like system-qualified addresses (bob@system-2) targeting
289
+ // a system we're not directly connected to but could reach via relay or hub.
290
+ const strategy = this.routing.getStrategy();
291
+ if (strategy !== "table") {
292
+ return this.handleUnknownRoute(addr, message);
293
+ }
294
+ this.queue.enqueue(peerId, message);
295
+ return { delivered: false, peerId, queued: true };
296
+ }
297
+
298
+ // Deliver — use real connection if available, otherwise emit event
299
+ const sendResult = await this.sendToPeer(peerId, addr, message);
300
+ if (sendResult) {
301
+ recipient.delivered_at = new Date().toISOString();
302
+ return { delivered: true, peerId };
303
+ }
304
+ // Send failed — queue for retry
305
+ this.queue.enqueue(peerId, message);
306
+ return { delivered: false, peerId, queued: true };
307
+ }
308
+
309
+ return { delivered: false, error: "No remote recipients found" };
310
+ }
311
+
312
+ /**
313
+ * Send a message to a peer via FederationGateway, real connection, or event.
314
+ * Returns true if send succeeded (or event was emitted).
315
+ */
316
+ private async sendToPeer(
317
+ peerId: string,
318
+ addr: FederatedAddress,
319
+ message: Message
320
+ ): Promise<boolean> {
321
+ // Phase 2: Try FederationGateway first (provides envelope, hop/loop detection)
322
+ const gateway = this.gateways.get(peerId);
323
+ if (gateway) {
324
+ try {
325
+ const mapMsg = inboxMessageToMap(message);
326
+ // Build a MAP-compatible message for the gateway.
327
+ // Use local agent address (not federated) since the gateway
328
+ // already handles cross-system routing. A federated `to` like
329
+ // { system: "system-b", agent: "bob" } would cause the remote
330
+ // MapServer to call routeToFederation() instead of deliverToAgent().
331
+ const targetAgentIds = addr.agent ? [addr.agent] : [];
332
+ const localTo =
333
+ targetAgentIds.length === 1
334
+ ? { agent: targetAgentIds[0] }
335
+ : targetAgentIds.length > 1
336
+ ? { agents: targetAgentIds }
337
+ : mapMsg.to;
338
+ const gatewayMessage = {
339
+ id: message.id,
340
+ from: message.sender_id,
341
+ to: localTo,
342
+ timestamp: Date.now(),
343
+ payload: mapMsg.payload,
344
+ meta: mapMsg.meta,
345
+ };
346
+ const routed = await gateway.route(gatewayMessage, targetAgentIds);
347
+ if (routed) return true;
348
+ // Fall through to direct connection if gateway route fails
349
+ } catch {
350
+ // Fall through to direct connection
351
+ }
352
+ }
353
+
354
+ const conn = this.connections.get(peerId);
355
+ if (conn) {
356
+ // Real transport — send via MAP SDK or mesh channel connection
357
+ try {
358
+ await conn.send(
359
+ { agentId: addr.agent, scope: addr.scope },
360
+ message.content,
361
+ {
362
+ messageId: message.id,
363
+ senderId: message.sender_id,
364
+ sourceSystem: this.systemId.id,
365
+ targetAgent: addr.agent,
366
+ importance: message.importance,
367
+ ...(message.subject ? { subject: message.subject } : {}),
368
+ ...(message.thread_tag ? { threadTag: message.thread_tag } : {}),
369
+ ...(message.in_reply_to ? { inReplyTo: message.in_reply_to } : {}),
370
+ }
371
+ );
372
+ return true;
373
+ } catch {
374
+ return false;
375
+ }
376
+ }
377
+
378
+ // No real connection — emit event (for testing / event-based mode)
379
+ this.events.emit("federation.route", { peerId, message });
380
+ return true;
381
+ }
382
+
383
+ /**
384
+ * Handle an incoming message from a federation peer connection.
385
+ * Delegates to the injected message handler (wired to router.routeMessage).
386
+ */
387
+ private handlePeerMessage(peerId: string, msg: IncomingMapMessage): void {
388
+ this.events.emit("federation.message.received", { peerId, message: msg });
389
+
390
+ if (this.onIncoming) {
391
+ this.onIncoming({
392
+ from: msg.from,
393
+ peerId,
394
+ payload: msg.payload,
395
+ meta: msg.meta,
396
+ });
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Handle routing to an unknown agent. Behavior depends on strategy.
402
+ */
403
+ private async handleUnknownRoute(
404
+ addr: FederatedAddress,
405
+ message: Message
406
+ ): Promise<DeliveryResult> {
407
+ const strategy = this.routing.getStrategy();
408
+
409
+ if (strategy === "broadcast") {
410
+ // Forward to all connected peers
411
+ const connectedPeers = this.getConnectedPeers();
412
+ if (connectedPeers.length === 0) {
413
+ return { delivered: false, error: "No connected peers for broadcast" };
414
+ }
415
+
416
+ // Try real connections first, fall back to event
417
+ for (const peer of connectedPeers) {
418
+ await this.sendToPeer(peer.peerId, addr, message);
419
+ }
420
+
421
+ this.events.emit("federation.broadcast", {
422
+ message,
423
+ peers: connectedPeers.map((p) => p.peerId),
424
+ timeout: this.routing.getBroadcastTimeout(),
425
+ });
426
+ return { delivered: true };
427
+ }
428
+
429
+ if (strategy === "hierarchical") {
430
+ // Delegate to upstream hubs
431
+ const upstream = this.routing.getUpstream();
432
+ for (const hubId of upstream) {
433
+ const link = this.peers.get(hubId);
434
+ if (link?.status === "connected") {
435
+ const sent = await this.sendToPeer(hubId, addr, message);
436
+ if (sent) {
437
+ return { delivered: true, peerId: hubId };
438
+ }
439
+ }
440
+ }
441
+ return { delivered: false, error: "No reachable upstream hubs" };
442
+ }
443
+
444
+ // Table strategy with refreshOnMiss — emit event for transport layer
445
+ if (this.routing.shouldRefreshOnMiss()) {
446
+ this.events.emit("federation.refresh", { address: addr });
447
+ }
448
+
449
+ // Queue for later if we have a system hint
450
+ if (addr.system) {
451
+ this.queue.enqueue(addr.system, message);
452
+ return { delivered: false, peerId: addr.system, queued: true };
453
+ }
454
+
455
+ return {
456
+ delivered: false,
457
+ error: `No route to agent "${addr.agent}"`,
458
+ };
459
+ }
460
+
461
+ /**
462
+ * Get all federation peer links.
463
+ */
464
+ getPeers(): FederationLink[] {
465
+ return Array.from(this.peers.values());
466
+ }
467
+
468
+ /**
469
+ * Get only connected peers.
470
+ */
471
+ getConnectedPeers(): FederationLink[] {
472
+ return Array.from(this.peers.values()).filter(
473
+ (p) => p.status === "connected"
474
+ );
475
+ }
476
+
477
+ /**
478
+ * Get a specific peer link.
479
+ */
480
+ getPeer(peerId: string): FederationLink | undefined {
481
+ return this.peers.get(peerId);
482
+ }
483
+
484
+ /**
485
+ * Check if a peer is connected.
486
+ */
487
+ isConnected(peerId: string): boolean {
488
+ return this.peers.get(peerId)?.status === "connected";
489
+ }
490
+
491
+ /**
492
+ * Check if a real MAP SDK connection exists for a peer.
493
+ */
494
+ hasTransport(peerId: string): boolean {
495
+ return this.connections.has(peerId);
496
+ }
497
+
498
+ /**
499
+ * Clean up all state. Call on shutdown.
500
+ */
501
+ /**
502
+ * Check if a FederationGateway exists for a peer.
503
+ */
504
+ hasGateway(peerId: string): boolean {
505
+ return this.gateways.has(peerId);
506
+ }
507
+
508
+ async destroy(): Promise<void> {
509
+ // Close all real connections
510
+ for (const [peerId, conn] of this.connections) {
511
+ try {
512
+ await conn.disconnect();
513
+ } catch {
514
+ // Best-effort
515
+ }
516
+ this.connections.delete(peerId);
517
+ }
518
+
519
+ this.gateways.clear();
520
+ this.routing.destroy();
521
+ this.queue.destroy();
522
+ this.peers.clear();
523
+ }
524
+
525
+ /**
526
+ * Resolve the system ID using tiered precedence:
527
+ * 1. Explicit config (INBOX_SYSTEM_ID)
528
+ * 2. Auto-generated (persisted to file for stability across restarts)
529
+ *
530
+ * Note: Tier 2 (MAP systemInfo) is handled after MAP connection is established.
531
+ */
532
+ private resolveSystemId(): SystemId {
533
+ // Tier 1: Explicit config
534
+ if (this.config.systemId) {
535
+ return { id: this.config.systemId, source: "config" };
536
+ }
537
+
538
+ // Tier 3: Auto-generated (with persistence)
539
+ const idFile = path.join(
540
+ os.homedir(),
541
+ ".claude",
542
+ "agent-inbox",
543
+ "system-id"
544
+ );
545
+ try {
546
+ const existing = fs.readFileSync(idFile, "utf-8").trim();
547
+ if (existing) {
548
+ return { id: existing, source: "auto" };
549
+ }
550
+ } catch {
551
+ // File doesn't exist — generate new ID
552
+ }
553
+
554
+ const newId = `inbox-${crypto.randomBytes(4).toString("hex")}`;
555
+ try {
556
+ fs.mkdirSync(path.dirname(idFile), { recursive: true });
557
+ fs.writeFileSync(idFile, newId);
558
+ } catch {
559
+ // Best-effort persistence
560
+ }
561
+ return { id: newId, source: "auto" };
562
+ }
563
+
564
+ /**
565
+ * Update system ID from MAP server's systemInfo (Tier 2).
566
+ * Only used if no explicit config was set.
567
+ */
568
+ updateSystemIdFromMap(mapSystemName: string): void {
569
+ if (this.systemId.source === "config") return; // Explicit config takes precedence
570
+ this.systemId = { id: mapSystemName, source: "map" };
571
+ this.events.emit("system.id.updated", this.systemId);
572
+ }
573
+ }