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,266 @@
1
+ /**
2
+ * Linked MockMeshPeer pair for e2e testing.
3
+ *
4
+ * Two MockMeshPeer instances are linked so that:
5
+ * - FederationGateway.route() on peer A delivers to peer B's message handler
6
+ * - Messages flow bidirectionally through the gateway bridge
7
+ *
8
+ * This simulates what happens in a real agentic-mesh network where
9
+ * MeshPeer federation gateways exchange messages over TunnelStreams.
10
+ */
11
+
12
+ import { EventEmitter } from "node:events";
13
+ import type {
14
+ MeshPeerLike,
15
+ MeshAgentConnection,
16
+ MeshMapServer,
17
+ MeshMapMessage,
18
+ MeshDeliveryHandler,
19
+ MeshFederationGateway,
20
+ } from "../../src/map/map-client.js";
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // MockAgentConnection
24
+ // ---------------------------------------------------------------------------
25
+
26
+ class MockAgentConnection extends EventEmitter implements MeshAgentConnection {
27
+ readonly agentId: string;
28
+ readonly isRegistered = true;
29
+
30
+ constructor(
31
+ agentId: string,
32
+ private server: LinkedMockMapServer
33
+ ) {
34
+ super();
35
+ this.agentId = agentId;
36
+ }
37
+
38
+ async register() {
39
+ return { id: this.agentId, name: this.agentId, state: "active" as const, ownerId: null };
40
+ }
41
+
42
+ async unregister() {
43
+ this.server.removeMessageHandler(this.agentId);
44
+ }
45
+
46
+ async send(to: unknown, payload: unknown, meta?: Record<string, unknown>) {
47
+ // Delegate to server's send (which may route locally or remotely)
48
+ return { messageId: `msg-${Date.now()}`, delivered: [] as string[] };
49
+ }
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // LinkedMockMapServer
54
+ // ---------------------------------------------------------------------------
55
+
56
+ export class LinkedMockMapServer extends EventEmitter implements MeshMapServer {
57
+ readonly systemId: string;
58
+ readonly systemName?: string;
59
+
60
+ private messageHandlers = new Map<
61
+ string,
62
+ (agentId: string, message: MeshMapMessage) => void | Promise<void>
63
+ >();
64
+ private deliveryHandler: MeshDeliveryHandler | null = null;
65
+ private agents = new Map<string, { id: string; name?: string; role?: string; state: string }>();
66
+
67
+ constructor(systemId: string, systemName?: string) {
68
+ super();
69
+ this.systemId = systemId;
70
+ this.systemName = systemName;
71
+ }
72
+
73
+ registerAgent(params: { agentId?: string; name?: string; role?: string }) {
74
+ const id = params.agentId ?? `agent-${Date.now()}`;
75
+ const agent = { id, name: params.name, role: params.role, state: "active", ownerId: null };
76
+ this.agents.set(id, agent);
77
+ return agent;
78
+ }
79
+
80
+ unregisterAgent(agentId: string) {
81
+ this.agents.delete(agentId);
82
+ this.messageHandlers.delete(agentId);
83
+ return { id: agentId, state: "stopped", ownerId: null };
84
+ }
85
+
86
+ setMessageHandler(
87
+ agentId: string,
88
+ handler: (agentId: string, message: MeshMapMessage) => void | Promise<void>
89
+ ) {
90
+ this.messageHandlers.set(agentId, handler);
91
+ }
92
+
93
+ removeMessageHandler(agentId: string) {
94
+ this.messageHandlers.delete(agentId);
95
+ }
96
+
97
+ setDeliveryHandler(handler: MeshDeliveryHandler): MeshDeliveryHandler {
98
+ const prev = this.deliveryHandler ?? {
99
+ async deliverToAgent() { return false; },
100
+ async forwardToPeer() { return false; },
101
+ };
102
+ this.deliveryHandler = handler;
103
+ return prev;
104
+ }
105
+
106
+ hasAgent(agentId: string): boolean {
107
+ return this.agents.has(agentId);
108
+ }
109
+
110
+ /**
111
+ * Deliver a message to a local agent.
112
+ * First tries the DeliveryHandler bridge (Phase 2),
113
+ * then falls back to the message handler (Phase 1 style).
114
+ */
115
+ async deliverLocally(agentId: string, message: MeshMapMessage): Promise<boolean> {
116
+ // Try delivery handler first (DeliveryBridge from agent-inbox)
117
+ if (this.deliveryHandler) {
118
+ const result = await this.deliveryHandler.deliverToAgent(agentId, message);
119
+ if (result) return true;
120
+ }
121
+
122
+ // Fall back to per-agent message handler
123
+ const handler = this.messageHandlers.get(agentId);
124
+ if (handler) {
125
+ await handler(agentId, message);
126
+ return true;
127
+ }
128
+
129
+ return false;
130
+ }
131
+ }
132
+
133
+ // ---------------------------------------------------------------------------
134
+ // LinkedFederationGateway
135
+ // ---------------------------------------------------------------------------
136
+
137
+ /**
138
+ * A mock FederationGateway that actually delivers messages to the
139
+ * linked peer's MapServer, simulating real cross-mesh federation.
140
+ */
141
+ class LinkedFederationGateway extends EventEmitter implements MeshFederationGateway {
142
+ readonly localSystemId: string;
143
+ readonly remoteSystemId: string;
144
+ isConnected = true;
145
+
146
+ private remotePeer: LinkedMockMeshPeer;
147
+
148
+ constructor(
149
+ localSystemId: string,
150
+ remoteSystemId: string,
151
+ remotePeer: LinkedMockMeshPeer
152
+ ) {
153
+ super();
154
+ this.localSystemId = localSystemId;
155
+ this.remoteSystemId = remoteSystemId;
156
+ this.remotePeer = remotePeer;
157
+ }
158
+
159
+ async route(message: unknown, targetAgentIds: string[]): Promise<boolean> {
160
+ const msg = message as MeshMapMessage;
161
+
162
+ // Deliver to each target agent on the remote peer's MapServer
163
+ let anyDelivered = false;
164
+ for (const agentId of targetAgentIds) {
165
+ const delivered = await this.remotePeer.server.deliverLocally(agentId, msg);
166
+ if (delivered) anyDelivered = true;
167
+ }
168
+
169
+ // If no specific targets, try delivering to "agent-inbox" (the inbox agent)
170
+ if (targetAgentIds.length === 0) {
171
+ anyDelivered = await this.remotePeer.server.deliverLocally("agent-inbox", msg);
172
+ }
173
+
174
+ return anyDelivered;
175
+ }
176
+ }
177
+
178
+ // ---------------------------------------------------------------------------
179
+ // LinkedMockMeshPeer
180
+ // ---------------------------------------------------------------------------
181
+
182
+ export class LinkedMockMeshPeer extends EventEmitter implements MeshPeerLike {
183
+ readonly peerId: string;
184
+ readonly server: LinkedMockMapServer;
185
+ private agentConnections = new Map<string, MockAgentConnection>();
186
+ private gateways = new Map<string, LinkedFederationGateway>();
187
+ private linkedPeers = new Map<string, LinkedMockMeshPeer>();
188
+
189
+ constructor(peerId: string, systemId?: string) {
190
+ super();
191
+ this.peerId = peerId;
192
+ this.server = new LinkedMockMapServer(systemId ?? peerId, `${peerId}-server`);
193
+ }
194
+
195
+ async createAgent(config: {
196
+ agentId?: string;
197
+ name?: string;
198
+ role?: string;
199
+ scopes?: string[];
200
+ capabilities?: Record<string, unknown>;
201
+ metadata?: Record<string, unknown>;
202
+ }): Promise<MeshAgentConnection> {
203
+ const agentId = config.agentId ?? `agent-${Date.now()}`;
204
+ this.server.registerAgent({ agentId, name: config.name, role: config.role });
205
+ const conn = new MockAgentConnection(agentId, this.server);
206
+ await conn.register();
207
+ this.agentConnections.set(agentId, conn);
208
+ return conn;
209
+ }
210
+
211
+ /** Link this peer to another for bidirectional message delivery. */
212
+ linkPeer(other: LinkedMockMeshPeer): void {
213
+ this.linkedPeers.set(other.peerId, other);
214
+ other.linkedPeers.set(this.peerId, this);
215
+ }
216
+
217
+ async federateWith(
218
+ remoteSystemId: string,
219
+ _config?: unknown
220
+ ): Promise<LinkedFederationGateway> {
221
+ // Find the linked peer by matching system ID or peer ID
222
+ let remotePeer: LinkedMockMeshPeer | undefined;
223
+ for (const [, peer] of this.linkedPeers) {
224
+ if (peer.server.systemId === remoteSystemId || peer.peerId === remoteSystemId) {
225
+ remotePeer = peer;
226
+ break;
227
+ }
228
+ }
229
+
230
+ if (!remotePeer) {
231
+ throw new Error(
232
+ `No linked peer found for system "${remoteSystemId}". Link peers first with linkPeer().`
233
+ );
234
+ }
235
+
236
+ const gateway = new LinkedFederationGateway(
237
+ this.server.systemId,
238
+ remoteSystemId,
239
+ remotePeer
240
+ );
241
+ this.gateways.set(remoteSystemId, gateway);
242
+ return gateway;
243
+ }
244
+
245
+ getFederationGateway(remoteSystemId: string): LinkedFederationGateway | undefined {
246
+ return this.gateways.get(remoteSystemId);
247
+ }
248
+ }
249
+
250
+ // ---------------------------------------------------------------------------
251
+ // Factory
252
+ // ---------------------------------------------------------------------------
253
+
254
+ /**
255
+ * Create a pair of linked MockMeshPeers that can exchange messages
256
+ * through their FederationGateways.
257
+ */
258
+ export function createLinkedMeshPeerPair(
259
+ peerA: { peerId: string; systemId?: string },
260
+ peerB: { peerId: string; systemId?: string }
261
+ ): [LinkedMockMeshPeer, LinkedMockMeshPeer] {
262
+ const a = new LinkedMockMeshPeer(peerA.peerId, peerA.systemId);
263
+ const b = new LinkedMockMeshPeer(peerB.peerId, peerB.systemId);
264
+ a.linkPeer(b);
265
+ return [a, b];
266
+ }
@@ -0,0 +1,226 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { mapMessageToInbox, inboxMessageToMap } from "../../src/mesh/type-mapper.js";
3
+ import type { MapMessage } from "../../src/mesh/type-mapper.js";
4
+ import type { Message } from "../../src/types.js";
5
+
6
+ function makeMapMessage(overrides: Partial<MapMessage> = {}): MapMessage {
7
+ return {
8
+ id: "map-msg-1",
9
+ from: "agent-alice",
10
+ to: { agent: "agent-bob" },
11
+ timestamp: Date.now(),
12
+ payload: { type: "text", text: "hello from MAP" },
13
+ meta: { priority: "normal" },
14
+ ...overrides,
15
+ };
16
+ }
17
+
18
+ describe("mapMessageToInbox", () => {
19
+ it("should convert a basic MAP message to inbox format", () => {
20
+ const mapMsg = makeMapMessage();
21
+ const inboxMsg = mapMessageToInbox(mapMsg, "test-scope");
22
+
23
+ expect(inboxMsg.scope).toBe("test-scope");
24
+ expect(inboxMsg.sender_id).toBe("agent-alice");
25
+ expect(inboxMsg.recipients).toHaveLength(1);
26
+ expect(inboxMsg.recipients[0].agent_id).toBe("agent-bob");
27
+ expect(inboxMsg.recipients[0].kind).toBe("to");
28
+ expect(inboxMsg.content).toEqual({ type: "text", text: "hello from MAP" });
29
+ expect(inboxMsg.importance).toBe("normal");
30
+ });
31
+
32
+ it("should map priority to importance", () => {
33
+ const urgent = mapMessageToInbox(
34
+ makeMapMessage({ meta: { priority: "urgent" } })
35
+ );
36
+ expect(urgent.importance).toBe("urgent");
37
+
38
+ const high = mapMessageToInbox(
39
+ makeMapMessage({ meta: { priority: "high" } })
40
+ );
41
+ expect(high.importance).toBe("high");
42
+
43
+ const low = mapMessageToInbox(
44
+ makeMapMessage({ meta: { priority: "low" } })
45
+ );
46
+ expect(low.importance).toBe("low");
47
+ });
48
+
49
+ it("should extract inbox fields from _meta", () => {
50
+ const mapMsg = makeMapMessage({
51
+ _meta: {
52
+ subject: "Code review",
53
+ threadTag: "thread-42",
54
+ inReplyTo: "prev-msg-1",
55
+ conversationId: "conv-1",
56
+ inboxMessageId: "custom-id-1",
57
+ recipientKind: "cc",
58
+ },
59
+ });
60
+
61
+ const inboxMsg = mapMessageToInbox(mapMsg);
62
+
63
+ expect(inboxMsg.id).toBe("custom-id-1");
64
+ expect(inboxMsg.subject).toBe("Code review");
65
+ expect(inboxMsg.thread_tag).toBe("thread-42");
66
+ expect(inboxMsg.in_reply_to).toBe("prev-msg-1");
67
+ expect(inboxMsg.conversation_id).toBe("conv-1");
68
+ expect(inboxMsg.recipients[0].kind).toBe("cc");
69
+ });
70
+
71
+ it("should fall back to meta.correlationId for thread_tag", () => {
72
+ const mapMsg = makeMapMessage({
73
+ meta: { priority: "normal", correlationId: "corr-123" },
74
+ });
75
+
76
+ const inboxMsg = mapMessageToInbox(mapMsg);
77
+ expect(inboxMsg.thread_tag).toBe("corr-123");
78
+ });
79
+
80
+ it("should handle string address", () => {
81
+ const mapMsg = makeMapMessage({ to: "bob" });
82
+ const inboxMsg = mapMessageToInbox(mapMsg);
83
+ expect(inboxMsg.recipients[0].agent_id).toBe("bob");
84
+ });
85
+
86
+ it("should handle multi-agent address", () => {
87
+ const mapMsg = makeMapMessage({ to: { agents: ["bob", "carol"] } });
88
+ const inboxMsg = mapMessageToInbox(mapMsg);
89
+ expect(inboxMsg.recipients).toHaveLength(2);
90
+ expect(inboxMsg.recipients[0].agent_id).toBe("bob");
91
+ expect(inboxMsg.recipients[1].agent_id).toBe("carol");
92
+ });
93
+
94
+ it("should handle scope address", () => {
95
+ const mapMsg = makeMapMessage({ to: { scope: "engineering" } });
96
+ const inboxMsg = mapMessageToInbox(mapMsg);
97
+ expect(inboxMsg.recipients[0].agent_id).toBe("engineering");
98
+ });
99
+
100
+ it("should handle federated address", () => {
101
+ const mapMsg = makeMapMessage({
102
+ to: { system: "remote-sys", agent: "bob" },
103
+ });
104
+ const inboxMsg = mapMessageToInbox(mapMsg);
105
+ expect(inboxMsg.recipients[0].agent_id).toBe("bob@remote-sys");
106
+ });
107
+
108
+ it("should handle broadcast address", () => {
109
+ const mapMsg = makeMapMessage({ to: { broadcast: true } });
110
+ const inboxMsg = mapMessageToInbox(mapMsg);
111
+ expect(inboxMsg.recipients[0].agent_id).toBe("*");
112
+ });
113
+
114
+ it("should normalize string payload", () => {
115
+ const mapMsg = makeMapMessage({ payload: "just a string" });
116
+ const inboxMsg = mapMessageToInbox(mapMsg);
117
+ expect(inboxMsg.content).toEqual({ type: "text", text: "just a string" });
118
+ });
119
+
120
+ it("should strip inbox-specific keys from metadata", () => {
121
+ const mapMsg = makeMapMessage({
122
+ _meta: {
123
+ subject: "Test",
124
+ threadTag: "t1",
125
+ customField: "keep-me",
126
+ anotherField: 42,
127
+ },
128
+ });
129
+
130
+ const inboxMsg = mapMessageToInbox(mapMsg);
131
+ expect(inboxMsg.metadata).toEqual({
132
+ customField: "keep-me",
133
+ anotherField: 42,
134
+ });
135
+ expect(inboxMsg.metadata).not.toHaveProperty("subject");
136
+ expect(inboxMsg.metadata).not.toHaveProperty("threadTag");
137
+ });
138
+ });
139
+
140
+ describe("inboxMessageToMap", () => {
141
+ function makeInboxMessage(overrides: Partial<Message> = {}): Message {
142
+ return {
143
+ id: "inbox-msg-1",
144
+ scope: "default",
145
+ sender_id: "agent-alice",
146
+ recipients: [{ agent_id: "agent-bob", kind: "to" }],
147
+ content: { type: "text", text: "hello" },
148
+ importance: "normal",
149
+ metadata: {},
150
+ created_at: new Date().toISOString(),
151
+ ...overrides,
152
+ };
153
+ }
154
+
155
+ it("should convert a basic inbox message to MAP format", () => {
156
+ const msg = makeInboxMessage();
157
+ const result = inboxMessageToMap(msg);
158
+
159
+ expect(result.to).toEqual({ agent: "agent-bob" });
160
+ expect(result.payload).toEqual({ type: "text", text: "hello" });
161
+ expect(result.meta.priority).toBe("normal");
162
+ expect(result.meta._meta.inboxMessageId).toBe("inbox-msg-1");
163
+ });
164
+
165
+ it("should encode inbox fields in _meta", () => {
166
+ const msg = makeInboxMessage({
167
+ subject: "Review",
168
+ thread_tag: "thread-1",
169
+ in_reply_to: "prev-1",
170
+ conversation_id: "conv-1",
171
+ });
172
+
173
+ const result = inboxMessageToMap(msg);
174
+ expect(result.meta._meta.subject).toBe("Review");
175
+ expect(result.meta._meta.threadTag).toBe("thread-1");
176
+ expect(result.meta._meta.inReplyTo).toBe("prev-1");
177
+ expect(result.meta._meta.conversationId).toBe("conv-1");
178
+ expect(result.meta.correlationId).toBe("thread-1");
179
+ });
180
+
181
+ it("should map importance to priority", () => {
182
+ expect(inboxMessageToMap(makeInboxMessage({ importance: "urgent" })).meta.priority).toBe("urgent");
183
+ expect(inboxMessageToMap(makeInboxMessage({ importance: "high" })).meta.priority).toBe("high");
184
+ expect(inboxMessageToMap(makeInboxMessage({ importance: "low" })).meta.priority).toBe("low");
185
+ });
186
+
187
+ it("should handle federated recipient address", () => {
188
+ const msg = makeInboxMessage({
189
+ recipients: [{ agent_id: "bob@remote-sys", kind: "to" }],
190
+ });
191
+ const result = inboxMessageToMap(msg);
192
+ expect(result.to).toEqual({ system: "remote-sys", agent: "bob" });
193
+ });
194
+
195
+ it("should handle multiple recipients", () => {
196
+ const msg = makeInboxMessage({
197
+ recipients: [
198
+ { agent_id: "bob", kind: "to" },
199
+ { agent_id: "carol", kind: "to" },
200
+ ],
201
+ });
202
+ const result = inboxMessageToMap(msg);
203
+ expect(result.to).toEqual({ agents: ["bob", "carol"] });
204
+ });
205
+
206
+ it("should only include 'to' recipients in address", () => {
207
+ const msg = makeInboxMessage({
208
+ recipients: [
209
+ { agent_id: "bob", kind: "to" },
210
+ { agent_id: "carol", kind: "cc" },
211
+ ],
212
+ });
213
+ const result = inboxMessageToMap(msg);
214
+ // Only "to" recipients go in the address
215
+ expect(result.to).toEqual({ agent: "bob" });
216
+ });
217
+
218
+ it("should handle no recipients with broadcast", () => {
219
+ const msg = makeInboxMessage({
220
+ recipients: [{ agent_id: "carol", kind: "cc" }],
221
+ });
222
+ // No "to" recipients → broadcast
223
+ const result = inboxMessageToMap(msg);
224
+ expect(result.to).toEqual({ broadcast: true });
225
+ });
226
+ });
@@ -0,0 +1,184 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { EventEmitter } from "node:events";
3
+ import { InMemoryStorage } from "../src/storage/memory.js";
4
+ import { MessageRouter, normalizeContent } from "../src/router/message-router.js";
5
+
6
+ describe("MessageRouter", () => {
7
+ let storage: InMemoryStorage;
8
+ let events: EventEmitter;
9
+ let router: MessageRouter;
10
+
11
+ beforeEach(() => {
12
+ storage = new InMemoryStorage();
13
+ events = new EventEmitter();
14
+ router = new MessageRouter(storage, events, "default");
15
+ });
16
+
17
+ describe("normalizeContent", () => {
18
+ it("should wrap strings as text content", () => {
19
+ expect(normalizeContent("hello")).toEqual({ type: "text", text: "hello" });
20
+ });
21
+
22
+ it("should pass through typed objects", () => {
23
+ const content = { type: "data", data: { foo: 1 } };
24
+ expect(normalizeContent(content)).toEqual(content);
25
+ });
26
+
27
+ it("should wrap untyped objects as data content", () => {
28
+ const payload = { foo: "bar" };
29
+ expect(normalizeContent(payload)).toEqual({ type: "data", data: payload });
30
+ });
31
+
32
+ it("should wrap null as data content", () => {
33
+ expect(normalizeContent(null)).toEqual({ type: "data", data: null });
34
+ });
35
+ });
36
+
37
+ describe("resolveRecipients", () => {
38
+ it("should resolve a single string", () => {
39
+ const result = router.resolveRecipients("agent-1");
40
+ expect(result).toEqual([{ agent_id: "agent-1", kind: "to" }]);
41
+ });
42
+
43
+ it("should resolve an array of strings", () => {
44
+ const result = router.resolveRecipients(["a1", "a2"]);
45
+ expect(result).toEqual([
46
+ { agent_id: "a1", kind: "to" },
47
+ { agent_id: "a2", kind: "to" },
48
+ ]);
49
+ });
50
+
51
+ it("should resolve structured recipients", () => {
52
+ const result = router.resolveRecipients([
53
+ { agent_id: "a1", kind: "to" },
54
+ { agent_id: "a2", kind: "cc" },
55
+ ]);
56
+ expect(result).toEqual([
57
+ { agent_id: "a1", kind: "to" },
58
+ { agent_id: "a2", kind: "cc" },
59
+ ]);
60
+ });
61
+ });
62
+
63
+ describe("routeMessage", () => {
64
+ it("should create and store a message", async () => {
65
+ const message = await router.routeMessage({
66
+ from: "alice",
67
+ to: "bob",
68
+ payload: "hello bob",
69
+ });
70
+
71
+ expect(message.id).toBeTruthy();
72
+ expect(message.sender_id).toBe("alice");
73
+ expect(message.recipients[0].agent_id).toBe("bob");
74
+ expect(message.content).toEqual({ type: "text", text: "hello bob" });
75
+ expect(message.scope).toBe("default");
76
+ expect(message.importance).toBe("normal");
77
+
78
+ // Should be stored
79
+ expect(storage.getMessage(message.id)).toBeDefined();
80
+ });
81
+
82
+ it("should emit message.created event", async () => {
83
+ let emitted: unknown = null;
84
+ events.on("message.created", (msg) => { emitted = msg; });
85
+
86
+ const message = await router.routeMessage({
87
+ from: "alice",
88
+ to: "bob",
89
+ payload: "hello",
90
+ });
91
+
92
+ expect(emitted).toBe(message);
93
+ });
94
+
95
+ it("should mark local agents as delivered", async () => {
96
+ storage.putAgent({
97
+ agent_id: "bob",
98
+ scope: "default",
99
+ status: "active",
100
+ metadata: {},
101
+ registered_at: "2025-01-01T00:00:00Z",
102
+ last_active_at: "2025-01-01T00:00:00Z",
103
+ });
104
+
105
+ const message = await router.routeMessage({
106
+ from: "alice",
107
+ to: "bob",
108
+ payload: "hello",
109
+ });
110
+
111
+ expect(message.recipients[0].delivered_at).toBeTruthy();
112
+ });
113
+
114
+ it("should inherit thread_tag from parent on reply", async () => {
115
+ const original = await router.routeMessage({
116
+ from: "alice",
117
+ to: "bob",
118
+ payload: "start thread",
119
+ threadTag: "sprint-1",
120
+ });
121
+
122
+ const reply = await router.routeMessage({
123
+ from: "bob",
124
+ to: "alice",
125
+ payload: "reply",
126
+ inReplyTo: original.id,
127
+ });
128
+
129
+ expect(reply.thread_tag).toBe("sprint-1");
130
+ expect(reply.in_reply_to).toBe(original.id);
131
+ });
132
+
133
+ it("should appear in recipient's inbox", async () => {
134
+ await router.routeMessage({
135
+ from: "alice",
136
+ to: "bob",
137
+ payload: "hello",
138
+ });
139
+
140
+ const inbox = storage.getInbox("bob");
141
+ expect(inbox).toHaveLength(1);
142
+ expect(inbox[0].sender_id).toBe("alice");
143
+ });
144
+ });
145
+
146
+ describe("markRead / markAcknowledged", () => {
147
+ it("should mark a message as read", async () => {
148
+ const msg = await router.routeMessage({
149
+ from: "alice",
150
+ to: "bob",
151
+ payload: "hello",
152
+ });
153
+
154
+ expect(router.markRead(msg.id, "bob")).toBe(true);
155
+ const updated = storage.getMessage(msg.id)!;
156
+ expect(updated.recipients[0].read_at).toBeTruthy();
157
+ });
158
+
159
+ it("should mark a message as acknowledged", async () => {
160
+ const msg = await router.routeMessage({
161
+ from: "alice",
162
+ to: "bob",
163
+ payload: "hello",
164
+ });
165
+
166
+ expect(router.markAcknowledged(msg.id, "bob")).toBe(true);
167
+ const updated = storage.getMessage(msg.id)!;
168
+ expect(updated.recipients[0].ack_at).toBeTruthy();
169
+ });
170
+
171
+ it("should return false for nonexistent message", () => {
172
+ expect(router.markRead("nonexistent", "bob")).toBe(false);
173
+ });
174
+
175
+ it("should return false for non-recipient", async () => {
176
+ const msg = await router.routeMessage({
177
+ from: "alice",
178
+ to: "bob",
179
+ payload: "hello",
180
+ });
181
+ expect(router.markRead(msg.id, "charlie")).toBe(false);
182
+ });
183
+ });
184
+ });