macro-agent 0.0.17 → 0.1.1

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 (338) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/.sudocode/specs.jsonl +4 -0
  3. package/CLAUDE.md +16 -14
  4. package/README.md +11 -29
  5. package/dist/acp/macro-agent.d.ts +17 -0
  6. package/dist/acp/macro-agent.d.ts.map +1 -1
  7. package/dist/acp/macro-agent.js +183 -55
  8. package/dist/acp/macro-agent.js.map +1 -1
  9. package/dist/acp/types.d.ts +32 -1
  10. package/dist/acp/types.d.ts.map +1 -1
  11. package/dist/acp/types.js.map +1 -1
  12. package/dist/agent/agent-manager.d.ts +65 -1
  13. package/dist/agent/agent-manager.d.ts.map +1 -1
  14. package/dist/agent/agent-manager.js +464 -183
  15. package/dist/agent/agent-manager.js.map +1 -1
  16. package/dist/agent/types.d.ts +1 -1
  17. package/dist/agent/types.d.ts.map +1 -1
  18. package/dist/api/server.d.ts +3 -0
  19. package/dist/api/server.d.ts.map +1 -1
  20. package/dist/api/server.js +37 -6
  21. package/dist/api/server.js.map +1 -1
  22. package/dist/auth/index.d.ts +2 -0
  23. package/dist/auth/index.d.ts.map +1 -0
  24. package/dist/auth/index.js +2 -0
  25. package/dist/auth/index.js.map +1 -0
  26. package/dist/auth/token.d.ts +41 -0
  27. package/dist/auth/token.d.ts.map +1 -0
  28. package/dist/auth/token.js +73 -0
  29. package/dist/auth/token.js.map +1 -0
  30. package/dist/cli/acp.d.ts +2 -23
  31. package/dist/cli/acp.d.ts.map +1 -1
  32. package/dist/cli/acp.js +127 -61
  33. package/dist/cli/acp.js.map +1 -1
  34. package/dist/cli/index.js +147 -15
  35. package/dist/cli/index.js.map +1 -1
  36. package/dist/cli/mcp.d.ts +6 -0
  37. package/dist/cli/mcp.d.ts.map +1 -1
  38. package/dist/cli/mcp.js +268 -181
  39. package/dist/cli/mcp.js.map +1 -1
  40. package/dist/cli/parse-args.d.ts +20 -0
  41. package/dist/cli/parse-args.d.ts.map +1 -0
  42. package/dist/cli/parse-args.js +43 -0
  43. package/dist/cli/parse-args.js.map +1 -0
  44. package/dist/cli/stable-instance-id.d.ts +8 -0
  45. package/dist/cli/stable-instance-id.d.ts.map +1 -0
  46. package/dist/cli/stable-instance-id.js +14 -0
  47. package/dist/cli/stable-instance-id.js.map +1 -0
  48. package/dist/config/project-config.d.ts +74 -7
  49. package/dist/config/project-config.d.ts.map +1 -1
  50. package/dist/config/project-config.js +123 -20
  51. package/dist/config/project-config.js.map +1 -1
  52. package/dist/map/adapter/acp-over-map.d.ts +23 -0
  53. package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
  54. package/dist/map/adapter/acp-over-map.js +482 -55
  55. package/dist/map/adapter/acp-over-map.js.map +1 -1
  56. package/dist/map/adapter/connection-manager.d.ts.map +1 -1
  57. package/dist/map/adapter/connection-manager.js +3 -0
  58. package/dist/map/adapter/connection-manager.js.map +1 -1
  59. package/dist/map/adapter/event-log.d.ts +87 -0
  60. package/dist/map/adapter/event-log.d.ts.map +1 -0
  61. package/dist/map/adapter/event-log.js +122 -0
  62. package/dist/map/adapter/event-log.js.map +1 -0
  63. package/dist/map/adapter/event-translator.js +6 -6
  64. package/dist/map/adapter/event-translator.js.map +1 -1
  65. package/dist/map/adapter/extensions/agent-lifecycle.d.ts +82 -0
  66. package/dist/map/adapter/extensions/agent-lifecycle.d.ts.map +1 -0
  67. package/dist/map/adapter/extensions/agent-lifecycle.js +164 -0
  68. package/dist/map/adapter/extensions/agent-lifecycle.js.map +1 -0
  69. package/dist/map/adapter/extensions/index.d.ts +10 -1
  70. package/dist/map/adapter/extensions/index.d.ts.map +1 -1
  71. package/dist/map/adapter/extensions/index.js +34 -0
  72. package/dist/map/adapter/extensions/index.js.map +1 -1
  73. package/dist/map/adapter/extensions/mcp-bridge.d.ts +57 -0
  74. package/dist/map/adapter/extensions/mcp-bridge.d.ts.map +1 -0
  75. package/dist/map/adapter/extensions/mcp-bridge.js +745 -0
  76. package/dist/map/adapter/extensions/mcp-bridge.js.map +1 -0
  77. package/dist/map/adapter/extensions/rename.d.ts +29 -0
  78. package/dist/map/adapter/extensions/rename.d.ts.map +1 -0
  79. package/dist/map/adapter/extensions/rename.js +49 -0
  80. package/dist/map/adapter/extensions/rename.js.map +1 -0
  81. package/dist/map/adapter/extensions/task.d.ts.map +1 -1
  82. package/dist/map/adapter/extensions/task.js +10 -0
  83. package/dist/map/adapter/extensions/task.js.map +1 -1
  84. package/dist/map/adapter/extensions/update-metadata.d.ts +29 -0
  85. package/dist/map/adapter/extensions/update-metadata.d.ts.map +1 -0
  86. package/dist/map/adapter/extensions/update-metadata.js +67 -0
  87. package/dist/map/adapter/extensions/update-metadata.js.map +1 -0
  88. package/dist/map/adapter/index.d.ts +2 -1
  89. package/dist/map/adapter/index.d.ts.map +1 -1
  90. package/dist/map/adapter/index.js +8 -2
  91. package/dist/map/adapter/index.js.map +1 -1
  92. package/dist/map/adapter/interface.d.ts +2 -0
  93. package/dist/map/adapter/interface.d.ts.map +1 -1
  94. package/dist/map/adapter/map-adapter.d.ts +4 -0
  95. package/dist/map/adapter/map-adapter.d.ts.map +1 -1
  96. package/dist/map/adapter/map-adapter.js +302 -30
  97. package/dist/map/adapter/map-adapter.js.map +1 -1
  98. package/dist/map/adapter/subscription-manager.d.ts.map +1 -1
  99. package/dist/map/adapter/subscription-manager.js +5 -1
  100. package/dist/map/adapter/subscription-manager.js.map +1 -1
  101. package/dist/map/adapter/types.d.ts +2 -0
  102. package/dist/map/adapter/types.d.ts.map +1 -1
  103. package/dist/mcp/map-client.d.ts +39 -0
  104. package/dist/mcp/map-client.d.ts.map +1 -0
  105. package/dist/mcp/map-client.js +129 -0
  106. package/dist/mcp/map-client.js.map +1 -0
  107. package/dist/mcp/mcp-server.d.ts +14 -0
  108. package/dist/mcp/mcp-server.d.ts.map +1 -1
  109. package/dist/mcp/mcp-server.js +113 -85
  110. package/dist/mcp/mcp-server.js.map +1 -1
  111. package/dist/mcp/types.d.ts +9 -1
  112. package/dist/mcp/types.d.ts.map +1 -1
  113. package/dist/mcp/types.js.map +1 -1
  114. package/dist/metrics/metrics.js +1 -1
  115. package/dist/metrics/metrics.js.map +1 -1
  116. package/dist/roles/capabilities.d.ts +3 -1
  117. package/dist/roles/capabilities.d.ts.map +1 -1
  118. package/dist/roles/capabilities.js +17 -7
  119. package/dist/roles/capabilities.js.map +1 -1
  120. package/dist/roles/config-loader.d.ts +6 -6
  121. package/dist/roles/config-loader.d.ts.map +1 -1
  122. package/dist/roles/config-loader.js +6 -6
  123. package/dist/roles/config-loader.js.map +1 -1
  124. package/dist/roles/registry.d.ts +2 -2
  125. package/dist/roles/registry.js +2 -2
  126. package/dist/server/combined-server.d.ts +20 -0
  127. package/dist/server/combined-server.d.ts.map +1 -1
  128. package/dist/server/combined-server.js +107 -8
  129. package/dist/server/combined-server.js.map +1 -1
  130. package/dist/store/event-store.d.ts +7 -1
  131. package/dist/store/event-store.d.ts.map +1 -1
  132. package/dist/store/event-store.js +91 -8
  133. package/dist/store/event-store.js.map +1 -1
  134. package/dist/store/types/agents.d.ts +23 -0
  135. package/dist/store/types/agents.d.ts.map +1 -1
  136. package/dist/store/types/events.d.ts +1 -1
  137. package/dist/store/types/events.d.ts.map +1 -1
  138. package/dist/task/backend/index.d.ts +47 -29
  139. package/dist/task/backend/index.d.ts.map +1 -1
  140. package/dist/task/backend/index.js +109 -71
  141. package/dist/task/backend/index.js.map +1 -1
  142. package/dist/task/backend/memory.d.ts +1 -0
  143. package/dist/task/backend/memory.d.ts.map +1 -1
  144. package/dist/task/backend/memory.js +3 -0
  145. package/dist/task/backend/memory.js.map +1 -1
  146. package/dist/task/backend/opentasks/backend.d.ts +140 -0
  147. package/dist/task/backend/opentasks/backend.d.ts.map +1 -0
  148. package/dist/task/backend/opentasks/backend.js +1023 -0
  149. package/dist/task/backend/opentasks/backend.js.map +1 -0
  150. package/dist/task/backend/opentasks/client.d.ts +337 -0
  151. package/dist/task/backend/opentasks/client.d.ts.map +1 -0
  152. package/dist/task/backend/opentasks/client.js +225 -0
  153. package/dist/task/backend/opentasks/client.js.map +1 -0
  154. package/dist/task/backend/opentasks/daemon-manager.d.ts +89 -0
  155. package/dist/task/backend/opentasks/daemon-manager.d.ts.map +1 -0
  156. package/dist/task/backend/opentasks/daemon-manager.js +195 -0
  157. package/dist/task/backend/opentasks/daemon-manager.js.map +1 -0
  158. package/dist/task/backend/opentasks/index.d.ts +21 -0
  159. package/dist/task/backend/opentasks/index.d.ts.map +1 -0
  160. package/dist/task/backend/opentasks/index.js +21 -0
  161. package/dist/task/backend/opentasks/index.js.map +1 -0
  162. package/dist/task/backend/opentasks/mapping.d.ts +48 -0
  163. package/dist/task/backend/opentasks/mapping.d.ts.map +1 -0
  164. package/dist/task/backend/opentasks/mapping.js +77 -0
  165. package/dist/task/backend/opentasks/mapping.js.map +1 -0
  166. package/dist/task/backend/types.d.ts +33 -53
  167. package/dist/task/backend/types.d.ts.map +1 -1
  168. package/dist/task/backend/types.js +7 -11
  169. package/dist/task/backend/types.js.map +1 -1
  170. package/dist/task/backend/unified-tool-provider.d.ts +57 -0
  171. package/dist/task/backend/unified-tool-provider.d.ts.map +1 -0
  172. package/dist/task/backend/unified-tool-provider.js +623 -0
  173. package/dist/task/backend/unified-tool-provider.js.map +1 -0
  174. package/dist/teams/team-loader.d.ts +2 -2
  175. package/dist/teams/team-loader.js +3 -3
  176. package/dist/teams/team-loader.js.map +1 -1
  177. package/dist/teams/team-runtime.d.ts.map +1 -1
  178. package/dist/teams/team-runtime.js +2 -0
  179. package/dist/teams/team-runtime.js.map +1 -1
  180. package/docs/architecture.md +7 -6
  181. package/docs/configuration.md +26 -62
  182. package/docs/implementation-details.md +5 -5
  183. package/docs/implementation-summary.md +17 -17
  184. package/docs/plan-self-driving-support.md +4 -4
  185. package/docs/spec-self-driving-support.md +10 -10
  186. package/docs/team-templates.md +2 -2
  187. package/docs/teams.md +3 -3
  188. package/docs/troubleshooting.md +10 -11
  189. package/package.json +6 -4
  190. package/src/__tests__/e2e/agent-spawn-visibility.e2e.test.ts +761 -0
  191. package/src/__tests__/e2e/full-agent-conflict-resolution.e2e.test.ts +2 -2
  192. package/src/__tests__/e2e/mcp-thin-client-bridge.e2e.test.ts +304 -0
  193. package/src/__tests__/e2e/mcp-tools-available.e2e.test.ts +324 -0
  194. package/src/__tests__/e2e/multi-agent.e2e.test.ts +5 -5
  195. package/src/__tests__/e2e/spawn-session-streaming.e2e.test.ts +563 -0
  196. package/src/acp/__tests__/history.test.ts +8 -4
  197. package/src/acp/__tests__/integration.test.ts +56 -31
  198. package/src/acp/__tests__/macro-agent.test.ts +16 -7
  199. package/src/acp/macro-agent.ts +230 -62
  200. package/src/acp/types.ts +46 -1
  201. package/src/agent/__tests__/agent-manager.test.ts +228 -2
  202. package/src/agent/agent-manager.ts +714 -261
  203. package/src/agent/types.ts +3 -1
  204. package/src/api/server.ts +41 -7
  205. package/src/auth/__tests__/token.test.ts +100 -0
  206. package/src/auth/index.ts +1 -0
  207. package/src/auth/token.ts +82 -0
  208. package/src/cli/__tests__/acp.test.ts +1 -1
  209. package/src/cli/__tests__/stable-instance-id.test.ts +1 -1
  210. package/src/cli/acp.ts +130 -72
  211. package/src/cli/index.ts +120 -14
  212. package/src/cli/mcp.ts +311 -207
  213. package/src/cli/parse-args.ts +54 -0
  214. package/src/cli/stable-instance-id.ts +14 -0
  215. package/src/config/project-config.ts +190 -27
  216. package/src/lifecycle/__tests__/cascade-termination.test.ts +1 -1
  217. package/src/map/adapter/__tests__/acp-over-map-cancel.test.ts +820 -0
  218. package/src/map/adapter/__tests__/acp-over-map-getmodels.test.ts +355 -0
  219. package/src/map/adapter/__tests__/acp-over-map-history.test.ts +724 -2
  220. package/src/map/adapter/__tests__/acp-over-map-persistence.e2e.test.ts +1 -1
  221. package/src/map/adapter/__tests__/event-broadcast.test.ts +420 -0
  222. package/src/map/adapter/__tests__/event-log.test.ts +527 -0
  223. package/src/map/adapter/__tests__/event-translator.test.ts +3 -3
  224. package/src/map/adapter/__tests__/extensions.test.ts +408 -0
  225. package/src/map/adapter/__tests__/map-adapter.test.ts +99 -0
  226. package/src/map/adapter/__tests__/mcp-bridge.test.ts +1187 -0
  227. package/src/map/adapter/__tests__/multi-client-broadcast.test.ts +711 -0
  228. package/src/map/adapter/__tests__/websocket-integration.test.ts +218 -0
  229. package/src/map/adapter/acp-over-map.ts +777 -92
  230. package/src/map/adapter/connection-manager.ts +3 -0
  231. package/src/map/adapter/event-log.ts +208 -0
  232. package/src/map/adapter/event-translator.ts +6 -6
  233. package/src/map/adapter/extensions/agent-lifecycle.ts +267 -0
  234. package/src/map/adapter/extensions/index.ts +60 -0
  235. package/src/map/adapter/extensions/mcp-bridge.ts +995 -0
  236. package/src/map/adapter/extensions/task.ts +11 -0
  237. package/src/map/adapter/extensions/update-metadata.ts +126 -0
  238. package/src/map/adapter/index.ts +28 -0
  239. package/src/map/adapter/interface.ts +2 -0
  240. package/src/map/adapter/map-adapter.ts +373 -38
  241. package/src/map/adapter/subscription-manager.ts +5 -1
  242. package/src/map/adapter/types.ts +2 -0
  243. package/src/mcp/__tests__/map-client.test.ts +386 -0
  244. package/src/mcp/__tests__/mcp-server-thin-client.test.ts +368 -0
  245. package/src/mcp/__tests__/mcp-server.test.ts +100 -1
  246. package/src/mcp/map-client.ts +177 -0
  247. package/src/mcp/mcp-server.ts +191 -100
  248. package/src/mcp/types.ts +6 -1
  249. package/src/metrics/metrics.ts +1 -1
  250. package/src/monitor/__tests__/stale-agent-flow.integration.test.ts +1 -1
  251. package/src/roles/__tests__/config-loader.test.ts +7 -7
  252. package/src/roles/capabilities.ts +17 -7
  253. package/src/roles/config-loader.ts +6 -6
  254. package/src/roles/registry.ts +2 -2
  255. package/src/server/__tests__/combined-server.test.ts +94 -21
  256. package/src/server/combined-server.ts +189 -33
  257. package/src/steering/__tests__/steering-integration.test.ts +1 -1
  258. package/src/store/__tests__/event-store.test.ts +236 -1
  259. package/src/store/__tests__/instance.test.ts +3 -3
  260. package/src/store/event-store.ts +109 -8
  261. package/src/store/types/agents.ts +16 -0
  262. package/src/store/types/events.ts +1 -1
  263. package/src/task/backend/__tests__/create-task-backend.test.ts +225 -0
  264. package/src/task/backend/__tests__/e2e/unified-tool-provider-opentasks.e2e.test.ts +524 -0
  265. package/src/task/backend/__tests__/unified-tool-provider.test.ts +579 -0
  266. package/src/task/backend/index.ts +156 -106
  267. package/src/task/backend/memory.ts +4 -0
  268. package/src/task/backend/opentasks/__tests__/backend.test.ts +968 -0
  269. package/src/task/backend/opentasks/__tests__/daemon-manager.test.ts +406 -0
  270. package/src/task/backend/opentasks/__tests__/mapping.test.ts +84 -0
  271. package/src/task/backend/opentasks/__tests__/opentasks-backend.e2e.test.ts +1338 -0
  272. package/src/task/backend/opentasks/backend.ts +1323 -0
  273. package/src/task/backend/opentasks/client.ts +652 -0
  274. package/src/task/backend/opentasks/daemon-manager.ts +253 -0
  275. package/src/task/backend/opentasks/index.ts +69 -0
  276. package/src/task/backend/opentasks/mapping.ts +94 -0
  277. package/src/task/backend/types.ts +42 -66
  278. package/src/task/backend/unified-tool-provider.ts +779 -0
  279. package/src/teams/__tests__/cross-subsystem.integration.test.ts +1 -1
  280. package/src/teams/team-loader.ts +3 -3
  281. package/src/teams/team-runtime.ts +2 -0
  282. package/test_fixtures/README.md +2 -3
  283. package/test_fixtures/fixtures/index.ts +0 -3
  284. package/test_fixtures/fixtures/projects/project-with-specs.ts +7 -149
  285. package/test_fixtures/fixtures/repos/index.ts +1 -3
  286. package/test_fixtures/fixtures/repos/temp-repo-factory.ts +0 -116
  287. package/test_fixtures/fixtures/repos/types.ts +0 -11
  288. package/test_fixtures/harness/__tests__/fixtures.test.ts +10 -102
  289. package/test_fixtures/harness/__tests__/temp-repo-and-simulator.test.ts +0 -33
  290. package/test_fixtures/harness/simulator/agent-simulator.ts +4 -4
  291. package/vitest.config.ts +1 -1
  292. package/vitest.e2e.config.ts +1 -1
  293. package/vitest.setup.ts +1 -30
  294. package/.macro-agent/teams/self-driving/prompts/grinder.md +0 -27
  295. package/.macro-agent/teams/self-driving/prompts/judge.md +0 -27
  296. package/.macro-agent/teams/self-driving/prompts/planner.md +0 -33
  297. package/.macro-agent/teams/self-driving/roles/grinder.yaml +0 -17
  298. package/.macro-agent/teams/self-driving/roles/judge.yaml +0 -24
  299. package/.macro-agent/teams/self-driving/roles/planner.yaml +0 -18
  300. package/.macro-agent/teams/self-driving/team.yaml +0 -103
  301. package/.macro-agent/teams/structured/prompts/developer.md +0 -26
  302. package/.macro-agent/teams/structured/prompts/lead.md +0 -25
  303. package/.macro-agent/teams/structured/prompts/reviewer.md +0 -24
  304. package/.macro-agent/teams/structured/roles/developer.yaml +0 -12
  305. package/.macro-agent/teams/structured/roles/lead.yaml +0 -11
  306. package/.macro-agent/teams/structured/roles/reviewer.yaml +0 -19
  307. package/.macro-agent/teams/structured/team.yaml +0 -89
  308. package/docs/sudocode-integration.md +0 -383
  309. package/src/task/backend/__tests__/backend-parity.test.ts +0 -451
  310. package/src/task/backend/__tests__/tool-provider-edge-cases.test.ts +0 -430
  311. package/src/task/backend/__tests__/tool-provider.test.ts +0 -983
  312. package/src/task/backend/sudocode/__tests__/backend-edge-cases.test.ts +0 -575
  313. package/src/task/backend/sudocode/__tests__/backend.test.ts +0 -1194
  314. package/src/task/backend/sudocode/__tests__/client-integration.test.ts +0 -418
  315. package/src/task/backend/sudocode/__tests__/client.test.ts +0 -345
  316. package/src/task/backend/sudocode/__tests__/e2e/backend.e2e.test.ts +0 -753
  317. package/src/task/backend/sudocode/__tests__/e2e/server-client.e2e.test.ts +0 -680
  318. package/src/task/backend/sudocode/__tests__/e2e-workflow.test.ts +0 -666
  319. package/src/task/backend/sudocode/__tests__/integration/standalone-client.integration.test.ts +0 -396
  320. package/src/task/backend/sudocode/__tests__/integration/sudocode-cli.integration.test.ts +0 -328
  321. package/src/task/backend/sudocode/__tests__/integration/test-utils.ts +0 -175
  322. package/src/task/backend/sudocode/__tests__/mapping-edge-cases.test.ts +0 -265
  323. package/src/task/backend/sudocode/__tests__/server-client.test.ts +0 -675
  324. package/src/task/backend/sudocode/__tests__/sync-policy-edge-cases.test.ts +0 -521
  325. package/src/task/backend/sudocode/__tests__/sync-policy.test.ts +0 -519
  326. package/src/task/backend/sudocode/__tests__/tools.test.ts +0 -471
  327. package/src/task/backend/sudocode/backend.ts +0 -1237
  328. package/src/task/backend/sudocode/client.ts +0 -515
  329. package/src/task/backend/sudocode/index.ts +0 -120
  330. package/src/task/backend/sudocode/mapping.ts +0 -93
  331. package/src/task/backend/sudocode/server-client.ts +0 -522
  332. package/src/task/backend/sudocode/standalone-client.ts +0 -623
  333. package/src/task/backend/sudocode/sync-policy.ts +0 -387
  334. package/src/task/backend/sudocode/tools.ts +0 -896
  335. package/src/task/backend/tool-provider.ts +0 -506
  336. package/test_fixtures/fixtures/sudocode/index.ts +0 -29
  337. package/test_fixtures/fixtures/sudocode/issues.ts +0 -185
  338. package/test_fixtures/fixtures/sudocode/specs.ts +0 -159
