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,269 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import { EventEmitter } from "node:events";
3
+ import { ConnectionManager } from "../../src/federation/connection-manager.js";
4
+ import type { IncomingMessageHandler } from "../../src/federation/connection-manager.js";
5
+ import { MeshConnector } from "../../src/mesh/mesh-connector.js";
6
+ import type { Message } from "../../src/types.js";
7
+ import type {
8
+ MapConnection,
9
+ MapAgentConnectionClass,
10
+ IncomingMapMessage,
11
+ } from "../../src/map/map-client.js";
12
+ import { createLinkedMeshPair, MockMeshContext } from "./mock-mesh.js";
13
+
14
+ function makeMessage(
15
+ recipientAddress: string,
16
+ scope = "default"
17
+ ): Message {
18
+ return {
19
+ id: "msg-1",
20
+ scope,
21
+ sender_id: "local-agent",
22
+ recipients: [{ agent_id: recipientAddress, kind: "to" }],
23
+ content: { type: "text", text: "hello" },
24
+ importance: "normal",
25
+ metadata: {},
26
+ created_at: new Date().toISOString(),
27
+ };
28
+ }
29
+
30
+ function createMockSdk() {
31
+ const mockConnections: Array<MapConnection & { url: string }> = [];
32
+ const mockSdkClass: MapAgentConnectionClass = {
33
+ async connect(server: string, _opts: unknown) {
34
+ const handlers: ((msg: IncomingMapMessage) => void)[] = [];
35
+ const conn: MapConnection & { url: string } = {
36
+ url: server,
37
+ send: vi.fn().mockResolvedValue(undefined),
38
+ onMessage: (handler) => handlers.push(handler),
39
+ disconnect: vi.fn().mockResolvedValue(undefined),
40
+ };
41
+ mockConnections.push(conn);
42
+ return conn;
43
+ },
44
+ };
45
+ return { mockSdkClass, mockConnections };
46
+ }
47
+
48
+ describe("Federation with mesh transport", () => {
49
+ let events: EventEmitter;
50
+ let meshA: MockMeshContext;
51
+ let meshB: MockMeshContext;
52
+
53
+ beforeEach(() => {
54
+ events = new EventEmitter();
55
+ [meshA, meshB] = createLinkedMeshPair("system-a", "system-b");
56
+ });
57
+
58
+ describe("federate() with meshPeerId", () => {
59
+ it("should connect via mesh transport", async () => {
60
+ const meshConnector = new MeshConnector(meshA, "system-a");
61
+ const cm = new ConnectionManager(
62
+ events,
63
+ {
64
+ systemId: "system-a",
65
+ trust: { allowedServers: [], scopePermissions: {}, requireAuth: false },
66
+ },
67
+ { meshConnector }
68
+ );
69
+
70
+ const link = await cm.federate({
71
+ systemId: "system-b",
72
+ meshPeerId: "system-b",
73
+ });
74
+
75
+ expect(link.peerId).toBe("system-b");
76
+ expect(link.status).toBe("connected");
77
+ expect(link.transport).toBe("mesh");
78
+ expect(link.url).toBe(""); // No URL for mesh peers
79
+ expect(cm.hasTransport("system-b")).toBe(true);
80
+
81
+ await cm.destroy();
82
+ });
83
+
84
+ it("should emit federation.connected event", async () => {
85
+ const meshConnector = new MeshConnector(meshA, "system-a");
86
+ const cm = new ConnectionManager(
87
+ events,
88
+ {
89
+ systemId: "system-a",
90
+ trust: { allowedServers: [], scopePermissions: {}, requireAuth: false },
91
+ },
92
+ { meshConnector }
93
+ );
94
+
95
+ const spy = vi.fn();
96
+ events.on("federation.connected", spy);
97
+
98
+ await cm.federate({
99
+ systemId: "system-b",
100
+ meshPeerId: "system-b",
101
+ });
102
+
103
+ expect(spy).toHaveBeenCalledWith("system-b");
104
+ await cm.destroy();
105
+ });
106
+ });
107
+
108
+ describe("federate() with url uses SDK transport", () => {
109
+ it("should prefer url when both are present", async () => {
110
+ const { mockSdkClass, mockConnections } = createMockSdk();
111
+ const meshConnector = new MeshConnector(meshA, "system-a");
112
+ const cm = new ConnectionManager(
113
+ events,
114
+ {
115
+ systemId: "system-a",
116
+ trust: { allowedServers: [], scopePermissions: {}, requireAuth: false },
117
+ },
118
+ { sdkClass: mockSdkClass, meshConnector }
119
+ );
120
+
121
+ // When meshPeerId is set but url is not, use mesh
122
+ const meshLink = await cm.federate({
123
+ systemId: "mesh-peer",
124
+ meshPeerId: "mesh-peer",
125
+ });
126
+ expect(meshLink.transport).toBe("mesh");
127
+
128
+ // When url is set (without meshPeerId), use websocket
129
+ const wsLink = await cm.federate({
130
+ systemId: "ws-peer",
131
+ url: "ws://ws-peer:3001",
132
+ });
133
+ expect(wsLink.transport).toBe("websocket");
134
+ expect(mockConnections).toHaveLength(1);
135
+
136
+ await cm.destroy();
137
+ });
138
+ });
139
+
140
+ describe("federate() validation", () => {
141
+ it("should throw when neither url nor meshPeerId provided", async () => {
142
+ const cm = new ConnectionManager(events, {
143
+ systemId: "system-a",
144
+ trust: { allowedServers: [], scopePermissions: {}, requireAuth: false },
145
+ });
146
+
147
+ await expect(
148
+ cm.federate({ systemId: "peer-x" })
149
+ ).rejects.toThrow("must have either url or meshPeerId");
150
+
151
+ await cm.destroy();
152
+ });
153
+ });
154
+
155
+ describe("route() delivers over mesh", () => {
156
+ it("should send messages to the remote peer via mesh", async () => {
157
+ // Set up two ConnectionManagers connected via mesh
158
+ const meshConnectorA = new MeshConnector(meshA, "system-a");
159
+ const meshConnectorB = new MeshConnector(meshB, "system-b");
160
+
161
+ const eventsA = new EventEmitter();
162
+ const eventsB = new EventEmitter();
163
+
164
+ const incomingOnB: Array<{ from: string; payload: unknown }> = [];
165
+ const onIncomingB: IncomingMessageHandler = (incoming) => {
166
+ incomingOnB.push({ from: incoming.from, payload: incoming.payload });
167
+ };
168
+
169
+ const cmA = new ConnectionManager(
170
+ eventsA,
171
+ {
172
+ systemId: "system-a",
173
+ trust: { allowedServers: [], scopePermissions: {}, requireAuth: false },
174
+ },
175
+ { meshConnector: meshConnectorA }
176
+ );
177
+ const cmB = new ConnectionManager(
178
+ eventsB,
179
+ {
180
+ systemId: "system-b",
181
+ trust: { allowedServers: [], scopePermissions: {}, requireAuth: false },
182
+ },
183
+ { meshConnector: meshConnectorB, onIncomingMessage: onIncomingB }
184
+ );
185
+
186
+ // Federate: A → B
187
+ await cmA.federate({ systemId: "system-b", meshPeerId: "system-b" });
188
+ // Federate: B → A (so B listens on the channel)
189
+ await cmB.federate({ systemId: "system-a", meshPeerId: "system-a" });
190
+
191
+ // Route a message from A to agent "bob@system-b"
192
+ const msg = makeMessage("bob@system-b");
193
+ const result = await cmA.route(msg);
194
+
195
+ // Wait for async delivery
196
+ await new Promise((r) => setTimeout(r, 20));
197
+
198
+ expect(result.delivered).toBe(true);
199
+ expect(result.peerId).toBe("system-b");
200
+
201
+ // B should have received the message
202
+ expect(incomingOnB).toHaveLength(1);
203
+ expect(incomingOnB[0].from).toBe("system-a");
204
+
205
+ await cmA.destroy();
206
+ await cmB.destroy();
207
+ });
208
+ });
209
+
210
+ describe("mixed federation: websocket + mesh peers", () => {
211
+ it("should support both transports simultaneously", async () => {
212
+ const { mockSdkClass, mockConnections } = createMockSdk();
213
+ const meshConnector = new MeshConnector(meshA, "system-a");
214
+
215
+ const cm = new ConnectionManager(
216
+ events,
217
+ {
218
+ systemId: "system-a",
219
+ trust: { allowedServers: [], scopePermissions: {}, requireAuth: false },
220
+ },
221
+ { sdkClass: mockSdkClass, meshConnector }
222
+ );
223
+
224
+ // Connect to a websocket peer
225
+ await cm.federate({ systemId: "ws-peer", url: "ws://ws:3001" });
226
+ // Connect to a mesh peer
227
+ await cm.federate({ systemId: "mesh-peer", meshPeerId: "system-b" });
228
+
229
+ expect(cm.isConnected("ws-peer")).toBe(true);
230
+ expect(cm.isConnected("mesh-peer")).toBe(true);
231
+ expect(cm.hasTransport("ws-peer")).toBe(true);
232
+ expect(cm.hasTransport("mesh-peer")).toBe(true);
233
+
234
+ const peers = cm.getPeers();
235
+ expect(peers).toHaveLength(2);
236
+
237
+ const wsPeer = peers.find((p) => p.peerId === "ws-peer");
238
+ const meshPeer = peers.find((p) => p.peerId === "mesh-peer");
239
+ expect(wsPeer?.transport).toBe("websocket");
240
+ expect(meshPeer?.transport).toBe("mesh");
241
+
242
+ await cm.destroy();
243
+ });
244
+ });
245
+
246
+ describe("offline mesh peer triggers queue", () => {
247
+ it("should queue messages when mesh peer is not connected", async () => {
248
+ const cm = new ConnectionManager(
249
+ events,
250
+ {
251
+ systemId: "system-a",
252
+ trust: { allowedServers: [], scopePermissions: {}, requireAuth: false },
253
+ }
254
+ );
255
+
256
+ // Register a routing entry but don't federate (no real connection)
257
+ cm.routing.updateFromExposure("system-b", ["bob"]);
258
+
259
+ const msg = makeMessage("bob@system-b");
260
+ const result = await cm.route(msg);
261
+
262
+ expect(result.delivered).toBe(false);
263
+ expect(result.queued).toBe(true);
264
+ expect(result.peerId).toBe("system-b");
265
+
266
+ await cm.destroy();
267
+ });
268
+ });
269
+ });
@@ -0,0 +1,66 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { MeshConnector } from "../../src/mesh/mesh-connector.js";
3
+ import { MeshTransport } from "../../src/mesh/mesh-transport.js";
4
+ import { createLinkedMeshPair, MockMeshContext } from "./mock-mesh.js";
5
+
6
+ describe("MeshConnector", () => {
7
+ let meshA: MockMeshContext;
8
+ let meshB: MockMeshContext;
9
+
10
+ beforeEach(() => {
11
+ [meshA, meshB] = createLinkedMeshPair("peer-a", "peer-b");
12
+ });
13
+
14
+ it("should create a MeshTransport implementing MapConnection", async () => {
15
+ const connector = new MeshConnector(meshA, "peer-a");
16
+ const conn = await connector.connect("peer-b");
17
+
18
+ // Should satisfy MapConnection interface
19
+ expect(typeof conn.send).toBe("function");
20
+ expect(typeof conn.onMessage).toBe("function");
21
+ expect(typeof conn.disconnect).toBe("function");
22
+
23
+ await conn.disconnect();
24
+ });
25
+
26
+ it("should target the correct remote peer", async () => {
27
+ const connector = new MeshConnector(meshA, "peer-a");
28
+ const conn = await connector.connect("peer-b");
29
+
30
+ // The transport should be a MeshTransport with systemName = remotePeerId
31
+ expect(conn.systemName).toBe("peer-b");
32
+
33
+ await conn.disconnect();
34
+ });
35
+
36
+ it("should use custom channel name", async () => {
37
+ const connector = new MeshConnector(meshA, "peer-a", "custom-channel");
38
+ const conn = await connector.connect("peer-b");
39
+
40
+ // The channel should exist on the mesh with the custom name
41
+ expect(meshA._getChannel("custom-channel")).toBeDefined();
42
+ expect(meshA._getChannel("proto:agent-inbox")).toBeUndefined();
43
+
44
+ await conn.disconnect();
45
+ });
46
+
47
+ it("should produce working connections that can exchange messages", async () => {
48
+ const connectorA = new MeshConnector(meshA, "peer-a");
49
+ const connectorB = new MeshConnector(meshB, "peer-b");
50
+
51
+ const connA = await connectorA.connect("peer-b");
52
+ const connB = await connectorB.connect("peer-a");
53
+
54
+ const received: unknown[] = [];
55
+ connB.onMessage((msg) => received.push(msg.payload));
56
+
57
+ await connA.send({ agentId: "bob" }, "hello from connector");
58
+ await new Promise((r) => setTimeout(r, 10));
59
+
60
+ expect(received).toHaveLength(1);
61
+ expect(received[0]).toBe("hello from connector");
62
+
63
+ await connA.disconnect();
64
+ await connB.disconnect();
65
+ });
66
+ });
@@ -0,0 +1,191 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import { MeshTransport } from "../../src/mesh/mesh-transport.js";
3
+ import type { IncomingMapMessage } from "../../src/map/map-client.js";
4
+ import { createLinkedMeshPair, MockMeshContext } from "./mock-mesh.js";
5
+
6
+ describe("MeshTransport", () => {
7
+ let meshA: MockMeshContext;
8
+ let meshB: MockMeshContext;
9
+
10
+ beforeEach(() => {
11
+ [meshA, meshB] = createLinkedMeshPair("peer-a", "peer-b");
12
+ });
13
+
14
+ describe("send and receive", () => {
15
+ it("should deliver a message from A to B", async () => {
16
+ const transportA = new MeshTransport(meshA, "peer-a", "peer-b");
17
+ const transportB = new MeshTransport(meshB, "peer-b", "peer-a");
18
+ await transportA.open();
19
+ await transportB.open();
20
+
21
+ const received: IncomingMapMessage[] = [];
22
+ transportB.onMessage((msg) => received.push(msg));
23
+
24
+ await transportA.send(
25
+ { agentId: "agent-bob" },
26
+ { type: "text", text: "hello" },
27
+ { importance: "normal" }
28
+ );
29
+
30
+ // Wait for async delivery
31
+ await new Promise((r) => setTimeout(r, 10));
32
+
33
+ expect(received).toHaveLength(1);
34
+ expect(received[0].from).toBe("peer-a");
35
+ expect(received[0].to).toEqual({ agentId: "agent-bob" });
36
+ expect(received[0].payload).toEqual({ type: "text", text: "hello" });
37
+ expect(received[0].meta).toEqual({ importance: "normal" });
38
+
39
+ await transportA.disconnect();
40
+ await transportB.disconnect();
41
+ });
42
+
43
+ it("should support bidirectional messaging", async () => {
44
+ const transportA = new MeshTransport(meshA, "peer-a", "peer-b");
45
+ const transportB = new MeshTransport(meshB, "peer-b", "peer-a");
46
+ await transportA.open();
47
+ await transportB.open();
48
+
49
+ const receivedByA: IncomingMapMessage[] = [];
50
+ const receivedByB: IncomingMapMessage[] = [];
51
+ transportA.onMessage((msg) => receivedByA.push(msg));
52
+ transportB.onMessage((msg) => receivedByB.push(msg));
53
+
54
+ await transportA.send({ agentId: "bob" }, "ping");
55
+ await transportB.send({ agentId: "alice" }, "pong");
56
+ await new Promise((r) => setTimeout(r, 10));
57
+
58
+ expect(receivedByB).toHaveLength(1);
59
+ expect(receivedByB[0].payload).toBe("ping");
60
+ expect(receivedByA).toHaveLength(1);
61
+ expect(receivedByA[0].payload).toBe("pong");
62
+
63
+ await transportA.disconnect();
64
+ await transportB.disconnect();
65
+ });
66
+ });
67
+
68
+ describe("peer filtering", () => {
69
+ it("should only receive messages from the configured remote peer", async () => {
70
+ // Three peers: A, B, C. A listens only for B.
71
+ const meshC = new MockMeshContext("peer-c");
72
+ meshA.linkPeer(meshC);
73
+
74
+ const transportA = new MeshTransport(meshA, "peer-a", "peer-b");
75
+ const transportB = new MeshTransport(meshB, "peer-b", "peer-a");
76
+ const transportC = new MeshTransport(meshC, "peer-c", "peer-a");
77
+ await transportA.open();
78
+ await transportB.open();
79
+ await transportC.open();
80
+
81
+ const receivedByA: IncomingMapMessage[] = [];
82
+ transportA.onMessage((msg) => receivedByA.push(msg));
83
+
84
+ // B sends to A — should arrive
85
+ await transportB.send({ agentId: "alice" }, "from-b");
86
+ // C sends to A — should NOT arrive (filtered by remotePeerId)
87
+ await transportC.send({ agentId: "alice" }, "from-c");
88
+ await new Promise((r) => setTimeout(r, 10));
89
+
90
+ expect(receivedByA).toHaveLength(1);
91
+ expect(receivedByA[0].payload).toBe("from-b");
92
+
93
+ await transportA.disconnect();
94
+ await transportB.disconnect();
95
+ await transportC.disconnect();
96
+ });
97
+ });
98
+
99
+ describe("multiple onMessage handlers", () => {
100
+ it("should fire all registered handlers", async () => {
101
+ const transportA = new MeshTransport(meshA, "peer-a", "peer-b");
102
+ const transportB = new MeshTransport(meshB, "peer-b", "peer-a");
103
+ await transportA.open();
104
+ await transportB.open();
105
+
106
+ const handler1 = vi.fn();
107
+ const handler2 = vi.fn();
108
+ transportB.onMessage(handler1);
109
+ transportB.onMessage(handler2);
110
+
111
+ await transportA.send({ agentId: "bob" }, "hello");
112
+ await new Promise((r) => setTimeout(r, 10));
113
+
114
+ expect(handler1).toHaveBeenCalledTimes(1);
115
+ expect(handler2).toHaveBeenCalledTimes(1);
116
+
117
+ await transportA.disconnect();
118
+ await transportB.disconnect();
119
+ });
120
+ });
121
+
122
+ describe("disconnect", () => {
123
+ it("should reject sends after disconnect", async () => {
124
+ const transport = new MeshTransport(meshA, "peer-a", "peer-b");
125
+ await transport.open();
126
+ await transport.disconnect();
127
+
128
+ await expect(
129
+ transport.send({ agentId: "bob" }, "hello")
130
+ ).rejects.toThrow("MeshTransport is closed");
131
+ });
132
+
133
+ it("should stop delivering messages after disconnect", async () => {
134
+ const transportA = new MeshTransport(meshA, "peer-a", "peer-b");
135
+ const transportB = new MeshTransport(meshB, "peer-b", "peer-a");
136
+ await transportA.open();
137
+ await transportB.open();
138
+
139
+ const handler = vi.fn();
140
+ transportB.onMessage(handler);
141
+ await transportB.disconnect();
142
+
143
+ await transportA.send({ agentId: "bob" }, "hello");
144
+ await new Promise((r) => setTimeout(r, 10));
145
+
146
+ expect(handler).not.toHaveBeenCalled();
147
+ });
148
+ });
149
+
150
+ describe("systemName", () => {
151
+ it("should return the remote peer ID", () => {
152
+ const transport = new MeshTransport(meshA, "peer-a", "peer-b");
153
+ expect(transport.systemName).toBe("peer-b");
154
+ });
155
+ });
156
+
157
+ describe("offline peer", () => {
158
+ it("should throw when sending to an unlinked peer", async () => {
159
+ const isolated = new MockMeshContext("isolated");
160
+ const transport = new MeshTransport(isolated, "isolated", "nobody");
161
+ await transport.open();
162
+
163
+ await expect(
164
+ transport.send({ agentId: "bob" }, "hello")
165
+ ).rejects.toThrow('Failed to send to peer "nobody"');
166
+
167
+ await transport.disconnect();
168
+ });
169
+ });
170
+
171
+ describe("channel sharing", () => {
172
+ it("should reuse the same channel for multiple transports to different peers", async () => {
173
+ const meshC = new MockMeshContext("peer-c");
174
+ meshA.linkPeer(meshC);
175
+
176
+ const t1 = new MeshTransport(meshA, "peer-a", "peer-b");
177
+ const t2 = new MeshTransport(meshA, "peer-a", "peer-c");
178
+ await t1.open();
179
+ await t2.open();
180
+
181
+ // Both transports should use the same underlying channel
182
+ const ch1 = meshA._getChannel("proto:agent-inbox");
183
+ expect(ch1).toBeDefined();
184
+ // Only one channel should exist on meshA
185
+ expect(meshA._getChannel("proto:agent-inbox")).toBe(ch1);
186
+
187
+ await t1.disconnect();
188
+ await t2.disconnect();
189
+ });
190
+ });
191
+ });