macro-agent 0.1.0 → 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 (337) 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 +15 -0
  6. package/dist/acp/macro-agent.d.ts.map +1 -1
  7. package/dist/acp/macro-agent.js +131 -35
  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 +17 -0
  53. package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
  54. package/dist/map/adapter/acp-over-map.js +384 -23
  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 +3 -0
  95. package/dist/map/adapter/map-adapter.d.ts.map +1 -1
  96. package/dist/map/adapter/map-adapter.js +258 -35
  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 +2 -1
  131. package/dist/store/event-store.d.ts.map +1 -1
  132. package/dist/store/event-store.js +69 -20
  133. package/dist/store/event-store.js.map +1 -1
  134. package/dist/store/types/agents.d.ts +18 -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__/integration.test.ts +56 -31
  197. package/src/acp/__tests__/macro-agent.test.ts +16 -7
  198. package/src/acp/macro-agent.ts +170 -36
  199. package/src/acp/types.ts +46 -1
  200. package/src/agent/__tests__/agent-manager.test.ts +228 -2
  201. package/src/agent/agent-manager.ts +714 -261
  202. package/src/agent/types.ts +3 -1
  203. package/src/api/server.ts +41 -7
  204. package/src/auth/__tests__/token.test.ts +100 -0
  205. package/src/auth/index.ts +1 -0
  206. package/src/auth/token.ts +82 -0
  207. package/src/cli/__tests__/acp.test.ts +1 -1
  208. package/src/cli/__tests__/stable-instance-id.test.ts +1 -1
  209. package/src/cli/acp.ts +130 -72
  210. package/src/cli/index.ts +120 -14
  211. package/src/cli/mcp.ts +311 -207
  212. package/src/cli/parse-args.ts +54 -0
  213. package/src/cli/stable-instance-id.ts +14 -0
  214. package/src/config/project-config.ts +190 -27
  215. package/src/lifecycle/__tests__/cascade-termination.test.ts +1 -1
  216. package/src/map/adapter/__tests__/acp-over-map-cancel.test.ts +22 -4
  217. package/src/map/adapter/__tests__/acp-over-map-getmodels.test.ts +355 -0
  218. package/src/map/adapter/__tests__/acp-over-map-history.test.ts +263 -0
  219. package/src/map/adapter/__tests__/acp-over-map-persistence.e2e.test.ts +1 -1
  220. package/src/map/adapter/__tests__/event-broadcast.test.ts +420 -0
  221. package/src/map/adapter/__tests__/event-log.test.ts +527 -0
  222. package/src/map/adapter/__tests__/event-translator.test.ts +3 -3
  223. package/src/map/adapter/__tests__/extensions.test.ts +408 -0
  224. package/src/map/adapter/__tests__/map-adapter.test.ts +99 -0
  225. package/src/map/adapter/__tests__/mcp-bridge.test.ts +1187 -0
  226. package/src/map/adapter/__tests__/multi-client-broadcast.test.ts +711 -0
  227. package/src/map/adapter/__tests__/websocket-integration.test.ts +218 -0
  228. package/src/map/adapter/acp-over-map.ts +678 -66
  229. package/src/map/adapter/connection-manager.ts +3 -0
  230. package/src/map/adapter/event-log.ts +208 -0
  231. package/src/map/adapter/event-translator.ts +6 -6
  232. package/src/map/adapter/extensions/agent-lifecycle.ts +267 -0
  233. package/src/map/adapter/extensions/index.ts +60 -0
  234. package/src/map/adapter/extensions/mcp-bridge.ts +995 -0
  235. package/src/map/adapter/extensions/task.ts +11 -0
  236. package/src/map/adapter/extensions/update-metadata.ts +126 -0
  237. package/src/map/adapter/index.ts +28 -0
  238. package/src/map/adapter/interface.ts +2 -0
  239. package/src/map/adapter/map-adapter.ts +312 -47
  240. package/src/map/adapter/subscription-manager.ts +5 -1
  241. package/src/map/adapter/types.ts +2 -0
  242. package/src/mcp/__tests__/map-client.test.ts +386 -0
  243. package/src/mcp/__tests__/mcp-server-thin-client.test.ts +368 -0
  244. package/src/mcp/__tests__/mcp-server.test.ts +100 -1
  245. package/src/mcp/map-client.ts +177 -0
  246. package/src/mcp/mcp-server.ts +191 -100
  247. package/src/mcp/types.ts +6 -1
  248. package/src/metrics/metrics.ts +1 -1
  249. package/src/monitor/__tests__/stale-agent-flow.integration.test.ts +1 -1
  250. package/src/roles/__tests__/config-loader.test.ts +7 -7
  251. package/src/roles/capabilities.ts +17 -7
  252. package/src/roles/config-loader.ts +6 -6
  253. package/src/roles/registry.ts +2 -2
  254. package/src/server/__tests__/combined-server.test.ts +94 -21
  255. package/src/server/combined-server.ts +189 -33
  256. package/src/steering/__tests__/steering-integration.test.ts +1 -1
  257. package/src/store/__tests__/event-store.test.ts +196 -1
  258. package/src/store/__tests__/instance.test.ts +3 -3
  259. package/src/store/event-store.ts +80 -21
  260. package/src/store/types/agents.ts +15 -0
  261. package/src/store/types/events.ts +1 -1
  262. package/src/task/backend/__tests__/create-task-backend.test.ts +225 -0
  263. package/src/task/backend/__tests__/e2e/unified-tool-provider-opentasks.e2e.test.ts +524 -0
  264. package/src/task/backend/__tests__/unified-tool-provider.test.ts +579 -0
  265. package/src/task/backend/index.ts +156 -106
  266. package/src/task/backend/memory.ts +4 -0
  267. package/src/task/backend/opentasks/__tests__/backend.test.ts +968 -0
  268. package/src/task/backend/opentasks/__tests__/daemon-manager.test.ts +406 -0
  269. package/src/task/backend/opentasks/__tests__/mapping.test.ts +84 -0
  270. package/src/task/backend/opentasks/__tests__/opentasks-backend.e2e.test.ts +1338 -0
  271. package/src/task/backend/opentasks/backend.ts +1323 -0
  272. package/src/task/backend/opentasks/client.ts +652 -0
  273. package/src/task/backend/opentasks/daemon-manager.ts +253 -0
  274. package/src/task/backend/opentasks/index.ts +69 -0
  275. package/src/task/backend/opentasks/mapping.ts +94 -0
  276. package/src/task/backend/types.ts +42 -66
  277. package/src/task/backend/unified-tool-provider.ts +779 -0
  278. package/src/teams/__tests__/cross-subsystem.integration.test.ts +1 -1
  279. package/src/teams/team-loader.ts +3 -3
  280. package/src/teams/team-runtime.ts +2 -0
  281. package/test_fixtures/README.md +2 -3
  282. package/test_fixtures/fixtures/index.ts +0 -3
  283. package/test_fixtures/fixtures/projects/project-with-specs.ts +7 -149
  284. package/test_fixtures/fixtures/repos/index.ts +1 -3
  285. package/test_fixtures/fixtures/repos/temp-repo-factory.ts +0 -116
  286. package/test_fixtures/fixtures/repos/types.ts +0 -11
  287. package/test_fixtures/harness/__tests__/fixtures.test.ts +10 -102
  288. package/test_fixtures/harness/__tests__/temp-repo-and-simulator.test.ts +0 -33
  289. package/test_fixtures/harness/simulator/agent-simulator.ts +4 -4
  290. package/vitest.config.ts +1 -1
  291. package/vitest.e2e.config.ts +1 -1
  292. package/vitest.setup.ts +1 -30
  293. package/.macro-agent/teams/self-driving/prompts/grinder.md +0 -27
  294. package/.macro-agent/teams/self-driving/prompts/judge.md +0 -27
  295. package/.macro-agent/teams/self-driving/prompts/planner.md +0 -33
  296. package/.macro-agent/teams/self-driving/roles/grinder.yaml +0 -17
  297. package/.macro-agent/teams/self-driving/roles/judge.yaml +0 -24
  298. package/.macro-agent/teams/self-driving/roles/planner.yaml +0 -18
  299. package/.macro-agent/teams/self-driving/team.yaml +0 -103
  300. package/.macro-agent/teams/structured/prompts/developer.md +0 -26
  301. package/.macro-agent/teams/structured/prompts/lead.md +0 -25
  302. package/.macro-agent/teams/structured/prompts/reviewer.md +0 -24
  303. package/.macro-agent/teams/structured/roles/developer.yaml +0 -12
  304. package/.macro-agent/teams/structured/roles/lead.yaml +0 -11
  305. package/.macro-agent/teams/structured/roles/reviewer.yaml +0 -19
  306. package/.macro-agent/teams/structured/team.yaml +0 -89
  307. package/docs/sudocode-integration.md +0 -383
  308. package/src/task/backend/__tests__/backend-parity.test.ts +0 -451
  309. package/src/task/backend/__tests__/tool-provider-edge-cases.test.ts +0 -430
  310. package/src/task/backend/__tests__/tool-provider.test.ts +0 -983
  311. package/src/task/backend/sudocode/__tests__/backend-edge-cases.test.ts +0 -575
  312. package/src/task/backend/sudocode/__tests__/backend.test.ts +0 -1194
  313. package/src/task/backend/sudocode/__tests__/client-integration.test.ts +0 -418
  314. package/src/task/backend/sudocode/__tests__/client.test.ts +0 -345
  315. package/src/task/backend/sudocode/__tests__/e2e/backend.e2e.test.ts +0 -753
  316. package/src/task/backend/sudocode/__tests__/e2e/server-client.e2e.test.ts +0 -680
  317. package/src/task/backend/sudocode/__tests__/e2e-workflow.test.ts +0 -666
  318. package/src/task/backend/sudocode/__tests__/integration/standalone-client.integration.test.ts +0 -396
  319. package/src/task/backend/sudocode/__tests__/integration/sudocode-cli.integration.test.ts +0 -328
  320. package/src/task/backend/sudocode/__tests__/integration/test-utils.ts +0 -175
  321. package/src/task/backend/sudocode/__tests__/mapping-edge-cases.test.ts +0 -265
  322. package/src/task/backend/sudocode/__tests__/server-client.test.ts +0 -675
  323. package/src/task/backend/sudocode/__tests__/sync-policy-edge-cases.test.ts +0 -521
  324. package/src/task/backend/sudocode/__tests__/sync-policy.test.ts +0 -519
  325. package/src/task/backend/sudocode/__tests__/tools.test.ts +0 -471
  326. package/src/task/backend/sudocode/backend.ts +0 -1237
  327. package/src/task/backend/sudocode/client.ts +0 -515
  328. package/src/task/backend/sudocode/index.ts +0 -120
  329. package/src/task/backend/sudocode/mapping.ts +0 -93
  330. package/src/task/backend/sudocode/server-client.ts +0 -522
  331. package/src/task/backend/sudocode/standalone-client.ts +0 -623
  332. package/src/task/backend/sudocode/sync-policy.ts +0 -387
  333. package/src/task/backend/sudocode/tools.ts +0 -896
  334. package/src/task/backend/tool-provider.ts +0 -506
  335. package/test_fixtures/fixtures/sudocode/index.ts +0 -29
  336. package/test_fixtures/fixtures/sudocode/issues.ts +0 -185
  337. package/test_fixtures/fixtures/sudocode/specs.ts +0 -159