@@ -7,6 +7,7 @@
7
7
  * - MessageRouter for subscription setup
8
8
  */
9
9
  import { nanoid } from "nanoid";
10
+ import { uniqueNamesGenerator, adjectives, animals, } from "unique-names-generator";
10
11
  import { AgentFactory, } from "acp-factory";
11
12
  import { AgentManagerError } from "./types.js";
12
13
  import { AGENT_CAPABILITIES } from "../roles/capabilities.js";
@@ -45,7 +46,9 @@ function getSpawnCapability(childRole) {
45
46
  // AgentManager Implementation
46
47
  // ─────────────────────────────────────────────────────────────────
47
48
  export function createAgentManager(eventStore, messageRouter, config = {}) {
48
- const { defaultPermissionMode = "auto-approve", defaultAgentType = "claude-code", defaultCwd = process.cwd(), workspaceManager, roleRegistry = new DefaultRoleRegistry(), healthCheckService, mailService: initialMailService, conversationMap: initialConversationMap, } = config;
49
+ const { defaultPermissionMode = "auto-approve", defaultAgentType = "claude-code", defaultCwd = process.cwd(), workspaceManager, roleRegistry = new DefaultRoleRegistry(), healthCheckService, mailService: initialMailService, conversationMap: initialConversationMap, serverUrl, serverToken, agentTokenManager, taskBackend: configTaskBackend, openTasksSocketPath: initialOpenTasksSocketPath, } = config;
50
+ // Mutable OpenTasks socket path (support late binding via setOpenTasksSocketPath)
51
+ let configOpenTasksSocketPath = initialOpenTasksSocketPath;
49
52
  // Mutable mail services (support late binding via setMailServices)
50
53
  let mailService = initialMailService;
51
54
  let conversationMap = initialConversationMap;
@@ -57,10 +60,66 @@ export function createAgentManager(eventStore, messageRouter, config = {}) {
57
60
  const agentWorkspaces = new Map();
58
61
  // Lifecycle event listeners
59
62
  const lifecycleListeners = new Set();
63
+ // Shutdown guard — prevents spawns during close()
64
+ let isShuttingDown = false;
65
+ // ─────────────────────────────────────────────────────────────────
66
+ // MCP Server Config
67
+ // ─────────────────────────────────────────────────────────────────
68
+ /**
69
+ * Build the macro-agent MCP server config for a Claude Code agent session.
70
+ * Used by spawn(), resume(), and forkAgent() to ensure every agent gets
71
+ * access to the macro-agent coordination tools.
72
+ */
73
+ function buildMacroAgentMcp(opts) {
74
+ // Common env vars for both thin-client and legacy modes
75
+ const env = [
76
+ { name: "MACRO_AGENT_ID", value: opts.agentId },
77
+ { name: "MACRO_PARENT_ID", value: opts.parentId },
78
+ { name: "MACRO_TASK_ID", value: opts.taskId },
79
+ { name: "MACRO_AGENT_CWD", value: opts.cwd },
80
+ { name: "MACRO_PERMISSION_MODE", value: opts.permissionMode },
81
+ {
82
+ name: "MACRO_TASK_BACKEND",
83
+ value: configTaskBackend ?? process.env.MACRO_TASK_BACKEND ?? "",
84
+ },
85
+ {
86
+ name: "OPENTASKS_SOCKET_PATH",
87
+ value: configOpenTasksSocketPath ?? process.env.OPENTASKS_SOCKET_PATH ?? "",
88
+ },
89
+ ];
90
+ if (serverUrl) {
91
+ // Thin-client mode: forward tool calls to main server via MAP WebSocket
92
+ env.push({ name: "MACRO_SERVER_URL", value: serverUrl }, {
93
+ name: "MACRO_AGENT_LINEAGE",
94
+ value: JSON.stringify(opts.lineage ?? []),
95
+ }, { name: "MACRO_SESSION_ID", value: opts.sessionId ?? "" });
96
+ // Auth tokens for thin-client connections
97
+ if (serverToken) {
98
+ env.push({ name: "MACRO_SERVER_TOKEN", value: serverToken });
99
+ }
100
+ if (agentTokenManager) {
101
+ const agentToken = agentTokenManager.createToken(opts.agentId);
102
+ env.push({ name: "MACRO_AGENT_TOKEN", value: agentToken });
103
+ }
104
+ }
105
+ else {
106
+ // Legacy mode: create local service stack with shared SQLite
107
+ env.push({ name: "MACRO_INSTANCE_ID", value: eventStore.instanceId }, { name: "MACRO_BASE_DIR", value: eventStore.baseDir });
108
+ }
109
+ return {
110
+ name: "macro-agent",
111
+ command: "npx",
112
+ args: ["multiagent-mcp"],
113
+ env,
114
+ };
115
+ }
60
116
  // ─────────────────────────────────────────────────────────────────
61
117
  // Lifecycle
62
118
  // ─────────────────────────────────────────────────────────────────
63
119
  async function spawn(rawOptions) {
120
+ if (isShuttingDown) {
121
+ throw new AgentManagerError("Cannot spawn agent during shutdown", "SHUTDOWN_IN_PROGRESS");
122
+ }
64
123
  // Apply spawn interceptor if set (used by TeamRuntime for team context injection)
65
124
  const options = spawnInterceptor
66
125
  ? await spawnInterceptor(rawOptions)
@@ -144,6 +203,13 @@ export function createAgentManager(eventStore, messageRouter, config = {}) {
144
203
  cwd,
145
204
  },
146
205
  });
206
+ // Generate a human-readable default name
207
+ const generatedName = uniqueNamesGenerator({
208
+ dictionaries: [adjectives, animals],
209
+ separator: "-",
210
+ length: 2,
211
+ });
212
+ eventStore.updateAgentMetadata(agentId, { name: generatedName });
147
213
  // Persist immediately so MCP server subprocess can read the agent
148
214
  await eventStore.persist();
149
215
  // Verify the agent is now in the store
@@ -157,162 +223,177 @@ export function createAgentManager(eventStore, messageRouter, config = {}) {
157
223
  permissionMode,
158
224
  env: agentConfig?.env,
159
225
  });
160
- // Build MCP server configuration for the macro-agent MCP server
161
- // Note: McpServerStdio doesn't have a 'type' field - stdio is the implicit default
162
- // when neither 'type: http' nor 'type: sse' is specified
163
- const macroAgentMcp = {
164
- name: "macro-agent",
165
- command: "npx",
166
- args: ["multiagent-mcp"],
167
- env: [
168
- { name: "MACRO_AGENT_ID", value: agentId },
169
- { name: "MACRO_PARENT_ID", value: parent ?? "" },
170
- { name: "MACRO_TASK_ID", value: taskId },
171
- { name: "MACRO_AGENT_CWD", value: cwd },
172
- { name: "MACRO_INSTANCE_ID", value: eventStore.instanceId },
173
- { name: "MACRO_BASE_DIR", value: eventStore.baseDir },
174
- ],
175
- };
176
- // Combine with any user-provided MCP servers
177
- // Note: Like macroAgentMcp, user MCP servers use stdio (no 'type' field)
178
- const userMcpServers = agentConfig?.mcpServers?.map((s) => ({
179
- name: s.name,
180
- command: s.command,
181
- args: s.args ?? [],
182
- env: s.env
183
- ? Object.entries(s.env).map(([name, value]) => ({ name, value }))
184
- : [],
185
- })) ?? [];
186
- // Create session with MCP servers
187
- // Note: The MCP server subprocess will start here and look for the agent
188
- // in EventStore. We already persisted the spawn event above.
189
- const session = await handle.createSession(cwd, {
190
- mcpServers: [macroAgentMcp, ...userMcpServers],
191
- });
192
- // Emit started status (session is ready)
193
- // Include the provider's session ID (e.g., Claude Code UUID) so
194
- // it can be used for handle.loadSession() during resume
195
- eventStore.emit({
196
- type: "status",
197
- source: { agent_id: agentId },
198
- payload: {
199
- status_type: "started",
200
- summary: "Agent session started",
201
- provider_session_id: session.id,
202
- },
203
- });
204
- // Persist the status event
205
- await eventStore.persist();
206
- // Set up default subscriptions via MessageRouter
207
- messageRouter.setupDefaultSubscriptions({
208
- agent_id: agentId,
209
- parent_id: parent ?? undefined,
210
- task_id: taskId,
211
- subscribe_parent: subscribeParent,
212
- additional_topics: topics,
213
- role: role ?? undefined,
214
- });
215
- // ─────────────────────────────────────────────────────────────────
216
- // Mail: Create task conversation for this agent
217
- // ─────────────────────────────────────────────────────────────────
218
- if (mailService && conversationMap) {
219
- try {
220
- const parentConversationId = parent
221
- ? conversationMap.getAgentConversation(parent) ??
222
- conversationMap.getSessionConversation(parent)
223
- : undefined;
224
- const { conversationId: taskConvId } = mailService.createConversation({
225
- type: "task",
226
- subject: task?.slice(0, 80),
227
- createdBy: parent ?? agentId,
228
- parentConversationId: parentConversationId,
229
- });
230
- // Join parent and child as participants
231
- if (parent) {
226
+ try {
227
+ const macroAgentMcp = buildMacroAgentMcp({
228
+ agentId,
229
+ parentId: parent ?? "",
230
+ taskId,
231
+ cwd,
232
+ permissionMode,
233
+ lineage: parentAgent?.lineage
234
+ ? [...parentAgent.lineage, parent]
235
+ : [],
236
+ sessionId,
237
+ });
238
+ // Combine with any user-provided MCP servers
239
+ // Note: Like macroAgentMcp, user MCP servers use stdio (no 'type' field)
240
+ const userMcpServers = agentConfig?.mcpServers?.map((s) => ({
241
+ name: s.name,
242
+ command: s.command,
243
+ args: s.args ?? [],
244
+ env: s.env
245
+ ? Object.entries(s.env).map(([name, value]) => ({ name, value }))
246
+ : [],
247
+ })) ?? [];
248
+ // Create session with MCP servers
249
+ // Note: The MCP server subprocess will start here and look for the agent
250
+ // in EventStore. We already persisted the spawn event above.
251
+ //
252
+ // When permissionMode is "interactive", we strip settingSources so that
253
+ // the Claude Code subprocess doesn't read pre-approved tool rules from
254
+ // the user's ~/.claude/settings.local.json. This ensures ALL tool calls
255
+ // go through the canUseTool → requestPermission ACP flow.
256
+ const agentMeta = permissionMode === "interactive"
257
+ ? { claudeCode: { options: { settingSources: [] } } }
258
+ : undefined;
259
+ const session = await handle.createSession(cwd, {
260
+ mcpServers: [macroAgentMcp, ...userMcpServers],
261
+ ...(agentMeta && { agentMeta }),
262
+ });
263
+ // Emit started status (session is ready)
264
+ // Include the provider's session ID (e.g., Claude Code UUID) so
265
+ // it can be used for handle.loadSession() during resume
266
+ eventStore.emit({
267
+ type: "status",
268
+ source: { agent_id: agentId },
269
+ payload: {
270
+ status_type: "started",
271
+ summary: "Agent session started",
272
+ provider_session_id: session.id,
273
+ },
274
+ });
275
+ // Persist the status event
276
+ await eventStore.persist();
277
+ // Set up default subscriptions via MessageRouter
278
+ messageRouter.setupDefaultSubscriptions({
279
+ agent_id: agentId,
280
+ parent_id: parent ?? undefined,
281
+ task_id: taskId,
282
+ subscribe_parent: subscribeParent,
283
+ additional_topics: topics,
284
+ role: role ?? undefined,
285
+ });
286
+ // ─────────────────────────────────────────────────────────────────
287
+ // Mail: Create task conversation for this agent
288
+ // ─────────────────────────────────────────────────────────────────
289
+ if (mailService && conversationMap) {
290
+ try {
291
+ const parentConversationId = parent
292
+ ? (conversationMap.getAgentConversation(parent) ??
293
+ conversationMap.getSessionConversation(parent))
294
+ : undefined;
295
+ const { conversationId: taskConvId } = mailService.createConversation({
296
+ type: "task",
297
+ subject: task?.slice(0, 80),
298
+ createdBy: parent ?? agentId,
299
+ parentConversationId: parentConversationId,
300
+ });
301
+ // Join parent and child as participants
302
+ if (parent) {
303
+ mailService.joinConversation({
304
+ conversationId: taskConvId,
305
+ participantId: parent,
306
+ participantType: "agent",
307
+ role: "initiator",
308
+ agentId: parent,
309
+ });
310
+ }
232
311
  mailService.joinConversation({
233
312
  conversationId: taskConvId,
234
- participantId: parent,
313
+ participantId: agentId,
235
314
  participantType: "agent",
236
- role: "initiator",
237
- agentId: parent,
315
+ role: "worker",
316
+ agentId,
238
317
  });
318
+ conversationMap.setAgentConversation(agentId, taskConvId);
319
+ }
320
+ catch (err) {
321
+ // Never fail spawn due to mail errors
322
+ console.warn(`[AgentManager] Failed to create task conversation for ${agentId}:`, err);
239
323
  }
240
- mailService.joinConversation({
241
- conversationId: taskConvId,
242
- participantId: agentId,
243
- participantType: "agent",
244
- role: "worker",
245
- agentId,
246
- });
247
- conversationMap.setAgentConversation(agentId, taskConvId);
248
- }
249
- catch (err) {
250
- // Never fail spawn due to mail errors
251
- console.warn(`[AgentManager] Failed to create task conversation for ${agentId}:`, err);
252
324
  }
253
- }
254
- // Track active session
255
- const activeSession = {
256
- agentId,
257
- handle,
258
- session,
259
- createdAt: Date.now(),
260
- isPrompting: false,
261
- };
262
- activeSessions.set(agentId, activeSession);
263
- // Get the agent from materialized view
264
- const agent = eventStore.getAgent(agentId);
265
- // ─────────────────────────────────────────────────────────────────
266
- // Workspace Creation (Phase 2)
267
- // ─────────────────────────────────────────────────────────────────
268
- let workspace;
269
- let resolvedStreamId = streamId;
270
- if (workspaceManager && role) {
271
- try {
272
- workspace = await createWorkspaceForRole(workspaceManager, agentId, role, {
273
- streamId,
274
- streamConfig,
275
- dataplaneTaskId,
276
- cwd,
277
- });
278
- if (workspace) {
279
- agentWorkspaces.set(agentId, workspace);
280
- resolvedStreamId = workspace.streamId;
281
- // Register with parent coordinator if applicable
282
- if (parent &&
283
- (role === "worker" || role === "integrator")) {
284
- const parentWorkspace = agentWorkspaces.get(parent);
285
- if (parentWorkspace?.role === "coordinator") {
286
- workspaceManager.registerChildWorkspace(parent, agentId, workspace.path);
325
+ // Track active session
326
+ const activeSession = {
327
+ agentId,
328
+ handle,
329
+ session,
330
+ createdAt: Date.now(),
331
+ isPrompting: false,
332
+ };
333
+ activeSessions.set(agentId, activeSession);
334
+ // Get the agent from materialized view
335
+ const agent = eventStore.getAgent(agentId);
336
+ // ─────────────────────────────────────────────────────────────────
337
+ // Workspace Creation (Phase 2)
338
+ // ─────────────────────────────────────────────────────────────────
339
+ let workspace;
340
+ let resolvedStreamId = streamId;
341
+ if (workspaceManager && role) {
342
+ try {
343
+ workspace = await createWorkspaceForRole(workspaceManager, agentId, role, {
344
+ streamId,
345
+ streamConfig,
346
+ dataplaneTaskId,
347
+ cwd,
348
+ });
349
+ if (workspace) {
350
+ agentWorkspaces.set(agentId, workspace);
351
+ resolvedStreamId = workspace.streamId;
352
+ // Register with parent coordinator if applicable
353
+ if (parent && (role === "worker" || role === "integrator")) {
354
+ const parentWorkspace = agentWorkspaces.get(parent);
355
+ if (parentWorkspace?.role === "coordinator") {
356
+ workspaceManager.registerChildWorkspace(parent, agentId, workspace.path);
357
+ }
287
358
  }
288
359
  }
289
360
  }
361
+ catch (wsError) {
362
+ console.error(`[AgentManager] Failed to create workspace for ${agentId}: ${wsError}`);
363
+ // Continue without workspace - don't fail the spawn
364
+ }
290
365
  }
291
- catch (wsError) {
292
- console.error(`[AgentManager] Failed to create workspace for ${agentId}: ${wsError}`);
293
- // Continue without workspace - don't fail the spawn
366
+ // Notify lifecycle listeners
367
+ notifyLifecycle({ type: "spawned", agent });
368
+ notifyLifecycle({ type: "started", agent });
369
+ // Start health monitoring for coordinators
370
+ if (healthCheckService && role === "coordinator") {
371
+ healthCheckService.startForCoordinator(agentId);
294
372
  }
373
+ return {
374
+ id: agentId,
375
+ session_id: sessionId, // Macro-agent's own session ID for ACP protocol mapping
376
+ agent,
377
+ session,
378
+ workspace,
379
+ streamId: resolvedStreamId,
380
+ };
295
381
  }
296
- // Notify lifecycle listeners
297
- notifyLifecycle({ type: "spawned", agent });
298
- notifyLifecycle({ type: "started", agent });
299
- // Start health monitoring for coordinators
300
- if (healthCheckService && role === "coordinator") {
301
- healthCheckService.startForCoordinator(agentId);
382
+ catch (handleError) {
383
+ // Close the spawned process to prevent orphaning
384
+ try {
385
+ await handle.close();
386
+ }
387
+ catch {
388
+ // Ignore errors during cleanup
389
+ }
390
+ throw handleError;
302
391
  }
303
- return {
304
- id: agentId,
305
- session_id: sessionId, // Macro-agent's own session ID for ACP protocol mapping
306
- agent,
307
- session,
308
- workspace,
309
- streamId: resolvedStreamId,
310
- };
311
392
  }
312
393
  catch (error) {
313
394
  // Clean up the spawn event we already emitted
314
395
  eventStore.emit({
315
- type: "terminate",
396
+ type: "stop",
316
397
  source: { agent_id: agentId },
317
398
  payload: {
318
399
  reason: "failed",
@@ -355,6 +436,10 @@ export function createAgentManager(eventStore, messageRouter, config = {}) {
355
436
  // Continue with termination even if workspace cleanup fails
356
437
  }
357
438
  }
439
+ // Revoke agent authentication token
440
+ if (agentTokenManager) {
441
+ agentTokenManager.revokeToken(agentId);
442
+ }
358
443
  // ─────────────────────────────────────────────────────────────────
359
444
  // Mail: Close task conversation on terminate
360
445
  // ─────────────────────────────────────────────────────────────────
@@ -383,9 +468,9 @@ export function createAgentManager(eventStore, messageRouter, config = {}) {
383
468
  console.warn(`[AgentManager] Failed to close conversation for ${agentId}:`, err);
384
469
  }
385
470
  }
386
- // Emit terminate event
471
+ // Emit stop event
387
472
  eventStore.emit({
388
- type: "terminate",
473
+ type: "stop",
389
474
  source: { agent_id: agentId },
390
475
  payload: {
391
476
  agent_id: agentId,
@@ -442,7 +527,10 @@ export function createAgentManager(eventStore, messageRouter, config = {}) {
442
527
  }
443
528
  }
444
529
  }
445
- async function resume(agentId) {
530
+ async function resume(agentId, overridePermissionMode) {
531
+ if (isShuttingDown) {
532
+ throw new AgentManagerError("Cannot resume agent during shutdown", "SHUTDOWN_IN_PROGRESS", agentId);
533
+ }
446
534
  const agent = eventStore.getAgent(agentId);
447
535
  if (!agent) {
448
536
  throw new AgentManagerError(`Agent not found: ${agentId}`, "AGENT_NOT_FOUND", agentId);
@@ -451,57 +539,216 @@ export function createAgentManager(eventStore, messageRouter, config = {}) {
451
539
  if (activeSessions.has(agentId)) {
452
540
  throw new AgentManagerError(`Agent already has active session: ${agentId}`, "ALREADY_RUNNING", agentId);
453
541
  }
542
+ const permissionMode = overridePermissionMode ?? defaultPermissionMode;
454
543
  // Spawn new process
455
544
  const handle = await AgentFactory.spawn(defaultAgentType, {
456
- permissionMode: defaultPermissionMode,
545
+ permissionMode,
457
546
  });
458
- const agentCwd = agent.cwd ?? defaultCwd;
459
- let session;
460
- if (agent.provider_session_id) {
461
- // Load existing session using the provider's session ID (e.g., Claude Code UUID)
462
- session = await handle.loadSession(agent.provider_session_id, agentCwd);
463
- }
464
- else {
465
- // No provider session ID available (agent predates this feature or wasn't persisted).
466
- // Create a new session instead of loading with the macro-agent session_id
467
- // which is not a valid provider session ID (e.g., Claude Code expects UUIDs).
468
- session = await handle.createSession(agentCwd);
469
- // Store the provider session ID for future resumes
547
+ try {
548
+ const agentCwd = agent.cwd ?? defaultCwd;
549
+ let session;
550
+ // When interactive mode, strip settings to prevent auto-approval
551
+ const resumeAgentMeta = permissionMode === "interactive"
552
+ ? { claudeCode: { options: { settingSources: [] } } }
553
+ : undefined;
554
+ const macroAgentMcp = buildMacroAgentMcp({
555
+ agentId,
556
+ parentId: agent.parent ?? "",
557
+ taskId: agent.task_id ?? "",
558
+ cwd: agentCwd,
559
+ permissionMode,
560
+ lineage: agent.lineage ?? [],
561
+ sessionId: agent.session_id ?? "",
562
+ });
563
+ const mcpServers = [macroAgentMcp];
564
+ if (agent.provider_session_id) {
565
+ // Load existing session using the provider's session ID (e.g., Claude Code UUID)
566
+ // Note: loadSession's TS type for mcpServers is { name, uri }[] but
567
+ // the underlying ACP protocol accepts full McpServerStdio. The JS
568
+ // implementation passes mcpServers through to the connection unchanged.
569
+ session = await handle.loadSession(agent.provider_session_id, agentCwd, mcpServers, resumeAgentMeta ? { agentMeta: resumeAgentMeta } : undefined);
570
+ }
571
+ else {
572
+ // No provider session ID available (agent predates this feature or wasn't persisted).
573
+ // Create a new session instead of loading with the macro-agent session_id
574
+ // which is not a valid provider session ID (e.g., Claude Code expects UUIDs).
575
+ session = await handle.createSession(agentCwd, {
576
+ mcpServers,
577
+ ...(resumeAgentMeta && { agentMeta: resumeAgentMeta }),
578
+ });
579
+ // Store the provider session ID for future resumes
580
+ eventStore.emit({
581
+ type: "status",
582
+ source: { agent_id: agentId },
583
+ payload: {
584
+ status_type: "started",
585
+ summary: "Agent session created (no provider session to resume)",
586
+ provider_session_id: session.id,
587
+ },
588
+ });
589
+ }
590
+ // Track active session
591
+ const activeSession = {
592
+ agentId,
593
+ handle,
594
+ session,
595
+ createdAt: Date.now(),
596
+ isPrompting: false,
597
+ };
598
+ activeSessions.set(agentId, activeSession);
599
+ // Emit status event for resume
470
600
  eventStore.emit({
471
601
  type: "status",
472
602
  source: { agent_id: agentId },
473
603
  payload: {
474
604
  status_type: "started",
475
- summary: "Agent session created (no provider session to resume)",
605
+ summary: "Agent session resumed",
476
606
  provider_session_id: session.id,
477
607
  },
478
608
  });
609
+ return {
610
+ id: agentId,
611
+ session_id: agent.session_id, // Macro-agent's own session ID
612
+ agent: eventStore.getAgent(agentId),
613
+ session,
614
+ };
479
615
  }
480
- // Track active session
481
- const activeSession = {
482
- agentId,
483
- handle,
484
- session,
485
- createdAt: Date.now(),
486
- isPrompting: false,
487
- };
488
- activeSessions.set(agentId, activeSession);
489
- // Emit status event for resume
616
+ catch (handleError) {
617
+ // Close the spawned process to prevent orphaning
618
+ try {
619
+ await handle.close();
620
+ }
621
+ catch {
622
+ // Ignore errors during cleanup
623
+ }
624
+ throw handleError;
625
+ }
626
+ }
627
+ // ─────────────────────────────────────────────────────────────────
628
+ // Fork
629
+ // ─────────────────────────────────────────────────────────────────
630
+ async function forkAgent(sourceAgentId, options) {
631
+ if (isShuttingDown) {
632
+ throw new AgentManagerError("Cannot fork agent during shutdown", "SHUTDOWN_IN_PROGRESS", sourceAgentId);
633
+ }
634
+ const sourceAgent = eventStore.getAgent(sourceAgentId);
635
+ if (!sourceAgent) {
636
+ throw new AgentManagerError(`Agent not found: ${sourceAgentId}`, "AGENT_NOT_FOUND", sourceAgentId);
637
+ }
638
+ // Need either an active session or a persisted provider_session_id
639
+ const activeSession = activeSessions.get(sourceAgentId);
640
+ if (!activeSession && !sourceAgent.provider_session_id) {
641
+ throw new AgentManagerError(`Agent has no session to fork: ${sourceAgentId}`, "FORK_NOT_SUPPORTED", sourceAgentId);
642
+ }
643
+ // Generate new IDs
644
+ const agentId = `agent_${nanoid(12)}`;
645
+ const taskId = `task_${nanoid(12)}`;
646
+ const sessionId = `session_${nanoid(12)}`;
647
+ const cwd = options?.cwd ?? sourceAgent.cwd ?? defaultCwd;
648
+ // Emit spawn event with fork metadata
490
649
  eventStore.emit({
491
- type: "status",
492
- source: { agent_id: agentId },
650
+ type: "spawn",
651
+ source: { agent_id: sourceAgentId },
493
652
  payload: {
494
- status_type: "started",
495
- summary: "Agent session resumed",
496
- provider_session_id: session.id,
653
+ agent_id: agentId,
654
+ session_id: sessionId,
655
+ task: options?.name ?? `[Fork of ${sourceAgentId}]`,
656
+ task_id: taskId,
657
+ parent: sourceAgent.parent ?? null,
658
+ role: sourceAgent.role ?? undefined,
659
+ config: {},
660
+ cwd,
661
+ metadata: { fork_of: sourceAgentId },
497
662
  },
498
663
  });
499
- return {
500
- id: agentId,
501
- session_id: agent.session_id, // Macro-agent's own session ID
502
- agent: eventStore.getAgent(agentId),
503
- session,
504
- };
664
+ // Generate a human-readable name
665
+ const generatedName = uniqueNamesGenerator({
666
+ dictionaries: [adjectives, animals],
667
+ separator: "-",
668
+ length: 2,
669
+ });
670
+ eventStore.updateAgentMetadata(agentId, { name: generatedName });
671
+ await eventStore.persist();
672
+ // Get the provider session ID to fork from
673
+ let forkedProviderSessionId;
674
+ if (activeSession) {
675
+ // Active session: fork with flush to ensure data is persisted
676
+ const forkedSession = await activeSession.session.forkWithFlush();
677
+ forkedProviderSessionId = forkedSession.id;
678
+ }
679
+ else {
680
+ // Stopped agent: use the persisted provider session ID directly
681
+ forkedProviderSessionId = sourceAgent.provider_session_id;
682
+ }
683
+ // Spawn a new process
684
+ const handle = await AgentFactory.spawn(defaultAgentType, {
685
+ permissionMode: defaultPermissionMode,
686
+ });
687
+ try {
688
+ const macroAgentMcp = buildMacroAgentMcp({
689
+ agentId,
690
+ parentId: sourceAgent.parent ?? "",
691
+ taskId,
692
+ cwd,
693
+ permissionMode: defaultPermissionMode,
694
+ lineage: sourceAgent.lineage ?? [],
695
+ sessionId,
696
+ });
697
+ // Load the forked session on the new process with correct MCP config.
698
+ // Note: loadSession's TS type for mcpServers is { name, uri }[] but
699
+ // the underlying ACP protocol accepts full McpServerStdio. The JS
700
+ // implementation passes mcpServers through to the connection unchanged.
701
+ const session = await handle.loadSession(forkedProviderSessionId, cwd, [
702
+ macroAgentMcp,
703
+ ]);
704
+ // Emit started status with provider session ID
705
+ eventStore.emit({
706
+ type: "status",
707
+ source: { agent_id: agentId },
708
+ payload: {
709
+ status_type: "started",
710
+ summary: "Agent session started (forked)",
711
+ provider_session_id: session.id,
712
+ },
713
+ });
714
+ await eventStore.persist();
715
+ // Set up message router subscriptions
716
+ messageRouter.setupDefaultSubscriptions({
717
+ agent_id: agentId,
718
+ parent_id: sourceAgent.parent ?? undefined,
719
+ task_id: taskId,
720
+ subscribe_parent: false,
721
+ additional_topics: [],
722
+ role: sourceAgent.role ?? undefined,
723
+ });
724
+ // Track active session
725
+ const newActiveSession = {
726
+ agentId,
727
+ handle,
728
+ session,
729
+ createdAt: Date.now(),
730
+ isPrompting: false,
731
+ };
732
+ activeSessions.set(agentId, newActiveSession);
733
+ const agent = eventStore.getAgent(agentId);
734
+ notifyLifecycle({ type: "spawned", agent });
735
+ notifyLifecycle({ type: "started", agent });
736
+ return {
737
+ id: agentId,
738
+ session_id: sessionId,
739
+ agent,
740
+ session,
741
+ };
742
+ }
743
+ catch (handleError) {
744
+ try {
745
+ await handle.close();
746
+ }
747
+ catch {
748
+ // Ignore errors during cleanup
749
+ }
750
+ throw handleError;
751
+ }
505
752
  }
506
753
  // ─────────────────────────────────────────────────────────────────
507
754
  // Queries
@@ -654,11 +901,12 @@ export function createAgentManager(eventStore, messageRouter, config = {}) {
654
901
  const agentCompletedStatus = statusEvents.find((e) => e.source?.agent_id === agentId &&
655
902
  (e.payload?.status_type === "completed" ||
656
903
  e.payload?.status_type === "failed" ||
657
- e.payload?.details?.signal === "WORKER_DONE"));
904
+ e.payload?.details?.signal ===
905
+ "WORKER_DONE"));
658
906
  if (agentCompletedStatus) {
659
907
  return {
660
908
  called: true,
661
- status: agentCompletedStatus.payload?.status_type
909
+ status: agentCompletedStatus.payload?.status_type,
662
910
  };
663
911
  }
664
912
  return { called: false };
@@ -789,6 +1037,29 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
789
1037
  return false;
790
1038
  }
791
1039
  }
1040
+ function setPermissionMode(agentId, mode) {
1041
+ const activeSession = activeSessions.get(agentId);
1042
+ if (!activeSession) {
1043
+ console.warn(`[AgentManager] Cannot set permission mode: no active session for agent ${agentId}`);
1044
+ return false;
1045
+ }
1046
+ try {
1047
+ activeSession.handle.setPermissionMode(mode);
1048
+ console.log(`[AgentManager] Set permission mode for agent ${agentId} to ${mode}`);
1049
+ return true;
1050
+ }
1051
+ catch (err) {
1052
+ console.error(`[AgentManager] Error setting permission mode for agent ${agentId}:`, err);
1053
+ return false;
1054
+ }
1055
+ }
1056
+ function getPermissionMode(agentId) {
1057
+ const activeSession = activeSessions.get(agentId);
1058
+ if (!activeSession) {
1059
+ return null;
1060
+ }
1061
+ return activeSession.handle.getPermissionMode();
1062
+ }
792
1063
  // ─────────────────────────────────────────────────────────────────
793
1064
  // Lifecycle Callbacks
794
1065
  // ─────────────────────────────────────────────────────────────────
@@ -807,6 +1078,12 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
807
1078
  }
808
1079
  }
809
1080
  // ─────────────────────────────────────────────────────────────────
1081
+ // OpenTasks Socket Path (Late Binding)
1082
+ // ─────────────────────────────────────────────────────────────────
1083
+ function setOpenTasksSocketPath(socketPath) {
1084
+ configOpenTasksSocketPath = socketPath;
1085
+ }
1086
+ // ─────────────────────────────────────────────────────────────────
810
1087
  // Mail Services (Late Binding)
811
1088
  // ─────────────────────────────────────────────────────────────────
812
1089
  function setMailServices(ms, cm) {
@@ -817,6 +1094,8 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
817
1094
  // Cleanup
818
1095
  // ─────────────────────────────────────────────────────────────────
819
1096
  async function close() {
1097
+ // Prevent new spawns/resumes from racing with cleanup
1098
+ isShuttingDown = true;
820
1099
  // Stop all health checks
821
1100
  if (healthCheckService) {
822
1101
  healthCheckService.stopAll();
@@ -876,9 +1155,7 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
876
1155
  }
877
1156
  const resumeContext = contextLines.join("\n");
878
1157
  // Spawn a continuation agent with same role, task, and context
879
- const taskDescription = options?.task ??
880
- agent.task ??
881
- `Continue work from ${agentId}`;
1158
+ const taskDescription = options?.task ?? agent.task ?? `Continue work from ${agentId}`;
882
1159
  const newAgent = await spawn({
883
1160
  task: taskDescription,
884
1161
  role: agent.role,
@@ -903,6 +1180,7 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
903
1180
  terminate,
904
1181
  resume,
905
1182
  continueAgent,
1183
+ forkAgent,
906
1184
  get,
907
1185
  list,
908
1186
  getChildren,
@@ -918,9 +1196,12 @@ Call done() NOW with status "completed" if your work is finished, or "blocked" i
918
1196
  isProcessRunning,
919
1197
  respondToPermission,
920
1198
  cancelPermission,
1199
+ setPermissionMode,
1200
+ getPermissionMode,
921
1201
  onLifecycleEvent,
922
1202
  setSpawnInterceptor,
923
1203
  getRoleRegistry,
1204
+ setOpenTasksSocketPath,
924
1205
  setMailServices,
925
1206
  close,
926
1207
  };