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,89 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { TrustManager } from "../../src/federation/trust.js";
3
+
4
+ describe("TrustManager", () => {
5
+ describe("canConnect", () => {
6
+ it("should allow all when allowedServers is empty (open federation)", () => {
7
+ const trust = new TrustManager({ allowedServers: [] });
8
+ expect(trust.canConnect("any-system")).toBe(true);
9
+ });
10
+
11
+ it("should allow servers in the allow-list", () => {
12
+ const trust = new TrustManager({
13
+ allowedServers: ["backend-team", "ml-team"],
14
+ });
15
+ expect(trust.canConnect("backend-team")).toBe(true);
16
+ expect(trust.canConnect("ml-team")).toBe(true);
17
+ });
18
+
19
+ it("should reject servers not in the allow-list", () => {
20
+ const trust = new TrustManager({
21
+ allowedServers: ["backend-team"],
22
+ });
23
+ expect(trust.canConnect("unknown-system")).toBe(false);
24
+ });
25
+ });
26
+
27
+ describe("canRoute", () => {
28
+ it("should allow all when no scope permissions defined", () => {
29
+ const trust = new TrustManager({});
30
+ expect(trust.canRoute("any-system", "any-scope")).toBe(true);
31
+ });
32
+
33
+ it("should allow permitted scope/system combinations", () => {
34
+ const trust = new TrustManager({
35
+ scopePermissions: { production: ["backend-team"] },
36
+ });
37
+ expect(trust.canRoute("backend-team", "production")).toBe(true);
38
+ });
39
+
40
+ it("should deny unpermitted scope/system combinations", () => {
41
+ const trust = new TrustManager({
42
+ scopePermissions: { production: ["backend-team"] },
43
+ });
44
+ expect(trust.canRoute("unknown-team", "production")).toBe(false);
45
+ });
46
+
47
+ it("should allow when scope has no restrictions", () => {
48
+ const trust = new TrustManager({
49
+ scopePermissions: { production: ["backend-team"] },
50
+ });
51
+ expect(trust.canRoute("any-system", "development")).toBe(true);
52
+ });
53
+ });
54
+
55
+ describe("validateAuth", () => {
56
+ it("should pass when requireAuth is false", () => {
57
+ const trust = new TrustManager({ requireAuth: false });
58
+ expect(trust.validateAuth("any-system")).toBe(true);
59
+ });
60
+
61
+ it("should pass when no token configured for system", () => {
62
+ const trust = new TrustManager({ requireAuth: true });
63
+ expect(trust.validateAuth("any-system")).toBe(true);
64
+ });
65
+
66
+ it("should validate token when configured", () => {
67
+ const trust = new TrustManager({
68
+ requireAuth: true,
69
+ authTokens: { "backend-team": "secret-123" },
70
+ });
71
+ expect(
72
+ trust.validateAuth("backend-team", { token: "secret-123" })
73
+ ).toBe(true);
74
+ expect(
75
+ trust.validateAuth("backend-team", { token: "wrong-token" })
76
+ ).toBe(false);
77
+ });
78
+ });
79
+
80
+ describe("updatePolicy", () => {
81
+ it("should update policy at runtime", () => {
82
+ const trust = new TrustManager({ allowedServers: [] });
83
+ expect(trust.canConnect("new-peer")).toBe(true);
84
+ trust.updatePolicy({ allowedServers: ["approved-only"] });
85
+ expect(trust.canConnect("new-peer")).toBe(false);
86
+ expect(trust.canConnect("approved-only")).toBe(true);
87
+ });
88
+ });
89
+ });
@@ -0,0 +1,113 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import * as net from "node:net";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { EventEmitter } from "node:events";
6
+ import { InMemoryStorage } from "../src/storage/memory.js";
7
+ import { MessageRouter } from "../src/router/message-router.js";
8
+ import { IpcServer } from "../src/ipc/ipc-server.js";
9
+ import { MailJsonRpcServer } from "../src/jsonrpc/mail-server.js";
10
+
11
+ function tmpSocketPath(): string {
12
+ return path.join(os.tmpdir(), `inbox-rpc-test-${Date.now()}-${Math.random().toString(36).slice(2)}.sock`);
13
+ }
14
+
15
+ function sendRaw(socketPath: string, payload: object): Promise<object> {
16
+ return new Promise((resolve, reject) => {
17
+ const client = net.createConnection(socketPath, () => {
18
+ client.write(JSON.stringify(payload) + "\n");
19
+ });
20
+ let buffer = "";
21
+ client.on("data", (data) => {
22
+ buffer += data.toString();
23
+ const idx = buffer.indexOf("\n");
24
+ if (idx !== -1) {
25
+ client.end();
26
+ resolve(JSON.parse(buffer.slice(0, idx)));
27
+ }
28
+ });
29
+ client.on("error", reject);
30
+ });
31
+ }
32
+
33
+ describe("IPC Server with JSON-RPC", () => {
34
+ let storage: InMemoryStorage;
35
+ let events: EventEmitter;
36
+ let router: MessageRouter;
37
+ let jsonRpc: MailJsonRpcServer;
38
+ let server: IpcServer;
39
+ let socketPath: string;
40
+
41
+ beforeEach(async () => {
42
+ storage = new InMemoryStorage();
43
+ events = new EventEmitter();
44
+ router = new MessageRouter(storage, events, "default");
45
+ jsonRpc = new MailJsonRpcServer(storage, router, events);
46
+ socketPath = tmpSocketPath();
47
+ server = new IpcServer(socketPath, router, storage, jsonRpc);
48
+ await server.start();
49
+ });
50
+
51
+ afterEach(async () => {
52
+ await server.stop();
53
+ });
54
+
55
+ it("should handle IPC commands (action-based)", async () => {
56
+ const resp = await sendRaw(socketPath, { action: "ping" });
57
+ expect(resp).toMatchObject({ ok: true });
58
+ });
59
+
60
+ it("should handle JSON-RPC requests (jsonrpc-based)", async () => {
61
+ const resp = await sendRaw(socketPath, {
62
+ jsonrpc: "2.0",
63
+ id: 1,
64
+ method: "mail/create",
65
+ params: { subject: "Test" },
66
+ }) as { jsonrpc: string; id: number; result: { id: string; subject: string } };
67
+
68
+ expect(resp.jsonrpc).toBe("2.0");
69
+ expect(resp.id).toBe(1);
70
+ expect(resp.result.subject).toBe("Test");
71
+ expect(resp.result.id).toMatch(/^conv-/);
72
+ });
73
+
74
+ it("should create conversation via JSON-RPC then query via JSON-RPC", async () => {
75
+ // Create
76
+ const createResp = await sendRaw(socketPath, {
77
+ jsonrpc: "2.0",
78
+ id: 1,
79
+ method: "mail/create",
80
+ params: { subject: "Sprint Review" },
81
+ }) as { result: { id: string } };
82
+
83
+ // List
84
+ const listResp = await sendRaw(socketPath, {
85
+ jsonrpc: "2.0",
86
+ id: 2,
87
+ method: "mail/list",
88
+ }) as { result: { conversations: { id: string; subject: string }[] } };
89
+
90
+ expect(listResp.result.conversations).toHaveLength(1);
91
+ expect(listResp.result.conversations[0].subject).toBe("Sprint Review");
92
+ });
93
+
94
+ it("should handle both protocols in sequence", async () => {
95
+ // IPC: send a message
96
+ const ipcResp = await sendRaw(socketPath, {
97
+ action: "send",
98
+ from: "alice",
99
+ to: "bob",
100
+ payload: "hello",
101
+ }) as { ok: boolean; messageId: string };
102
+ expect(ipcResp.ok).toBe(true);
103
+
104
+ // JSON-RPC: create a conversation
105
+ const rpcResp = await sendRaw(socketPath, {
106
+ jsonrpc: "2.0",
107
+ id: 1,
108
+ method: "mail/create",
109
+ params: { subject: "Test" },
110
+ }) as { result: { id: string } };
111
+ expect(rpcResp.result.id).toBeTruthy();
112
+ });
113
+ });
@@ -0,0 +1,197 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import * as net from "node:net";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import * as fs from "node:fs";
6
+ import { EventEmitter } from "node:events";
7
+ import { InMemoryStorage } from "../src/storage/memory.js";
8
+ import { MessageRouter } from "../src/router/message-router.js";
9
+ import { IpcServer } from "../src/ipc/ipc-server.js";
10
+
11
+ function tmpSocketPath(): string {
12
+ return path.join(os.tmpdir(), `inbox-test-${Date.now()}-${Math.random().toString(36).slice(2)}.sock`);
13
+ }
14
+
15
+ function sendCommand(socketPath: string, command: object): Promise<object> {
16
+ return new Promise((resolve, reject) => {
17
+ const client = net.createConnection(socketPath, () => {
18
+ client.write(JSON.stringify(command) + "\n");
19
+ });
20
+ let buffer = "";
21
+ client.on("data", (data) => {
22
+ buffer += data.toString();
23
+ const idx = buffer.indexOf("\n");
24
+ if (idx !== -1) {
25
+ const line = buffer.slice(0, idx);
26
+ client.end();
27
+ resolve(JSON.parse(line));
28
+ }
29
+ });
30
+ client.on("error", reject);
31
+ });
32
+ }
33
+
34
+ describe("IpcServer", () => {
35
+ let storage: InMemoryStorage;
36
+ let events: EventEmitter;
37
+ let router: MessageRouter;
38
+ let server: IpcServer;
39
+ let socketPath: string;
40
+
41
+ beforeEach(async () => {
42
+ storage = new InMemoryStorage();
43
+ events = new EventEmitter();
44
+ router = new MessageRouter(storage, events, "default");
45
+ socketPath = tmpSocketPath();
46
+ server = new IpcServer(socketPath, router, storage);
47
+ await server.start();
48
+ });
49
+
50
+ afterEach(async () => {
51
+ await server.stop();
52
+ });
53
+
54
+ it("should respond to ping", async () => {
55
+ const resp = await sendCommand(socketPath, { action: "ping" });
56
+ expect(resp).toMatchObject({ ok: true, pid: process.pid });
57
+ });
58
+
59
+ it("should handle send command", async () => {
60
+ const resp = await sendCommand(socketPath, {
61
+ action: "send",
62
+ from: "alice",
63
+ to: "bob",
64
+ payload: "hello bob",
65
+ }) as { ok: boolean; messageId: string };
66
+
67
+ expect(resp.ok).toBe(true);
68
+ expect(resp.messageId).toBeTruthy();
69
+
70
+ // Message should be stored
71
+ const msg = storage.getMessage(resp.messageId);
72
+ expect(msg).toBeDefined();
73
+ expect(msg!.sender_id).toBe("alice");
74
+ expect(msg!.content).toEqual({ type: "text", text: "hello bob" });
75
+ });
76
+
77
+ it("should handle notify spawn", async () => {
78
+ const resp = await sendCommand(socketPath, {
79
+ action: "notify",
80
+ event: {
81
+ type: "agent.spawn",
82
+ agent: {
83
+ agentId: "executor-1",
84
+ name: "Executor",
85
+ scopes: ["swarm:team"],
86
+ metadata: { template: "gsd" },
87
+ },
88
+ },
89
+ });
90
+
91
+ expect(resp).toMatchObject({ ok: true });
92
+ const agent = storage.getAgent("executor-1");
93
+ expect(agent).toBeDefined();
94
+ expect(agent!.display_name).toBe("Executor");
95
+ expect(agent!.scope).toBe("swarm:team");
96
+ expect(agent!.status).toBe("active");
97
+ });
98
+
99
+ it("should handle notify done", async () => {
100
+ // First spawn
101
+ await sendCommand(socketPath, {
102
+ action: "notify",
103
+ event: {
104
+ type: "agent.spawn",
105
+ agent: { agentId: "executor-1", name: "Executor", scopes: ["default"] },
106
+ },
107
+ });
108
+
109
+ // Then done
110
+ const resp = await sendCommand(socketPath, {
111
+ action: "notify",
112
+ event: { type: "agent.done", agentId: "executor-1" },
113
+ });
114
+
115
+ expect(resp).toMatchObject({ ok: true });
116
+ const agent = storage.getAgent("executor-1");
117
+ expect(agent!.status).toBe("offline");
118
+ });
119
+
120
+ it("should handle check_inbox command", async () => {
121
+ // Send a message first
122
+ await sendCommand(socketPath, {
123
+ action: "send",
124
+ from: "alice",
125
+ to: "bob",
126
+ payload: "hello bob",
127
+ });
128
+
129
+ // Check inbox
130
+ const resp = await sendCommand(socketPath, {
131
+ action: "check_inbox",
132
+ agentId: "bob",
133
+ }) as { ok: boolean; messages: unknown[] };
134
+
135
+ expect(resp.ok).toBe(true);
136
+ expect(resp.messages).toHaveLength(1);
137
+ expect((resp.messages[0] as { sender_id: string }).sender_id).toBe("alice");
138
+ });
139
+
140
+ it("should handle check_inbox with clear", async () => {
141
+ await sendCommand(socketPath, {
142
+ action: "send",
143
+ from: "alice",
144
+ to: "bob",
145
+ payload: "msg 1",
146
+ });
147
+
148
+ // Check and clear
149
+ const resp1 = await sendCommand(socketPath, {
150
+ action: "check_inbox",
151
+ agentId: "bob",
152
+ clear: true,
153
+ }) as { ok: boolean; messages: unknown[] };
154
+
155
+ expect(resp1.ok).toBe(true);
156
+ expect(resp1.messages).toHaveLength(1);
157
+
158
+ // Check again with unreadOnly — should be empty since we cleared
159
+ const resp2 = await sendCommand(socketPath, {
160
+ action: "check_inbox",
161
+ agentId: "bob",
162
+ unreadOnly: true,
163
+ }) as { ok: boolean; messages: unknown[] };
164
+
165
+ expect(resp2.ok).toBe(true);
166
+ expect(resp2.messages).toHaveLength(0);
167
+ });
168
+
169
+ it("should return empty array for empty inbox", async () => {
170
+ const resp = await sendCommand(socketPath, {
171
+ action: "check_inbox",
172
+ agentId: "nobody",
173
+ }) as { ok: boolean; messages: unknown[] };
174
+
175
+ expect(resp.ok).toBe(true);
176
+ expect(resp.messages).toHaveLength(0);
177
+ });
178
+
179
+ it("should handle unknown actions gracefully", async () => {
180
+ const resp = await sendCommand(socketPath, { action: "unknown" }) as { ok: boolean; error: string };
181
+ expect(resp.ok).toBe(false);
182
+ expect(resp.error).toContain("Unknown action");
183
+ });
184
+
185
+ it("should handle concurrent clients", async () => {
186
+ const results = await Promise.all([
187
+ sendCommand(socketPath, { action: "send", from: "a1", to: "b1", payload: "m1" }),
188
+ sendCommand(socketPath, { action: "send", from: "a2", to: "b2", payload: "m2" }),
189
+ sendCommand(socketPath, { action: "ping" }),
190
+ ]);
191
+
192
+ expect(results).toHaveLength(3);
193
+ for (const r of results) {
194
+ expect((r as { ok: boolean }).ok).toBe(true);
195
+ }
196
+ });
197
+ });
@@ -0,0 +1,285 @@
1
+ import { describe, it, expect, beforeEach, vi } from "vitest";
2
+ import { EventEmitter } from "node:events";
3
+ import { InMemoryStorage } from "../src/storage/memory.js";
4
+ import { MessageRouter } from "../src/router/message-router.js";
5
+ import { MailJsonRpcServer } from "../src/jsonrpc/mail-server.js";
6
+
7
+ describe("MailJsonRpcServer", () => {
8
+ let storage: InMemoryStorage;
9
+ let events: EventEmitter;
10
+ let router: MessageRouter;
11
+ let server: MailJsonRpcServer;
12
+
13
+ beforeEach(() => {
14
+ storage = new InMemoryStorage();
15
+ events = new EventEmitter();
16
+ router = new MessageRouter(storage, events, "default");
17
+ server = new MailJsonRpcServer(storage, router, events);
18
+ });
19
+
20
+ function rpc(method: string, params: Record<string, unknown> = {}) {
21
+ return server.handleRequest({
22
+ jsonrpc: "2.0",
23
+ id: 1,
24
+ method,
25
+ params,
26
+ });
27
+ }
28
+
29
+ describe("mail/create", () => {
30
+ it("should create a conversation", async () => {
31
+ const resp = await rpc("mail/create", { subject: "Test conv", scope: "default" });
32
+ expect(resp.result).toBeDefined();
33
+ const conv = resp.result as { id: string; subject: string };
34
+ expect(conv.subject).toBe("Test conv");
35
+ expect(conv.id).toMatch(/^conv-/);
36
+ });
37
+ });
38
+
39
+ describe("mail/get", () => {
40
+ it("should get a conversation with turns", async () => {
41
+ const createResp = await rpc("mail/create", { subject: "Test" });
42
+ const convId = (createResp.result as { id: string }).id;
43
+
44
+ // Add a turn
45
+ await rpc("mail/turn", {
46
+ conversationId: convId,
47
+ participantId: "alice",
48
+ content: { type: "text", text: "hello" },
49
+ });
50
+
51
+ const getResp = await rpc("mail/get", { id: convId });
52
+ const result = getResp.result as { conversation: { id: string }; turns: unknown[] };
53
+ expect(result.conversation.id).toBe(convId);
54
+ expect(result.turns).toHaveLength(1);
55
+ });
56
+
57
+ it("should error for missing conversation", async () => {
58
+ const resp = await rpc("mail/get", { id: "nonexistent" });
59
+ expect(resp.error).toBeDefined();
60
+ expect(resp.error!.code).toBe(-32001);
61
+ });
62
+ });
63
+
64
+ describe("mail/list", () => {
65
+ it("should list conversations", async () => {
66
+ await rpc("mail/create", { subject: "Conv 1" });
67
+ await rpc("mail/create", { subject: "Conv 2" });
68
+
69
+ const resp = await rpc("mail/list");
70
+ const result = resp.result as { conversations: unknown[] };
71
+ expect(result.conversations).toHaveLength(2);
72
+ });
73
+
74
+ it("should filter by scope", async () => {
75
+ await rpc("mail/create", { subject: "A", scope: "team-a" });
76
+ await rpc("mail/create", { subject: "B", scope: "team-b" });
77
+
78
+ const resp = await rpc("mail/list", { scope: "team-a" });
79
+ const result = resp.result as { conversations: unknown[] };
80
+ expect(result.conversations).toHaveLength(1);
81
+ });
82
+ });
83
+
84
+ describe("mail/close", () => {
85
+ it("should close a conversation", async () => {
86
+ const createResp = await rpc("mail/create", { subject: "Test" });
87
+ const convId = (createResp.result as { id: string }).id;
88
+
89
+ const closeResp = await rpc("mail/close", { id: convId });
90
+ expect((closeResp.result as { ok: boolean }).ok).toBe(true);
91
+
92
+ const conv = storage.getConversation(convId)!;
93
+ expect(conv.status).toBe("completed");
94
+ });
95
+ });
96
+
97
+ describe("mail/join & mail/leave", () => {
98
+ it("should add and remove participants", async () => {
99
+ const createResp = await rpc("mail/create", { subject: "Test" });
100
+ const convId = (createResp.result as { id: string }).id;
101
+
102
+ await rpc("mail/join", { conversationId: convId, agentId: "alice" });
103
+ let conv = storage.getConversation(convId)!;
104
+ expect(conv.participants).toHaveLength(1);
105
+
106
+ await rpc("mail/leave", { conversationId: convId, agentId: "alice" });
107
+ conv = storage.getConversation(convId)!;
108
+ expect(conv.participants).toHaveLength(0);
109
+ });
110
+ });
111
+
112
+ describe("mail/invite", () => {
113
+ it("should invite an agent to a conversation", async () => {
114
+ const createResp = await rpc("mail/create", { subject: "Test" });
115
+ const convId = (createResp.result as { id: string }).id;
116
+
117
+ await rpc("mail/invite", { conversationId: convId, agentId: "bob", role: "reviewer" });
118
+ const conv = storage.getConversation(convId)!;
119
+ expect(conv.participants).toHaveLength(1);
120
+ expect(conv.participants[0].agent_id).toBe("bob");
121
+ expect(conv.participants[0].role).toBe("reviewer");
122
+ });
123
+ });
124
+
125
+ describe("mail/turn", () => {
126
+ it("should add a turn", async () => {
127
+ const createResp = await rpc("mail/create", { subject: "Test" });
128
+ const convId = (createResp.result as { id: string }).id;
129
+
130
+ const turnResp = await rpc("mail/turn", {
131
+ conversationId: convId,
132
+ participantId: "alice",
133
+ content: { type: "text", text: "hello world" },
134
+ });
135
+
136
+ const turn = turnResp.result as { id: string };
137
+ expect(turn.id).toMatch(/^turn-/);
138
+
139
+ const turns = storage.getTurns(convId);
140
+ expect(turns).toHaveLength(1);
141
+ });
142
+ });
143
+
144
+ describe("mail/turns/list", () => {
145
+ it("should list turns", async () => {
146
+ const createResp = await rpc("mail/create", { subject: "Test" });
147
+ const convId = (createResp.result as { id: string }).id;
148
+
149
+ await rpc("mail/turn", { conversationId: convId, participantId: "alice", content: { type: "text", text: "m1" } });
150
+ await rpc("mail/turn", { conversationId: convId, participantId: "bob", content: { type: "text", text: "m2" } });
151
+
152
+ const resp = await rpc("mail/turns/list", { conversationId: convId });
153
+ const result = resp.result as { turns: unknown[] };
154
+ expect(result.turns).toHaveLength(2);
155
+ });
156
+ });
157
+
158
+ describe("mail/thread/create & mail/thread/list", () => {
159
+ it("should create and list threads", async () => {
160
+ const createResp = await rpc("mail/create", { subject: "Test" });
161
+ const convId = (createResp.result as { id: string }).id;
162
+
163
+ await rpc("mail/thread/create", {
164
+ conversationId: convId,
165
+ rootTurnId: "turn-1",
166
+ subject: "Sub-discussion",
167
+ });
168
+
169
+ const resp = await rpc("mail/thread/list", { conversationId: convId });
170
+ const result = resp.result as { threads: { subject: string }[] };
171
+ expect(result.threads).toHaveLength(1);
172
+ expect(result.threads[0].subject).toBe("Sub-discussion");
173
+ });
174
+ });
175
+
176
+ describe("mail/replay", () => {
177
+ it("should replay full conversation history", async () => {
178
+ const createResp = await rpc("mail/create", { subject: "Test" });
179
+ const convId = (createResp.result as { id: string }).id;
180
+
181
+ await rpc("mail/turn", { conversationId: convId, participantId: "alice", content: { type: "text", text: "msg 1" } });
182
+ await rpc("mail/turn", { conversationId: convId, participantId: "bob", content: { type: "text", text: "msg 2" } });
183
+
184
+ const resp = await rpc("mail/replay", { id: convId });
185
+ const result = resp.result as { conversation: { id: string }; turns: unknown[] };
186
+ expect(result.conversation.id).toBe(convId);
187
+ expect(result.turns).toHaveLength(2);
188
+ });
189
+ });
190
+
191
+ describe("event emissions", () => {
192
+ it("should emit mail.created on mail/create", async () => {
193
+ const listener = vi.fn();
194
+ events.on("mail.created", listener);
195
+
196
+ await rpc("mail/create", { subject: "Test" });
197
+
198
+ expect(listener).toHaveBeenCalledOnce();
199
+ expect(listener.mock.calls[0][0]).toMatchObject({ subject: "Test" });
200
+ });
201
+
202
+ it("should emit mail.turn.added on mail/turn", async () => {
203
+ const listener = vi.fn();
204
+ events.on("mail.turn.added", listener);
205
+
206
+ const createResp = await rpc("mail/create", { subject: "Test" });
207
+ const convId = (createResp.result as { id: string }).id;
208
+
209
+ await rpc("mail/turn", {
210
+ conversationId: convId,
211
+ participantId: "alice",
212
+ content: { type: "text", text: "hello" },
213
+ });
214
+
215
+ expect(listener).toHaveBeenCalledOnce();
216
+ expect(listener.mock.calls[0][0]).toMatchObject({
217
+ conversation_id: convId,
218
+ participant_id: "alice",
219
+ });
220
+ });
221
+
222
+ it("should emit mail.participant.joined on mail/join", async () => {
223
+ const listener = vi.fn();
224
+ events.on("mail.participant.joined", listener);
225
+
226
+ const createResp = await rpc("mail/create", { subject: "Test" });
227
+ const convId = (createResp.result as { id: string }).id;
228
+
229
+ await rpc("mail/join", { conversationId: convId, agentId: "bob" });
230
+
231
+ expect(listener).toHaveBeenCalledOnce();
232
+ expect(listener.mock.calls[0][0]).toEqual({
233
+ conversation_id: convId,
234
+ agent_id: "bob",
235
+ });
236
+ });
237
+
238
+ it("should not emit mail.participant.joined for duplicate join", async () => {
239
+ const listener = vi.fn();
240
+ events.on("mail.participant.joined", listener);
241
+
242
+ const createResp = await rpc("mail/create", { subject: "Test" });
243
+ const convId = (createResp.result as { id: string }).id;
244
+
245
+ await rpc("mail/join", { conversationId: convId, agentId: "bob" });
246
+ await rpc("mail/join", { conversationId: convId, agentId: "bob" });
247
+
248
+ expect(listener).toHaveBeenCalledOnce();
249
+ });
250
+
251
+ it("should emit mail.closed on mail/close", async () => {
252
+ const listener = vi.fn();
253
+ events.on("mail.closed", listener);
254
+
255
+ const createResp = await rpc("mail/create", { subject: "Test" });
256
+ const convId = (createResp.result as { id: string }).id;
257
+
258
+ await rpc("mail/close", { id: convId });
259
+
260
+ expect(listener).toHaveBeenCalledOnce();
261
+ expect(listener.mock.calls[0][0]).toEqual({
262
+ conversation_id: convId,
263
+ status: "completed",
264
+ });
265
+ });
266
+ });
267
+
268
+ describe("error handling", () => {
269
+ it("should reject non-2.0 jsonrpc", async () => {
270
+ const resp = await server.handleRequest({
271
+ jsonrpc: "1.0" as "2.0",
272
+ id: 1,
273
+ method: "mail/list",
274
+ });
275
+ expect(resp.error).toBeDefined();
276
+ expect(resp.error!.code).toBe(-32600);
277
+ });
278
+
279
+ it("should reject unknown methods", async () => {
280
+ const resp = await rpc("mail/unknown");
281
+ expect(resp.error).toBeDefined();
282
+ expect(resp.error!.code).toBe(-32601);
283
+ });
284
+ });
285
+ });