@@ -53,7 +53,9 @@
53
53
  "Bash(npm unlink:*)",
54
54
  "mcp__macro-agent__spawn_agent",
55
55
  "Bash(git stash:*)",
56
- "Bash(do echo \"Testing $f\")"
56
+ "Bash(do echo \"Testing $f\")",
57
+ "Bash(ps:*)",
58
+ "Bash(sort:*)"
57
59
  ]
58
60
  }
59
61
  }
@@ -46,3 +46,7 @@
46
46
  {"id":"s-5w36","uuid":"2e027264-1fcb-4943-bab6-4b2e35a5c2a0","title":"Session Continuations (Phase 4)","file_path":"specs/s-5w36_session_continuations_phase_4.md","content":"\n# Session Continuations (Phase 4)\n\nPersist agent session history so long-running agents can be resumed across process restarts.\n\n## Status: COMPLETE (untested continuation monitoring)\n\n## Components\n\n### continueAgent() (`src/agent/agent-manager.ts`, lines 1384-1450)\n1. Load original agent from EventStore\n2. Query status events (up to `maxMessages`, default 50)\n3. Format event summaries as \"Prior Session Context\" markdown\n4. Spawn new agent with same role/parent + resume context as `customPrompt`\n5. Emit continuation event (`continuation_of: agentId`)\n\n```typescript\ninterface ContinueAgentOptions {\n maxMessages?: number; // History depth (default: 50)\n task?: string; // Override task description\n additionalContext?: string; // Extra context to prepend\n}\n```\n\n### Continuation monitoring (`src/teams/team-runtime.ts`)\n- `monitorContinuations()` watches agent lifecycle events via `onLifecycleEvent()`\n- Monitors root and companion agent IDs\n- On unexpected stop (not \"completed\" or \"cancelled\"): wait 1s → `continueAgent()` → update tracking\n- Only active when `lifecycle.continuations.enabled === true` in team manifest\n- Unsubscribes on teardown\n\n## Known Gap\n- **No test coverage** for `monitorContinuations()` — edge cases untested (multiple lifecycle events, spawn failure, state consistency)\n","priority":1,"archived":0,"archived_at":null,"created_at":"2026-02-07 21:26:08","updated_at":"2026-02-07 21:26:08","parent_id":"s-1z9o","parent_uuid":"251030e1-ea2a-4857-95b3-2d94619f0b69","relationships":[{"from":"s-5w36","from_type":"spec","to":"s-1z9o","to_type":"spec","type":"implements"}],"tags":["continuations","phase-4","self-driving","teams"]}
47
47
  {"id":"s-ir97","uuid":"9d8a27cd-2a68-4664-8384-a5c1bb657f71","title":"Autonomous Observability (Phase 5)","file_path":"specs/s-ir97_autonomous_observability_phase_5.md","content":"\n# Autonomous Observability (Phase 5)\n\nMetrics primitives for monitoring throughput, error rates, and agent utilization during long-running multi-agent runs.\n\n## Status: COMPLETE\n\n## Components\n\n### Metrics functions (`src/metrics/metrics.ts`, 280 LOC)\n\n| Function | Queries | Returns |\n|----------|---------|---------|\n| `getThroughputMetrics(store, windowMs)` | Task events | tasksCompleted, tasksFailed, tasksCreated, completedPerMinute, avgCompletionTimeMs |\n| `getUtilizationMetrics(store, windowMs)` | Agent spawn/terminate | activeAgents, totalSpawned, totalStopped, agentsByRole, agentsByState |\n| `getErrorMetrics(store, windowMs, limit)` | Failed status + task events | totalErrors, errorsByType, recentErrors |\n\nAll functions query EventStore directly — no new event types or materialized views needed.\n\n### REST API endpoints (`src/api/server.ts`)\n\n| Endpoint | Query Params | Description |\n|----------|-------------|-------------|\n| `GET /api/metrics/throughput` | `window_ms` (default: 5min) | Task completion rates |\n| `GET /api/metrics/utilization` | — | Agent counts by role and state |\n| `GET /api/metrics/errors` | `window_ms` (default: 30min), `limit` (default: 20) | Error counts and recent failures |\n","priority":1,"archived":0,"archived_at":null,"created_at":"2026-02-07 21:26:16","updated_at":"2026-02-07 21:26:16","parent_id":"s-1z9o","parent_uuid":"251030e1-ea2a-4857-95b3-2d94619f0b69","relationships":[{"from":"s-ir97","from_type":"spec","to":"s-1z9o","to_type":"spec","type":"implements"}],"tags":["metrics","phase-5","self-driving","teams"]}
48
48
  {"id":"s-29pg","uuid":"7e8bbb6d-51df-403b-8929-5b2793603367","title":"Communication Topology Gaps","file_path":"specs/s-29pg_communication_topology_gaps.md","content":"\n# Communication Topology Gaps\n\nFeatures where YAML config was loaded and validated but not wired into runtime behavior. Identified in the post-implementation audit.\n\n## Status: COMPLETE\n\nAll 5 gaps resolved. Implementation feedback attached to each closed issue.\n\n## Gap 1: Signal Filtering — IMPLEMENTED\n\n**Issue**: `i-3o8g` (closed)\n\n`SignalFilter` callback on MessageRouter, installed by TeamRuntime. Checks two sources:\n1. Peer connection filters (per-agent-pair, directional) from `routing.peers[].signals`\n2. Channel subscription filters (per-role) from `subscriptions[role][].signals`\n\nSignal name carried in `details.signal` field. Untagged events always pass through.\n\n## Gap 2: Peer Routing from Config — IMPLEMENTED\n\n**Issue**: `i-96f6` (closed)\n\n`wirePeerRoutes()` in TeamRuntime reads `routing.peers`, maps `via` to subscription type:\n- `direct` → subtree subscription (directional)\n- `topic` → shared named topic for both agents\n- `scope` → role-based subscription\n\nDeferred wiring for roles not spawned at bootstrap. Signal filters stored per-peer.\n\n## Gap 3: Wake Logic for Status Delivery — IMPLEMENTED\n\n**Issue**: `i-4dh7` (closed)\n\nBoth `routeStatusToSubtreeSubscribers()` and `routeStatusToTopicSubscribers()` now call `wakeHandler` with `priority: \"normal\"` (wakes idle agents, queues for busy).\n\n## Gap 4: Emission Restrictions — IMPLEMENTED\n\n**Issue**: `i-1zso` (closed)\n\n`EmissionValidator` callback on MessageRouter. Checks agent role against `communication.emissions` allowlist. Enforcement mode determines action.\n\n## Gap 5: Enforcement Mode — IMPLEMENTED\n\n**Issue**: `i-1zso` (closed)\n\nBranches on enforcement mode in emission validator:\n- `strict` → reject (block emission)\n- `permissive` → warn (allow through)\n- `audit` → record `emission_violation` event in EventStore, allow through\n","priority":2,"archived":0,"archived_at":null,"created_at":"2026-02-07 21:26:38","updated_at":"2026-02-09 08:17:22","parent_id":"s-1z9o","parent_uuid":"251030e1-ea2a-4857-95b3-2d94619f0b69","relationships":[{"from":"s-29pg","from_type":"spec","to":"s-1z9o","to_type":"spec","type":"implements"}],"tags":["communication","gaps","self-driving","teams"]}
