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
@@ -30,6 +30,7 @@ function createMockAgent(overrides: Partial<Agent> = {}): Agent {
30
30
  lineage: [],
31
31
  config: {},
32
32
  cwd: "/test/cwd",
33
+ plan: [],
33
34
  created_at: Date.now(),
34
35
  started_at: Date.now(),
35
36
  ...overrides,
@@ -268,7 +269,7 @@ describe("ACP-over-MAP history persistence", () => {
268
269
  title: "Read file",
269
270
  status: "completed",
270
271
  rawInput: { path: "/test.txt" },
271
- output: "file contents",
272
+ rawOutput: "file contents",
272
273
  },
273
274
  ]);
274
275
 
@@ -306,6 +307,93 @@ describe("ACP-over-MAP history persistence", () => {
306
307
  });
307
308
  });
308
309
 
310
+ it("should extract tool output from rawOutput ContentBlock array", async () => {
311
+ await setup([
312
+ {
313
+ sessionUpdate: "tool_call",
314
+ toolCallId: "tc-array",
315
+ title: "Read file",
316
+ status: "completed",
317
+ rawInput: { path: "/test.txt" },
318
+ rawOutput: [
319
+ { type: "text", text: "line 1" },
320
+ { type: "text", text: "line 2" },
321
+ ],
322
+ },
323
+ ]);
324
+
325
+ const streamId = "test-stream-array-output";
326
+ const agentId = "agent-1" as AgentId;
327
+ const sessionId = await initAndCreateSession(streamId, agentId);
328
+
329
+ await handler.processRequest(
330
+ agentId,
331
+ envelope(streamId, "session/prompt", {
332
+ prompt: [{ type: "text", text: "Read it" }],
333
+ }, sessionId),
334
+ );
335
+
336
+ const historyResult = await handler.processRequest(
337
+ agentId,
338
+ envelope(streamId, "_macro/getHistory", { sessionId }),
339
+ );
340
+
341
+ const turns = (historyResult.acp.result as { turns: { role: string; content: unknown }[] }).turns;
342
+ const assistantContent = turns[1].content as {
343
+ parts: { type: string; output?: string }[];
344
+ };
345
+ const toolPart = assistantContent.parts.find((p) => p.type === "tool");
346
+ expect(toolPart?.output).toBe("line 1\nline 2");
347
+ });
348
+
349
+ it("should merge title from initial tool_call into tool_call_update", async () => {
350
+ // Simulates WebSearch/WebFetch: initial tool_call has title, but
351
+ // tool_call_update (completed) does not include title.
352
+ await setup([
353
+ {
354
+ sessionUpdate: "tool_call",
355
+ toolCallId: "tc-ws",
356
+ title: "Search query here",
357
+ status: "pending",
358
+ rawInput: { query: "test" },
359
+ _meta: { claudeCode: { toolName: "WebSearch" } },
360
+ },
361
+ {
362
+ sessionUpdate: "tool_call_update",
363
+ toolCallId: "tc-ws",
364
+ status: "completed",
365
+ rawOutput: "search results",
366
+ // No title, no rawInput, no _meta — should use cached values
367
+ },
368
+ ]);
369
+
370
+ const streamId = "test-stream-merge-title";
371
+ const agentId = "agent-1" as AgentId;
372
+ const sessionId = await initAndCreateSession(streamId, agentId);
373
+
374
+ await handler.processRequest(
375
+ agentId,
376
+ envelope(streamId, "session/prompt", {
377
+ prompt: [{ type: "text", text: "Search for test" }],
378
+ }, sessionId),
379
+ );
380
+
381
+ const historyResult = await handler.processRequest(
382
+ agentId,
383
+ envelope(streamId, "_macro/getHistory", { sessionId }),
384
+ );
385
+
386
+ const turns = (historyResult.acp.result as { turns: { role: string; content: unknown }[] }).turns;
387
+ const assistantContent = turns[1].content as {
388
+ parts: { type: string; title?: string; name?: string; input?: unknown; output?: string }[];
389
+ };
390
+ const toolPart = assistantContent.parts.find((p) => p.type === "tool");
391
+ expect(toolPart?.title).toBe("Search query here");
392
+ expect(toolPart?.name).toBe("WebSearch");
393
+ expect(toolPart?.input).toEqual({ query: "test" });
394
+ expect(toolPart?.output).toBe("search results");
395
+ });
396
+
309
397
  it("should accumulate history across multiple prompts", async () => {
310
398
  const { agentManager } = await setup([
311
399
  {
@@ -392,7 +480,7 @@ describe("ACP-over-MAP history persistence", () => {
392
480
  title: "Done tool",
393
481
  status: "completed",
394
482
  rawInput: { x: 1 },
395
- output: "result",
483
+ rawOutput: "result",
396
484
  },
397
485
  ]);
398
486
 
@@ -661,4 +749,638 @@ describe("ACP-over-MAP history persistence", () => {
661
749
  const turnsNoAgent = (historyNoAgent.acp.result as { turns: unknown[] }).turns;
662
750
  expect(turnsNoAgent).toHaveLength(0);
663
751
  });
752
+
753
+ it("should include agent cwd in getHistory response", async () => {
754
+ await setup([
755
+ {
756
+ sessionUpdate: "agent_message_chunk",
757
+ content: { type: "text", text: "Working in dir" },
758
+ },
759
+ ]);
760
+
761
+ const agentId = "agent-1" as AgentId;
762
+ const streamId = "test-stream-cwd";
763
+ const sessionId = await initAndCreateSession(streamId, agentId);
764
+
765
+ await handler.processRequest(
766
+ agentId,
767
+ envelope(streamId, "session/prompt", {
768
+ prompt: [{ type: "text", text: "Where are you?" }],
769
+ }, sessionId),
770
+ );
771
+
772
+ const historyResult = await handler.processRequest(
773
+ agentId,
774
+ envelope(streamId, "_macro/getHistory", { sessionId, agentId }),
775
+ );
776
+
777
+ const result = historyResult.acp.result as { turns: unknown[]; cwd: string | null };
778
+ expect(result.cwd).toBe("/test/cwd");
779
+ });
780
+
781
+ it("should return null cwd when agentId is not provided", async () => {
782
+ await setup([
783
+ {
784
+ sessionUpdate: "agent_message_chunk",
785
+ content: { type: "text", text: "Hello" },
786
+ },
787
+ ]);
788
+
789
+ const agentId = "agent-1" as AgentId;
790
+ const streamId = "test-stream-cwd-null";
791
+ const sessionId = await initAndCreateSession(streamId, agentId);
792
+
793
+ await handler.processRequest(
794
+ agentId,
795
+ envelope(streamId, "session/prompt", {
796
+ prompt: [{ type: "text", text: "Hi" }],
797
+ }, sessionId),
798
+ );
799
+
800
+ // Query without agentId — cwd should be null
801
+ const historyResult = await handler.processRequest(
802
+ agentId,
803
+ envelope(streamId, "_macro/getHistory", { sessionId }),
804
+ );
805
+
806
+ const result = historyResult.acp.result as { turns: unknown[]; cwd: string | null };
807
+ expect(result.cwd).toBeNull();
808
+ });
809
+
810
+ it("should capture plan entries from streaming and include in getHistory", async () => {
811
+ await setup([
812
+ {
813
+ sessionUpdate: "agent_message_chunk",
814
+ content: { type: "text", text: "Planning..." },
815
+ },
816
+ {
817
+ sessionUpdate: "plan",
818
+ entries: [
819
+ { content: "Analyze codebase", priority: "high", status: "in_progress" },
820
+ { content: "Write tests", priority: "medium", status: "pending" },
821
+ { content: "Deploy", priority: "low", status: "pending" },
822
+ ],
823
+ },
824
+ {
825
+ sessionUpdate: "agent_message_chunk",
826
+ content: { type: "text", text: " Done." },
827
+ },
828
+ ]);
829
+
830
+ const agentId = "agent-1" as AgentId;
831
+ const streamId = "test-stream-plan";
832
+ const sessionId = await initAndCreateSession(streamId, agentId);
833
+
834
+ await handler.processRequest(
835
+ agentId,
836
+ envelope(streamId, "session/prompt", {
837
+ prompt: [{ type: "text", text: "Make a plan" }],
838
+ }, sessionId),
839
+ );
840
+
841
+ const historyResult = await handler.processRequest(
842
+ agentId,
843
+ envelope(streamId, "_macro/getHistory", { sessionId, agentId }),
844
+ );
845
+
846
+ const result = historyResult.acp.result as {
847
+ turns: unknown[];
848
+ plan: Array<{ content: string; priority: string; status: string }>;
849
+ };
850
+
851
+ expect(result.plan).toHaveLength(3);
852
+ expect(result.plan[0]).toEqual({
853
+ content: "Analyze codebase",
854
+ priority: "high",
855
+ status: "in_progress",
856
+ });
857
+ expect(result.plan[1]).toEqual({
858
+ content: "Write tests",
859
+ priority: "medium",
860
+ status: "pending",
861
+ });
862
+ expect(result.plan[2]).toEqual({
863
+ content: "Deploy",
864
+ priority: "low",
865
+ status: "pending",
866
+ });
867
+ });
868
+
869
+ it("should return empty plan when no plan updates were received", async () => {
870
+ await setup([
871
+ {
872
+ sessionUpdate: "agent_message_chunk",
873
+ content: { type: "text", text: "No plan here" },
874
+ },
875
+ ]);
876
+
877
+ const agentId = "agent-1" as AgentId;
878
+ const streamId = "test-stream-no-plan";
879
+ const sessionId = await initAndCreateSession(streamId, agentId);
880
+
881
+ await handler.processRequest(
882
+ agentId,
883
+ envelope(streamId, "session/prompt", {
884
+ prompt: [{ type: "text", text: "Just chat" }],
885
+ }, sessionId),
886
+ );
887
+
888
+ const historyResult = await handler.processRequest(
889
+ agentId,
890
+ envelope(streamId, "_macro/getHistory", { sessionId, agentId }),
891
+ );
892
+
893
+ const result = historyResult.acp.result as {
894
+ turns: unknown[];
895
+ plan: unknown[];
896
+ };
897
+
898
+ expect(result.plan).toEqual([]);
899
+ });
900
+
901
+ it("should use latest plan when multiple plan updates are received", async () => {
902
+ await setup([
903
+ {
904
+ sessionUpdate: "plan",
905
+ entries: [
906
+ { content: "Step 1", priority: "high", status: "pending" },
907
+ ],
908
+ },
909
+ {
910
+ sessionUpdate: "agent_message_chunk",
911
+ content: { type: "text", text: "Working..." },
912
+ },
913
+ {
914
+ sessionUpdate: "plan",
915
+ entries: [
916
+ { content: "Step 1", priority: "high", status: "completed" },
917
+ { content: "Step 2", priority: "medium", status: "in_progress" },
918
+ ],
919
+ },
920
+ ]);
921
+
922
+ const agentId = "agent-1" as AgentId;
923
+ const streamId = "test-stream-plan-latest";
924
+ const sessionId = await initAndCreateSession(streamId, agentId);
925
+
926
+ await handler.processRequest(
927
+ agentId,
928
+ envelope(streamId, "session/prompt", {
929
+ prompt: [{ type: "text", text: "Work on it" }],
930
+ }, sessionId),
931
+ );
932
+
933
+ const historyResult = await handler.processRequest(
934
+ agentId,
935
+ envelope(streamId, "_macro/getHistory", { sessionId, agentId }),
936
+ );
937
+
938
+ const result = historyResult.acp.result as {
939
+ turns: unknown[];
940
+ plan: Array<{ content: string; priority: string; status: string }>;
941
+ };
942
+
943
+ // Should have the LATEST plan (second update)
944
+ expect(result.plan).toHaveLength(2);
945
+ expect(result.plan[0].status).toBe("completed");
946
+ expect(result.plan[1].status).toBe("in_progress");
947
+ });
948
+
949
+ it("should persist plan across multiple prompts (latest wins)", async () => {
950
+ const { agentManager } = await setup([
951
+ {
952
+ sessionUpdate: "plan",
953
+ entries: [
954
+ { content: "Initial task", priority: "high", status: "in_progress" },
955
+ ],
956
+ },
957
+ {
958
+ sessionUpdate: "agent_message_chunk",
959
+ content: { type: "text", text: "First response" },
960
+ },
961
+ ]);
962
+
963
+ const agentId = "agent-1" as AgentId;
964
+ const streamId = "test-stream-plan-persist";
965
+ const sessionId = await initAndCreateSession(streamId, agentId);
966
+
967
+ // First prompt — sets initial plan
968
+ await handler.processRequest(
969
+ agentId,
970
+ envelope(streamId, "session/prompt", {
971
+ prompt: [{ type: "text", text: "Start working" }],
972
+ }, sessionId),
973
+ );
974
+
975
+ // Verify plan from first prompt
976
+ let historyResult = await handler.processRequest(
977
+ agentId,
978
+ envelope(streamId, "_macro/getHistory", { sessionId, agentId }),
979
+ );
980
+ let result = historyResult.acp.result as {
981
+ turns: unknown[];
982
+ plan: Array<{ content: string; priority: string; status: string }>;
983
+ };
984
+ expect(result.plan).toHaveLength(1);
985
+ expect(result.plan[0].content).toBe("Initial task");
986
+
987
+ // Second prompt with updated plan
988
+ (agentManager.prompt as ReturnType<typeof vi.fn>).mockReturnValue({
989
+ [Symbol.asyncIterator]: async function* () {
990
+ yield {
991
+ sessionUpdate: "plan",
992
+ entries: [
993
+ { content: "Initial task", priority: "high", status: "completed" },
994
+ { content: "New task", priority: "medium", status: "in_progress" },
995
+ ],
996
+ };
997
+ yield {
998
+ sessionUpdate: "agent_message_chunk",
999
+ content: { type: "text", text: "Second response" },
1000
+ };
1001
+ },
1002
+ } as any);
1003
+
1004
+ await handler.processRequest(
1005
+ agentId,
1006
+ envelope(streamId, "session/prompt", {
1007
+ prompt: [{ type: "text", text: "Continue" }],
1008
+ }, sessionId),
1009
+ );
1010
+
1011
+ // Plan should now reflect the second prompt's update
1012
+ historyResult = await handler.processRequest(
1013
+ agentId,
1014
+ envelope(streamId, "_macro/getHistory", { sessionId, agentId }),
1015
+ );
1016
+ result = historyResult.acp.result as {
1017
+ turns: unknown[];
1018
+ plan: Array<{ content: string; priority: string; status: string }>;
1019
+ };
1020
+ expect(result.plan).toHaveLength(2);
1021
+ expect(result.plan[0].status).toBe("completed");
1022
+ expect(result.plan[1].content).toBe("New task");
1023
+ });
1024
+
1025
+ it("should keep plan from earlier prompt if new prompt has no plan updates", async () => {
1026
+ const { agentManager } = await setup([
1027
+ {
1028
+ sessionUpdate: "plan",
1029
+ entries: [
1030
+ { content: "Persistent task", priority: "high", status: "in_progress" },
1031
+ ],
1032
+ },
1033
+ {
1034
+ sessionUpdate: "agent_message_chunk",
1035
+ content: { type: "text", text: "First" },
1036
+ },
1037
+ ]);
1038
+
1039
+ const agentId = "agent-1" as AgentId;
1040
+ const streamId = "test-stream-plan-keep";
1041
+ const sessionId = await initAndCreateSession(streamId, agentId);
1042
+
1043
+ // First prompt — sets plan
1044
+ await handler.processRequest(
1045
+ agentId,
1046
+ envelope(streamId, "session/prompt", {
1047
+ prompt: [{ type: "text", text: "Plan it" }],
1048
+ }, sessionId),
1049
+ );
1050
+
1051
+ // Second prompt with NO plan updates
1052
+ (agentManager.prompt as ReturnType<typeof vi.fn>).mockReturnValue({
1053
+ [Symbol.asyncIterator]: async function* () {
1054
+ yield {
1055
+ sessionUpdate: "agent_message_chunk",
1056
+ content: { type: "text", text: "No plan this time" },
1057
+ };
1058
+ },
1059
+ } as any);
1060
+
1061
+ await handler.processRequest(
1062
+ agentId,
1063
+ envelope(streamId, "session/prompt", {
1064
+ prompt: [{ type: "text", text: "Just chat" }],
1065
+ }, sessionId),
1066
+ );
1067
+
1068
+ // Plan should still be present from the first prompt
1069
+ const historyResult = await handler.processRequest(
1070
+ agentId,
1071
+ envelope(streamId, "_macro/getHistory", { sessionId, agentId }),
1072
+ );
1073
+ const result = historyResult.acp.result as {
1074
+ turns: unknown[];
1075
+ plan: Array<{ content: string; priority: string; status: string }>;
1076
+ };
1077
+ expect(result.plan).toHaveLength(1);
1078
+ expect(result.plan[0].content).toBe("Persistent task");
1079
+ });
1080
+
1081
+ // ─────────────────────────────────────────────────────────────────
1082
+ // Fork history tests
1083
+ // ─────────────────────────────────────────────────────────────────
1084
+
1085
+ /** Register a forked agent with fork_of metadata */
1086
+ function registerForkedAgent(
1087
+ agentId: string,
1088
+ sessionId: string,
1089
+ sourceAgentId: string,
1090
+ createdAt?: number,
1091
+ ): void {
1092
+ eventStore.emit({
1093
+ type: "spawn",
1094
+ source: { agent_id: sourceAgentId },
1095
+ payload: {
1096
+ agent_id: agentId,
1097
+ session_id: sessionId,
1098
+ task: `[Fork of ${sourceAgentId}]`,
1099
+ task_id: "task-fork",
1100
+ cwd: "/test/cwd",
1101
+ },
1102
+ });
1103
+ // Set fork_of metadata (mirrors what agent-manager.ts does)
1104
+ eventStore.updateAgentMetadata(agentId as AgentId, {
1105
+ metadata: { fork_of: sourceAgentId },
1106
+ });
1107
+ // Backdate created_at if specified (for timestamp filtering tests)
1108
+ if (createdAt !== undefined) {
1109
+ // Overwrite the agent row's created_at by re-emitting a lifecycle event
1110
+ // at the desired time — but since we can't change created_at directly,
1111
+ // we rely on the spawn event timestamp. For testing, we'll emit the
1112
+ // spawn before recording turns to ensure correct ordering.
1113
+ }
1114
+ // Mark as running
1115
+ eventStore.emit({
1116
+ type: "lifecycle",
1117
+ source: { agent_id: agentId },
1118
+ payload: {
1119
+ agent_id: agentId,
1120
+ action: "started",
1121
+ },
1122
+ });
1123
+ }
1124
+
1125
+ /** Record a turn directly in the event store (bypasses prompt flow) */
1126
+ function recordTurn(
1127
+ agentId: string,
1128
+ conversationId: string,
1129
+ role: "user" | "assistant",
1130
+ content: unknown,
1131
+ ): void {
1132
+ const now = Date.now();
1133
+ eventStore.emit({
1134
+ type: "turn",
1135
+ source: { agent_id: agentId },
1136
+ payload: {
1137
+ action: "recorded",
1138
+ turn_id: `turn_${role}_${now}_${Math.random().toString(36).slice(2, 8)}`,
1139
+ conversation_id: conversationId,
1140
+ participant: role,
1141
+ timestamp: now,
1142
+ content_type: role === "user" ? "user_prompt" : "assistant_response",
1143
+ content,
1144
+ source_type: "acp",
1145
+ },
1146
+ });
1147
+ }
1148
+
1149
+ it("should include source agent history for forked agents", async () => {
1150
+ await setup();
1151
+
1152
+ const sourceAgentId = "agent-source" as AgentId;
1153
+ const sourceSessionId = "session-source";
1154
+ const forkedAgentId = "agent-forked" as AgentId;
1155
+ const forkedSessionId = "session-forked";
1156
+
1157
+ // Register source agent and record turns
1158
+ registerAgent(sourceAgentId, sourceSessionId);
1159
+ recordTurn(sourceAgentId, sourceSessionId, "user", "Hello source");
1160
+ recordTurn(sourceAgentId, sourceSessionId, "assistant", { parts: [{ type: "text", text: "Hi from source" }] });
1161
+
1162
+ // Register forked agent with fork_of metadata
1163
+ registerForkedAgent(forkedAgentId, forkedSessionId, sourceAgentId);
1164
+
1165
+ // Initialize stream for forked agent
1166
+ const streamId = "fork-history-stream";
1167
+ await handler.processRequest(
1168
+ forkedAgentId,
1169
+ envelope(streamId, "initialize", {
1170
+ protocolVersion: 1,
1171
+ capabilities: {},
1172
+ clientInfo: { name: "test", version: "1.0" },
1173
+ }),
1174
+ );
1175
+
1176
+ // Query history for forked agent
1177
+ const historyResult = await handler.processRequest(
1178
+ forkedAgentId,
1179
+ envelope(streamId, "_macro/getHistory", { agentId: forkedAgentId }),
1180
+ );
1181
+
1182
+ const turns = (historyResult.acp.result as { turns: { role: string; content: unknown }[] }).turns;
1183
+
1184
+ // Should include source agent's 2 turns
1185
+ expect(turns).toHaveLength(2);
1186
+ expect(turns[0].role).toBe("user");
1187
+ expect(turns[0].content).toBe("Hello source");
1188
+ expect(turns[1].role).toBe("assistant");
1189
+ });
1190
+
1191
+ it("should combine source and forked agent turns in order", async () => {
1192
+ await setup();
1193
+
1194
+ const sourceAgentId = "agent-src" as AgentId;
1195
+ const sourceSessionId = "session-src";
1196
+ const forkedAgentId = "agent-fork" as AgentId;
1197
+ const forkedSessionId = "session-fork";
1198
+
1199
+ // Source agent conversation
1200
+ registerAgent(sourceAgentId, sourceSessionId);
1201
+ recordTurn(sourceAgentId, sourceSessionId, "user", "Question 1");
1202
+ recordTurn(sourceAgentId, sourceSessionId, "assistant", { parts: [{ type: "text", text: "Answer 1" }] });
1203
+
1204
+ // Fork the agent
1205
+ registerForkedAgent(forkedAgentId, forkedSessionId, sourceAgentId);
1206
+
1207
+ // Forked agent has its own turns
1208
+ recordTurn(forkedAgentId, forkedSessionId, "user", "Question 2 (forked)");
1209
+ recordTurn(forkedAgentId, forkedSessionId, "assistant", { parts: [{ type: "text", text: "Answer 2 (forked)" }] });
1210
+
1211
+ const streamId = "fork-combined-stream";
1212
+ await handler.processRequest(
1213
+ forkedAgentId,
1214
+ envelope(streamId, "initialize", {
1215
+ protocolVersion: 1,
1216
+ capabilities: {},
1217
+ clientInfo: { name: "test", version: "1.0" },
1218
+ }),
1219
+ );
1220
+
1221
+ const historyResult = await handler.processRequest(
1222
+ forkedAgentId,
1223
+ envelope(streamId, "_macro/getHistory", { agentId: forkedAgentId }),
1224
+ );
1225
+
1226
+ const turns = (historyResult.acp.result as { turns: { role: string; content: unknown }[] }).turns;
1227
+
1228
+ // Source turns (2) + forked turns (2) = 4
1229
+ expect(turns).toHaveLength(4);
1230
+ expect(turns[0].content).toBe("Question 1");
1231
+ expect(turns[2].content).toBe("Question 2 (forked)");
1232
+ });
1233
+
1234
+ it("should not include source turns recorded after the fork", async () => {
1235
+ await setup();
1236
+
1237
+ const sourceAgentId = "agent-pre" as AgentId;
1238
+ const sourceSessionId = "session-pre";
1239
+ const forkedAgentId = "agent-post" as AgentId;
1240
+ const forkedSessionId = "session-post";
1241
+
1242
+ // Source agent: record a turn before the fork
1243
+ registerAgent(sourceAgentId, sourceSessionId);
1244
+ recordTurn(sourceAgentId, sourceSessionId, "user", "Before fork");
1245
+ recordTurn(sourceAgentId, sourceSessionId, "assistant", { parts: [{ type: "text", text: "Pre-fork reply" }] });
1246
+
1247
+ // Small delay to ensure fork timestamp is after source turns
1248
+ await new Promise((r) => setTimeout(r, 10));
1249
+
1250
+ // Fork the agent
1251
+ registerForkedAgent(forkedAgentId, forkedSessionId, sourceAgentId);
1252
+
1253
+ // Small delay to ensure post-fork turn timestamp is after fork
1254
+ await new Promise((r) => setTimeout(r, 10));
1255
+
1256
+ // Source agent continues after fork — these should NOT appear in forked history
1257
+ recordTurn(sourceAgentId, sourceSessionId, "user", "After fork on source");
1258
+ recordTurn(sourceAgentId, sourceSessionId, "assistant", { parts: [{ type: "text", text: "Post-fork source reply" }] });
1259
+
1260
+ const streamId = "fork-filter-stream";
1261
+ await handler.processRequest(
1262
+ forkedAgentId,
1263
+ envelope(streamId, "initialize", {
1264
+ protocolVersion: 1,
1265
+ capabilities: {},
1266
+ clientInfo: { name: "test", version: "1.0" },
1267
+ }),
1268
+ );
1269
+
1270
+ const historyResult = await handler.processRequest(
1271
+ forkedAgentId,
1272
+ envelope(streamId, "_macro/getHistory", { agentId: forkedAgentId }),
1273
+ );
1274
+
1275
+ const turns = (historyResult.acp.result as { turns: { role: string; content: unknown }[] }).turns;
1276
+
1277
+ // Should only include the 2 pre-fork turns, not the 2 post-fork ones
1278
+ expect(turns).toHaveLength(2);
1279
+ expect(turns[0].content).toBe("Before fork");
1280
+ expect(turns.find((t: { content: unknown }) => t.content === "After fork on source")).toBeUndefined();
1281
+ });
1282
+
1283
+ it("should return empty history for forked agent when source has no turns", async () => {
1284
+ await setup();
1285
+
1286
+ const sourceAgentId = "agent-empty-src" as AgentId;
1287
+ const sourceSessionId = "session-empty-src";
1288
+ const forkedAgentId = "agent-empty-fork" as AgentId;
1289
+ const forkedSessionId = "session-empty-fork";
1290
+
1291
+ // Source agent exists but has no conversation turns
1292
+ registerAgent(sourceAgentId, sourceSessionId);
1293
+ registerForkedAgent(forkedAgentId, forkedSessionId, sourceAgentId);
1294
+
1295
+ const streamId = "fork-empty-stream";
1296
+ await handler.processRequest(
1297
+ forkedAgentId,
1298
+ envelope(streamId, "initialize", {
1299
+ protocolVersion: 1,
1300
+ capabilities: {},
1301
+ clientInfo: { name: "test", version: "1.0" },
1302
+ }),
1303
+ );
1304
+
1305
+ const historyResult = await handler.processRequest(
1306
+ forkedAgentId,
1307
+ envelope(streamId, "_macro/getHistory", { agentId: forkedAgentId }),
1308
+ );
1309
+
1310
+ const turns = (historyResult.acp.result as { turns: unknown[] }).turns;
1311
+ expect(turns).toEqual([]);
1312
+ });
1313
+
1314
+ it("should not affect non-forked agent history queries", async () => {
1315
+ await setup();
1316
+
1317
+ const agentId = "agent-normal" as AgentId;
1318
+ const sessionId = "session-normal";
1319
+
1320
+ registerAgent(agentId, sessionId);
1321
+ recordTurn(agentId, sessionId, "user", "Normal question");
1322
+ recordTurn(agentId, sessionId, "assistant", { parts: [{ type: "text", text: "Normal reply" }] });
1323
+
1324
+ const streamId = "normal-history-stream";
1325
+ await handler.processRequest(
1326
+ agentId,
1327
+ envelope(streamId, "initialize", {
1328
+ protocolVersion: 1,
1329
+ capabilities: {},
1330
+ clientInfo: { name: "test", version: "1.0" },
1331
+ }),
1332
+ );
1333
+
1334
+ const historyResult = await handler.processRequest(
1335
+ agentId,
1336
+ envelope(streamId, "_macro/getHistory", { agentId }),
1337
+ );
1338
+
1339
+ const turns = (historyResult.acp.result as { turns: { role: string; content: unknown }[] }).turns;
1340
+ expect(turns).toHaveLength(2);
1341
+ expect(turns[0].content).toBe("Normal question");
1342
+ });
1343
+
1344
+ it("should include both plan and cwd together in getHistory response", async () => {
1345
+ await setup([
1346
+ {
1347
+ sessionUpdate: "plan",
1348
+ entries: [
1349
+ { content: "Do something", priority: "high", status: "pending" },
1350
+ ],
1351
+ },
1352
+ {
1353
+ sessionUpdate: "agent_message_chunk",
1354
+ content: { type: "text", text: "Got it" },
1355
+ },
1356
+ ]);
1357
+
1358
+ const agentId = "agent-1" as AgentId;
1359
+ const streamId = "test-stream-plan-cwd";
1360
+ const sessionId = await initAndCreateSession(streamId, agentId);
1361
+
1362
+ await handler.processRequest(
1363
+ agentId,
1364
+ envelope(streamId, "session/prompt", {
1365
+ prompt: [{ type: "text", text: "Go" }],
1366
+ }, sessionId),
1367
+ );
1368
+
1369
+ const historyResult = await handler.processRequest(
1370
+ agentId,
1371
+ envelope(streamId, "_macro/getHistory", { sessionId, agentId }),
1372
+ );
1373
+
1374
+ const result = historyResult.acp.result as {
1375
+ turns: unknown[];
1376
+ plan: Array<{ content: string; priority: string; status: string }>;
1377
+ cwd: string | null;
1378
+ };
1379
+
1380
+ // Both fields present
1381
+ expect(result.turns).toHaveLength(2);
1382
+ expect(result.plan).toHaveLength(1);
1383
+ expect(result.plan[0].content).toBe("Do something");
1384
+ expect(result.cwd).toBe("/test/cwd");
1385
+ });
664
1386
  });