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
@@ -9,6 +9,21 @@
9
9
  */
10
10
  import { SessionMapper } from "../../acp/session-mapper.js";
11
11
  // ─────────────────────────────────────────────────────────────────
12
+ // Helpers
13
+ // ─────────────────────────────────────────────────────────────────
14
+ /** Extract a plain-text output string from `rawOutput` (string | ContentBlock[] | undefined). */
15
+ function extractToolOutput(rawOutput) {
16
+ if (typeof rawOutput === "string")
17
+ return rawOutput;
18
+ if (Array.isArray(rawOutput)) {
19
+ return (rawOutput
20
+ .filter((item) => item.type === "text" && typeof item.text === "string")
21
+ .map((item) => item.text)
22
+ .join("\n") || undefined);
23
+ }
24
+ return undefined;
25
+ }
26
+ // ─────────────────────────────────────────────────────────────────
12
27
  // ACP-over-MAP Handler
13
28
  // ─────────────────────────────────────────────────────────────────
14
29
  export class ACPOverMAPHandler {
@@ -16,15 +31,52 @@ export class ACPOverMAPHandler {
16
31
  eventStore;
17
32
  taskManager;
18
33
  defaultCwd;
34
+ onAgentRegistered;
19
35
  /** Stream states by streamId */
20
36
  streams = new Map();
21
37
  /** Session mapper for ACP session -> Agent mapping */
22
- sessionMapper = new SessionMapper();
38
+ sessionMapper;
23
39
  constructor(config) {
24
40
  this.agentManager = config.agentManager;
25
41
  this.eventStore = config.eventStore;
26
42
  this.taskManager = config.taskManager;
27
43
  this.defaultCwd = config.defaultCwd ?? process.cwd();
44
+ this.onAgentRegistered = config.onAgentRegistered;
45
+ // Initialize session mapper with EventStore for persistence and recovery
46
+ this.sessionMapper = new SessionMapper(this.eventStore);
47
+ const recovered = this.sessionMapper.recoverFromStore();
48
+ if (recovered > 0) {
49
+ console.error(`[ACP-over-MAP] Recovered ${recovered} session(s) from store`);
50
+ }
51
+ }
52
+ /**
53
+ * Notify subscribers that a new agent was registered.
54
+ * Looks up agent details from EventStore and calls the onAgentRegistered callback.
55
+ */
56
+ notifyAgentRegistered(agentId) {
57
+ if (!this.onAgentRegistered)
58
+ return;
59
+ const agent = this.eventStore.getAgent(agentId);
60
+ this.onAgentRegistered({
61
+ id: agentId,
62
+ name: agent?.name,
63
+ role: agent?.role,
64
+ parent: agent?.parent ?? undefined,
65
+ metadata: agent?.metadata,
66
+ });
67
+ }
68
+ /**
69
+ * Abort all active streams targeting a specific agent.
70
+ * Called when an agent is stopped via MAP protocol to interrupt
71
+ * any in-progress ACP prompt streaming.
72
+ */
73
+ abortStreamsForAgent(agentId) {
74
+ for (const [streamId, streamState] of this.streams) {
75
+ if (streamState.agentId === agentId) {
76
+ console.error(`[ACP-over-MAP] Aborting stream ${streamId} for stopped agent ${agentId}`);
77
+ streamState.abortController.abort();
78
+ }
79
+ }
28
80
  }
29
81
  /**
30
82
  * Process an ACP request and return the response.
@@ -108,6 +160,14 @@ export class ACPOverMAPHandler {
108
160
  throw new Error("Stream already initialized");
109
161
  }
110
162
  streamState.initialized = true;
163
+ // Extract permission mode from _meta.macroConfig if provided
164
+ const meta = params?._meta;
165
+ const macroConfig = meta?.macroConfig;
166
+ const defaultSubAgentConfig = macroConfig?.defaultSubAgentConfig;
167
+ if (defaultSubAgentConfig?.permissionMode) {
168
+ streamState.permissionMode =
169
+ defaultSubAgentConfig.permissionMode;
170
+ }
111
171
  return {
112
172
  protocolVersion: 1,
113
173
  agentCapabilities: {
@@ -134,6 +194,7 @@ export class ACPOverMAPHandler {
134
194
  const spawned = await this.agentManager.getOrCreateHeadManager({
135
195
  cwd: workingDir,
136
196
  forceNew: true,
197
+ permissionMode: streamState.permissionMode,
137
198
  });
138
199
  const sessionId = spawned.session_id;
139
200
  streamState.sessionId = sessionId;
@@ -141,6 +202,8 @@ export class ACPOverMAPHandler {
141
202
  // Create session mapping
142
203
  this.sessionMapper.createMapping(sessionId, spawned.id);
143
204
  console.error(`[ACP-over-MAP] Created session ${sessionId} -> agent ${spawned.id}`);
205
+ // Notify subscribers that a new agent was registered
206
+ this.notifyAgentRegistered(spawned.id);
144
207
  // Emit session_info_update so client has title/timestamps
145
208
  this.emitSessionInfo(streamState, sessionId, emitNotification);
146
209
  return { sessionId };
@@ -149,7 +212,7 @@ export class ACPOverMAPHandler {
149
212
  if (!streamState.initialized) {
150
213
  throw new Error("Must call initialize before loadSession");
151
214
  }
152
- const { sessionId: rawSessionId, cwd, _meta } = params ?? {};
215
+ const { sessionId: rawSessionId, cwd, _meta, } = params ?? {};
153
216
  if (!rawSessionId) {
154
217
  throw new Error("sessionId required");
155
218
  }
@@ -163,6 +226,32 @@ export class ACPOverMAPHandler {
163
226
  }
164
227
  sessionId = agent.session_id;
165
228
  console.error(`[ACP-over-MAP] loadSession: Resolved agentId ${metaAgentId} to session ${sessionId}`);
229
+ // Handle the agent directly — works for both head managers and sub-agents.
230
+ // The previous code only searched listHeadManagers(), so sub-agents
231
+ // were never found and a new head manager was created instead.
232
+ if (this.agentManager.hasActiveSession(metaAgentId)) {
233
+ console.error(`[ACP-over-MAP] loadSession: Reusing active session for agent ${metaAgentId}`);
234
+ }
235
+ else {
236
+ // Agent exists but no active session — resume it
237
+ console.error(`[ACP-over-MAP] loadSession: Resuming agent ${metaAgentId}`);
238
+ try {
239
+ await this.agentManager.resume(metaAgentId, streamState.permissionMode);
240
+ }
241
+ catch (resumeErr) {
242
+ // ALREADY_RUNNING can happen in a race — safe to ignore
243
+ const code = resumeErr.code;
244
+ if (code !== "ALREADY_RUNNING") {
245
+ throw resumeErr;
246
+ }
247
+ console.error(`[ACP-over-MAP] loadSession: Agent ${metaAgentId} already running (race), continuing`);
248
+ }
249
+ }
250
+ streamState.sessionId = sessionId;
251
+ streamState.agentId = metaAgentId;
252
+ this.sessionMapper.createMapping(sessionId, metaAgentId);
253
+ this.emitSessionInfo(streamState, sessionId, emitNotification);
254
+ return { sessionId };
166
255
  }
167
256
  const workingDir = cwd ?? this.defaultCwd;
168
257
  // Try to find an existing head manager with this session ID
@@ -180,7 +269,7 @@ export class ACPOverMAPHandler {
180
269
  }
181
270
  // Agent exists but no active session - resume it
182
271
  console.error(`[ACP-over-MAP] loadSession: Resuming stopped agent ${existing.id}`);
183
- const spawned = await this.agentManager.resume(existing.id);
272
+ const spawned = await this.agentManager.resume(existing.id, streamState.permissionMode);
184
273
  streamState.sessionId = sessionId;
185
274
  streamState.agentId = spawned.id;
186
275
  this.sessionMapper.createMapping(sessionId, spawned.id);
@@ -192,10 +281,13 @@ export class ACPOverMAPHandler {
192
281
  const spawned = await this.agentManager.getOrCreateHeadManager({
193
282
  cwd: workingDir,
194
283
  sessionId,
284
+ permissionMode: streamState.permissionMode,
195
285
  });
196
286
  streamState.sessionId = sessionId;
197
287
  streamState.agentId = spawned.id;
198
288
  this.sessionMapper.createMapping(sessionId, spawned.id);
289
+ // Notify subscribers that a new agent was registered
290
+ this.notifyAgentRegistered(spawned.id);
199
291
  this.emitSessionInfo(streamState, sessionId, emitNotification);
200
292
  return { sessionId };
201
293
  }
@@ -264,9 +356,13 @@ export class ACPOverMAPHandler {
264
356
  this.ensureConversation(sessionId, agentId);
265
357
  // Accumulate response content for history recording
266
358
  const buffer = {
267
- textChunks: [],
268
- toolCalls: [],
359
+ parts: [],
269
360
  };
361
+ // Track latest plan for persistence
362
+ let latestPlan = null;
363
+ // Track tool info from initial tool_call events (title, name, input)
364
+ // so we can merge them when tool_call_update arrives with status "completed"
365
+ const toolInfoCache = new Map();
270
366
  try {
271
367
  // Stream responses from the agent
272
368
  let updateCount = 0;
@@ -275,24 +371,57 @@ export class ACPOverMAPHandler {
275
371
  if (streamState.abortController.signal.aborted) {
276
372
  return { stopReason: "cancelled" };
277
373
  }
278
- // Accumulate content for history persistence
374
+ // Accumulate content for history persistence (preserving text/tool interleaving order)
279
375
  const u = update;
280
376
  const updateType = u.sessionUpdate ?? u.type;
377
+ // Annotate permission_request updates with agentId so clients can respond
378
+ if (updateType === "permission_request") {
379
+ u._agentId = agentId;
380
+ }
281
381
  if (updateType === "agent_message_chunk") {
282
382
  const content = u.content;
283
383
  if (content?.text) {
284
- buffer.textChunks.push(content.text);
384
+ const last = buffer.parts[buffer.parts.length - 1];
385
+ if (last && last.type === "text") {
386
+ last.text += content.text;
387
+ }
388
+ else {
389
+ buffer.parts.push({ type: "text", text: content.text });
390
+ }
285
391
  }
286
392
  }
287
- else if (updateType === "tool_call" || updateType === "tool_call_update") {
393
+ else if (updateType === "plan") {
394
+ const entries = u.entries;
395
+ if (entries) {
396
+ latestPlan = entries;
397
+ }
398
+ }
399
+ else if (updateType === "tool_call" ||
400
+ updateType === "tool_call_update") {
401
+ const toolCallId = u.toolCallId;
288
402
  const status = u.status;
289
- if (status === "completed" || (updateType === "tool_call" && status !== "running")) {
290
- buffer.toolCalls.push({
291
- toolCallId: u.toolCallId,
403
+ const meta = u._meta;
404
+ // Cache tool info from initial tool_call events
405
+ if (updateType === "tool_call" && toolCallId) {
406
+ toolInfoCache.set(toolCallId, {
292
407
  title: u.title,
293
- status: u.status,
408
+ name: meta?.claudeCode?.toolName,
294
409
  input: u.rawInput,
295
- output: u.output,
410
+ });
411
+ }
412
+ if (status === "completed" || status === "failed") {
413
+ // Merge cached info for tool_call_update events that lack title/input
414
+ const cached = toolCallId
415
+ ? toolInfoCache.get(toolCallId)
416
+ : undefined;
417
+ buffer.parts.push({
418
+ type: "tool",
419
+ toolCallId,
420
+ title: u.title ?? cached?.title,
421
+ name: meta?.claudeCode?.toolName ?? cached?.name,
422
+ status: u.status,
423
+ input: u.rawInput ?? cached?.input,
424
+ output: extractToolOutput(u.rawOutput),
296
425
  });
297
426
  }
298
427
  }
@@ -300,18 +429,45 @@ export class ACPOverMAPHandler {
300
429
  emitSessionUpdate(update);
301
430
  updateCount++;
302
431
  }
303
- console.error(`[ACP-over-MAP] Prompt completed for agent ${agentId}, ${updateCount} updates`);
432
+ // Check if the loop ended because of cancellation
433
+ const wasCancelled = streamState.abortController.signal.aborted;
434
+ const stopReason = wasCancelled ? "cancelled" : "end_turn";
435
+ console.error(`[ACP-over-MAP] Prompt completed for agent ${agentId}, ${updateCount} updates, stopReason=${stopReason}`);
304
436
  // Persist conversation turns for history
305
437
  this.recordPromptTurns(sessionId, agentId, messageContent, buffer);
438
+ // Persist latest plan in EventStore for history loading across restarts
439
+ if (latestPlan) {
440
+ this.eventStore.updateAgentPlan(agentId, latestPlan);
441
+ }
306
442
  // Emit updated session info after prompt completes
307
443
  this.emitSessionInfo(streamState, sessionId, emitNotification);
308
- return { stopReason: "end_turn" };
444
+ return { stopReason };
309
445
  }
310
446
  catch (error) {
311
- console.error(`[ACP-over-MAP] Prompt error:`, error);
447
+ // Extract a meaningful error message — errors from the ACP SDK may be
448
+ // plain objects ({code, message}) rather than Error instances.
449
+ let errorMessage;
450
+ if (error instanceof Error) {
451
+ errorMessage = error.message;
452
+ }
453
+ else if (typeof error === "object" &&
454
+ error !== null &&
455
+ "message" in error &&
456
+ typeof error.message === "string") {
457
+ errorMessage = error.message;
458
+ }
459
+ else {
460
+ try {
461
+ errorMessage = JSON.stringify(error);
462
+ }
463
+ catch {
464
+ errorMessage = String(error);
465
+ }
466
+ }
467
+ console.error(`[ACP-over-MAP] Prompt error for agent ${agentId}:`, errorMessage);
312
468
  return {
313
469
  stopReason: "end_turn",
314
- error: error instanceof Error ? error.message : String(error),
470
+ error: errorMessage,
315
471
  };
316
472
  }
317
473
  finally {
@@ -322,25 +478,28 @@ export class ACPOverMAPHandler {
322
478
  const { sessionId: paramSessionId } = params ?? {};
323
479
  // Prefer server's resolved session ID over client's potentially stale one
324
480
  const sessionId = streamState.sessionId ?? paramSessionId ?? sessionIdFromContext;
325
- // Signal cancellation
481
+ const agentId = sessionId
482
+ ? this.sessionMapper.getAgentId(sessionId)
483
+ : streamState.agentId;
484
+ console.error(`[ACP-over-MAP] Cancel - streamId=${streamState.streamId} sessionId=${sessionId} agentId=${agentId}`);
485
+ // 1. Abort the for-await loop in handlePrompt so it stops yielding updates
326
486
  streamState.abortController.abort();
327
- if (!sessionId) {
328
- return { cancelled: true };
329
- }
330
- // Get the mapped agent
331
- const agentId = this.sessionMapper.getAgentId(sessionId);
332
- if (!agentId) {
333
- return { cancelled: true };
334
- }
335
- // Terminate the agent
336
- try {
337
- await this.agentManager.terminate(agentId, "cancelled");
338
- }
339
- catch (error) {
340
- console.warn(`[ACP-over-MAP] Error terminating agent ${agentId}:`, error);
487
+ // 2. Cancel the agent's active session (sends session/cancel to the subprocess)
488
+ // This does NOT terminate the agent — it only interrupts the current prompt.
489
+ // The subprocess stays alive and can accept new prompts.
490
+ // Use map/agents/stop for full agent termination.
491
+ if (agentId) {
492
+ const session = this.agentManager.getSession(agentId);
493
+ if (session) {
494
+ try {
495
+ await session.cancel();
496
+ console.error(`[ACP-over-MAP] Session cancelled for agent ${agentId}`);
497
+ }
498
+ catch (error) {
499
+ console.warn(`[ACP-over-MAP] session.cancel() failed for ${agentId}:`, error);
500
+ }
501
+ }
341
502
  }
342
- // Clean up
343
- this.sessionMapper.removeMapping(sessionId);
344
503
  return { cancelled: true };
345
504
  }
346
505
  // ─────────────────────────────────────────────────────────────────
@@ -370,7 +529,11 @@ export class ACPOverMAPHandler {
370
529
  task,
371
530
  cwd: cwd ?? this.defaultCwd,
372
531
  role: "worker",
532
+ topics,
533
+ config,
373
534
  });
535
+ // Notify subscribers that a new agent was registered
536
+ this.notifyAgentRegistered(spawned.id);
374
537
  return {
375
538
  agentId: spawned.id,
376
539
  sessionId: spawned.session_id,
@@ -402,8 +565,17 @@ export class ACPOverMAPHandler {
402
565
  if (!agent) {
403
566
  throw new Error(`Agent not found: ${agentId}`);
404
567
  }
568
+ // If the agent is already running, return its current state
569
+ // instead of throwing. The caller likely just wants to ensure
570
+ // the agent is active, which it already is.
405
571
  if (agent.state !== "stopped" && agent.state !== "failed") {
406
- throw new Error(`Agent ${agentId} is ${agent.state} only stopped or failed agents can be resumed`);
572
+ console.error(`[ACP-over-MAP] _macro/resume: Agent ${agentId} is already ${agent.state}, returning current state`);
573
+ return {
574
+ success: true,
575
+ agentId: agent.id,
576
+ sessionId: agent.session_id,
577
+ alreadyRunning: true,
578
+ };
407
579
  }
408
580
  const spawned = await this.agentManager.resume(agentId);
409
581
  return {
@@ -413,18 +585,18 @@ export class ACPOverMAPHandler {
413
585
  };
414
586
  }
415
587
  case "_macro/getHistory": {
416
- const { sessionId, agentId: historyAgentId, limit } = methodParams;
588
+ const { sessionId, agentId: historyAgentId, limit, } = methodParams;
417
589
  // Resolve conversationId: prefer agentId lookup (resolves to the
418
590
  // original session_id where turns were recorded), fall back to
419
591
  // explicit sessionId. This allows history to survive across server
420
592
  // restarts even when the ACP session ID changes (e.g., resume()
421
593
  // fails → TUI creates new session with different ID).
594
+ const agent = historyAgentId
595
+ ? this.eventStore.getAgent(historyAgentId)
596
+ : undefined;
422
597
  let conversationId;
423
- if (historyAgentId) {
424
- const agent = this.eventStore.getAgent(historyAgentId);
425
- if (agent) {
426
- conversationId = agent.session_id;
427
- }
598
+ if (agent) {
599
+ conversationId = agent.session_id;
428
600
  }
429
601
  if (!conversationId) {
430
602
  conversationId = sessionId;
@@ -432,11 +604,40 @@ export class ACPOverMAPHandler {
432
604
  if (!conversationId) {
433
605
  return { turns: [] };
434
606
  }
435
- const turns = this.eventStore.listTurns({
436
- conversationId,
437
- order: "asc",
438
- limit: limit ?? 200,
439
- });
607
+ // For forked agents, include the source agent's conversation history
608
+ // (pre-fork turns) followed by this agent's own turns.
609
+ // Only include source turns from before the fork to avoid leaking
610
+ // turns that the source recorded after the fork point.
611
+ const sourceAgentId = agent?.metadata?.fork_of;
612
+ let turns;
613
+ if (sourceAgentId) {
614
+ const sourceAgent = this.eventStore.getAgent(sourceAgentId);
615
+ const sourceConversationId = sourceAgent?.session_id;
616
+ const forkTimestamp = agent.created_at;
617
+ const sourceTurns = sourceConversationId
618
+ ? this.eventStore
619
+ .listTurns({
620
+ conversationId: sourceConversationId,
621
+ order: "asc",
622
+ limit: limit ?? 200,
623
+ })
624
+ .filter((t) => t.timestamp <= forkTimestamp)
625
+ : [];
626
+ const ownTurns = this.eventStore.listTurns({
627
+ conversationId,
628
+ order: "asc",
629
+ limit: limit ?? 200,
630
+ });
631
+ turns = [...sourceTurns, ...ownTurns];
632
+ }
633
+ else {
634
+ turns = this.eventStore.listTurns({
635
+ conversationId,
636
+ order: "asc",
637
+ limit: limit ?? 200,
638
+ });
639
+ }
640
+ const plan = agent?.plan ?? [];
440
641
  return {
441
642
  turns: turns.map((turn) => ({
442
643
  role: turn.contentType === "user_prompt"
@@ -445,8 +646,241 @@ export class ACPOverMAPHandler {
445
646
  timestamp: turn.timestamp,
446
647
  content: turn.content,
447
648
  })),
649
+ plan,
650
+ cwd: agent?.cwd ?? null,
651
+ };
652
+ }
653
+ case "_macro/respondToPermission": {
654
+ const { agentId: targetAgentId, requestId, optionId, } = methodParams;
655
+ if (!targetAgentId || !requestId || !optionId) {
656
+ throw new Error("agentId, requestId, and optionId are required");
657
+ }
658
+ const success = this.agentManager.respondToPermission(targetAgentId, requestId, optionId);
659
+ return { success };
660
+ }
661
+ case "_macro/cancelPermission": {
662
+ const { agentId: targetAgentId, requestId } = methodParams;
663
+ if (!targetAgentId || !requestId) {
664
+ throw new Error("agentId and requestId are required");
665
+ }
666
+ const success = this.agentManager.cancelPermission(targetAgentId, requestId);
667
+ return { success };
668
+ }
669
+ case "_macro/setPermissionMode": {
670
+ const { agentId: targetAgentId, permissionMode } = methodParams;
671
+ if (!targetAgentId || !permissionMode) {
672
+ throw new Error("agentId and permissionMode are required");
673
+ }
674
+ const previousMode = this.agentManager.getPermissionMode(targetAgentId);
675
+ const success = this.agentManager.setPermissionMode(targetAgentId, permissionMode);
676
+ if (success) {
677
+ return { success: true, previousMode: previousMode ?? undefined };
678
+ }
679
+ return {
680
+ success: false,
681
+ error: `No active session found for agent ${targetAgentId}`,
682
+ };
683
+ }
684
+ case "_macro/forkAgent": {
685
+ const { agentId, name, prompt, cwd } = methodParams;
686
+ if (!agentId) {
687
+ throw new Error("agentId is required");
688
+ }
689
+ const sourceAgent = this.eventStore.getAgent(agentId);
690
+ if (!sourceAgent) {
691
+ throw new Error(`Agent not found: ${agentId}`);
692
+ }
693
+ const forked = await this.agentManager.forkAgent(agentId, {
694
+ name,
695
+ prompt,
696
+ cwd: cwd ?? sourceAgent.cwd ?? this.defaultCwd,
697
+ });
698
+ // Fire-and-forget initial prompt if provided
699
+ if (prompt) {
700
+ (async () => {
701
+ try {
702
+ for await (const _update of this.agentManager.prompt(forked.id, prompt)) {
703
+ // drain iterator
704
+ }
705
+ }
706
+ catch {
707
+ // best-effort
708
+ }
709
+ })();
710
+ }
711
+ return {
712
+ newAgentId: forked.id,
713
+ newSessionId: forked.session_id,
714
+ originalAgentId: agentId,
715
+ providerSessionId: forked.session?.id,
716
+ };
717
+ }
718
+ case "_macro/agents/update": {
719
+ const { agentId, name, plan, metadata } = methodParams;
720
+ if (!agentId) {
721
+ throw new Error("agentId is required");
722
+ }
723
+ if (name === undefined &&
724
+ plan === undefined &&
725
+ metadata === undefined) {
726
+ throw new Error("At least one field to update is required (name, plan, or metadata)");
727
+ }
728
+ if (name !== undefined && !name.trim()) {
729
+ throw new Error("name must not be empty");
730
+ }
731
+ const agent = this.eventStore.getAgent(agentId);
732
+ if (!agent) {
733
+ throw new Error(`Agent not found: ${agentId}`);
734
+ }
735
+ const updates = {};
736
+ const updatedFields = [];
737
+ if (name !== undefined) {
738
+ updates.name = name.trim();
739
+ updatedFields.push("name");
740
+ }
741
+ if (plan !== undefined) {
742
+ updates.plan = plan;
743
+ updatedFields.push("plan");
744
+ }
745
+ if (metadata !== undefined) {
746
+ updates.metadata = metadata;
747
+ updatedFields.push("metadata");
748
+ }
749
+ this.eventStore.updateAgentMetadata(agentId, updates);
750
+ return {
751
+ success: true,
752
+ agentId,
753
+ updated: updatedFields,
754
+ };
755
+ }
756
+ case "_macro/getModels": {
757
+ const { sessionId } = methodParams;
758
+ const agentId = this.sessionMapper.getAgentId(sessionId);
759
+ if (!agentId) {
760
+ return { currentModelId: null, availableModels: [] };
761
+ }
762
+ const session = this.agentManager.getSession(agentId);
763
+ if (!session) {
764
+ return { currentModelId: null, availableModels: [] };
765
+ }
766
+ // Try clientHandler's model info store first (from _model_state_update notification)
767
+ const clientHandler = session.clientHandler;
768
+ const modelInfo = clientHandler?.getSessionModelInfo?.(session.id);
769
+ if (modelInfo && modelInfo.availableModels.length > 0) {
770
+ return modelInfo;
771
+ }
772
+ // Fall back to Session.models (from initial session response — just IDs)
773
+ if (session.models && session.models.length > 0) {
774
+ return {
775
+ currentModelId: session.models[0],
776
+ availableModels: session.models.map((id) => ({
777
+ modelId: id,
778
+ name: id,
779
+ })),
780
+ };
781
+ }
782
+ return { currentModelId: null, availableModels: [] };
783
+ }
784
+ case "_session/setCompaction": {
785
+ // Compaction is handled internally by the agent process.
786
+ // Accept the request as a no-op so the client doesn't get an error.
787
+ // TODO: Make sure this overrides if needed.
788
+ return { success: true };
789
+ }
790
+ // ── Task Management Extensions ────────────────────────────────
791
+ // These mirror the registered adapter extensions (_macro/task/*)
792
+ // so they're accessible via ACP-over-MAP streams.
793
+ case "_macro/task/list": {
794
+ const { filter } = (methodParams ?? {});
795
+ const taskFilter = filter
796
+ ? {
797
+ status: filter.status,
798
+ assigned_agent: filter.assignedAgent,
799
+ parent_task: filter.parentTask,
800
+ created_by: filter.createdBy,
801
+ rootTasksOnly: filter.rootTasksOnly,
802
+ }
803
+ : undefined;
804
+ const tasks = this.taskManager.list(taskFilter);
805
+ return {
806
+ tasks: tasks.map((t) => ({
807
+ id: t.id,
808
+ description: t.description,
809
+ status: t.status,
810
+ assignedAgent: t.assigned_agent,
811
+ createdBy: t.created_by,
812
+ createdAt: t.created_at,
813
+ parentTask: t.parent_task,
814
+ isBlocked: t.isBlocked,
815
+ externalId: t.external_id,
816
+ })),
448
817
  };
449
818
  }
819
+ case "_macro/task/get": {
820
+ const { taskId } = methodParams;
821
+ if (!taskId)
822
+ throw new Error("taskId is required");
823
+ const task = this.taskManager.get(taskId);
824
+ if (!task)
825
+ throw new Error(`Task not found: ${taskId}`);
826
+ return {
827
+ task: {
828
+ id: task.id,
829
+ description: task.description,
830
+ status: task.status,
831
+ assignedAgent: task.assigned_agent,
832
+ createdBy: task.created_by,
833
+ createdAt: task.created_at,
834
+ parentTask: task.parent_task,
835
+ isBlocked: task.isBlocked,
836
+ externalId: task.external_id,
837
+ },
838
+ };
839
+ }
840
+ case "_macro/task/create": {
841
+ const { description, parentTask, externalId } = methodParams;
842
+ if (!description)
843
+ throw new Error("description is required");
844
+ // Determine who is creating the task — use the stream's agent or a default
845
+ const createdBy = (streamState.agentId ?? "tui");
846
+ const task = this.taskManager.create({
847
+ description,
848
+ created_by: createdBy,
849
+ parent_task: parentTask,
850
+ });
851
+ return {
852
+ task: {
853
+ id: task.id,
854
+ description: task.description,
855
+ status: task.status,
856
+ assignedAgent: task.assigned_agent,
857
+ createdBy: task.created_by,
858
+ createdAt: task.created_at,
859
+ parentTask: task.parent_task,
860
+ },
861
+ };
862
+ }
863
+ case "_macro/task/assign": {
864
+ const { taskId, agentId, role } = methodParams;
865
+ if (!taskId)
866
+ throw new Error("taskId is required");
867
+ if (!agentId)
868
+ throw new Error("agentId is required");
869
+ this.taskManager.assign(taskId, agentId, role);
870
+ return { success: true };
871
+ }
872
+ case "_macro/task/complete": {
873
+ const { taskId, outputs } = methodParams;
874
+ if (!taskId)
875
+ throw new Error("taskId is required");
876
+ // Update status to completed
877
+ this.taskManager.updateStatus(taskId, "completed");
878
+ // If outputs provided, update task metadata
879
+ if (outputs) {
880
+ this.taskManager.update(taskId, { outputs });
881
+ }
882
+ return { success: true };
883
+ }
450
884
  default:
451
885
  throw new Error(`Unknown extension method: ${method}`);
452
886
  }
@@ -505,15 +939,8 @@ export class ACPOverMAPHandler {
505
939
  },
506
940
  });
507
941
  }
508
- // Record assistant turn with accumulated content
509
- const assistantText = buffer.textChunks.join("");
510
- const parts = [];
511
- if (assistantText) {
512
- parts.push({ type: "text", text: assistantText });
513
- }
514
- for (const tool of buffer.toolCalls) {
515
- parts.push({ type: "tool", ...tool });
516
- }
942
+ // Record assistant turn with accumulated content (parts already in order)
943
+ const parts = buffer.parts;
517
944
  if (parts.length > 0) {
518
945
  this.eventStore.emit({
519
946
  type: "turn",