49
+ {"id":"s-7umt","uuid":"d8cca27a-4005-43dd-9732-33dde3ac85d5","title":"Multi-Team Support: Concurrent Teams on Single Server","file_path":"specs/s-7umt_multi_team_support_concurrent_teams_on_single_serv.md","content":"\n## Overview\n\nEnable multiple concurrent teams to coexist on a single macro-agent server instance. Currently the system supports exactly one team at a time due to global singleton callback slots (spawn interceptor, signal filter, emission validator) and a single `team_config` event in EventStore.\n\n## Key Design Decision: Multiplexer Pattern\n\nRather than making every global service team-aware (which would be invasive and break the clean separation of concerns), introduce a **TeamManager** that sits between the global services and multiple TeamRuntime instances. The TeamManager installs a single composite callback on each global service that dispatches to the correct TeamRuntime based on agent membership.\n\n## Requirements\n\n1. Multiple TeamRuntime instances coexist in same process\n2. Each team has isolated spawn interceptor logic, signal filters, emission validators\n3. Agents are associated with a specific team (persisted)\n4. Teams can be started/stopped independently\n5. Teams don't interfere with each other's communication topology\n6. Backward compatible: single team still works identically\n7. Agents not in any team continue to work normally (no team context applied)\n8. Cross-team messaging is blocked by default (agents can only communicate within their team)\n\n## Architecture\n\n```\nCLI (--team alpha --team beta)\n │\n ▼\n TeamManager\n ├── TeamRuntime(\"alpha\")\n │ ├── roleAgentMap\n │ ├── agentRoleMap\n │ ├── peerSignalFilters\n │ └── integrationStrategy\n ├── TeamRuntime(\"beta\")\n │ └── ...\n ├── compositeSpawnInterceptor → AgentManager.setSpawnInterceptor()\n ├── compositeSignalFilter → MessageRouter.setSignalFilter()\n └── compositeEmissionValidator → MessageRouter.setEmissionValidator()\n```\n\n## Phases\n\n### Phase 1: Agent Team Binding (Foundation)\n- Add `team_id` field to Agent record in EventStore\n- Pass `team_id` through spawn event payload\n- Update `applySpawnEvent` to persist team_id\n- Update `rowToAgent` to read team_id\n\n### Phase 2: TeamManager Core\n- New `src/teams/team-manager.ts` class\n- Registry of active TeamRuntime instances\n- Agent-to-team lookup via EventStore (team_id field)\n- Composite spawn interceptor that dispatches by parent team\n- Composite signal filter that dispatches by agent team\n- Composite emission validator that dispatches by agent team\n\n### Phase 3: EventStore Multi-Team Config\n- Change team_config event to include team_name in payload (already present)\n- Add query method to find team_config by team_name\n- MCP subprocess reads correct team_config for its agent's team\n\n### Phase 4: RoleRegistry Namespacing\n- Add optional team scope to custom role registration\n- Resolution: team-scoped custom > global custom > project > builtin\n- Prevent cross-team role name collisions\n\n### Phase 5: CLI and Config Changes\n- Support multiple `--team` flags or comma-separated values\n- Support `teams` array in config.json\n- Loop: load each team, add to TeamManager\n- Shutdown: teardown all teams\n\n### Phase 6: API and Observability\n- `GET /api/teams` endpoint listing all active teams\n- `GET /api/team/:name` endpoint for specific team info\n- Team-scoped agent/task filtering\n- Metrics per team\n\n## Cross-Team Messaging Policy\n\nBy default, cross-team messaging is blocked:\n- The composite signal filter returns `false` for messages between agents in different teams\n- Agents not in any team can communicate freely with each other\n- A future extension could add explicit cross-team channels\n\n## Error Cases\n\n- **Team name conflict**: Reject addTeam() if name already registered\n- **Team teardown with running agents**: Stop team agents before removing from registry\n- **Agent spawned by non-team parent for team role**: Spawn interceptor checks parent team membership\n- **MCP subprocess for agent in unknown team**: Falls back to no team context (backward compat)\n","priority":1,"archived":0,"archived_at":null,"created_at":"2026-02-18 09:33:32","updated_at":"2026-02-18 09:33:32","parent_id":null,"parent_uuid":null,"relationships":[],"tags":["architecture","multi-team","multiplexer"]}
50
+ {"id":"s-931n","uuid":"2d8ea3d7-c317-4d2e-8bb7-bd47d4a2affe","title":"Runtime Configuration Manager","file_path":"specs/s-931n_runtime_configuration_manager.md","content":"\n# Runtime Configuration Manager\n\n## Overview\n\nAdd a `ConfigManager` service that holds runtime configuration in memory, exposes typed getters/setters, and notifies subscribers of changes via an EventEmitter pattern. API endpoints write to ConfigManager; subscribing services (TeamRuntime, RoleRegistry) react to changes and update their own behavior accordingly.\n\n## Design Principles\n\n- **API-first**: Config changes flow through API endpoints, not file edits\n- **Decoupled**: ConfigManager doesn't know about MessageRouter, AgentManager, etc. — subscribers own the wiring to their services\n- **Layered**: File-based config is the base layer; runtime changes are an overlay that takes precedence\n- **Optional persistence**: Some changes write back to files (roles), others are ephemeral (tuning knobs reset to file defaults on restart)\n\n## Configuration Tiers\n\n### Tier 1: Infrastructure (static, load-once)\n**NOT managed by ConfigManager.** Stays as-is in `loadMergedConfig()`.\n- `port`, `host`\n- `auth.disabled`, `auth.secret`\n- `task.backend`, `task.opentasks.socket_path`\n\n### Tier 2: Templates (runtime-writable, affects future spawns)\n- **Role definitions** — capabilities, workspace, lifecycle, tools, prompts\n- **Team role definitions** — team-specific role overrides\n\n### Tier 3: Tuning (runtime-writable, affects running system)\n- `communication.enforcement` — strict | permissive | audit\n- `macro_agent.task_assignment.pull.*` — idle_timeout_s, claim_retry_delay_ms, max_concurrent_per_agent\n- `macro_agent.lifecycle.scaling.*` — min_workers, max_workers, idle_drain\n- `macro_agent.observability.*` — metrics_window_s, snapshot_interval_s\n- `macro_agent.integration.config.*` — max_retries, conflict_action (strategy params, not strategy choice)\n\n## ConfigManager Interface\n\n```typescript\ninterface ConfigManager extends EventEmitter {\n // ─── Read ────────────────────────────────────────────────\n /** Get a config value by dot-path. Returns runtime override if set, else file-based default. */\n get<T>(path: string): T | undefined;\n\n /** Get full section (e.g., \"roles\", \"tuning.scaling\") */\n getSection<T>(section: string): T | undefined;\n\n /** Get effective config: file base merged with runtime overrides */\n getEffective(): RuntimeConfig;\n\n // ─── Write ───────────────────────────────────────────────\n /** Set a runtime override. Emits \"config:<section>\" event. */\n set(path: string, value: unknown): void;\n\n /** Merge a partial config into a section. Emits \"config:<section>\" event. */\n merge(section: string, partial: Record<string, unknown>): void;\n\n /** Remove a runtime override, reverting to file-based default. */\n unset(path: string): void;\n\n // ─── Persistence ─────────────────────────────────────────\n /** Persist current runtime overrides to file (for sections that support it) */\n persist(section?: string): Promise<void>;\n\n // ─── Lifecycle ───────────────────────────────────────────\n /** Initialize from file-based config */\n initialize(fileConfig: MultiagentConfig, teamManifest?: TeamManifest): void;\n}\n```\n\n## Config Sections & Events\n\n| Section | Event name | Subscriber | Action on change |\n|---------|-----------|------------|-----------------|\n| `roles.<name>` | `config:roles` | RoleRegistry | `registerRole()` with updated definition |\n| `enforcement` | `config:enforcement` | TeamRuntime | Re-install emission validator via `setEmissionValidator()` |\n| `tuning.scaling` | `config:tuning.scaling` | TeamRuntime | Update scaling params (affects future spawn decisions) |\n| `tuning.task_assignment` | `config:tuning.task_assignment` | TeamRuntime | Update spawn interceptor with new pull-mode params |\n| `tuning.observability` | `config:tuning.observability` | Metrics service | Update metrics window/snapshot interval |\n| `tuning.integration` | `config:tuning.integration` | IntegrationStrategy | Update strategy params (max_retries, conflict_action) |\n\n## RuntimeConfig Schema\n\n```typescript\ninterface RuntimeConfig {\n /** Role overrides (Tier 2) */\n roles: Record<string, Partial<RoleDefinition>>;\n\n /** Communication enforcement level (Tier 3) */\n enforcement?: \"strict\" | \"permissive\" | \"audit\";\n\n /** Tuning knobs (Tier 3) */\n tuning: {\n scaling?: {\n min_workers?: number;\n max_workers?: number;\n idle_drain?: boolean;\n };\n task_assignment?: {\n idle_timeout_s?: number;\n claim_retry_delay_ms?: number;\n max_concurrent_per_agent?: number;\n };\n observability?: {\n metrics_window_s?: number;\n snapshot_interval_s?: number;\n };\n integration?: {\n max_retries?: number;\n conflict_action?: string;\n [key: string]: unknown; // strategy-specific params\n };\n };\n}\n```\n\n## API Endpoints\n\n```\nGET /api/config → full effective config (file base + runtime overrides)\nGET /api/config/:section → single section (e.g., /api/config/enforcement)\nPUT /api/config/:section → replace a section\nPATCH /api/config/:section → merge into a section\nDELETE /api/config/:section/:key → unset a runtime override (revert to file default)\nPOST /api/config/persist → write runtime overrides to disk (optional, section filter)\n```\n\n### Example requests\n\n```\nPATCH /api/config/enforcement\n{ \"value\": \"permissive\" }\n\nPATCH /api/config/tuning/scaling\n{ \"max_workers\": 8, \"idle_drain\": true }\n\nPUT /api/config/roles/worker\n{ \"capabilities\": [\"file.read\", \"file.write\", \"git.commit\", \"exec.command\"] }\n\nDELETE /api/config/tuning/scaling/max_workers\n(reverts to team.yaml default)\n```\n\n## Subscriber Wiring\n\nSubscribers register during initialization, not inside ConfigManager:\n\n```typescript\n// In TeamRuntime.initialize():\nconfigManager.on(\"config:enforcement\", (value) => {\n const newValidator = this.createEmissionValidator(value);\n this.messageRouter.setEmissionValidator(newValidator);\n});\n\nconfigManager.on(\"config:tuning.task_assignment\", (value) => {\n // Rebuild spawn interceptor with updated pull-mode params\n this.agentManager.setSpawnInterceptor(this.createSpawnInterceptor());\n});\n\n// In RoleRegistry (or wherever roles are managed):\nconfigManager.on(\"config:roles\", (roles) => {\n for (const [name, definition] of Object.entries(roles)) {\n registry.registerRole({ name, ...definition });\n }\n});\n```\n\n## Cross-Process Visibility\n\nWhen ConfigManager applies a change, it optionally emits a `config_update` event to EventStore:\n\n```typescript\neventStore.emit({\n type: \"config_update\",\n source: { agent_id: \"system\" },\n payload: {\n section: \"enforcement\",\n value: \"permissive\",\n timestamp: Date.now()\n }\n});\n```\n\nMCP subprocesses that need to read current config can query EventStore for the latest `config_update` events. This is secondary to the in-memory path — not all config changes need cross-process visibility.\n\n## Persistence Strategy\n\n| Section | Persists to | On restart |\n|---------|------------|-----------|\n| `roles.*` | `.multiagent/roles.json` | Roles survive restart |\n| `enforcement` | Not persisted | Reverts to `team.yaml` default |\n| `tuning.*` | Not persisted | Reverts to `team.yaml` default |\n\nRationale: Role changes are definitional (you want them to stick). Tuning knobs are operational (the YAML file is the source of truth for defaults; runtime tweaks are temporary adjustments).\n\nUsers can explicitly call `POST /api/config/persist` to write tuning overrides to file if they want them to survive restarts.\n\n## Initialization Flow\n\n```\nServer startup:\n 1. loadMergedConfig() → infrastructure config (port, host, auth, task)\n 2. new ConfigManager()\n 3. configManager.initialize(mergedConfig, teamManifest)\n → loads file-based defaults for roles, team tuning\n → runtime overlay starts empty\n 4. TeamRuntime.initialize(configManager)\n → subscribes to config:enforcement, config:tuning.*\n 5. RoleRegistry loaded, subscribes to config:roles\n 6. API server started with configManager reference\n```\n\n## File Layout\n\n```\nsrc/config/\n├── project-config.ts # Existing: static config loader (unchanged)\n├── config-manager.ts # NEW: runtime config manager\n├── types.ts # NEW: RuntimeConfig, ConfigSection types\n└── index.ts # Updated: export ConfigManager\n```\n\n## Out of Scope\n\n- Changing topology at runtime (root, companions, spawn rules, channels, subscriptions)\n- Swapping integration strategy (queue → trunk)\n- Changing task backend (memory → opentasks)\n- Config validation schemas (can add later)\n- Config diffing / rollback\n- WebSocket config change notifications to clients (can add later)\n","priority":1,"archived":0,"archived_at":null,"created_at":"2026-02-18 23:35:39","updated_at":"2026-02-18 23:35:39","parent_id":null,"parent_uuid":null,"relationships":[],"tags":["api","config","runtime"]}
51
+ {"id":"s-573k","uuid":"68b7357d-feaa-4200-abad-c689f9208ed9","title":"Recursive Teams: Composable Multi-Team Support","file_path":"specs/s-573k_recursive_teams_composable_multi_team_support.md","content":"## Overview\n\nEnable teams to be **composable and recursive** -- a team's topology can reference sub-teams, whose roots become children of the parent team's agents. This enables hierarchical multi-agent structures while keeping each team config self-contained and reusable.\n\n## Design Model\n\nTeams nest via the agent hierarchy. A sub-team's root agent is spawned as a **child** of a parent team agent. Each team is autonomous internally (own roles, communication, integration strategy). Cross-team communication flows through the hierarchy (parent-child subtree subscriptions) and optionally through shared topics.\n\n```\n[coordinator] team: project ← parent team's root\n├── [backend.lead] team: backend ← sub-team root (child of coordinator)\n│ ├── [worker] team: backend\n│ └── [reviewer] team: backend\n├── [frontend.lead] team: frontend ← sub-team root (child of coordinator)\n│ ├── [designer] team: frontend\n│ └── [implementer] team: frontend\n└── [qa-monitor] team: project ← parent team's companion\n```\n\n### Key Principles\n\n1. **Teams are self-contained**: A team.yaml doesn't know or care whether it's used standalone or as a sub-team\n2. **Nesting uses agent hierarchy**: Sub-team roots become children, not peers. Communication flows through parent-child relationships naturally\n3. **Each team governs itself**: Roles, communication channels, signal filters, emission validation, and integration strategy are scoped to each team's own agents\n4. **Cross-team communication**: Flows through hierarchy (subtree subscriptions) or shared topics bridged via the parent team\n\n## Config Schema\n\n### Sub-Team Reference (v1 - minimal)\n\nNew field `topology.teams` on team.yaml:\n\n```yaml\n# .multiagent/teams/project/team.yaml\nname: project\nroles: [coordinator, qa-monitor]\n\ntopology:\n root:\n role: coordinator\n companions:\n - role: qa-monitor\n teams: # NEW\n - name: backend # loads .multiagent/teams/backend/team.yaml\n - name: coding-team # can reuse same template twice\n as: frontend # instance name (becomes team_id)\n parent: root # which topology node owns this sub-team\n topics: [coordination] # parent-level topics sub-team root subscribes to\n\ncommunication:\n channels:\n - name: coordination\n signals: [status_update, blocking_issue, handoff]\n subscriptions:\n coordinator:\n - channel: coordination\n```\n\n### Sub-Team Reference Fields\n\n| Field | Required | Default | Description |\n|-------|----------|---------|-------------|\n| `name` | yes | — | Team template directory name under `.multiagent/teams/` |\n| `as` | no | same as `name` | Instance name. Becomes `team_id` for agents. Required when same template used twice. |\n| `parent` | no | `root` | Role name of the topology node that spawns this sub-team's root as its child |\n| `topics` | no | `[]` | Parent-level topic names the sub-team root auto-subscribes to |\n\n### Sub-team template (standalone, reusable)\n\n```yaml\n# .multiagent/teams/backend/team.yaml\nname: backend\nroles: [lead, worker, reviewer]\n\ntopology:\n root:\n role: lead\n companions:\n - role: worker\n - role: reviewer\n\ncommunication:\n channels:\n - name: work_coordination\n signals: [task_assigned, review_request, task_completed]\n subscriptions:\n lead:\n - channel: work_coordination\n worker:\n - channel: work_coordination\n reviewer:\n - channel: work_coordination\n```\n\nThis template works identically whether used standalone (`--team backend`) or as a sub-team of another team. When used as a sub-team, the only difference is its root agent gets a parent (instead of `parent: null`).\n\n### Recursion\n\nSub-teams can themselves reference sub-teams:\n\n```yaml\n# .multiagent/teams/backend/team.yaml\nname: backend\ntopology:\n root:\n role: lead\n companions:\n - role: reviewer\n teams:\n - name: api-team\n - name: database-team\n```\n\nThis composes arbitrarily deep.\n\n## Agent Records: `team_id`\n\nEvery agent gets a `team_id` field identifying which team it belongs to:\n\n- Set at spawn time (via spawn event payload)\n- Inherited from team context during bootstrap, or injected by spawn interceptor for dynamically spawned children\n- Persisted in EventStore agent materialized view\n- Used by TeamManager to dispatch callbacks to the correct TeamRuntime\n\n## TeamManager (Multiplexer)\n\nA flat registry of all active TeamRuntime instances (parent + all sub-teams). Installs single composite callbacks on global services:\n\n### Composite Spawn Interceptor\n1. Look up parent agent's `team_id` from EventStore\n2. If parent is in a team → delegate to that team's spawn interceptor\n3. Inject `team_id` into child spawn options (team membership inherits)\n4. For bootstrap spawns (`parent: null`), `team_id` is set explicitly\n\n### Composite Signal Filter\n1. Look up source and target agents' `team_id`\n2. Same team → delegate to that team's signal filter\n3. Cross-team → allow (the hierarchy handles visibility via subtree subscriptions)\n4. Unaffiliated → allow (no filtering for non-team agents)\n\n### Composite Emission Validator\n1. Look up emitting agent's `team_id`\n2. If in a team → delegate to that team's emission validator\n3. Unaffiliated → allow\n\n## Recursive Bootstrap Flow\n\n1. Parent team bootstraps normally (spawn root + companions)\n2. For each sub-team reference in `topology.teams`:\n a. Resolve the `parent` role → get parent agent ID from roleAgentMap\n b. Create child TeamRuntime from pre-loaded sub-team manifest\n c. Register child TeamRuntime with TeamManager\n d. Bootstrap child with `parentAgentId` set (root spawned as child, not peer)\n e. Subscribe sub-team root to bridged `topics`\n3. Sub-team may recursively bootstrap its own sub-teams (same process)\n\n## Role Namespacing\n\nWhen multiple teams define the same role name (e.g., both \"backend\" and \"frontend\" define \"lead\"):\n\n- **Internal to each team**: roles use short names (\"lead\", \"worker\")\n- **At parent level**: auto-prefixed with team instance name (\"backend.lead\", \"frontend.lead\")\n- **RoleRegistry**: team-scoped registration (`registerTeamRole(teamName, role)`)\n- **Resolution**: `resolveRole(\"lead\", teamName: \"backend\")` → backend's lead definition\n\n## Cross-Team Communication Patterns\n\n### Through Hierarchy (default)\nParent agent (coordinator) subscribes to sub-team root's subtree. Sees all events. Can relay messages between sub-teams.\n\n### Through Shared Topics (explicit)\nSub-team refs specify `topics: [coordination]`. Sub-team roots subscribe to parent-level topic channels. Enables direct pub/sub between sub-teams on parent-defined channels.\n\n### Direct Addressing (always available)\nAny agent can send to any other agent by ID. Team boundaries don't block direct messages -- they only scope signal filters and emission validators.\n\n## Backward Compatibility\n\n- No `topology.teams` → identical behavior to today\n- Existing team YAMLs → valid as-is, usable both standalone and as sub-team templates\n- Agents without `team_id` → unaffiliated, unchanged behavior\n- Single `--team` flag → TeamManager wraps the single team transparently\n","priority":1,"archived":0,"archived_at":null,"created_at":"2026-02-18 23:38:34","updated_at":"2026-02-18 23:38:34","parent_id":null,"parent_uuid":null,"relationships":[],"tags":["architecture","multi-team","recursive","teams"]}
52
+ {"id":"s-8bbq","uuid":"b03dc85d-f7f9-4f1b-9933-1fa945bfc9c4","title":"Auto-Start OpenTasks Daemon with Central + Per-Project Connection Model","file_path":"specs/s-8bbq_auto_start_opentasks_daemon_with_central_per_proje.md","content":"## Context\n\nThe macro-agent server orchestrates agents across arbitrary working directories. Each project may have its own `.opentasks/` graph (git-tracked task/spec data). The macro-agent needs a unified task view for orchestration while preserving per-project task ownership.\n\nCurrently, the opentasks backend requires a **pre-running external daemon** — if the daemon isn't available, macro-agent falls back to an in-memory task backend. This spec defines how macro-agent auto-starts and manages opentasks daemons.\n\n## Key Design Constraint: Task Provider Lifecycle\n\n**Daemon spawning is part of the task provider lifecycle.** The `createTaskBackend()` factory owns daemon start/stop — callers (CLI entry points) don't need to manage daemon lifecycle separately. This keeps daemon management encapsulated within the task backend module.\n\n```\ncreateTaskBackend({ backend: { type: \"opentasks\" } })\n └── DaemonManager.ensureDaemon()\n ├── checkExistingDaemon(centralPath)\n │ ├── Running → connect to existing socket\n │ └── Not running → createDaemonWithStore() + start()\n └── Return { client, socketPath, ownsDaemon }\n\nTaskBackendResult.shutdown()\n └── DaemonManager.shutdown()\n ├── ownsDaemon=true → daemon.stop()\n └── ownsDaemon=false → client.disconnect() only\n```\n\n## Architecture: Central Daemon + Per-Project Connections\n\n```\n~/.multiagent/\n├── config.json # global config (layered config system)\n├── opentasks/ # central opentasks location\n│ ├── graph.jsonl # orchestration-level tasks\n│ ├── config.json # location identity + connections list\n│ ├── daemon.sock # IPC socket\n│ ├── daemon.lock # exclusive lock\n│ └── cache.db # SQLite query cache\n└── <instanceId>/ # EventStore data (per-instance)\n └── events.db\n\n/projects/frontend/.opentasks/ # pre-existing project opentasks\n├── graph.jsonl # project tasks (git-tracked)\n└── config.json # connected to central as child\n\n/projects/backend/.opentasks/ # another project\n├── graph.jsonl\n└── config.json\n```\n\n### Central Daemon\n\n- Lives at `~/.multiagent/opentasks/`\n- Auto-started by `createTaskBackend()` when opentasks backend is configured\n- Owns orchestration-level tasks (cross-project coordination)\n- Maintains connections to per-project `.opentasks/` locations\n- Provides federated queries across all connected locations\n- Stopped via `TaskBackendResult.shutdown()` (only if we started it)\n\n### Per-Project Connections\n\n- When an agent is spawned into a cwd that has `.opentasks/`, the central daemon auto-connects to it\n- Connect-only model: macro-agent does **not** auto-init `.opentasks/` in new directories\n- Connections are declared in the central daemon's `config.json` via the opentasks connections API\n- Cross-project task references use URI notation: `opentasks://<project-hash>/t-abc`\n\n## Implementation\n\n### New Module: `src/task/backend/opentasks/daemon-manager.ts`\n\nInternal to the opentasks backend module — not exposed as a standalone service.\n\n```typescript\ninterface DaemonManagerConfig {\n /** Central daemon location (default: ~/.multiagent/opentasks) */\n centralPath?: string;\n /** Whether to auto-connect project .opentasks/ on connectProject() */\n connectOnSpawn?: boolean;\n}\n\ninterface DaemonManagerResult {\n /** Connected OpenTasks client */\n client: OpenTasksClient;\n /** Daemon socket path */\n socketPath: string;\n /** Whether we started the daemon (vs connecting to existing) */\n ownsDaemon: boolean;\n}\n\ninterface DaemonManager {\n /** Start or connect to central daemon */\n ensureDaemon(): Promise<DaemonManagerResult>;\n /** Connect a project's .opentasks/ to the central daemon */\n connectProject(projectPath: string): Promise<void>;\n /** Check if a project is already connected */\n isProjectConnected(projectPath: string): boolean;\n /** Stop the daemon if we started it, disconnect client */\n shutdown(): Promise<void>;\n}\n```\n\n### Updated `TaskBackendResult`\n\n```typescript\ninterface TaskBackendResult {\n backend: TaskBackend;\n openTasksClient?: OpenTasksClient;\n /** Shutdown function — stops daemon if we started it */\n shutdown?: () => Promise<void>;\n}\n```\n\n### Updated `createTaskBackend()` Flow\n\nWhen `backendConfig.type === \"opentasks\"`:\n\n```\n1. Create DaemonManager with config\n2. Call daemonManager.ensureDaemon()\n ├── Determine centralPath: config.centralPath ?? ~/.multiagent/opentasks/\n ├── mkdir -p centralPath (ensure directory exists)\n ├── checkExistingDaemon(centralPath)\n │ ├── running=true → connect client to existing socketPath\n │ └── running=false →\n │ a. createDaemonWithStore({ locationPath: centralPath, version })\n │ b. await daemon.start()\n │ c. connect client to daemon.socketPath\n │ d. Set ownsDaemon=true\n └── Return { client, socketPath, ownsDaemon }\n3. Create OpenTasksTaskBackend with client\n4. Return { backend, openTasksClient, shutdown: () => daemonManager.shutdown() }\n```\n\n### Config Integration\n\nThe `.multiagent/config.json` (layered config system) controls behavior:\n\n```json\n{\n \"task\": {\n \"backend\": \"opentasks\",\n \"opentasks\": {\n \"auto_start\": true,\n \"central_path\": \"~/.multiagent/opentasks\",\n \"connect_on_spawn\": true\n }\n }\n}\n```\n\n| Config Key | Default | Description |\n| --- | --- | --- |\n| `task.opentasks.auto_start` | `true` | Auto-start central daemon in createTaskBackend() |\n| `task.opentasks.central_path` | `~/.multiagent/opentasks` | Central daemon location |\n| `task.opentasks.connect_on_spawn` | `true` | Auto-connect project .opentasks/ on agent spawn |\n\n### CLI Entry Point Changes\n\nMinimal — daemon lifecycle is encapsulated in the backend:\n\n```typescript\n// In acp.ts / index.ts:\nconst result = await createTaskBackend(taskConfig, eventStore);\ntaskBackend = result.backend;\n// ...\nconst cleanup = async () => {\n await result.shutdown?.(); // Stops daemon if we started it\n await eventStore.close();\n};\n```\n\n### Connect-on-Spawn (Phase 2)\n\nStill wired via `agent-manager.ts` but calls into the daemon manager:\n\n```typescript\n// agent-manager.ts\nagentManager.onLifecycleEvent((event) => {\n if (event.type === \"spawned\" && daemonManager) {\n daemonManager.connectProject(event.agent.cwd);\n }\n});\n```\n\n## opentasks Package APIs Used\n\n| Operation | API | Import Path |\n| --- | --- | --- |\n| Check existing daemon | `checkExistingDaemon(locationPath)` | `opentasks` (dynamic) |\n| Create daemon with store | `createDaemonWithStore({ locationPath, version })` | `opentasks` (dynamic) |\n| Start/stop daemon | `daemon.start()` / `daemon.stop()` | Daemon instance |\n| Connect client | `IPCOpenTasksClient({ socketPath })` | local client.ts |\n| Create connection | `createConnection(targetPath, role, basePath)` | `opentasks` (dynamic) |\n| Discover locations | `discoverLocations(startDir, { direction })` | `opentasks` (dynamic) |\n\nAll opentasks imports are dynamic (`await import(\"opentasks\")`) to avoid hard dependencies.\n\n## Edge Cases\n\n### Multiple macro-agent instances on same machine\n\n- `checkExistingDaemon()` detects pre-existing daemon at `~/.multiagent/opentasks/`\n- Second instance connects to existing daemon (shared central graph)\n- Only the instance that started the daemon stops it (tracked via `ownsDaemon` flag)\n\n### opentasks package not installed\n\n- Dynamic import fails → fall back to memory backend (existing behavior in CLI entry points)\n- Log warning: \"opentasks package not available, using memory backend\"\n\n### Daemon crashes mid-session\n\n- Client operations throw `CONNECTION_FAILED`\n- For v1: fall back to memory backend on daemon loss (existing try/catch in CLI entry points)\n- Future: reconnect logic in daemon manager\n\n### Agent spawned in subdirectory of project with .opentasks/\n\n- Walk up from agent cwd to find nearest `.opentasks/`\n- Use `discoverLocations(cwd, { direction: 'up' })` from opentasks API\n- Connect the discovered location, not the agent's exact cwd\n\n## Phases\n\n### Phase 1: Auto-start central daemon (within createTaskBackend)\n\n- Create `DaemonManager` module at `src/task/backend/opentasks/daemon-manager.ts`\n- Integrate into `createTaskBackend()` factory\n- Add `shutdown` to `TaskBackendResult`\n- Update CLI cleanup to call `result.shutdown?.()`\n- Fall back to memory on failure (existing behavior)\n\n### Phase 2: Connect-on-spawn\n\n- Hook into agent spawn lifecycle via agent-manager\n- Auto-discover and connect project `.opentasks/` directories\n- Surface connected projects in API/health endpoint\n\n### Phase 3: Federated queries\n\n- Ensure `query({ ready: {} })` works across connected locations\n- Surface project origin in task metadata\n- Cross-project edge creation via URI references\n\n## Non-Goals\n\n- Auto-initializing `.opentasks/` in new project directories (connect-only model)\n- Running separate daemons per project (single central daemon)\n- Migrating existing EventStore tasks to opentasks (dual persistence continues)\n- Replacing the memory backend fallback (always available as safety net)","priority":1,"archived":0,"archived_at":null,"created_at":"2026-02-18 23:58:49","updated_at":"2026-02-19 00:52:32","parent_id":null,"parent_uuid":null,"relationships":[],"tags":["architecture","config","daemon","opentasks"]}
package/CLAUDE.md CHANGED
@@ -10,7 +10,7 @@ macro-agent enables coordinated work across multiple AI agents with:
10
10
  - **Pluggable integration strategies** (queue, trunk, optimistic)
