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
@@ -2,14 +2,47 @@
2
2
  * sidecar-client.mjs — UNIX socket client for communicating with the MAP sidecar
3
3
  *
4
4
  * Provides send, health check, and recovery logic used by hooks.
5
+ * Supports per-session sidecar instances via sessionId parameter.
5
6
  */
6
7
 
7
8
  import fs from "fs";
8
9
  import path from "path";
9
10
  import net from "net";
10
11
  import { spawn } from "child_process";
11
- import { SOCKET_PATH, PID_PATH, pluginDir } from "./paths.mjs";
12
- import { resolveScope, DEFAULTS } from "./config.mjs";
12
+ import { SOCKET_PATH, PID_PATH, pluginDir, sessionPaths } from "./paths.mjs";
13
+ import { resolveScope, resolveMapServer, DEFAULTS } from "./config.mjs";
14
+ import { meshFireAndForget } from "./mesh-connection.mjs";
15
+
16
+ /**
17
+ * Send a command to the agent-inbox IPC socket and return the response.
18
+ * Returns the parsed response or null on failure. Never throws.
19
+ */
20
+ export function sendToInbox(command, socketPath) {
21
+ return new Promise((resolve) => {
22
+ const client = net.createConnection(socketPath, () => {
23
+ client.write(JSON.stringify(command) + "\n");
24
+ });
25
+ let buffer = "";
26
+ client.on("data", (data) => {
27
+ buffer += data.toString();
28
+ const idx = buffer.indexOf("\n");
29
+ if (idx !== -1) {
30
+ const line = buffer.slice(0, idx);
31
+ client.end();
32
+ try {
33
+ resolve(JSON.parse(line));
34
+ } catch {
35
+ resolve(null);
36
+ }
37
+ }
38
+ });
39
+ client.on("error", () => resolve(null));
40
+ setTimeout(() => {
41
+ client.destroy();
42
+ resolve(null);
43
+ }, 2000);
44
+ });
45
+ }
13
46
 
14
47
  /**
15
48
  * Send a command to the sidecar via UNIX socket.
@@ -33,9 +66,9 @@ export function sendToSidecar(command, socketPath = SOCKET_PATH) {
33
66
  /**
34
67
  * Check if the sidecar process is alive via PID file.
35
68
  */
