@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
@@ -32,17 +32,17 @@
32
32
  * is the one point a scenario can rely on: the STOP is keyed on the
33
33
  * persisted status's CONTENT (the COMPLETED row), not on which persist
34
34
  * happens to carry it, because a tool-call transition force-flushes
35
- * (`contentDirty`) whatever the cadence says. (Until S3 M5 the scheduler
35
+ * (the builder's `dirty` flag) whatever the cadence says. (Until #1096 the scheduler
36
36
  * paced text-only persists on REAL `performance.now()` time; the scripted
37
- * clock fakes it since Q-M5-7, so every persist is deterministic now.)
37
+ * clock fakes it since then, so every persist is deterministic now.)
38
38
  *
39
- * Engine disposition and cancellation since S2 M3 (entry 20260911.03): the
40
- * adapter cancels the SDK run on EVERY stop (Q-M3-4; before, a platform stop
39
+ * Engine disposition and cancellation since #1070: the
40
+ * adapter cancels the SDK run on EVERY stop (before, a platform stop
41
41
  * broke the loop and left the run executing) and parks the handle on every
42
- * non-failed exit (Q-S2-6; before, a stall dropped it — the adapter cannot
42
+ * non-failed exit (before, a stall dropped it — the adapter cannot
43
43
  * tell a stall from a cost cap, and a wedged parked handle is caught by the
44
44
  * poisoned-handle recovery on the next turn). The two assertions that moved
45
- * say so on their line. The cost-cap golden was regenerated at M3b with a
45
+ * say so on their line. The cost-cap golden was regenerated in #1070 with a
46
46
  * timestamp-only diff (every terminal stamp one scripted second earlier):
47
47
  * the run is now cancelled the instant the signal aborts, before the SDK
48
48
  * double pulls — and the clock ticks on — one more step.
@@ -64,6 +64,9 @@ import {
64
64
  vi.mock("@cursor/sdk", async () =>
65
65
  (await import("../../__test-utils__/scripted-sdk.js")).scriptedCursorSdkModule(),
66
66
  );
67
+ vi.mock("@cursor/sdk/sqlite", async () =>
68
+ (await import("../../__test-utils__/scripted-sdk.js")).scriptedCursorSqliteModule(),
69
+ );
67
70
  vi.mock("../../../../client/stigmer-client.js", async () =>
68
71
  (await import("../../../../__test-utils__/hermetic-activity.js")).hermeticStigmerClientModule(),
69
72
  );
@@ -185,7 +188,7 @@ describe("ExecuteCursor hermetic — the stream loop's self-stop arms", () => {
185
188
  // ── Assert: engine disposition ───────────────────────────────────────────
186
189
  expect(agent.runs[0].cancelCalls, "the runtime's stop cancelled the run once").toHaveLength(1);
187
190
  expect(agent.closeCalls, "the handle is not closed").toBe(0);
188
- // Q-S2-6 (S2 M3): parked on every non-failed exit; before M3 a stall dropped it.
191
+ // Since #1070: parked on every non-failed exit; before, a stall dropped it.
189
192
  expect(_parkedAgentCountForTests(), "parked for the session's next turn").toBe(1);
190
193
  expect(record.sessionUpdates).toHaveLength(1);
191
194
 
@@ -213,7 +216,7 @@ describe("ExecuteCursor hermetic — the stream loop's self-stop arms", () => {
213
216
  step.event(ev.init()),
214
217
  step.event(ev.assistant("Reading the whole repository first.")),
215
218
  // 600 000 input tokens at the fixture's $1/M base rate = $0.60 > $0.50.
216
- step.turnEnded({ inputTokens: 600_000, outputTokens: 0 }),
219
+ step.turnEnded({ inputTokens: 600_000, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }),
217
220
  step.event(ev.assistant(NEVER_SEEN)),
218
221
  step.finished({ result: NEVER_SEEN }),
219
222
  ],
@@ -315,7 +318,7 @@ describe("ExecuteCursor hermetic — the stream loop's self-stop arms", () => {
315
318
  expect(final.messages.some((m) => m.content === NEVER_SEEN), "the event after the STOP is never processed").toBe(false);
316
319
 
317
320
  // ── Assert: engine disposition ───────────────────────────────────────────
318
- // Q-M3-4 (S2 M3): the SDK run is cancelled on every stop; before M3 a
321
+ // Since #1070: the SDK run is cancelled on every stop; before, a
319
322
  // platform stop broke the loop and left the run executing behind the
320
323
  // parked handle.
321
324
  expect(agent.runs[0].cancelCalls, "the runtime's stop cancelled the run once").toHaveLength(1);
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Hermetic golden: SUB-AGENT DELEGATION on the Cursor harness — the root agent
3
+ * hands work to a sub-agent through the SDK's `task` tool, and the sub-agent's
4
+ * own conversation arrives, whole, in the `task` call's result.
5
+ *
6
+ * Invariant pinned: the translator opens a sub-agent for every
7
+ * `tool_call` named `task` (`sub_agent_started` before the row's own start) as
8
+ * well as writing the row to the root transcript. The `running` event opens a `SubAgentExecution` keyed by the
9
+ * task call id (`name` from `subagentType`, `subject` from `description`,
10
+ * `input` from `prompt`, IN_PROGRESS); the `completed` event closes it
11
+ * COMPLETED with `output` (the stringified result) and rebuilds its transcript
12
+ * from the result's `conversationSteps` (`sub-agent-steps.ts`): an
13
+ * `assistantMessage` step is an AI row, and a `toolCall` step is a tool row on
14
+ * the transcript's LAST AI message — the assistant step that proposed it — named
15
+ * from its `<kind>ToolCall` key (`buildSubAgentToolCall`), with the SDK's
16
+ * `toolCallId` as its id, its status from the `result` oneof, both timestamps,
17
+ * `args` and `argsPreview`; a tool step with no assistant step before it gets
18
+ * an empty AI message of its own, the root transcript's rule. The root
19
+ * transcript carries the `task` row itself (COMPLETED, `result` = the same
20
+ * stringified object) on the message that proposed it. This is Cursor's
21
+ * timing — the sub-agent's rows exist only at completion — and stays Cursor's.
22
+ *
23
+ * Moved 2026-09-14 (#1097), the ONE hunk this golden was
24
+ * predicted to take: the sub-agent's `read` row sits on the AI message of the
25
+ * assistant step that precedes it (`messages[0]`, "Looking at README.md."),
26
+ * and the `content: ""` message that carried it until then (`messages[1]`) is
27
+ * gone — the canonical builder attaches a tool row to its scope's current AI
28
+ * message, the shape native's sub-agent transcripts took in #1096, and the
29
+ * accumulator's steps extractor was aligned before the swap to the shared
30
+ * builder so the swap moved nothing.
31
+ *
32
+ * On the record as found (#1134): the `task` row's `result` and the
33
+ * sub-agent's `output` are two copies of one stringified object.
34
+ *
35
+ * Why this net exists: none of the seventeen earlier Cursor goldens carried
36
+ * a sub-agent row, and #1097 deleted the transcript-rebuild whole.
37
+ *
38
+ * Regenerate ONLY after a deliberate behavior change:
39
+ * npx vitest run src/activities/execute-cursor/__tests__/hermetic -u
40
+ */
41
+
42
+ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
43
+ import { toJson } from "@bufbuild/protobuf";
44
+ import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
45
+ import {
46
+ ExecutionPhase,
47
+ MessageType,
48
+ SubAgentStatus,
49
+ ToolCallStatus,
50
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
51
+
52
+ vi.mock("@cursor/sdk", async () =>
53
+ (await import("../../__test-utils__/scripted-sdk.js")).scriptedCursorSdkModule(),
54
+ );
55
+ vi.mock("../../../../client/stigmer-client.js", async () =>
56
+ (await import("../../../../__test-utils__/hermetic-activity.js")).hermeticStigmerClientModule(),
57
+ );
58
+
59
+ import {
60
+ ScriptedClock,
61
+ createHermeticEnvironment,
62
+ type HermeticEnvironment,
63
+ } from "../../../../__test-utils__/hermetic-activity.js";
64
+ import { ScriptedCursorAgent, sdkEvents, step } from "../../__test-utils__/scripted-agent.js";
65
+ import {
66
+ FIXTURE,
67
+ SDK_CATALOG,
68
+ beginCursorScenario,
69
+ cursorExecutionRecord,
70
+ runCursorTurn,
71
+ } from "../../__test-utils__/hermetic-cursor.js";
72
+ import { stubRegistryFetch } from "../../../../__test-utils__/model-registry-fixture.js";
73
+
74
+ const AGENT_ID = "agent-hermetic-subagent-0001";
75
+ const RUN_ID = "run-hermetic-subagent-0001";
76
+ const TASK_CALL_ID = "call-hermetic-task-0001";
77
+ const SUB_READ_CALL_ID = "call-hermetic-sub-read-0001";
78
+ const USER_MESSAGE = "Ask the helper what README.md says.";
79
+ const ROOT_OPENING = "Delegating to the helper.";
80
+ const TASK_ARGS = {
81
+ subagentType: "helper",
82
+ description: "Read README.md and report its content.",
83
+ prompt: "Read README.md and reply with its content in one line.",
84
+ };
85
+ const HELPER_OPENING = "Looking at README.md.";
86
+ const HELPER_READ_ARGS = { path: "README.md" };
87
+ const HELPER_READ_RESULT = "# Hermetic\n\nA fixture readme.\n";
88
+ const HELPER_ANSWER = "README.md is a one-line fixture description.";
89
+ /** The SDK's task result: the sub-agent's conversation as protobuf-oneof JSON steps. */
90
+ const TASK_RESULT = {
91
+ conversationSteps: [
92
+ { type: "assistantMessage", message: { text: HELPER_OPENING } },
93
+ {
94
+ toolCall: {
95
+ toolCallId: SUB_READ_CALL_ID,
96
+ readToolCall: { args: HELPER_READ_ARGS, result: { success: { content: HELPER_READ_RESULT } } },
97
+ },
98
+ },
99
+ { type: "assistantMessage", message: { text: HELPER_ANSWER } },
100
+ ],
101
+ };
102
+ const ROOT_CLOSING = "The helper reports: README.md is a one-line fixture description.";
103
+
104
+ describe("ExecuteCursor hermetic — sub-agent delegation", () => {
105
+ let env: HermeticEnvironment;
106
+ let registry: ReturnType<typeof stubRegistryFetch>;
107
+ const clock = new ScriptedClock();
108
+
109
+ beforeAll(() => {
110
+ env = createHermeticEnvironment();
111
+ registry = stubRegistryFetch();
112
+ clock.install();
113
+ });
114
+
115
+ afterAll(() => {
116
+ clock.uninstall();
117
+ registry.restore();
118
+ env.dispose();
119
+ });
120
+
121
+ it("tracks the task as a SubAgentExecution keyed by the call id and rebuilds its transcript at completion", async () => {
122
+ // ── Arrange ──────────────────────────────────────────────────────────────
123
+ const ev = sdkEvents(AGENT_ID, RUN_ID);
124
+ const agent = new ScriptedCursorAgent({
125
+ agentId: AGENT_ID,
126
+ runIds: [RUN_ID],
127
+ observeStep: () => clock.tick(),
128
+ turns: [
129
+ [
130
+ step.event(ev.init()),
131
+ step.event(ev.assistant(ROOT_OPENING)),
132
+ step.event(ev.toolCall(TASK_CALL_ID, "task", "running", TASK_ARGS)),
133
+ step.event(ev.toolCall(TASK_CALL_ID, "task", "completed", TASK_ARGS, TASK_RESULT)),
134
+ step.event(ev.assistant(ROOT_CLOSING)),
135
+ step.turnEnded({ inputTokens: 3_900, outputTokens: 102, cacheReadTokens: 0, cacheWriteTokens: 0 }),
136
+ step.finished({ result: ROOT_CLOSING, model: { id: FIXTURE.model, params: [] } }),
137
+ ],
138
+ ],
139
+ });
140
+ const record = cursorExecutionRecord({ message: USER_MESSAGE });
141
+ const scenario = beginCursorScenario({
142
+ env,
143
+ clock,
144
+ record,
145
+ sdk: { agents: [agent], catalog: SDK_CATALOG },
146
+ });
147
+
148
+ // ── Act ──────────────────────────────────────────────────────────────────
149
+ const invocation = await runCursorTurn(scenario);
150
+
151
+ // ── Assert: outcome ──────────────────────────────────────────────────────
152
+ expect(invocation.outcome.kind).toBe("returned");
153
+ expect((invocation.outcome as { value: Record<string, unknown> }).value.phase).toBe("EXECUTION_COMPLETED");
154
+ expect(record.persistedPhases.at(-1)).toBe(ExecutionPhase.EXECUTION_COMPLETED);
155
+
156
+ // ── Assert: the root transcript ──────────────────────────────────────────
157
+ const final = record.lastFullStatus!;
158
+ expect(record.toolCalls().map((tc) => [tc.id, tc.name, tc.status])).toEqual([
159
+ [TASK_CALL_ID, "task", ToolCallStatus.TOOL_CALL_COMPLETED],
160
+ ]);
161
+ expect(final.messages.map((m) => [m.type, m.content, m.toolCalls.map((tc) => tc.id)])).toEqual([
162
+ [MessageType.MESSAGE_AI, ROOT_OPENING, [TASK_CALL_ID]],
163
+ [MessageType.MESSAGE_AI, ROOT_CLOSING, []],
164
+ ]);
165
+
166
+ // ── Assert: the sub-agent execution ──────────────────────────────────────
167
+ expect(final.subAgentExecutions).toHaveLength(1);
168
+ const sub = final.subAgentExecutions[0];
169
+ expect(sub.id, "keyed by the task call id").toBe(TASK_CALL_ID);
170
+ expect(sub.name).toBe("helper");
171
+ expect(sub.subject).toBe(TASK_ARGS.description);
172
+ expect(sub.input).toBe(TASK_ARGS.prompt);
173
+ expect(sub.status).toBe(SubAgentStatus.SUB_AGENT_COMPLETED);
174
+ expect(sub.startedAt < sub.completedAt).toBe(true);
175
+ expect(sub.output, "the stringified task result").toBe(JSON.stringify(TASK_RESULT));
176
+
177
+ // The rebuilt transcript: the tool step joins the assistant step that
178
+ // proposed it, the root transcript's own boundary rule.
179
+ expect(sub.messages.map((m) => [m.type, m.content, m.toolCalls.map((tc) => tc.id)])).toEqual([
180
+ [MessageType.MESSAGE_AI, HELPER_OPENING, [SUB_READ_CALL_ID]],
181
+ [MessageType.MESSAGE_AI, HELPER_ANSWER, []],
182
+ ]);
183
+ const subRead = sub.messages[0].toolCalls[0];
184
+ expect(subRead.name, "the bare tool name from the readToolCall key").toBe("read");
185
+ expect(subRead.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
186
+ expect(subRead.args).toEqual(HELPER_READ_ARGS);
187
+ expect(subRead.result).toBe(JSON.stringify({ content: HELPER_READ_RESULT }));
188
+
189
+ // ── Assert: hermeticity ──────────────────────────────────────────────────
190
+ expect(registry.urls.every((u) => u.includes("/model-registry"))).toBe(true);
191
+
192
+ // ── Assert: the golden ───────────────────────────────────────────────────
193
+ const json = JSON.stringify(toJson(AgentExecutionStatusSchema, final), null, 2) + "\n";
194
+ await expect(json).toMatchFileSnapshot("./goldens/sub-agent-delegation.status.json");
195
+ });
196
+ });
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Hermetic golden: a turn with MODEL THINKING on both sides of a tool call,
3
+ * through the whole `ExecuteCursor` activity.
4
+ *
5
+ * Invariant pinned (the rule the canonical builder had to reproduce when it
6
+ * took over in #1097): the Cursor SDK has ONE `run_id` per `send()`, so the transcript's
7
+ * segmentation is not by run but by tool call — a `tool_call` event closes the
8
+ * run's streaming AI and THINKING rows (the translator's `message_finish` for
9
+ * the open segment; until #1097 the accumulator's own finalize on a tool call),
10
+ * and the thinking or text that follows opens NEW rows. Hence the shape:
11
+ * THINKING(T1), AI(A1 + the read row), THINKING(T2), AI(A2). The read row lands
12
+ * on A1 (the last AI message by backward scan; a THINKING row is skipped), and
13
+ * no row is left `isStreaming` when the turn ends.
14
+ *
15
+ * Predicted for the swap to the shared builder (2026-09-14): NO move.
16
+ * The builder keeps one THINKING row per segment on Cursor (the translator
17
+ * mints a segment id per text-or-thinking run between tool calls) and keeps
18
+ * the tool on the message that proposed it. A diff in this golden is a
19
+ * pause, not a regeneration.
20
+ *
21
+ * Why this net exists: none of the seventeen earlier Cursor goldens carried
22
+ * a THINKING row, and the thinking rule is one #1097 rewrote.
23
+ *
24
+ * Regenerate ONLY after a deliberate behavior change:
25
+ * npx vitest run src/activities/execute-cursor/__tests__/hermetic -u
26
+ */
27
+
28
+ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
29
+ import { toJson } from "@bufbuild/protobuf";
30
+ import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
31
+ import { ExecutionPhase, MessageType, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
32
+
33
+ vi.mock("@cursor/sdk", async () =>
34
+ (await import("../../__test-utils__/scripted-sdk.js")).scriptedCursorSdkModule(),
35
+ );
36
+ vi.mock("../../../../client/stigmer-client.js", async () =>
37
+ (await import("../../../../__test-utils__/hermetic-activity.js")).hermeticStigmerClientModule(),
38
+ );
39
+
40
+ import {
41
+ ScriptedClock,
42
+ createHermeticEnvironment,
43
+ type HermeticEnvironment,
44
+ } from "../../../../__test-utils__/hermetic-activity.js";
45
+ import { ScriptedCursorAgent, sdkEvents, step } from "../../__test-utils__/scripted-agent.js";
46
+ import {
47
+ FIXTURE,
48
+ SDK_CATALOG,
49
+ beginCursorScenario,
50
+ cursorExecutionRecord,
51
+ runCursorTurn,
52
+ } from "../../__test-utils__/hermetic-cursor.js";
53
+ import { stubRegistryFetch } from "../../../../__test-utils__/model-registry-fixture.js";
54
+
55
+ const AGENT_ID = "agent-hermetic-thinking-0001";
56
+ const RUN_ID = "run-hermetic-thinking-0001";
57
+ const CALL_ID = "call-hermetic-thinking-read-0001";
58
+ const USER_MESSAGE = "Summarise README.md.";
59
+ const THINKING_BEFORE = "The user wants a summary; I should read the file first.";
60
+ const TEXT_BEFORE = "Reading the file.";
61
+ const READ_ARGS = { path: "README.md" };
62
+ const READ_RESULT = "# Hermetic\n\nA fixture readme.\n";
63
+ const THINKING_AFTER = "One line of content; a one-sentence summary will do.";
64
+ const TEXT_AFTER = "README.md is a one-line fixture description.";
65
+
66
+ describe("ExecuteCursor hermetic — thinking on both sides of a tool call", () => {
67
+ let env: HermeticEnvironment;
68
+ let registry: ReturnType<typeof stubRegistryFetch>;
69
+ const clock = new ScriptedClock();
70
+
71
+ beforeAll(() => {
72
+ env = createHermeticEnvironment();
73
+ registry = stubRegistryFetch();
74
+ clock.install();
75
+ });
76
+
77
+ afterAll(() => {
78
+ clock.uninstall();
79
+ registry.restore();
80
+ env.dispose();
81
+ });
82
+
83
+ it("folds into THINKING, AI+row, THINKING, AI with nothing left streaming and completes", async () => {
84
+ // ── Arrange ──────────────────────────────────────────────────────────────
85
+ const ev = sdkEvents(AGENT_ID, RUN_ID);
86
+ const agent = new ScriptedCursorAgent({
87
+ agentId: AGENT_ID,
88
+ runIds: [RUN_ID],
89
+ observeStep: () => clock.tick(),
90
+ turns: [
91
+ [
92
+ step.event(ev.init()),
93
+ step.event(ev.thinking(THINKING_BEFORE)),
94
+ step.event(ev.assistant(TEXT_BEFORE)),
95
+ step.event(ev.toolCall(CALL_ID, "read", "running", READ_ARGS)),
96
+ step.event(ev.toolCall(CALL_ID, "read", "completed", READ_ARGS, READ_RESULT)),
97
+ step.event(ev.thinking(THINKING_AFTER)),
98
+ step.event(ev.assistant(TEXT_AFTER)),
99
+ step.turnEnded({ inputTokens: 2_400, outputTokens: 140, cacheReadTokens: 0, cacheWriteTokens: 0 }),
100
+ step.finished({ result: TEXT_AFTER, model: { id: FIXTURE.model, params: [] } }),
101
+ ],
102
+ ],
103
+ });
104
+ const record = cursorExecutionRecord({ message: USER_MESSAGE });
105
+ const scenario = beginCursorScenario({
106
+ env,
107
+ clock,
108
+ record,
109
+ sdk: { agents: [agent], catalog: SDK_CATALOG },
110
+ });
111
+
112
+ // ── Act ──────────────────────────────────────────────────────────────────
113
+ const invocation = await runCursorTurn(scenario);
114
+
115
+ // ── Assert: outcome ──────────────────────────────────────────────────────
116
+ expect(invocation.outcome.kind).toBe("returned");
117
+ expect((invocation.outcome as { value: Record<string, unknown> }).value.phase).toBe("EXECUTION_COMPLETED");
118
+ expect(record.persistedPhases.at(-1)).toBe(ExecutionPhase.EXECUTION_COMPLETED);
119
+
120
+ // ── Assert: the segmentation ─────────────────────────────────────────────
121
+ const final = record.lastFullStatus!;
122
+ expect(
123
+ final.messages.map((m) => [m.type, m.content]),
124
+ "a tool call closes the run's THINKING and AI rows; what follows opens new ones",
125
+ ).toEqual([
126
+ [MessageType.MESSAGE_THINKING, THINKING_BEFORE],
127
+ [MessageType.MESSAGE_AI, TEXT_BEFORE],
128
+ [MessageType.MESSAGE_THINKING, THINKING_AFTER],
129
+ [MessageType.MESSAGE_AI, TEXT_AFTER],
130
+ ]);
131
+ expect(final.messages.every((m) => !m.isStreaming), "finalize clears every streaming flag").toBe(true);
132
+
133
+ // ── Assert: the row sits on the text that proposed it ────────────────────
134
+ const rows = record.toolCalls();
135
+ expect(rows.map((tc) => [tc.id, tc.status])).toEqual([[CALL_ID, ToolCallStatus.TOOL_CALL_COMPLETED]]);
136
+ expect(final.messages[1].toolCalls.map((tc) => tc.id), "the read row is on AI(A1), not on a THINKING row").toEqual([CALL_ID]);
137
+
138
+ // ── Assert: hermeticity ──────────────────────────────────────────────────
139
+ expect(registry.urls.every((u) => u.includes("/model-registry"))).toBe(true);
140
+
141
+ // ── Assert: the golden ───────────────────────────────────────────────────
142
+ const json = JSON.stringify(toJson(AgentExecutionStatusSchema, final), null, 2) + "\n";
143
+ await expect(json).toMatchFileSnapshot("./goldens/thinking-turn.status.json");
144
+ });
145
+ });
@@ -28,7 +28,7 @@
28
28
  * plane ever sees for these executions is EXECUTION_FAILED — `persistedPhases`
29
29
  * is `[FAILED]`, not `[IN_PROGRESS, FAILED]`.
30
30
  *
31
- * Golden hunk since S3 M4 (Q-M4-7, ruled Q-M4-12 A): `sdk-error-at-create`
31
+ * Golden hunk since #1096: `sdk-error-at-create`
32
32
  * carries an EMPTY `fileChangeProgress` (`changeSetId`, `capturedAt`, no
33
33
  * entries). The runtime refreshes the strip on every write, and this turn's
34
34
  * FAILED write is its first write in capture mode (the hermetic environment
@@ -49,6 +49,9 @@ import { ExecutionPhase, MessageType } from "@stigmer/protos/ai/stigmer/agentic/
49
49
  vi.mock("@cursor/sdk", async () =>
50
50
  (await import("../../__test-utils__/scripted-sdk.js")).scriptedCursorSdkModule(),
51
51
  );
52
+ vi.mock("@cursor/sdk/sqlite", async () =>
53
+ (await import("../../__test-utils__/scripted-sdk.js")).scriptedCursorSqliteModule(),
54
+ );
52
55
  vi.mock("../../../../client/stigmer-client.js", async () =>
53
56
  (await import("../../../../__test-utils__/hermetic-activity.js")).hermeticStigmerClientModule(),
54
57
  );
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Hermetic golden: the agent's TO-DO LIST — two `updateTodos` tool calls, the
3
+ * second a `merge`, through the whole `ExecuteCursor` activity.
4
+ *
5
+ * Invariant pinned: a todo tool call is a row like any other AND a projection.
6
+ * The shared builder projects a COMPLETED `ToolKind.TODO` row's `args.todos`
7
+ * into `status.todos` through the shared `applyTodoUpdate` — a full replace by
8
+ * default, a MERGE when the SDK's args say `merge: true` (the first write's
9
+ * `createdAt` survives, `updatedAt` moves) — and the row stays on the message
10
+ * that proposed it: an `updateTodos`
11
+ * row (COMPLETED, `toolKind` TODO, `args`, `argsPreview`, `result`) on
12
+ * `messages[0]` and another on `messages[1]`. So: three AI messages, two
13
+ * rows, two todos. The web console hides the row (`isInternalTool`); the CLI
14
+ * TUI shows it, as it does for native.
15
+ *
16
+ * Moved 2026-09-14 (#1097), the TWO hunks this golden was
17
+ * predicted to take: until then the accumulator DROPPED the row (a suppressed-
18
+ * tool-names set) and the transcript showed three AI messages with
19
+ * no rows — a per-harness branch on a tool name the canonical builder does
20
+ * not have (native has always kept its `write_todos` row). The accumulator
21
+ * was aligned before the swap to the shared builder so the swap moved nothing;
22
+ * `todos` was and is byte-identical. The todo tracker went at the swap, where
23
+ * the builder took over projecting the todos from the row.
24
+ *
25
+ * Why this net exists: no earlier Cursor golden carried a todo.
26
+ *
27
+ * Regenerate ONLY after a deliberate behavior change:
28
+ * npx vitest run src/activities/execute-cursor/__tests__/hermetic -u
29
+ */
30
+
31
+ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
32
+ import { toJson } from "@bufbuild/protobuf";
33
+ import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
34
+ import { ExecutionPhase, MessageType, TodoStatus, ToolCallStatus, ToolKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
35
+
36
+ vi.mock("@cursor/sdk", async () =>
37
+ (await import("../../__test-utils__/scripted-sdk.js")).scriptedCursorSdkModule(),
38
+ );
39
+ vi.mock("../../../../client/stigmer-client.js", async () =>
40
+ (await import("../../../../__test-utils__/hermetic-activity.js")).hermeticStigmerClientModule(),
41
+ );
42
+
43
+ import {
44
+ ScriptedClock,
45
+ createHermeticEnvironment,
46
+ type HermeticEnvironment,
47
+ } from "../../../../__test-utils__/hermetic-activity.js";
48
+ import { ScriptedCursorAgent, sdkEvents, step } from "../../__test-utils__/scripted-agent.js";
49
+ import {
50
+ FIXTURE,
51
+ SDK_CATALOG,
52
+ beginCursorScenario,
53
+ cursorExecutionRecord,
54
+ runCursorTurn,
55
+ } from "../../__test-utils__/hermetic-cursor.js";
56
+ import { stubRegistryFetch } from "../../../../__test-utils__/model-registry-fixture.js";
57
+
58
+ const AGENT_ID = "agent-hermetic-todo-0001";
59
+ const RUN_ID = "run-hermetic-todo-0001";
60
+ const FIRST_CALL_ID = "call-hermetic-todo-0001";
61
+ const SECOND_CALL_ID = "call-hermetic-todo-0002";
62
+ const USER_MESSAGE = "Plan the two steps, then do the first.";
63
+ const TEXT_PLAN = "Two steps; writing them down.";
64
+ /** The current SDK's `updateTodos` shape: camelCase statuses, no per-item id. */
65
+ const FIRST_WRITE = {
66
+ todos: [
67
+ { content: "Read the fixture", status: "pending" },
68
+ { content: "Summarise it", status: "pending" },
69
+ ],
70
+ };
71
+ const TEXT_WORK = "First step done.";
72
+ const SECOND_WRITE = {
73
+ todos: [{ content: "Read the fixture", status: "completed" }],
74
+ merge: true,
75
+ };
76
+ const TEXT_CLOSING = "The first step is complete; the second is pending.";
77
+
78
+ describe("ExecuteCursor hermetic — todo writes", () => {
79
+ let env: HermeticEnvironment;
80
+ let registry: ReturnType<typeof stubRegistryFetch>;
81
+ const clock = new ScriptedClock();
82
+
83
+ beforeAll(() => {
84
+ env = createHermeticEnvironment();
85
+ registry = stubRegistryFetch();
86
+ clock.install();
87
+ });
88
+
89
+ afterAll(() => {
90
+ clock.uninstall();
91
+ registry.restore();
92
+ env.dispose();
93
+ });
94
+
95
+ it("projects a replace then a merge into status.todos and keeps a row per write in the transcript", async () => {
96
+ // ── Arrange ──────────────────────────────────────────────────────────────
97
+ const ev = sdkEvents(AGENT_ID, RUN_ID);
98
+ const agent = new ScriptedCursorAgent({
99
+ agentId: AGENT_ID,
100
+ runIds: [RUN_ID],
101
+ observeStep: () => clock.tick(),
102
+ turns: [
103
+ [
104
+ step.event(ev.init()),
105
+ step.event(ev.assistant(TEXT_PLAN)),
106
+ step.event(ev.toolCall(FIRST_CALL_ID, "updateTodos", "running", FIRST_WRITE)),
107
+ step.event(ev.toolCall(FIRST_CALL_ID, "updateTodos", "completed", FIRST_WRITE, "ok")),
108
+ step.event(ev.assistant(TEXT_WORK)),
109
+ step.event(ev.toolCall(SECOND_CALL_ID, "updateTodos", "running", SECOND_WRITE)),
110
+ step.event(ev.toolCall(SECOND_CALL_ID, "updateTodos", "completed", SECOND_WRITE, "ok")),
111
+ step.event(ev.assistant(TEXT_CLOSING)),
112
+ step.turnEnded({ inputTokens: 2_600, outputTokens: 160, cacheReadTokens: 0, cacheWriteTokens: 0 }),
113
+ step.finished({ result: TEXT_CLOSING, model: { id: FIXTURE.model, params: [] } }),
114
+ ],
115
+ ],
116
+ });
117
+ const record = cursorExecutionRecord({ message: USER_MESSAGE });
118
+ const scenario = beginCursorScenario({
119
+ env,
120
+ clock,
121
+ record,
122
+ sdk: { agents: [agent], catalog: SDK_CATALOG },
123
+ });
124
+
125
+ // ── Act ──────────────────────────────────────────────────────────────────
126
+ const invocation = await runCursorTurn(scenario);
127
+
128
+ // ── Assert: outcome ──────────────────────────────────────────────────────
129
+ expect(invocation.outcome.kind).toBe("returned");
130
+ expect((invocation.outcome as { value: Record<string, unknown> }).value.phase).toBe("EXECUTION_COMPLETED");
131
+ expect(record.persistedPhases.at(-1)).toBe(ExecutionPhase.EXECUTION_COMPLETED);
132
+
133
+ // ── Assert: the transcript — a row per write, on the message that proposed it ──
134
+ const final = record.lastFullStatus!;
135
+ expect(
136
+ final.messages.map((m) => [m.type, m.content, m.toolCalls.length]),
137
+ "the todo call splits the text like any tool, and its row stays",
138
+ ).toEqual([
139
+ [MessageType.MESSAGE_AI, TEXT_PLAN, 1],
140
+ [MessageType.MESSAGE_AI, TEXT_WORK, 1],
141
+ [MessageType.MESSAGE_AI, TEXT_CLOSING, 0],
142
+ ]);
143
+ const rows = record.toolCalls();
144
+ expect(rows.map((tc) => [tc.id, tc.toolKind, tc.status])).toEqual([
145
+ [FIRST_CALL_ID, ToolKind.TODO, ToolCallStatus.TOOL_CALL_COMPLETED],
146
+ [SECOND_CALL_ID, ToolKind.TODO, ToolCallStatus.TOOL_CALL_COMPLETED],
147
+ ]);
148
+
149
+ // ── Assert: the todos, by key (byte-identical across the swap) ──────────
150
+ expect(Object.keys(final.todos).sort()).toEqual(["todo-0", "todo-1"]);
151
+ const first = final.todos["todo-0"];
152
+ const second = final.todos["todo-1"];
153
+ expect([first.content, first.status]).toEqual(["Read the fixture", TodoStatus.TODO_COMPLETED]);
154
+ expect(first.createdAt < first.updatedAt, "the merge kept the first write's createdAt and moved updatedAt").toBe(true);
155
+ expect([second.content, second.status]).toEqual(["Summarise it", TodoStatus.TODO_PENDING]);
156
+ expect(second.createdAt, "untouched by the merge").toBe(second.updatedAt);
157
+
158
+ // ── Assert: hermeticity ──────────────────────────────────────────────────
159
+ expect(registry.urls.every((u) => u.includes("/model-registry"))).toBe(true);
160
+
161
+ // ── Assert: the golden ───────────────────────────────────────────────────
162
+ const json = JSON.stringify(toJson(AgentExecutionStatusSchema, final), null, 2) + "\n";
163
+ await expect(json).toMatchFileSnapshot("./goldens/todo-write.status.json");
164
+ });
165
+ });
@@ -8,11 +8,11 @@
8
8
  * event's args, `result` from the completed event, `startedAt`/`completedAt`
9
9
  * from the two events' instants, `toolKind` classified, no approval fields set
10
10
  * (a read-only built-in is not gated) — and the turn ends COMPLETED. The golden
11
- * (`goldens/tool-call.status.json`) is the row shape S4's canonical transcript
12
- * builder must reproduce byte for byte.
11
+ * (`goldens/tool-call.status.json`) is the row shape the canonical transcript
12
+ * builder (#1097) must reproduce byte for byte.
13
13
  *
14
14
  * Parent phase rows exercised beyond `plain-turn`: run the turn and consume
15
- * the engine stream (tool-call folding in `message-translator.ts`); the turn
15
+ * the engine stream (`translator.ts` into the shared `TranscriptBuilder`); the turn
16
16
  * boundary with nothing to gate or capture (a read tool writes no file; the
17
17
  * per-session workspace is unchanged, so the capture finds no candidate).
18
18
  *
@@ -28,6 +28,9 @@ import { ExecutionPhase, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agent
28
28
  vi.mock("@cursor/sdk", async () =>
29
29
  (await import("../../__test-utils__/scripted-sdk.js")).scriptedCursorSdkModule(),
30
30
  );
31
+ vi.mock("@cursor/sdk/sqlite", async () =>
32
+ (await import("../../__test-utils__/scripted-sdk.js")).scriptedCursorSqliteModule(),
33
+ );
31
34
  vi.mock("../../../../client/stigmer-client.js", async () =>
32
35
  (await import("../../../../__test-utils__/hermetic-activity.js")).hermeticStigmerClientModule(),
33
36
  );
@@ -86,7 +89,7 @@ describe("ExecuteCursor hermetic — ungated tool call", () => {
86
89
  step.event(ev.toolCall(CALL_ID, "read", "running", READ_ARGS)),
87
90
  step.event(ev.toolCall(CALL_ID, "read", "completed", READ_ARGS, READ_RESULT)),
88
91
  step.event(ev.assistant(ASSISTANT_TEXT)),
89
- step.turnEnded({ inputTokens: 2_000, outputTokens: 90 }),
92
+ step.turnEnded({ inputTokens: 2_000, outputTokens: 90, cacheReadTokens: 0, cacheWriteTokens: 0 }),
90
93
  step.finished({ result: ASSISTANT_TEXT, model: { id: FIXTURE.model, params: [] } }),
91
94
  ],
92
95
  ],