11
11
  - **Workspace isolation** via git worktrees
12
12
  - **Merge queue** for serialized integration
13
- - **Task backend** abstraction (memory or sudocode) with push and pull modes
13
+ - **Task backend** abstraction (memory or opentasks) with push and pull modes
14
14
  - **In-flight steering** via context injection
15
15
  - **Signal filtering and emission enforcement** for communication topology
16
16
  - **Session continuations** for long-running daemon agents
@@ -46,7 +46,7 @@ macro-agent enables coordinated work across multiple AI agents with:
46
46
  │ Roles │ │ Workspace │ │ Tasks │
47
47
  │ Built-in + │ │ Bare Repo │ │ Backend │
48
48
  │ Team-defined│ │ Worktrees │ │ (memory/ │
49
- │ (via YAML) │ │ Strategies │ │ sudocode) │
49
+ │ (via YAML) │ │ Strategies │ │ opentasks) │
50
50
  │ │ │ (queue/ │ │ Push/Pull │
51
51
  │ │ │ trunk/opt) │ │ modes │
52
52
  └──────────────┘ └──────────────┘ └──────────────┘
@@ -92,7 +92,7 @@ src/
92
92
  │ └── index.ts # CLI commands (start, chat, status, --team)
93
93
 
94
94
  ├── config/ # Project configuration