36
- export function isSidecarAlive() {
69
+ export function isSidecarAlive(pidPath = PID_PATH) {
37
70
  try {
38
- const pid = parseInt(fs.readFileSync(PID_PATH, "utf-8").trim());
71
+ const pid = parseInt(fs.readFileSync(pidPath, "utf-8").trim());
39
72
  process.kill(pid, 0);
40
73
  return true;
41
74
  } catch {
@@ -46,35 +79,47 @@ export function isSidecarAlive() {
46
79
  /**
47
80
  * Start the sidecar as a detached background process.
48
81
  * Writes PID file and waits for socket to appear (up to 2s).
82
+ * When sessionId is provided, the sidecar uses per-session paths.
49
83
  * Returns true if sidecar is ready.
50
84
  */
51
- export async function startSidecar(config, pluginDirOverride) {
85
+ export async function startSidecar(config, pluginDirOverride, sessionId) {
52
86
  const dir = pluginDirOverride || pluginDir();
53
87
  const sidecarPath = path.join(dir, "scripts", "map-sidecar.mjs");
88
+ const sPaths = sessionPaths(sessionId);
54
89
 
55
- const server = config.map?.server || DEFAULTS.mapServer;
90
+ const server = resolveMapServer(config);
56
91
  const scope = resolveScope(config);
57
92
  const systemId = config.map?.systemId || DEFAULTS.mapSystemId;
58
93
 
59
94
  try {
60
- fs.mkdirSync(path.dirname(PID_PATH), { recursive: true });
61
-
62
- const child = spawn(
63
- "node",
64
- [sidecarPath, "--server", server, "--scope", scope, "--system-id", systemId],
65
- {
66
- detached: true,
67
- stdio: ["ignore", "ignore", "ignore"],
95
+ fs.mkdirSync(path.dirname(sPaths.pidPath), { recursive: true });
96
+
97
+ const args = [sidecarPath, "--server", server, "--scope", scope, "--system-id", systemId];
98
+ if (sessionId) {
99
+ args.push("--session-id", sessionId);
100
+ }
101
+ if (config.inbox?.enabled) {
102
+ args.push("--inbox-config", JSON.stringify(config.inbox));
103
+ }
104
+ if (config.mesh?.enabled) {
105
+ args.push("--mesh-enabled");
106
+ if (config.mesh.peerId) {
107
+ args.push("--mesh-peer-id", config.mesh.peerId);
68
108
  }
69
- );
109
+ }
110
+
111
+ const child = spawn("node", args, {
112
+ detached: true,
113
+ stdio: ["ignore", "ignore", "ignore"],
114
+ });
70
115
  child.unref();
71
- fs.writeFileSync(PID_PATH, String(child.pid));
116
+ fs.writeFileSync(sPaths.pidPath, String(child.pid));
72
117
 
73
118
  // Wait for socket to appear (up to 2s)
74
119
  for (let i = 0; i < 8; i++) {
75
120
  await new Promise((r) => setTimeout(r, 250));
76
- if (fs.existsSync(SOCKET_PATH)) {
77
- const ok = await sendToSidecar({ action: "ping" });
121
+ if (fs.existsSync(sPaths.socketPath)) {
122
+ const ok = await sendToSidecar({ action: "ping" }, sPaths.socketPath);
78
123
  if (ok) return true;
79
124
  }
80
125
  }
@@ -87,21 +132,28 @@ export async function startSidecar(config, pluginDirOverride) {
87
132
 
88
133
  /**
89
134
  * Kill an existing sidecar process (for session restart).
135
+ * When sessionId is provided, kills only that session's sidecar.
90
136
  */
91
- export function killSidecar() {
137
+ export function killSidecar(sessionId) {
138
+ const sPaths = sessionPaths(sessionId);
92
139
  try {
93
- const pid = parseInt(fs.readFileSync(PID_PATH, "utf-8").trim());
140
+ const pid = parseInt(fs.readFileSync(sPaths.pidPath, "utf-8").trim());
94
141
  process.kill(pid);
95
142
  } catch {
96
143
  // Process already dead or PID file missing
97
144
  }
98
145
  try {
99
- fs.unlinkSync(PID_PATH);
146
+ fs.unlinkSync(sPaths.pidPath);
147
+ } catch {
148
+ // ignore
149
+ }
150
+ try {
151
+ fs.unlinkSync(sPaths.socketPath);
100
152
  } catch {
101
153
  // ignore
102
154
  }
103
155
  try {
104
- fs.unlinkSync(SOCKET_PATH);
156
+ fs.unlinkSync(sPaths.inboxSocketPath);
105
157
  } catch {
106
158
  // ignore
107
159
  }
@@ -111,21 +163,23 @@ export function killSidecar() {
111
163
  * Ensure the sidecar is running. If not, attempt recovery.
112
164
  * Returns true if sidecar is available after this call.
113
165
  */
114
- export async function ensureSidecar(config) {
166
+ export async function ensureSidecar(config, sessionId) {
167
+ const sPaths = sessionPaths(sessionId);
168
+
115
169
  // 1. Try pinging
116
- const alive = await sendToSidecar({ action: "ping" });
170
+ const alive = await sendToSidecar({ action: "ping" }, sPaths.socketPath);
117
171
  if (alive) return true;
118
172
 
119
173
  // 2. Only attempt recovery in session mode
120
174
  if (config.map?.sidecar === "persistent") return false;
121
175
 
122
176
  // 3. Check PID
123
- if (isSidecarAlive()) {
177
+ if (isSidecarAlive(sPaths.pidPath)) {
124
178
  // Process exists but socket not ready — wait briefly
125
179
  await new Promise((r) => setTimeout(r, 500));
126
- return sendToSidecar({ action: "ping" });
180
+ return sendToSidecar({ action: "ping" }, sPaths.socketPath);
127
181
  }
128
182
 
129
183
  // 4. Restart sidecar
130
- return startSidecar(config);
184
+ return startSidecar(config, undefined, sessionId);
131
185
  }
@@ -3,6 +3,11 @@
3
3
  *
4
4
  * Provides the socket server that hooks communicate with, and the command
5
5
  * dispatch logic for all sidecar operations.
6
+ *
7
+ * Supports two transport modes:
8
+ * - "mesh": Uses inbox registry for agent lifecycle (spawn/done), MeshPeer
9
+ * connection for task bridge events and trajectory.
10
+ * - "websocket": Uses MAP SDK primitives directly (legacy mode).
6
11
  */
7
12
 
8
13
  import fs from "fs";
@@ -37,7 +42,13 @@ export function createSocketServer(socketPath, onCommand) {
37
42
  if (!line.trim()) continue;
38
43
  try {
39
44
  const command = JSON.parse(line);
40
- onCommand(command, client);
45
+ // Must await the async handler to catch SDK errors;
46
+ // without this, rejections become uncaught and crash the process.
47
+ onCommand(command, client).catch((err) => {
48
+ process.stderr.write(
49
+ `[sidecar] Async command error (${command.action}): ${err.message}\n`
50
+ );
51
+ });
41
52
  } catch (err) {
42
53
  process.stderr.write(
43
54
  `[sidecar] Invalid command: ${err.message}\n`
@@ -67,19 +78,27 @@ export function createSocketServer(socketPath, onCommand) {
67
78
  /**
68
79
  * Create a command handler for the sidecar socket server.
69
80
  *
70
- * Uses MAP SDK primitives: conn.spawn() for agent registration (server
71
- * auto-emits agent_registered), conn.updateState() for state changes
72
- * (server auto-emits agent_state_changed), and conn.send() only for
73
- * typed message payloads (task lifecycle). No custom swarm.* event types.
81
+ * In mesh mode, agent lifecycle (spawn/done) is delegated to the inbox
82
+ * registry when available, providing structured agent management. Task
83
+ * bridge events and other MAP primitives still go through the connection.
74
84
  *
75
- * @param {object|null} connection - MAP AgentConnection (or null if disconnected)
85
+ * In websocket mode, all operations use MAP SDK primitives directly
86
+ * (conn.spawn, conn.updateState, conn.send, etc.).
87
+ *
88
+ * @param {object|null} connection - MAP AgentConnection or MeshPeer connection
76
89
  * @param {string} scope - MAP scope name
77
90
  * @param {Map} registeredAgents - Map of agentId → spawn metadata
91
+ * @param {object} [opts] - Additional options
92
+ * @param {object} [opts.inboxInstance] - Agent-inbox instance (mesh mode)
93
+ * @param {object} [opts.meshPeer] - MeshPeer instance (mesh mode, for agent registration)
94
+ * @param {string} [opts.transportMode] - "mesh" or "websocket"
78
95
  * @returns {Function} async (command, client) => void
79
96
  */
80
- export function createCommandHandler(connection, scope, registeredAgents) {
97
+ export function createCommandHandler(connection, scope, registeredAgents, opts = {}) {
81
98
  // Use a getter pattern so the connection ref can be updated
82
99
  let conn = connection;
100
+ const { inboxInstance, meshPeer, transportMode = "websocket" } = opts;
101
+ const useMeshRegistry = transportMode === "mesh" && inboxInstance;
83
102
 
84
103
  const handler = async (command, client) => {
85
104
  const { action } = command;
@@ -106,12 +125,48 @@ export function createCommandHandler(connection, scope, registeredAgents) {
106
125
  break;
107
126
  }
108
127
 
109
- // --- SDK-native agent lifecycle ---
128
+ // --- Agent lifecycle ---
129
+ // In mesh mode: delegate to inbox registry
130
+ // In websocket mode: use MAP SDK primitives
110
131
 
111
132
  case "spawn": {
112
- if (conn) {
113
- const { agentId, name, role, scopes: agentScopes, metadata } =
114
- command.agent;
133
+ const { agentId, name, role, scopes: agentScopes, metadata } =
134
+ command.agent;
135
+
136
+ if (useMeshRegistry) {
137
+ // Mesh mode: register via inbox registry + MeshPeer MapServer
138
+ try {
139
+ // Register in inbox storage for message routing
140
+ if (inboxInstance.storage) {
141
+ inboxInstance.storage.putAgent({
142
+ agent_id: agentId,
143
+ scope,
144
+ status: "active",
145
+ metadata: { name, role, ...metadata },
146
+ registered_at: new Date().toISOString(),
147
+ last_active_at: new Date().toISOString(),
148
+ });
149
+ }
150
+ registeredAgents.set(agentId, { name, role, metadata });
151
+
152
+ // Also register on the MeshPeer's MapServer for observability
153
+ if (meshPeer) {
154
+ try {
155
+ await meshPeer.createAgent({ agentId, name, role, metadata });
156
+ } catch {
157
+ // Best-effort — agent may already exist
158
+ }
159
+ }
160
+
161
+ respond(client, { ok: true, agent: { agentId, name, role } });
162
+ } catch (err) {
163
+ process.stderr.write(
164
+ `[sidecar] spawn (mesh) failed: ${err.message}\n`
165
+ );
166
+ respond(client, { ok: false, error: err.message });
167
+ }
168
+ } else if (conn) {
169
+ // WebSocket mode: use MAP SDK
115
170
  try {
116
171
  const result = await conn.spawn({
117
172
  agentId,
@@ -135,8 +190,40 @@ export function createCommandHandler(connection, scope, registeredAgents) {
135
190
  }
136
191
 
137
192
  case "done": {
138
- if (conn) {
139
- const { agentId, reason } = command;
193
+ const { agentId, reason } = command;
194
+
195
+ if (useMeshRegistry) {
196
+ // Mesh mode: disconnect via inbox registry + MeshPeer MapServer
197
+ try {
198
+ // Remove from inbox storage
199
+ if (inboxInstance.storage) {
200
+ try {
201
+ inboxInstance.storage.putAgent({
202
+ agent_id: agentId,
203
+ scope,
204
+ status: "disconnected",
205
+ metadata: {},
206
+ registered_at: new Date().toISOString(),
207
+ last_active_at: new Date().toISOString(),
208
+ });
209
+ } catch { /* best-effort */ }
210
+ }
211
+ } catch {
212
+ // Agent may already be gone
213
+ }
214
+
215
+ // Also unregister from MeshPeer's MapServer
216
+ if (meshPeer) {
217
+ try {
218
+ meshPeer.server.unregisterAgent(agentId);
219
+ } catch {
220
+ // Best-effort
221
+ }
222
+ }
223
+
224
+ registeredAgents.delete(agentId);
225
+ } else if (conn) {
226
+ // WebSocket mode: use MAP SDK
140
227
  try {
141
228
  await conn.callExtension("map/agents/unregister", {
142
229
  agentId,
@@ -184,12 +271,84 @@ export function createCommandHandler(connection, scope, registeredAgents) {
184
271
  break;
185
272
  }
186
273
 
274
+ // --- Task event bridge (opentasks → MAP) ---
275
+ // These emit task events over the shared connection using
276
+ // the opentasks event bridge pattern. Works identically in
277
+ // both mesh and websocket modes.
278
+
279
+ case "bridge-task-created": {
280
+ if (conn) {
281
+ try {
282
+ await conn.send({ scope }, {
283
+ type: "task.created",
284
+ task: command.task,
285
+ _origin: command.agentId || "opentasks",
286
+ }, { relationship: "broadcast" });
287
+ } catch { /* best effort */ }
288
+ }
289
+ respond(client, { ok: true });
290
+ break;
291
+ }
292
+
293
+ case "bridge-task-status": {
294
+ if (conn) {
295
+ try {
296
+ await conn.send({ scope }, {
297
+ type: "task.status",
298
+ taskId: command.taskId,
299
+ previous: command.previous || "open",
300
+ current: command.current,
301
+ _origin: command.agentId || "opentasks",
302
+ }, { relationship: "broadcast" });
303
+ // Also emit task.completed for terminal states
304
+ if (command.current === "completed" || command.current === "closed") {
305
+ await conn.send({ scope }, {
306
+ type: "task.completed",
307
+ taskId: command.taskId,
308
+ _origin: command.agentId || "opentasks",
309
+ }, { relationship: "broadcast" });
310
+ }
311
+ } catch { /* best effort */ }
312
+ }
313
+ respond(client, { ok: true });
314
+ break;
315
+ }
316
+
317
+ case "bridge-task-assigned": {
318
+ if (conn) {
319
+ try {
320
+ await conn.send({ scope }, {
321
+ type: "task.assigned",
322
+ taskId: command.taskId,
323
+ agentId: command.assignee,
324
+ _origin: command.agentId || "opentasks",
325
+ }, { relationship: "broadcast" });
326
+ } catch { /* best effort */ }
327
+ }
328
+ respond(client, { ok: true });
329
+ break;
330
+ }
331
+
187
332
  case "state": {
188
333
  if (conn) {
189
334
  try {
190
335
  if (command.agentId) {
191
- // State update for a specific child agent — update via metadata
192
- const existing = registeredAgents.get(command.agentId);
336
+ // State update for a specific child agent
337
+ let agentKey = command.agentId;
338
+ let existing = registeredAgents.get(agentKey);
339
+ if (!existing) {
340
+ // Extract role from fallback ID (e.g. "gsd-coordinator" → "coordinator")
341
+ const fallbackRole = command.agentId.includes("-")
342
+ ? command.agentId.split("-").slice(1).join("-")
343
+ : command.agentId;
344
+ for (const [id, entry] of registeredAgents) {
345
+ if (entry.role === fallbackRole || id.endsWith(`/${fallbackRole}`)) {
346
+ agentKey = id;
347
+ existing = entry;
348
+ break;
349
+ }
350
+ }
351
+ }
193
352
  if (existing) {
194
353
  existing.lastState = command.state;
195
354
  if (command.metadata) {
@@ -212,7 +371,7 @@ export function createCommandHandler(connection, scope, registeredAgents) {
212
371
  }
213
372
 
214
373
  case "ping": {
215
- respond(client, { ok: true, pid: process.pid });
374
+ respond(client, { ok: true, pid: process.pid, transport: transportMode });
216
375
  break;
217
376
  }
218
377
 
@@ -0,0 +1,173 @@
1
+ /**
2
+ * skilltree-client.mjs — Skill-tree loadout compilation for claude-code-swarm
3
+ *
4
+ * Compiles per-role skill loadouts from team.yaml skilltree extension.
5
+ * Uses skill-tree's SkillBank + SkillGraphServer programmatically.
6
+ * Results are cached per template alongside generated AGENT.md files.
7
+ *
8
+ * Never throws — returns empty results on any error (best-effort pattern).
9
+ */
10
+
11
+ import fs from "fs";
12
+ import path from "path";
13
+ import { createRequire } from "module";
14
+ import { getGlobalNodeModules } from "./swarmkit-resolver.mjs";
15
+
16
+ const require = createRequire(import.meta.url);
17
+
18
+ let _skillTree = undefined;
19
+
20
+ /**
21
+ * Load the skill-tree module. Returns null if not available.
22
+ * Tries local require first, then falls back to global node_modules.
23
+ */
24
+ function loadSkillTree() {
25
+ if (_skillTree !== undefined) return _skillTree;
26
+
27
+ // 1. Local require (works if skill-tree is in node_modules or NODE_PATH)
28
+ try {
29
+ _skillTree = require("skill-tree");
30
+ return _skillTree;
31
+ } catch {
32
+ // Not locally available
33
+ }
34
+
35
+ // 2. Global node_modules fallback (where swarmkit installs it)
36
+ const globalNm = getGlobalNodeModules();
37
+ if (globalNm) {
38
+ const globalPath = path.join(globalNm, "skill-tree");
39
+ if (fs.existsSync(globalPath)) {
40
+ try {
41
+ _skillTree = require(globalPath);
42
+ return _skillTree;
43
+ } catch {
44
+ // require failed
45
+ }
46
+ }
47
+ }
48
+
49
+ _skillTree = null;
50
+ return null;
51
+ }
52
+
53
+ /**
54
+ * Parse the skilltree extension namespace from a team.yaml manifest.
55
+ * Returns { defaults, roles } where defaults is a LoadoutCriteria
56
+ * and roles is a map of roleName → LoadoutCriteria.
57
+ *
58
+ * team.yaml example:
59
+ * skilltree:
60
+ * defaults:
61
+ * profile: implementation
62
+ * maxSkills: 6
63
+ * roles:
64
+ * orchestrator:
65
+ * profile: code-review
66
+ * executor:
67
+ * profile: implementation
68
+ * tags: [development]
69
+ * verifier:
70
+ * profile: testing
71
+ */
72
+ export function parseSkillTreeExtension(manifest) {
73
+ const ext = manifest?.skilltree;
74
+ if (!ext) return { defaults: {}, roles: {} };
75
+
76
+ return {
77
+ defaults: ext.defaults || {},
78
+ roles: ext.roles || {},
79
+ };
80
+ }
81
+
82
+ /**
83
+ * Compile a skill loadout for a single role.
84
+ * Creates a temporary SkillBank, sets the loadout via criteria or profile,
85
+ * and returns the rendered markdown string.
86
+ *
87
+ * @param {string} roleName - Role name (for logging)
88
+ * @param {object} criteria - LoadoutCriteria (profile, tags, maxSkills, etc.)
89
+ * @param {object} config - Plugin config (skilltree section)
90
+ * @returns {Promise<string>} Rendered loadout markdown, or empty string on failure
91
+ */
92
+ export async function compileRoleLoadout(roleName, criteria, config) {
93
+ const st = loadSkillTree();
94
+ if (!st?.createSkillBank) return "";
95
+
96
+ try {
97
+ // Determine skill bank base path
98
+ const basePath = config?.basePath || ".swarm/skill-tree";
99
+
100
+ // Only attempt if the base path exists
101
+ if (!fs.existsSync(basePath)) return "";
102
+
103
+ const bank = st.createSkillBank({
104
+ storage: { basePath },
105
+ });
106
+ await bank.initialize();
107
+
108
+ try {
109
+ const { server } = await bank.createServingLayer({
110
+ outputFormat: "markdown",
111
+ });
112
+
113
+ // Set loadout from profile or criteria
114
+ if (criteria.profile) {
115
+ try {
116
+ await server.setLoadoutFromProfile(criteria.profile);
117
+ } catch {
118
+ // Profile not found — skip this role
119
+ return "";
120
+ }
121
+ } else if (criteria.tags || criteria.include || criteria.taskDescription) {
122
+ await server.setLoadout(criteria);
123
+ } else {
124
+ // No criteria specified
125
+ return "";
126
+ }
127
+
128
+ return server.renderSystemPrompt();
129
+ } finally {
130
+ await bank.shutdown();
131
+ }
132
+ } catch (err) {
133
+ process.stderr.write(`[skilltree] Warning: loadout compilation failed for ${roleName}: ${err.message}\n`);
134
+ return "";
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Compile skill loadouts for all roles in a team manifest.
140
+ * Reads the skilltree extension from the manifest, compiles loadouts per role.
141
+ *
142
+ * @param {object} manifest - Parsed team.yaml manifest
143
+ * @param {object} config - Plugin config (skilltree section)
144
+ * @returns {Promise<object>} Map of roleName → loadout markdown
145
+ */
146
+ export async function compileAllRoleLoadouts(manifest, config) {
147
+ const { defaults, roles: roleOverrides } = parseSkillTreeExtension(manifest);
148
+ const allRoles = manifest.roles || [];
149
+ const result = {};
150
+
151
+ for (const roleName of allRoles) {
152
+ // Merge defaults with role-specific overrides
153
+ const roleCriteria = roleOverrides[roleName]
154
+ ? { ...defaults, ...roleOverrides[roleName] }
155
+ : defaults;
156
+
157
+ // Apply config-level default profile as final fallback
158
+ if (!roleCriteria.profile && !roleCriteria.tags && !roleCriteria.include && !roleCriteria.taskDescription) {
159
+ if (config?.defaultProfile) {
160
+ roleCriteria.profile = config.defaultProfile;
161
+ } else {
162
+ continue; // No criteria at all — skip
163
+ }
164
+ }
165
+
166
+ const loadout = await compileRoleLoadout(roleName, roleCriteria, config);
167
+ if (loadout) {
168
+ result[roleName] = loadout;
169
+ }
170
+ }
171
+
172
+ return result;
173
+ }