95
- │ └── project-config.ts # .macro-agent/config.json loader
95
+ │ └── project-config.ts # .multiagent/config.json loader
96
96
 
97
97
  ├── lifecycle/ # Agent lifecycle management
98
98
  │ ├── handlers/ # Role-specific done() handlers
@@ -152,8 +152,8 @@ src/
152
152
  │ └── backend/ # Pluggable task backends
153
153
  │ ├── types.ts # TaskBackend interface (+ claim/unclaim/listClaimable)
154
154
  │ ├── memory.ts # InMemoryTaskBackend (push + pull)
155
- │ ├── tool-provider.ts # Task MCP tools
156
- │ └── sudocode/ # Sudocode integration
155
+ │ ├── unified-tool-provider.ts # Unified task MCP tool provider (7 tools)
156
+ │ └── opentasks/ # OpenTasks integration
157
157
 
158
158
  ├── teams/ # Team template system
159
159
  │ ├── types.ts # TeamManifest, TeamTopology, TeamCommunication
@@ -219,9 +219,12 @@ Each worker gets an isolated git worktree:
219
219
 
220
220
  Two backends available:
221
221
  - **memory**: In-memory tasks with EventStore persistence (supports push + pull modes)
222
- - **sudocode**: External issue tracking with dependency management (push mode only)
222
+ - **opentasks**: External issue tracking with dependency management (supports push + pull modes)
223
223
 
224
- Pull mode adds `claim_task`, `unclaim_task`, `list_claimable_tasks` MCP tools (gated by `task.claim` capability).
224
+ The unified task tool provider exposes 7 MCP tools:
225
+ - Always available: `create_task`, `get_task`, `list_tasks`, `assign_task`
226
+ - When OpenTasks client available: `task` (upsert), `link`, `annotate`
227
+ - Pull mode adds: `claim_task`, `unclaim_task`, `list_claimable_tasks` (gated by `task.claim` capability)
225
228
 
226
229
  ### Communication Topology
227
230
 
@@ -301,23 +304,23 @@ npm run test:e2e # E2E tests (requires RUN_E2E_TESTS=true)
301
304
 
302
305
  ### Adding a Team Role (via YAML)
303
306
 
304
- 1. Create `.macro-agent/teams/<team>/roles/<role>.yaml` with `extends` base role
307
+ 1. Create `.multiagent/teams/<team>/roles/<role>.yaml` with `extends` base role
305
308
  2. Add `capabilities_add`/`capabilities_remove` as needed
306
- 3. Create `.macro-agent/teams/<team>/prompts/<role>.md` for custom prompt
309
+ 3. Create `.multiagent/teams/<team>/prompts/<role>.md` for custom prompt
307
310
  4. Reference the role in `team.yaml` topology and communication sections
308
311
 
309
312
  ### Modifying Task Backend
310
313
 
311
314
  1. Update interface in `src/task/backend/types.ts`
312
- 2. Implement in both `memory.ts` and `sudocode/`
313
- 3. Update tool provider if adding new operations
315
+ 2. Implement in both `memory.ts` and `opentasks/`
316
+ 3. Update unified tool provider if adding new operations
314
317
 
315
318
  ## Environment Variables
316
319
 
317
320
  | Variable | Description | Default |
318
321
  |----------|-------------|---------|
319
- | `MACRO_TASK_BACKEND` | Task backend: `memory` or `sudocode` | `memory` |
320
- | `SUDOCODE_PROJECT_PATH` | Path to sudocode project | `cwd` |
322
+ | `MACRO_TASK_BACKEND` | Task backend: `memory` or `opentasks` | `opentasks` |
323
+ | `OPENTASKS_SOCKET_PATH` | Path to OpenTasks socket | |
321
324
  | `MACRO_WORKSPACE_POOL_SIZE` | Max concurrent workspaces | `10` |
322
325
  | `MACRO_MERGE_QUEUE_DB` | Merge queue SQLite path | `:memory:` |
323
326
  | `MACRO_TEAM_NAME` | Team name (injected into agent env by team runtime) | — |
@@ -333,4 +336,3 @@ npm run test:e2e # E2E tests (requires RUN_E2E_TESTS=true)
333
336
  - [docs/configuration.md](docs/configuration.md) - Configuration reference
334
337
  - [docs/teams.md](docs/teams.md) - Team template schema reference
335
338
  - [docs/team-templates.md](docs/team-templates.md) - Team template format and examples
336
- - [docs/sudocode-integration.md](docs/sudocode-integration.md) - Sudocode backend details
package/README.md CHANGED
@@ -19,28 +19,7 @@ A multi-agent orchestration system for spawning and managing hierarchical Claude
19
19
  - **Context Injection** - Push context into running agents without waiting for message checks
20
20
  - **Session Continuations** - Auto-resume long-running agents across process restarts
21
21
  - **Observability** - Throughput, utilization, and error metrics via REST API
22
- - **Sudocode Integration** - Optional issue tracking with dependency management
23
-
24
- ## Sudocode Integration
25
-
26
- macro-agent can integrate with [sudocode](https://github.com/sudocode-ai/sudocode) for external issue tracking:
27
-
28
- ```bash
29
- # Enable sudocode backend
30
- export MACRO_TASK_BACKEND=sudocode
31
- export SUDOCODE_PROJECT_PATH=/path/to/project
32
-
33
- # Start macro-agent server (full mode with ACP + MAP + REST)
34
- npx multiagent
35
- ```
36
-
37
- With sudocode enabled:
38
- - Tasks are bound to sudocode issues via `external_id`
39
- - Blocking relationships come from sudocode's issue links
40
- - `listReady()` returns only tasks with no incomplete blockers
41
- - Task status can sync with issue status
42
-
43
- See [docs/sudocode-integration.md](docs/sudocode-integration.md) for full documentation.
22
+ - **OpenTasks Integration** - Optional issue tracking with dependency management
44
23
 
45
24
  ## Team Templates
46
25
 
@@ -50,15 +29,15 @@ Teams define multi-agent topologies as YAML configuration. A team template speci
50
29
  # Start with a team template
51
30
  npx multiagent --team self-driving
52
31
 
53
- # Or set in project config (.macro-agent/config.json)
54
- echo '{ "team": "self-driving" }' > .macro-agent/config.json
32
+ # Or set in project config (.multiagent/config.json)
33
+ echo '{ "team": "self-driving" }' > .multiagent/config.json
55
34
  npx multiagent
56
35
  ```
57
36
 
58
- Teams are stored in `.macro-agent/teams/<name>/`:
37
+ Teams are stored in `.multiagent/teams/<name>/`:
59
38
 
60
39
  ```
61
- .macro-agent/teams/self-driving/
40
+ .multiagent/teams/self-driving/
62
41
  ├── team.yaml # Team manifest (topology, communication, strategy)
63
42
  ├── roles/
64
43
  │ ├── planner.yaml # Custom role (extends coordinator)
@@ -295,7 +274,11 @@ Fallback chain: `inject()` → `interruptWith()` → high-priority message
295
274
  | `stop_agent` | Terminate an agent |
296
275
  | `create_task` | Create a new task |
297
276
  | `get_task` | Get task details |
298
- | `list_ready_tasks` | List tasks with no blockers |
277
+ | `list_tasks` | List tasks with optional filters |
278
+ | `assign_task` | Assign task to agent |
279
+ | `task` | Upsert task (OpenTasks only) |
280
+ | `link` | Link tasks (OpenTasks only) |
281
+ | `annotate` | Add annotations (OpenTasks only) |
299
282
  | `claim_task` | Claim next available task (pull mode) |
300
283
  | `unclaim_task` | Return claimed task to pool (pull mode) |
301
284
  | `list_claimable_tasks` | List claimable tasks (pull mode) |
@@ -332,7 +315,7 @@ npx multiagent --acp --cwd /path/to/project
332
315
  | `--port <port>` | Server port (default: 3001) |
333
316
  | `--host <host>` | Server host (default: localhost) |
334
317
  | `--cwd <path>` | Working directory for agents |
335
- | `--team <name>` | Load team template from `.macro-agent/teams/<name>/` |
318
+ | `--team <name>` | Load team template from `.multiagent/teams/<name>/` |
336
319
  | `--acp` | Stdio ACP-only mode (for embedded use with acp-factory) |
337
320
 
338
321
  ### Multi-Client Architecture
@@ -454,7 +437,6 @@ RUN_E2E_TESTS=true ANTHROPIC_API_KEY=xxx npm run test:e2e
454
437
  - [Configuration Reference](docs/configuration.md) - Environment variables and config options
455
438
  - [Team Templates](docs/team-templates.md) - Team template format and examples
456
439
  - [Team Schema Reference](docs/teams.md) - Full YAML schema reference
457
- - [Sudocode Integration](docs/sudocode-integration.md) - External issue tracking
458
440
  - [Troubleshooting Guide](docs/troubleshooting.md) - Common issues and solutions
459
441
 
460
442
  ## License
@@ -173,6 +173,10 @@ export declare class MacroAgent implements Agent {
173
173
  * then calls the agent manager to cancel the pending permission.
174
174
  */
175
175
  private handleCancelPermission;
176
+ /**
177
+ * Change the permission mode for a running agent at runtime
178
+ */
179
+ private handleSetPermissionMode;
176
180
  /**
177
181
  * Resume a stopped/failed agent
178
182
  */
@@ -229,6 +233,17 @@ export declare class MacroAgent implements Agent {
229
233
  * Record user and assistant turns after a prompt completes.
230
234
  */
231
235
  private recordPromptTurns;
236
+ /**
237
+ * Handle _macro/getModels extension — returns the session's available models.
238
+ * Claude Code populates models asynchronously after session creation
239
+ * (via _model_state_update notification), so this allows the TUI to poll
240
+ * for the model list once it's available.
241
+ *
242
+ * Returns full model info (modelId + name) since Claude Code uses shorthand
243
+ * model IDs ("default", "sonnet") that don't match models.dev. The name
244
+ * field (e.g., "Claude Sonnet 4") enables better model registry matching.
245
+ */
246
+ private handleGetModels;
232
247
  /**
233
248
  * Handle _macro/getHistory extension — returns conversation turns for a session
234
249
  */
@@ -1 +1 @@
1
- {"version":3,"file":"macro-agent.d.ts","sourceRoot":"","sources":["../../src/acp/macro-agent.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,KAAK,EACL,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,aAAa,EACb,cAAc,EACd,kBAAkB,EAEnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EACV,YAAY,EAYZ,oBAAoB,EA2BrB,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAEvE,OAAO,KAAK,EAAE,YAAY,EAAc,MAAM,mBAAmB,CAAC;AAkDlE,MAAM,WAAW,gBAAgB;IAC/B,oDAAoD;IACpD,YAAY,EAAE,YAAY,CAAC;IAE3B,iCAAiC;IACjC,UAAU,EAAE,UAAU,CAAC;IAEvB,sCAAsC;IACtC,WAAW,EAAE,WAAW,CAAC;IAEzB,iEAAiE;IACjE,WAAW,CAAC,EAAE,WAAW,CAAC;IAE1B,kEAAkE;IAClE,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IAEtC,+FAA+F;IAC/F,YAAY,CAAC,EAAE,YAAY,CAAC;IAE5B,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAMD;;;GAGG;AACH,qBAAa,UAAW,YAAW,KAAK;IACtC,OAAO,CAAC,UAAU,CAAsB;IACxC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,iBAAiB,CAAgC;IACzD,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,UAAU,CAAS;IAE3B,4CAA4C;IAC5C,OAAO,CAAC,UAAU,CAA4B;IAE9C,8DAA8D;IAC9D,OAAO,CAAC,uBAAuB,CACnB;IAEZ,2FAA2F;IAC3F,OAAO,CAAC,aAAa,CAGP;IAEd,sFAAsF;IACtF,OAAO,CAAC,cAAc,CAGR;gBAEF,UAAU,EAAE,mBAAmB,EAAE,MAAM,EAAE,gBAAgB;IAwBrE;;;;;OAKG;IACG,UAAU,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA0BxE;;OAEG;IACG,UAAU,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAiCxE;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAyE3E;;OAEG;IACG,YAAY,CAChB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,oBAAoB,CAAC;IAKhC;;OAEG;IACG,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;IAwE5D;;OAEG;IACG,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAqCvD;;OAEG;IACG,SAAS,CACb,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAyGnC;;OAEG;YACW,gBAAgB;IAmE9B;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAqB1B;;OAEG;YACW,kBAAkB;IAsChC;;OAEG;YACW,aAAa;IAgB3B;;;;;OAKG;YACW,gBAAgB;IAoC9B;;;;;OAKG;YACW,eAAe;IAuD7B;;;;OAIG;YACW,qBAAqB;IA0CnC;;;;;OAKG;YACW,qBAAqB;IAyCnC;;;;;OAKG;YACW,wBAAwB;IA6BtC;;;;;;OAMG;YACW,wBAAwB;IA6BtC;;OAEG;YACW,qBAAqB;IAsBnC;;OAEG;YACW,sBAAsB;IAoBpC;;OAEG;YACW,qBAAqB;IAqBnC;;OAEG;YACW,qBAAqB;IAsBnC;;;;;OAKG;YACW,yBAAyB;IAkCvC;;;;;OAKG;YACW,sBAAsB;IA8BpC;;OAEG;YACW,iBAAiB;IAqC/B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAiB7B;;;;;;;;;;;;;;;;;OAiBG;YACW,oBAAoB;IAgOlC;;;OAGG;YACW,eAAe;IAyB7B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAkBzB;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAqC3B;;OAEG;IACH,OAAO,CAAC,aAAa;IAuBrB;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IA8B1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA4DzB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IA2BxB;;OAEG;IACH,gBAAgB,IAAI,aAAa;IAIjC;;OAEG;IACH,gBAAgB,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,GAAG,SAAS;IAIjE;;OAEG;IACH,aAAa,IAAI,oBAAoB;CAGtC"}
1
+ {"version":3,"file":"macro-agent.d.ts","sourceRoot":"","sources":["../../src/acp/macro-agent.ts"],"names":[],"mappings":"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"}
@@ -43,10 +43,27 @@ const SUPPORTED_EXTENSIONS = [
43
43
  "_macro/checkCapability",
44
44
  "_macro/respondToPermission",
45
45
  "_macro/cancelPermission",
46
+ "_macro/setPermissionMode",
46
47
  "_macro/resume",
47
48
  "_macro/getHistory",
49
+ "_macro/getModels",
48
50
  ];
49
51
  // ─────────────────────────────────────────────────────────────────
52
+ // Helpers
53
+ // ─────────────────────────────────────────────────────────────────
54
+ /**
55
+ * Build a SessionModelState from the acp-factory Session.models (string[]).
56
+ * Returns null if the models array is empty or missing.
57
+ */
58
+ function buildModelState(models) {
59
+ if (!models || models.length === 0)
60
+ return null;
61
+ return {
62
+ currentModelId: models[0],
63
+ availableModels: models.map((id) => ({ modelId: id, name: id })),
64
+ };
65
+ }
66
+ // ─────────────────────────────────────────────────────────────────
50
67
  // MacroAgent Implementation
51
68
  // ─────────────────────────────────────────────────────────────────
52
69
  /**
@@ -145,6 +162,7 @@ export class MacroAgent {
145
162
  await this.emitSessionInfo(acpSessionId);
146
163
  return {
147
164
  sessionId: acpSessionId,
165
+ models: buildModelState(spawned.session.models),
148
166
  };
149
167
  }
150
168
  /**
@@ -153,6 +171,7 @@ export class MacroAgent {
153
171
  async loadSession(params) {
154
172
  let acpSessionId = params.sessionId;
155
173
  const cwd = params.cwd ?? this.defaultCwd;
174
+ // DEBUG: Log loadSession params
156
175
  // Extension: If _meta.agentId provided, look up session from agent record
157
176
  // This allows resuming a stopped head manager by MAP agent ID
158
177
  // when the TUI doesn't know the ACP session ID
@@ -180,30 +199,40 @@ export class MacroAgent {
180
199
  }
181
200
  this.ensureConversation(acpSessionId, existing.id);
182
201
  await this.emitSessionInfo(acpSessionId);
183
- return {};
202
+ const activeSession = this.agentManager.getSession(existing.id);
203
+ return {
204
+ models: activeSession ? buildModelState(activeSession.models) : null,
205
+ };
184
206
  }
185
207
  // Agent exists but no active session - resume it
186
208
  console.log(`[MacroAgent] loadSession: Resuming stopped agent ${existing.id}`);
187
- const spawned = await this.agentManager.resume(existing.id);
209
+ const defaultConfig = this.initConfig.defaultSubAgentConfig;
210
+ const spawned = await this.agentManager.resume(existing.id, defaultConfig?.permissionMode);
188
211
  // Create session mapping
189
212
  this.sessionMapper.createMapping(acpSessionId, spawned.id);
190
213
  this.cancellationControllers.set(acpSessionId, new AbortController());
191
214
  this.ensureConversation(acpSessionId, spawned.id);
192
215
  await this.emitSessionInfo(acpSessionId);
193
- return {};
216
+ return {
217
+ models: buildModelState(spawned.session.models),
218
+ };
194
219
  }
195
220
  // No existing agent found - try to get or create with the specific session ID
196
221
  console.log(`[MacroAgent] loadSession: No existing agent for session ${acpSessionId}, creating new`);
222
+ const defaultConfig = this.initConfig.defaultSubAgentConfig;
197
223
  const spawned = await this.agentManager.getOrCreateHeadManager({
198
224
  cwd,
199
225
  sessionId: acpSessionId,
226
+ permissionMode: defaultConfig?.permissionMode,
200
227
  });
201
228
  // Create session mapping
202
229
  this.sessionMapper.createMapping(acpSessionId, spawned.id);
203
230
  this.cancellationControllers.set(acpSessionId, new AbortController());
204
231
  this.ensureConversation(acpSessionId, spawned.id);
205
232
  await this.emitSessionInfo(acpSessionId);
206
- return {};
233
+ return {
234
+ models: buildModelState(spawned.session.models),
235
+ };
207
236
  }
208
237
  /**
209
238
  * Authenticate - macro-agent doesn't require authentication
@@ -342,10 +371,14 @@ export class MacroAgent {
342
371
  return this.handleRespondToPermission(params);
343
372
  case "_macro/cancelPermission":
344
373
  return this.handleCancelPermission(params);
374
+ case "_macro/setPermissionMode":
375
+ return this.handleSetPermissionMode(params);
345
376
  case "_macro/resume":
346
377
  return this.handleResumeAgent(params);
347
378
  case "_macro/getHistory":
348
379
  return this.handleGetHistory(params);
380
+ case "_macro/getModels":
381
+ return this.handleGetModels(params);
349
382
  default:
350
383
  throw new ACPError(`Unknown extension method: ${method}`, "INVALID_EXTENSION");
351
384
  }
@@ -515,40 +548,42 @@ export class MacroAgent {
515
548
  * Uses native fork if available, otherwise falls back to loadSession.
516
549
  */
517
550
  async handleForkAgent(params) {
518
- const { agentId, name } = params;
519
- // Get the original agent
551
+ const { agentId, name, prompt, cwd } = params;
552
+ // Validate source agent exists
520
553
  const originalAgent = this.agentManager.get(agentId);
521
554
  if (!originalAgent) {
522
555
  throw new ACPError(`Agent not found: ${agentId}`, "AGENT_NOT_FOUND", {
523
556
  agentId,
524
557
  });
525
558
  }
526
- // Check if the agent has an active session we can fork from
527
- const hasSession = this.agentManager.hasActiveSession(agentId);
528
- if (!hasSession) {
529
- throw new ACPError(`Agent has no active session to fork: ${agentId}`, "FORK_NOT_SUPPORTED", { agentId });
530
- }
531
- // For now, create a new agent with the same task as a "fork"
532
- // In a full implementation, we would:
533
- // 1. Check if acp-factory supports native fork
534
- // 2. Use loadSession to clone the conversation state
535
- // Since acp-factory doesn't expose these yet, we create a new agent
536
- // with the same task description as a simplified fork
537
- const taskDescription = name
538
- ? `[Fork of ${agentId}] ${name}`
539
- : `[Fork of ${agentId}] ${originalAgent.task ?? "Forked task"}`;
540
- const spawned = await this.agentManager.spawn({
541
- task: taskDescription,
542
- parent: originalAgent.parent ?? null,
543
- cwd: this.defaultCwd,
544
- subscribeParent: false,
559
+ // Check forkable: needs active session or persisted provider_session_id
560
+ if (!this.agentManager.hasActiveSession(agentId) &&
561
+ !originalAgent.provider_session_id) {
562
+ throw new ACPError(`Agent has no session to fork: ${agentId}`, "FORK_NOT_SUPPORTED", { agentId });
563
+ }
564
+ // Fork via AgentManager (handles forkWithFlush + new process + loadSession)
565
+ const forked = await this.agentManager.forkAgent(agentId, {
566
+ name,
567
+ cwd: cwd ?? originalAgent.cwd ?? this.defaultCwd,
545
568
  });
546
- // Emit a fork event for tracking (the EventStore records this via spawn)
547
- // In a more complete implementation, we'd add a dedicated "fork" event type
569
+ // Fire-and-forget initial prompt if provided
570
+ if (prompt) {
571
+ (async () => {
572
+ try {
573
+ for await (const _update of this.agentManager.prompt(forked.id, prompt)) {
574
+ // Drain the async iterable
575
+ }
576
+ }
577
+ catch (err) {
578
+ console.warn(`[MacroAgent] Failed to send initial prompt to forked agent ${forked.id}:`, err);
579
+ }
580
+ })();
581
+ }
548
582
  return {
549
- newAgentId: spawned.id,
550
- newSessionId: spawned.session_id,
583
+ newAgentId: forked.id,
584
+ newSessionId: forked.session_id,
551
585
  originalAgentId: agentId,
586
+ providerSessionId: forked.session.id,
552
587
  };
553
588
  }
554
589
  // ─────────────────────────────────────────────────────────────────
@@ -774,6 +809,29 @@ export class MacroAgent {
774
809
  };
775
810
  }
776
811
  }
812
+ /**
813
+ * Change the permission mode for a running agent at runtime
814
+ */
815
+ async handleSetPermissionMode(params) {
816
+ const { agentId, permissionMode } = params;
817
+ // Get the current mode before changing
818
+ const previousMode = this.agentManager.getPermissionMode(agentId);
819
+ // Set the new mode via agent manager
820
+ const success = this.agentManager.setPermissionMode(agentId, permissionMode);
821
+ if (success) {
822
+ console.log(`[MacroAgent] Set permission mode for agent ${agentId} from ${previousMode} to ${permissionMode}`);
823
+ return {
824
+ success: true,
825
+ previousMode: previousMode ?? undefined,
826
+ };
827
+ }
828
+ else {
829
+ return {
830
+ success: false,
831
+ error: `No active session found for agent ${agentId}`,
832
+ };
833
+ }
834
+ }
777
835
  /**
778
836
  * Resume a stopped/failed agent
779
837
  */
@@ -873,17 +931,20 @@ export class MacroAgent {
873
931
  }
874
932
  case "permission_request": {
875
933
  // Handle permission_request specially - ACP SDK doesn't recognize it as a session update
876
- // We need to call requestPermission on the connection to forward to sudocode
934
+ // We need to call requestPermission on the connection to forward to the client
877
935
  // Extract permission request data
878
936
  const permReq = sessionUpdate;
879
- // Get the agent ID for this session to forward the response back
880
- const agentId = this.sessionMapper.getAgentId(permReq.sessionId);
937
+ // Look up agent ID using the ACP session ID (from the client connection),
938
+ // NOT permReq.sessionId which is the agent's internal session ID.
939
+ // The session mapper maps ACP session IDs → agent IDs, so using the
940
+ // internal session ID would fail silently and drop the permission request.
941
+ const agentId = this.sessionMapper.getAgentId(acpSessionId);
881
942
  if (!agentId) {
882
- console.warn(`[MacroAgent] No agent found for session ${permReq.sessionId}, cannot forward permission request`);
943
+ console.warn(`[MacroAgent] No agent found for ACP session ${acpSessionId}, cannot forward permission request`);
883
944
  return;
884
945
  }
885
- // Forward to sudocode via requestPermission RPC
886
- // This will trigger sudocode's WebSocketClientHandler which will show the prompt
946
+ // Forward via requestPermission RPC
947
+ // This will trigger the client's handler which will show the prompt
887
948
  try {
888
949
  const response = await this.connection.requestPermission({
889
950
  sessionId: acpSessionId,
@@ -1164,6 +1225,41 @@ export class MacroAgent {
1164
1225
  this.toolInfoCaches.delete(acpSessionId);
1165
1226
  }
1166
1227
  }
1228
+ /**
1229
+ * Handle _macro/getModels extension — returns the session's available models.
1230
+ * Claude Code populates models asynchronously after session creation
1231
+ * (via _model_state_update notification), so this allows the TUI to poll
1232
+ * for the model list once it's available.
1233
+ *
1234
+ * Returns full model info (modelId + name) since Claude Code uses shorthand
1235
+ * model IDs ("default", "sonnet") that don't match models.dev. The name
1236
+ * field (e.g., "Claude Sonnet 4") enables better model registry matching.
1237
+ */
1238
+ handleGetModels(params) {
1239
+ const { sessionId } = params;
1240
+ const agentId = this.sessionMapper.getAgentId(sessionId);
1241
+ if (!agentId) {
1242
+ return { currentModelId: null, availableModels: [] };
1243
+ }
1244
+ const session = this.agentManager.getSession(agentId);
1245
+ if (!session) {
1246
+ return { currentModelId: null, availableModels: [] };
1247
+ }
1248
+ // Try clientHandler's model info store first (from _model_state_update notification)
1249
+ const clientHandler = session.clientHandler;
1250
+ const modelInfo = clientHandler?.getSessionModelInfo?.(session.id);
1251
+ if (modelInfo && modelInfo.availableModels.length > 0) {
1252
+ return modelInfo;
1253
+ }
1254
+ // Fall back to Session.models (from initial session response — just IDs)
1255
+ if (session.models && session.models.length > 0) {
1256
+ return {
1257
+ currentModelId: session.models[0],
1258
+ availableModels: session.models.map((id) => ({ modelId: id, name: id })),
1259
+ };
1260
+ }
1261
+ return { currentModelId: null, availableModels: [] };
1262
+ }
1167
1263
  /**
1168
1264
  * Handle _macro/getHistory extension — returns conversation turns for a session
1169
1265
  */