@stigmer/runner 3.15.0 → 3.15.2-dev.20260913223433

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 (518) hide show
  1. package/README.md +2 -4
  2. package/dist/.build-fingerprint +1 -1
  3. package/dist/__test-utils__/execution-record-fixture.d.ts +14 -0
  4. package/dist/__test-utils__/execution-record-fixture.js +4 -0
  5. package/dist/__test-utils__/execution-record-fixture.js.map +1 -1
  6. package/dist/__test-utils__/file-review-projection.d.ts +40 -0
  7. package/dist/__test-utils__/file-review-projection.js +136 -0
  8. package/dist/__test-utils__/file-review-projection.js.map +1 -0
  9. package/dist/__test-utils__/harness-contract/types.d.ts +23 -1
  10. package/dist/__test-utils__/harness-contract/types.js +15 -0
  11. package/dist/__test-utils__/harness-contract/types.js.map +1 -1
  12. package/dist/__test-utils__/hermetic-activity.d.ts +44 -12
  13. package/dist/__test-utils__/hermetic-activity.js +52 -14
  14. package/dist/__test-utils__/hermetic-activity.js.map +1 -1
  15. package/dist/__test-utils__/model-registry-fixture.d.ts +32 -7
  16. package/dist/__test-utils__/model-registry-fixture.js +31 -7
  17. package/dist/__test-utils__/model-registry-fixture.js.map +1 -1
  18. package/dist/__test-utils__/turn-input-fixture.d.ts +1 -1
  19. package/dist/__test-utils__/turn-input-fixture.js +1 -1
  20. package/dist/__test-utils__/turn-input-fixture.js.map +1 -1
  21. package/dist/activities/call-agent.js +2 -2
  22. package/dist/activities/call-agent.js.map +1 -1
  23. package/dist/activities/execute-cursor/__test-utils__/contract-subject.js +45 -3
  24. package/dist/activities/execute-cursor/__test-utils__/contract-subject.js.map +1 -1
  25. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.d.ts +0 -9
  26. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +2 -41
  27. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -1
  28. package/dist/activities/execute-cursor/adapter.d.ts +7 -5
  29. package/dist/activities/execute-cursor/adapter.js +7 -5
  30. package/dist/activities/execute-cursor/adapter.js.map +1 -1
  31. package/dist/activities/execute-cursor/approval-policy.d.ts +5 -2
  32. package/dist/activities/execute-cursor/approval-policy.js +5 -2
  33. package/dist/activities/execute-cursor/approval-policy.js.map +1 -1
  34. package/dist/activities/execute-cursor/approval-state.d.ts +16 -8
  35. package/dist/activities/execute-cursor/approval-state.js +6 -3
  36. package/dist/activities/execute-cursor/approval-state.js.map +1 -1
  37. package/dist/activities/execute-cursor/cas-observations.d.ts +14 -2
  38. package/dist/activities/execute-cursor/cas-observations.js +19 -2
  39. package/dist/activities/execute-cursor/cas-observations.js.map +1 -1
  40. package/dist/activities/execute-cursor/cursor-capabilities.d.ts +8 -4
  41. package/dist/activities/execute-cursor/cursor-capabilities.js +21 -4
  42. package/dist/activities/execute-cursor/cursor-capabilities.js.map +1 -1
  43. package/dist/activities/execute-cursor/message-translator.d.ts +12 -20
  44. package/dist/activities/execute-cursor/message-translator.js +19 -32
  45. package/dist/activities/execute-cursor/message-translator.js.map +1 -1
  46. package/dist/activities/execute-cursor/prompt-builder.d.ts +48 -58
  47. package/dist/activities/execute-cursor/prompt-builder.js +76 -95
  48. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  49. package/dist/activities/execute-cursor/turn-boundary.d.ts +27 -55
  50. package/dist/activities/execute-cursor/turn-boundary.js +30 -90
  51. package/dist/activities/execute-cursor/turn-boundary.js.map +1 -1
  52. package/dist/activities/execute-cursor/turn-settle.d.ts +11 -7
  53. package/dist/activities/execute-cursor/turn-settle.js +37 -49
  54. package/dist/activities/execute-cursor/turn-settle.js.map +1 -1
  55. package/dist/activities/execute-cursor/turn-setup.d.ts +16 -24
  56. package/dist/activities/execute-cursor/turn-setup.js +29 -59
  57. package/dist/activities/execute-cursor/turn-setup.js.map +1 -1
  58. package/dist/activities/execute-cursor/turn-stream.d.ts +3 -8
  59. package/dist/activities/execute-cursor/turn-stream.js +9 -22
  60. package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
  61. package/dist/activities/execute-cursor/turn.js +5 -1
  62. package/dist/activities/execute-cursor/turn.js.map +1 -1
  63. package/dist/activities/execute-deep-agent/__test-utils__/contract-subject.d.ts +93 -0
  64. package/dist/activities/execute-deep-agent/__test-utils__/contract-subject.js +348 -0
  65. package/dist/activities/execute-deep-agent/__test-utils__/contract-subject.js.map +1 -0
  66. package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.d.ts +170 -0
  67. package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js +225 -0
  68. package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js.map +1 -0
  69. package/dist/activities/execute-deep-agent/__test-utils__/hitl-script.d.ts +24 -0
  70. package/dist/activities/execute-deep-agent/__test-utils__/hitl-script.js +41 -0
  71. package/dist/activities/execute-deep-agent/__test-utils__/hitl-script.js.map +1 -0
  72. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.d.ts +54 -0
  73. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.js +72 -0
  74. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.js.map +1 -0
  75. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.d.ts +187 -23
  76. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js +238 -27
  77. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js.map +1 -1
  78. package/dist/activities/execute-deep-agent/adapter.d.ts +41 -0
  79. package/dist/activities/execute-deep-agent/adapter.js +78 -0
  80. package/dist/activities/execute-deep-agent/adapter.js.map +1 -0
  81. package/dist/activities/execute-deep-agent/cas-capture-observer.d.ts +8 -0
  82. package/dist/activities/execute-deep-agent/cas-capture-observer.js +9 -0
  83. package/dist/activities/execute-deep-agent/cas-capture-observer.js.map +1 -1
  84. package/dist/activities/execute-deep-agent/deep-agent-capabilities.d.ts +31 -0
  85. package/dist/activities/execute-deep-agent/deep-agent-capabilities.js +40 -0
  86. package/dist/activities/execute-deep-agent/deep-agent-capabilities.js.map +1 -0
  87. package/dist/activities/execute-deep-agent/execution-state.d.ts +4 -11
  88. package/dist/activities/execute-deep-agent/execution-state.js +4 -12
  89. package/dist/activities/execute-deep-agent/execution-state.js.map +1 -1
  90. package/dist/activities/execute-deep-agent/hitl.d.ts +49 -47
  91. package/dist/activities/execute-deep-agent/hitl.js +77 -103
  92. package/dist/activities/execute-deep-agent/hitl.js.map +1 -1
  93. package/dist/activities/execute-deep-agent/prompt-builder.d.ts +51 -23
  94. package/dist/activities/execute-deep-agent/prompt-builder.js +115 -76
  95. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  96. package/dist/activities/execute-deep-agent/status-builder-shared.d.ts +18 -32
  97. package/dist/activities/execute-deep-agent/status-builder-shared.js +10 -65
  98. package/dist/activities/execute-deep-agent/status-builder-shared.js.map +1 -1
  99. package/dist/activities/execute-deep-agent/streaming-side-effects.d.ts +10 -8
  100. package/dist/activities/execute-deep-agent/streaming-side-effects.js +12 -14
  101. package/dist/activities/execute-deep-agent/streaming-side-effects.js.map +1 -1
  102. package/dist/activities/execute-deep-agent/subagent-tracker.d.ts +36 -13
  103. package/dist/activities/execute-deep-agent/subagent-tracker.js +76 -39
  104. package/dist/activities/execute-deep-agent/subagent-tracker.js.map +1 -1
  105. package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +25 -28
  106. package/dist/activities/execute-deep-agent/subagent-transformer.js +29 -82
  107. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  108. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +3 -2
  109. package/dist/activities/execute-deep-agent/subagent-wiring.js +2 -2
  110. package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
  111. package/dist/activities/execute-deep-agent/turn-settle.d.ts +42 -0
  112. package/dist/activities/execute-deep-agent/turn-settle.js +146 -0
  113. package/dist/activities/execute-deep-agent/turn-settle.js.map +1 -0
  114. package/dist/activities/execute-deep-agent/turn-setup.d.ts +212 -0
  115. package/dist/activities/execute-deep-agent/turn-setup.js +430 -0
  116. package/dist/activities/execute-deep-agent/turn-setup.js.map +1 -0
  117. package/dist/activities/execute-deep-agent/turn-stream.d.ts +97 -0
  118. package/dist/activities/execute-deep-agent/turn-stream.js +242 -0
  119. package/dist/activities/execute-deep-agent/turn-stream.js.map +1 -0
  120. package/dist/activities/execute-deep-agent/turn.d.ts +42 -0
  121. package/dist/activities/execute-deep-agent/turn.js +116 -0
  122. package/dist/activities/execute-deep-agent/turn.js.map +1 -0
  123. package/dist/activities/execute-deep-agent/v3-event-recorder.d.ts +6 -5
  124. package/dist/activities/execute-deep-agent/v3-event-recorder.js +6 -5
  125. package/dist/activities/execute-deep-agent/v3-event-recorder.js.map +1 -1
  126. package/dist/activities/execute-deep-agent/v3-events.d.ts +1 -1
  127. package/dist/activities/execute-deep-agent/v3-events.js +1 -1
  128. package/dist/activities/execute-deep-agent/v3-status-builder.d.ts +60 -17
  129. package/dist/activities/execute-deep-agent/v3-status-builder.js +72 -57
  130. package/dist/activities/execute-deep-agent/v3-status-builder.js.map +1 -1
  131. package/dist/harness/approval-decisions.d.ts +61 -0
  132. package/dist/harness/approval-decisions.js +102 -0
  133. package/dist/harness/approval-decisions.js.map +1 -0
  134. package/dist/harness/capabilities.d.ts +16 -5
  135. package/dist/harness/capabilities.js +9 -0
  136. package/dist/harness/capabilities.js.map +1 -1
  137. package/dist/harness/capture.d.ts +144 -0
  138. package/dist/harness/capture.js +219 -0
  139. package/dist/harness/capture.js.map +1 -0
  140. package/dist/harness/persist-chokepoint.d.ts +19 -4
  141. package/dist/harness/persist-chokepoint.js +17 -4
  142. package/dist/harness/persist-chokepoint.js.map +1 -1
  143. package/dist/harness/run-turn.js +115 -29
  144. package/dist/harness/run-turn.js.map +1 -1
  145. package/dist/harness/terminal-table.d.ts +40 -11
  146. package/dist/harness/terminal-table.js +58 -19
  147. package/dist/harness/terminal-table.js.map +1 -1
  148. package/dist/harness/turn-context.d.ts +141 -76
  149. package/dist/harness/turn-context.js +194 -99
  150. package/dist/harness/turn-context.js.map +1 -1
  151. package/dist/harness/types.d.ts +65 -12
  152. package/dist/harness/types.js +8 -2
  153. package/dist/harness/types.js.map +1 -1
  154. package/dist/harness-adapters.d.ts +12 -8
  155. package/dist/harness-adapters.js +17 -9
  156. package/dist/harness-adapters.js.map +1 -1
  157. package/dist/middleware/approval-gate.d.ts +1 -1
  158. package/dist/middleware/approval-gate.js +1 -1
  159. package/dist/middleware/approval-gate.js.map +1 -1
  160. package/dist/middleware/cost-advisory.d.ts +39 -0
  161. package/dist/middleware/{cost-cap.js → cost-advisory.js} +37 -55
  162. package/dist/middleware/cost-advisory.js.map +1 -0
  163. package/dist/middleware/index.d.ts +23 -12
  164. package/dist/middleware/index.js +26 -18
  165. package/dist/middleware/index.js.map +1 -1
  166. package/dist/middleware/types.d.ts +3 -2
  167. package/dist/runner-manager.js +1 -5
  168. package/dist/runner-manager.js.map +1 -1
  169. package/dist/runner.js +1 -5
  170. package/dist/runner.js.map +1 -1
  171. package/dist/shared/attachment-resolver.d.ts +41 -18
  172. package/dist/shared/attachment-resolver.js +178 -46
  173. package/dist/shared/attachment-resolver.js.map +1 -1
  174. package/dist/shared/attachment-zip.d.ts +85 -0
  175. package/dist/shared/attachment-zip.js +185 -0
  176. package/dist/shared/attachment-zip.js.map +1 -0
  177. package/dist/shared/connect-backfill.js +5 -4
  178. package/dist/shared/connect-backfill.js.map +1 -1
  179. package/dist/shared/cost-guard.d.ts +3 -3
  180. package/dist/shared/cost-guard.js +3 -3
  181. package/dist/shared/execution-status-writer.d.ts +5 -5
  182. package/dist/shared/execution-status-writer.js +5 -5
  183. package/dist/shared/extract-structured-output.d.ts +8 -7
  184. package/dist/shared/extract-structured-output.js +8 -7
  185. package/dist/shared/extract-structured-output.js.map +1 -1
  186. package/dist/shared/filereview/cas-progress.d.ts +1 -18
  187. package/dist/shared/filereview/cas-progress.js +4 -0
  188. package/dist/shared/filereview/cas-progress.js.map +1 -1
  189. package/dist/shared/filereview/cas-touched.d.ts +56 -0
  190. package/dist/shared/filereview/cas-touched.js +63 -0
  191. package/dist/shared/filereview/cas-touched.js.map +1 -0
  192. package/dist/shared/filereview/index.d.ts +2 -1
  193. package/dist/shared/filereview/index.js +2 -1
  194. package/dist/shared/filereview/index.js.map +1 -1
  195. package/dist/shared/mcp-resolver.d.ts +4 -5
  196. package/dist/shared/mcp-resolver.js +4 -5
  197. package/dist/shared/mcp-resolver.js.map +1 -1
  198. package/dist/shared/persist-decision.d.ts +3 -4
  199. package/dist/shared/persist-decision.js +3 -4
  200. package/dist/shared/persist-decision.js.map +1 -1
  201. package/dist/shared/plan-mode-permissions.d.ts +2 -2
  202. package/dist/shared/plan-mode-permissions.js +2 -2
  203. package/dist/shared/plan-mode-prompt.d.ts +1 -1
  204. package/dist/shared/plan-mode-prompt.js +1 -1
  205. package/dist/shared/prompt-sections.d.ts +163 -0
  206. package/dist/shared/prompt-sections.js +164 -0
  207. package/dist/shared/prompt-sections.js.map +1 -0
  208. package/dist/shared/skill-resolver.d.ts +4 -2
  209. package/dist/shared/skill-resolver.js +4 -2
  210. package/dist/shared/skill-resolver.js.map +1 -1
  211. package/dist/shared/tool-rounds.d.ts +32 -0
  212. package/dist/shared/tool-rounds.js +43 -0
  213. package/dist/shared/tool-rounds.js.map +1 -1
  214. package/dist/shared/tool-row.d.ts +34 -0
  215. package/dist/shared/tool-row.js +52 -0
  216. package/dist/shared/tool-row.js.map +1 -1
  217. package/dist/shared/workspace/session-provision.d.ts +3 -3
  218. package/dist/shared/workspace/session-provision.js +3 -3
  219. package/dist/shared/workspace/stigmer-link.d.ts +3 -3
  220. package/dist/shared/workspace/stigmer-link.js +3 -3
  221. package/dist/shared/workspace/writeback-coordinator.d.ts +15 -22
  222. package/dist/shared/workspace/writeback-coordinator.js +16 -53
  223. package/dist/shared/workspace/writeback-coordinator.js.map +1 -1
  224. package/dist/tools/index.d.ts +1 -1
  225. package/dist/tools/index.js +1 -1
  226. package/package.json +4 -4
  227. package/src/__test-utils__/__tests__/harness-contract-self-check.test.ts +1 -0
  228. package/src/__test-utils__/execution-record-fixture.ts +18 -2
  229. package/src/__test-utils__/file-review-projection.ts +156 -0
  230. package/src/__test-utils__/git-workspace-fixture.ts +58 -0
  231. package/src/__test-utils__/harness-boot-order-child.ts +7 -2
  232. package/src/__test-utils__/harness-contract/contract.ts +33 -20
  233. package/src/__test-utils__/harness-contract/recording-sink.ts +11 -1
  234. package/src/__test-utils__/harness-contract/runtime-contract.ts +328 -10
  235. package/src/__test-utils__/harness-contract/scripted-adapter.ts +38 -6
  236. package/src/__test-utils__/harness-contract/types.ts +27 -3
  237. package/src/__test-utils__/hermetic-activity.ts +52 -14
  238. package/src/__test-utils__/model-registry-fixture.ts +32 -7
  239. package/src/__test-utils__/module-specifiers.ts +24 -0
  240. package/src/__test-utils__/turn-input-fixture.ts +2 -2
  241. package/src/__tests__/harness-boot-order.test.ts +9 -2
  242. package/src/activities/call-agent.ts +2 -2
  243. package/src/activities/execute-cursor/__test-utils__/contract-subject.ts +47 -3
  244. package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +2 -48
  245. package/src/activities/execute-cursor/__tests__/approval-decisions-agree.test.ts +1 -1
  246. package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +6 -1
  247. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +52 -27
  248. package/src/activities/execute-cursor/__tests__/cas-observations.test.ts +25 -0
  249. package/src/activities/execute-cursor/__tests__/goldens/prompt.enhanced.everything.prompt.md +166 -0
  250. package/src/activities/execute-cursor/__tests__/goldens/prompt.enhanced.plan-mode.prompt.md +186 -0
  251. package/src/activities/execute-cursor/__tests__/goldens/prompt.hitl.recovery.prompt.md +191 -0
  252. package/src/activities/execute-cursor/__tests__/goldens/prompt.hitl.reinvocation.prompt.md +13 -0
  253. package/src/activities/execute-cursor/__tests__/goldens/prompt.resumed.prefixed.prompt.md +32 -0
  254. package/src/activities/execute-cursor/__tests__/goldens/prompt.resumed.raw.prompt.md +1 -0
  255. package/src/activities/execute-cursor/__tests__/hermetic/file-review-capture.test.ts +1 -2
  256. package/src/activities/execute-cursor/__tests__/hermetic/goldens/sdk-error-at-create.status.json +4 -0
  257. package/src/activities/execute-cursor/__tests__/hermetic/harness-contract.test.ts +24 -4
  258. package/src/activities/execute-cursor/__tests__/hermetic/stream-self-stop-arms.test.ts +6 -5
  259. package/src/activities/execute-cursor/__tests__/hermetic/thrown-error-arms.test.ts +9 -0
  260. package/src/activities/execute-cursor/__tests__/hitl-resume-history.test.ts +11 -8
  261. package/src/activities/execute-cursor/__tests__/prompt-goldens.test.ts +229 -0
  262. package/src/activities/execute-cursor/__tests__/same-identity-reproposal.test.ts +3 -3
  263. package/src/activities/execute-cursor/__tests__/sequential-gate-resume.test.ts +2 -2
  264. package/src/activities/execute-cursor/__tests__/turn-boundary.test.ts +78 -234
  265. package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +1 -26
  266. package/src/activities/execute-cursor/adapter.ts +7 -5
  267. package/src/activities/execute-cursor/approval-policy.ts +5 -2
  268. package/src/activities/execute-cursor/approval-state.ts +16 -8
  269. package/src/activities/execute-cursor/cas-observations.ts +20 -2
  270. package/src/activities/execute-cursor/cursor-capabilities.ts +24 -5
  271. package/src/activities/execute-cursor/message-translator.ts +29 -41
  272. package/src/activities/execute-cursor/prompt-builder.ts +99 -157
  273. package/src/activities/execute-cursor/turn-boundary.ts +34 -140
  274. package/src/activities/execute-cursor/turn-settle.ts +37 -51
  275. package/src/activities/execute-cursor/turn-setup.ts +30 -70
  276. package/src/activities/execute-cursor/turn-stream.ts +8 -31
  277. package/src/activities/execute-cursor/turn.ts +5 -1
  278. package/src/activities/execute-deep-agent/__test-utils__/contract-subject.ts +391 -0
  279. package/src/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.ts +313 -0
  280. package/src/activities/execute-deep-agent/__test-utils__/hitl-script.ts +48 -0
  281. package/src/activities/execute-deep-agent/__test-utils__/scripted-model-module.ts +85 -0
  282. package/src/activities/execute-deep-agent/__test-utils__/scripted-model.ts +383 -36
  283. package/src/activities/execute-deep-agent/__tests__/adapter-graph-is-sdk-free.test.ts +76 -0
  284. package/src/activities/execute-deep-agent/__tests__/adapter-is-temporal-free.test.ts +47 -0
  285. package/src/activities/execute-deep-agent/__tests__/adapter.test.ts +80 -0
  286. package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +1 -1
  287. package/src/activities/execute-deep-agent/__tests__/execution-state-extended.test.ts +0 -2
  288. package/src/activities/execute-deep-agent/__tests__/execution-state.test.ts +0 -3
  289. package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.build-from-plan.prompt.md +172 -0
  290. package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.everything.prompt.md +167 -0
  291. package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.minimal.prompt.md +36 -0
  292. package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.plan-mode.prompt.md +178 -0
  293. package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.skills-below-threshold.prompt.md +158 -0
  294. package/src/activities/execute-deep-agent/__tests__/hermetic/file-review-capture.test.ts +210 -0
  295. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve-all-lease.turn1.status.json +74 -0
  296. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve-all-lease.turn2.status.json +104 -0
  297. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve.turn2.status.json +78 -0
  298. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/file-review-capture.status.json +138 -0
  299. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/gate.turn1.status.json +68 -0
  300. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/inline-artifact.turn1.status.json +154 -0
  301. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/inline-artifact.turn2.status.json +116 -0
  302. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/pause.loop.after-tool.status.json +69 -0
  303. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/pause.loop.mid-tool.status.json +67 -0
  304. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/plain-turn.status.json +47 -0
  305. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/platform-stop.status.json +68 -0
  306. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/recursion-limit.persisted.status.json +280 -0
  307. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/reject.turn2.status.json +77 -0
  308. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/resolution-error.status.json +18 -0
  309. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sequential-gates.turn2.status.json +99 -0
  310. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sequential-gates.turn3.status.json +109 -0
  311. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/skip.turn2.status.json +76 -0
  312. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/structured-output.text-fallback.status.json +51 -0
  313. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/structured-output.tier2.status.json +51 -0
  314. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/structured-output.tool-strategy.status.json +56 -0
  315. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sub-agent-delegation.status.json +90 -0
  316. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/tool-call.status.json +70 -0
  317. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/worker-shutdown.status.json +71 -0
  318. package/src/activities/execute-deep-agent/__tests__/hermetic/harness-contract.test.ts +144 -0
  319. package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-approve-all-lease.test.ts +184 -0
  320. package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-round-trips.test.ts +203 -0
  321. package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-sequential-gates.test.ts +170 -0
  322. package/src/activities/execute-deep-agent/__tests__/hermetic/inline-artifact-across-gate.test.ts +175 -0
  323. package/src/activities/execute-deep-agent/__tests__/hermetic/pause-vs-shutdown.test.ts +206 -0
  324. package/src/activities/execute-deep-agent/__tests__/hermetic/plain-turn.test.ts +142 -0
  325. package/src/activities/execute-deep-agent/__tests__/hermetic/platform-stop.test.ts +126 -0
  326. package/src/activities/execute-deep-agent/__tests__/hermetic/recursion-limit.test.ts +139 -0
  327. package/src/activities/execute-deep-agent/__tests__/hermetic/resolution-error.test.ts +122 -0
  328. package/src/activities/execute-deep-agent/__tests__/hermetic/structured-output.test.ts +236 -0
  329. package/src/activities/execute-deep-agent/__tests__/hermetic/sub-agent-delegation.test.ts +161 -0
  330. package/src/activities/execute-deep-agent/__tests__/hermetic/tool-call.test.ts +157 -0
  331. package/src/activities/execute-deep-agent/__tests__/hermetic/workspace-lock-timeout.test.ts +152 -0
  332. package/src/activities/execute-deep-agent/__tests__/hitl.test.ts +50 -122
  333. package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +8 -6
  334. package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +2 -2
  335. package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +35 -35
  336. package/src/activities/execute-deep-agent/__tests__/prompt-goldens.test.ts +247 -0
  337. package/src/activities/execute-deep-agent/__tests__/scripted-model.test.ts +329 -0
  338. package/src/activities/execute-deep-agent/__tests__/status-builder-shared.test.ts +1 -20
  339. package/src/activities/execute-deep-agent/__tests__/streaming-side-effects.test.ts +78 -0
  340. package/src/activities/execute-deep-agent/__tests__/subagent-gitignored-capture.test.ts +1 -1
  341. package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +2 -2
  342. package/src/activities/execute-deep-agent/__tests__/subagent-tracker.test.ts +57 -31
  343. package/src/activities/execute-deep-agent/__tests__/subagent-transformer.test.ts +19 -79
  344. package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +15 -15
  345. package/src/activities/execute-deep-agent/__tests__/summarization-verification.test.ts +1 -1
  346. package/src/activities/execute-deep-agent/__tests__/turn-stream.test.ts +391 -0
  347. package/src/activities/execute-deep-agent/__tests__/v3-status-builder.test.ts +343 -38
  348. package/src/activities/execute-deep-agent/__tests__/vision-input.test.ts +5 -5
  349. package/src/activities/execute-deep-agent/adapter.ts +94 -0
  350. package/src/activities/execute-deep-agent/cas-capture-observer.ts +11 -0
  351. package/src/activities/execute-deep-agent/deep-agent-capabilities.ts +43 -0
  352. package/src/activities/execute-deep-agent/execution-state.ts +4 -13
  353. package/src/activities/execute-deep-agent/hitl.ts +100 -123
  354. package/src/activities/execute-deep-agent/prompt-builder.ts +141 -115
  355. package/src/activities/execute-deep-agent/status-builder-shared.ts +21 -84
  356. package/src/activities/execute-deep-agent/streaming-side-effects.ts +13 -19
  357. package/src/activities/execute-deep-agent/subagent-tracker.ts +77 -42
  358. package/src/activities/execute-deep-agent/subagent-transformer.ts +46 -105
  359. package/src/activities/execute-deep-agent/subagent-wiring.ts +5 -4
  360. package/src/activities/execute-deep-agent/turn-settle.ts +164 -0
  361. package/src/activities/execute-deep-agent/turn-setup.ts +541 -0
  362. package/src/activities/execute-deep-agent/turn-stream.ts +313 -0
  363. package/src/activities/execute-deep-agent/turn.ts +131 -0
  364. package/src/activities/execute-deep-agent/v3-event-recorder.ts +6 -5
  365. package/src/activities/execute-deep-agent/v3-events.ts +1 -1
  366. package/src/activities/execute-deep-agent/v3-status-builder.ts +84 -63
  367. package/src/harness/__tests__/approval-decisions.test.ts +116 -0
  368. package/src/harness/__tests__/capture.test.ts +268 -0
  369. package/src/harness/__tests__/mount-skills.test.ts +169 -0
  370. package/src/harness/__tests__/persist-chokepoint.test.ts +38 -2
  371. package/src/harness/__tests__/run-turn-deterministic.test.ts +94 -0
  372. package/src/harness/__tests__/run-turn.test.ts +39 -23
  373. package/src/harness/__tests__/turn-attachments-vision.test.ts +80 -0
  374. package/src/harness/__tests__/turn-context.test.ts +73 -26
  375. package/src/harness/approval-decisions.ts +104 -0
  376. package/src/harness/capabilities.ts +16 -5
  377. package/src/harness/capture.ts +301 -0
  378. package/src/harness/persist-chokepoint.ts +24 -4
  379. package/src/harness/run-turn.ts +120 -29
  380. package/src/harness/terminal-table.ts +61 -20
  381. package/src/harness/turn-context.ts +208 -111
  382. package/src/harness/types.ts +66 -12
  383. package/src/harness-adapters.ts +17 -9
  384. package/src/middleware/__tests__/approval-gate.test.ts +1 -1
  385. package/src/middleware/__tests__/cost-advisory.test.ts +113 -0
  386. package/src/middleware/approval-gate.ts +2 -2
  387. package/src/middleware/{cost-cap.ts → cost-advisory.ts} +40 -70
  388. package/src/middleware/index.ts +29 -29
  389. package/src/middleware/types.ts +3 -2
  390. package/src/runner-manager.ts +0 -8
  391. package/src/runner.ts +0 -8
  392. package/src/shared/__tests__/attachment-naming.test.ts +6 -19
  393. package/src/shared/__tests__/attachment-resolver.test.ts +158 -8
  394. package/src/shared/__tests__/attachment-zip.test.ts +236 -0
  395. package/src/shared/__tests__/bedrock-adapter.test.ts +1 -1
  396. package/src/shared/__tests__/blueprint-resolver.test.ts +43 -0
  397. package/src/shared/__tests__/cost-guard.test.ts +3 -3
  398. package/src/shared/__tests__/prompt-sections.test.ts +167 -0
  399. package/src/shared/__tests__/secret-leak-scan.test.ts +2 -2
  400. package/src/{activities/execute-deep-agent → shared}/__tests__/stamp-flowed-rows.test.ts +33 -10
  401. package/src/shared/__tests__/vertex-adapter.test.ts +1 -1
  402. package/src/shared/attachment-resolver.ts +218 -55
  403. package/src/shared/attachment-zip.ts +281 -0
  404. package/src/shared/connect-backfill.ts +9 -11
  405. package/src/shared/cost-guard.ts +3 -3
  406. package/src/shared/execution-status-writer.ts +5 -5
  407. package/src/shared/extract-structured-output.ts +8 -7
  408. package/src/shared/filereview/__tests__/capture.test.ts +86 -4
  409. package/src/shared/filereview/__tests__/cas-progress.test.ts +2 -1
  410. package/src/shared/filereview/cas-progress.ts +5 -19
  411. package/src/shared/filereview/cas-touched.ts +89 -0
  412. package/src/shared/filereview/index.ts +4 -2
  413. package/src/shared/mcp-resolver.ts +4 -5
  414. package/src/shared/persist-decision.ts +3 -4
  415. package/src/shared/plan-mode-permissions.ts +2 -2
  416. package/src/shared/plan-mode-prompt.ts +1 -1
  417. package/src/shared/prompt-sections.ts +256 -0
  418. package/src/shared/skill-resolver.ts +4 -2
  419. package/src/shared/tool-rounds.ts +48 -0
  420. package/src/shared/tool-row.ts +61 -0
  421. package/src/shared/workspace/__tests__/stigmer-link.test.ts +7 -22
  422. package/src/shared/workspace/__tests__/writeback-coordinator.test.ts +20 -61
  423. package/src/shared/workspace/session-provision.ts +3 -3
  424. package/src/shared/workspace/stigmer-link.ts +3 -3
  425. package/src/shared/workspace/writeback-coordinator.ts +16 -64
  426. package/src/tools/index.ts +1 -1
  427. package/dist/activities/execute-cursor/capture-flow.d.ts +0 -142
  428. package/dist/activities/execute-cursor/capture-flow.js +0 -285
  429. package/dist/activities/execute-cursor/capture-flow.js.map +0 -1
  430. package/dist/activities/execute-cursor/command-provenance.d.ts +0 -48
  431. package/dist/activities/execute-cursor/command-provenance.js +0 -38
  432. package/dist/activities/execute-cursor/command-provenance.js.map +0 -1
  433. package/dist/activities/execute-deep-agent/attachment-injector.d.ts +0 -134
  434. package/dist/activities/execute-deep-agent/attachment-injector.js +0 -386
  435. package/dist/activities/execute-deep-agent/attachment-injector.js.map +0 -1
  436. package/dist/activities/execute-deep-agent/command-provenance.d.ts +0 -61
  437. package/dist/activities/execute-deep-agent/command-provenance.js +0 -72
  438. package/dist/activities/execute-deep-agent/command-provenance.js.map +0 -1
  439. package/dist/activities/execute-deep-agent/environment.d.ts +0 -24
  440. package/dist/activities/execute-deep-agent/environment.js +0 -58
  441. package/dist/activities/execute-deep-agent/environment.js.map +0 -1
  442. package/dist/activities/execute-deep-agent/event-recorder.d.ts +0 -21
  443. package/dist/activities/execute-deep-agent/event-recorder.js +0 -67
  444. package/dist/activities/execute-deep-agent/event-recorder.js.map +0 -1
  445. package/dist/activities/execute-deep-agent/index.d.ts +0 -15
  446. package/dist/activities/execute-deep-agent/index.js +0 -918
  447. package/dist/activities/execute-deep-agent/index.js.map +0 -1
  448. package/dist/activities/execute-deep-agent/mcp-gate.d.ts +0 -28
  449. package/dist/activities/execute-deep-agent/mcp-gate.js +0 -22
  450. package/dist/activities/execute-deep-agent/mcp-gate.js.map +0 -1
  451. package/dist/activities/execute-deep-agent/post-stream.d.ts +0 -23
  452. package/dist/activities/execute-deep-agent/post-stream.js +0 -71
  453. package/dist/activities/execute-deep-agent/post-stream.js.map +0 -1
  454. package/dist/activities/execute-deep-agent/setup.d.ts +0 -136
  455. package/dist/activities/execute-deep-agent/setup.js +0 -767
  456. package/dist/activities/execute-deep-agent/setup.js.map +0 -1
  457. package/dist/activities/execute-deep-agent/stamp-flowed-rows.d.ts +0 -36
  458. package/dist/activities/execute-deep-agent/stamp-flowed-rows.js +0 -56
  459. package/dist/activities/execute-deep-agent/stamp-flowed-rows.js.map +0 -1
  460. package/dist/activities/execute-deep-agent/status-builder.d.ts +0 -93
  461. package/dist/activities/execute-deep-agent/status-builder.js +0 -365
  462. package/dist/activities/execute-deep-agent/status-builder.js.map +0 -1
  463. package/dist/activities/execute-deep-agent/streaming-terminal.d.ts +0 -21
  464. package/dist/activities/execute-deep-agent/streaming-terminal.js +0 -80
  465. package/dist/activities/execute-deep-agent/streaming-terminal.js.map +0 -1
  466. package/dist/activities/execute-deep-agent/streaming-v3.d.ts +0 -13
  467. package/dist/activities/execute-deep-agent/streaming-v3.js +0 -174
  468. package/dist/activities/execute-deep-agent/streaming-v3.js.map +0 -1
  469. package/dist/activities/execute-deep-agent/streaming.d.ts +0 -81
  470. package/dist/activities/execute-deep-agent/streaming.js +0 -160
  471. package/dist/activities/execute-deep-agent/streaming.js.map +0 -1
  472. package/dist/middleware/cost-cap.d.ts +0 -22
  473. package/dist/middleware/cost-cap.js.map +0 -1
  474. package/dist/middleware/graceful-stop.d.ts +0 -17
  475. package/dist/middleware/graceful-stop.js +0 -63
  476. package/dist/middleware/graceful-stop.js.map +0 -1
  477. package/dist/shared/skill-writer.d.ts +0 -75
  478. package/dist/shared/skill-writer.js +0 -207
  479. package/dist/shared/skill-writer.js.map +0 -1
  480. package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +0 -1032
  481. package/src/activities/execute-cursor/__tests__/command-provenance.test.ts +0 -240
  482. package/src/activities/execute-cursor/__tests__/progress-substrate.test.ts +0 -169
  483. package/src/activities/execute-cursor/capture-flow.ts +0 -379
  484. package/src/activities/execute-cursor/command-provenance.ts +0 -73
  485. package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +0 -1161
  486. package/src/activities/execute-deep-agent/__tests__/command-provenance.test.ts +0 -252
  487. package/src/activities/execute-deep-agent/__tests__/environment.test.ts +0 -108
  488. package/src/activities/execute-deep-agent/__tests__/event-recorder.test.ts +0 -150
  489. package/src/activities/execute-deep-agent/__tests__/hitl-integration.test.ts +0 -226
  490. package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +0 -318
  491. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +0 -351
  492. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +0 -293
  493. package/src/activities/execute-deep-agent/__tests__/index.test.ts +0 -99
  494. package/src/activities/execute-deep-agent/__tests__/mcp-gate.test.ts +0 -44
  495. package/src/activities/execute-deep-agent/__tests__/post-stream.test.ts +0 -112
  496. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +0 -358
  497. package/src/activities/execute-deep-agent/__tests__/status-builder.test.ts +0 -1933
  498. package/src/activities/execute-deep-agent/__tests__/streaming-terminal.test.ts +0 -40
  499. package/src/activities/execute-deep-agent/__tests__/streaming-v3.test.ts +0 -597
  500. package/src/activities/execute-deep-agent/__tests__/streaming.test.ts +0 -508
  501. package/src/activities/execute-deep-agent/attachment-injector.ts +0 -627
  502. package/src/activities/execute-deep-agent/command-provenance.ts +0 -102
  503. package/src/activities/execute-deep-agent/environment.ts +0 -76
  504. package/src/activities/execute-deep-agent/event-recorder.ts +0 -95
  505. package/src/activities/execute-deep-agent/index.ts +0 -1072
  506. package/src/activities/execute-deep-agent/mcp-gate.ts +0 -37
  507. package/src/activities/execute-deep-agent/post-stream.ts +0 -109
  508. package/src/activities/execute-deep-agent/setup.ts +0 -1081
  509. package/src/activities/execute-deep-agent/stamp-flowed-rows.ts +0 -64
  510. package/src/activities/execute-deep-agent/status-builder.ts +0 -481
  511. package/src/activities/execute-deep-agent/streaming-terminal.ts +0 -106
  512. package/src/activities/execute-deep-agent/streaming-v3.ts +0 -277
  513. package/src/activities/execute-deep-agent/streaming.ts +0 -313
  514. package/src/middleware/__tests__/cost-cap.test.ts +0 -192
  515. package/src/middleware/__tests__/graceful-stop.test.ts +0 -105
  516. package/src/middleware/graceful-stop.ts +0 -86
  517. package/src/shared/__tests__/skill-writer.test.ts +0 -372
  518. package/src/shared/skill-writer.ts +0 -266
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Hermetic goldens: two SEQUENTIAL approval gates — three invocations on the
3
+ * durable (sqlite) checkpointer.
4
+ *
5
+ * Turn 1 opens exactly as every gated arm does (the shared
6
+ * `gate.turn1.status.json`): gated `execute` A, WAITING_FOR_APPROVAL.
7
+ * APPROVE. Turn 2 resumes inside gate A, runs A, and the model proposes
8
+ * gated `execute` B: the graph pauses again and the activity persists
9
+ * WAITING_FOR_APPROVAL with BOTH rows — A COMPLETED (its committed identity
10
+ * and result intact), B WAITING. APPROVE. Turn 3 resumes inside gate B, runs
11
+ * B, the model closes, COMPLETED with both rows COMPLETED and each decision
12
+ * recorded on its own row.
13
+ *
14
+ * Invariant carried from the retired `sequential-gate-resume.test.ts`: the
15
+ * status persisted at gate B must hold gate A's already-committed tool-call
16
+ * row — a status rebuilt from scratch that dropped it would trip the server's
17
+ * append-only-at-identity guard and strand the execution in
18
+ * WAITING_FOR_APPROVAL with zero pending approvals. That test modelled the
19
+ * memory checkpointer's replay through a mocked graph. On the real graph the
20
+ * memory posture (a test-only opt-in; `config.ts` resolves `sqlite` locally
21
+ * and `http` in cloud) re-interrupts the same call on replay and duplicates
22
+ * the row (S3 M0 finding F-M0-3), so the invariant is recorded on the durable
23
+ * posture, where the reinvocation resumes instead of replaying.
24
+ *
25
+ * What every reinvocation golden here shows since S3 M2a (the runtime over
26
+ * the native adapter; Q-S3-16 and Q-M2a-2): `startedAt` is THIS turn's own,
27
+ * not turn 1's carried by the orchestrator's whole-status clone; the
28
+ * `fileReviewEventStream` carries only the events THIS turn authored (the
29
+ * runtime seeds messages, sub-agents, artifacts, write-backs, todos, usage,
30
+ * the memory report and the structured output — never the server-owned
31
+ * ledgers, which the server appends to by event id in `update-status.ts`
32
+ * `appendRunnerEvents`; the hermetic record replaces wholesale, so a
33
+ * golden reads as what the runner SENT); and `streamingUsage` carries the
34
+ * runtime accountant's fields (`model`, `estimatedCostUsd`, the requested
35
+ * tier and thinking mode).
36
+ *
37
+ * Regenerate ONLY after a deliberate behavior change:
38
+ * npx vitest run src/activities/execute-deep-agent/__tests__/hermetic -u
39
+ */
40
+
41
+ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
42
+ import { toJson } from "@bufbuild/protobuf";
43
+ import {
44
+ AgentExecutionStatusSchema,
45
+ type AgentExecutionStatus,
46
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
47
+ import {
48
+ ApprovalAction,
49
+ ExecutionPhase,
50
+ ToolCallStatus,
51
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
52
+
53
+ vi.mock("../../../../shared/model-client.js", async () =>
54
+ (await import("../../__test-utils__/scripted-model-module.js")).scriptedModelClientModule(),
55
+ );
56
+ vi.mock("../../../../client/stigmer-client.js", async () =>
57
+ (await import("../../../../__test-utils__/hermetic-activity.js")).hermeticStigmerClientModule(),
58
+ );
59
+
60
+ import {
61
+ ScriptedClock,
62
+ createHermeticEnvironment,
63
+ type HermeticEnvironment,
64
+ } from "../../../../__test-utils__/hermetic-activity.js";
65
+ import { stubRegistryFetch } from "../../../../__test-utils__/model-registry-fixture.js";
66
+ import {
67
+ beginDeepAgentScenario,
68
+ deepAgentExecutionRecord,
69
+ runDeepAgentTurn,
70
+ } from "../../__test-utils__/hermetic-deep-agent.js";
71
+ import {
72
+ CLOSING_TURN,
73
+ EXECUTE_CALL_A,
74
+ EXECUTE_CALL_B,
75
+ GATED_OPENING_TURN,
76
+ GATE_TURN1_GOLDEN,
77
+ executeApprovalMessage,
78
+ } from "../../__test-utils__/hitl-script.js";
79
+
80
+ const USER_MESSAGE = "Run the command for me.";
81
+ const DECIDED_A_AT = "2026-01-01T00:00:30.000Z";
82
+ const DECIDED_B_AT = "2026-01-01T00:01:00.000Z";
83
+
84
+ function statusJson(status: AgentExecutionStatus): string {
85
+ return JSON.stringify(toJson(AgentExecutionStatusSchema, status), null, 2) + "\n";
86
+ }
87
+
88
+ describe("ExecuteDeepAgent hermetic — two sequential gates (sqlite)", () => {
89
+ let env: HermeticEnvironment;
90
+ let registry: ReturnType<typeof stubRegistryFetch>;
91
+ const clock = new ScriptedClock();
92
+
93
+ beforeAll(() => {
94
+ env = createHermeticEnvironment();
95
+ registry = stubRegistryFetch();
96
+ clock.install();
97
+ });
98
+
99
+ afterAll(() => {
100
+ clock.uninstall();
101
+ registry.restore();
102
+ env.dispose();
103
+ });
104
+
105
+ it("keeps gate A's committed row through gate B's pause and completes after both approvals", async () => {
106
+ // ── Arrange ──────────────────────────────────────────────────────────────
107
+ const record = deepAgentExecutionRecord({ message: USER_MESSAGE });
108
+ const scenario = beginDeepAgentScenario({
109
+ env,
110
+ clock,
111
+ record,
112
+ checkpointer: "sqlite",
113
+ script: () => ({
114
+ turns: [
115
+ GATED_OPENING_TURN,
116
+ { text: "Now the second command.", toolCalls: [EXECUTE_CALL_B], usage: { inputTokens: 1_600, outputTokens: 40 } },
117
+ CLOSING_TURN,
118
+ ],
119
+ }),
120
+ });
121
+
122
+ // ── Turn 1: gate A ───────────────────────────────────────────────────────
123
+ const turn1 = await runDeepAgentTurn(scenario, { turnSeq: 0 });
124
+ expect(turn1.outcome.kind).toBe("returned");
125
+ expect(record.waitingToolCalls().map((tc) => tc.id)).toEqual([EXECUTE_CALL_A.id]);
126
+ const atGateA = record.lastFullStatus!;
127
+ await expect(statusJson(atGateA), "the same opening as every gated arm").toMatchFileSnapshot(GATE_TURN1_GOLDEN);
128
+ expect(record.decideWaitingToolCalls(ApprovalAction.APPROVE, DECIDED_A_AT)).toBe(1);
129
+
130
+ // ── Turn 2: A runs, gate B pauses ────────────────────────────────────────
131
+ const turn2 = await runDeepAgentTurn(scenario, { turnSeq: 1 });
132
+ expect(turn2.outcome.kind).toBe("returned");
133
+ expect((turn2.outcome as { value: Record<string, unknown> }).value.phase).toBe("EXECUTION_WAITING_FOR_APPROVAL");
134
+ const atGateB = record.lastFullStatus!;
135
+ const rowsAtB = atGateB.messages.flatMap((m) => m.toolCalls);
136
+ expect(rowsAtB.map((tc) => [tc.id, tc.status]), "A committed and kept, B waiting").toEqual([
137
+ [EXECUTE_CALL_A.id, ToolCallStatus.TOOL_CALL_COMPLETED],
138
+ [EXECUTE_CALL_B.id, ToolCallStatus.TOOL_CALL_WAITING_APPROVAL],
139
+ ]);
140
+ expect(rowsAtB[0].result).toContain("hermetic-a");
141
+ expect(rowsAtB[0].approvalAction).toBe(ApprovalAction.APPROVE);
142
+ expect(rowsAtB[1].approvalMessage).toBe(executeApprovalMessage(EXECUTE_CALL_B));
143
+ expect(record.waitingToolCalls().map((tc) => tc.id), "exactly one pending approval").toEqual([EXECUTE_CALL_B.id]);
144
+ // The gate-B transcript is a superset of the gate-A one: every id, every message, in order.
145
+ const gateAIds = atGateA.messages.flatMap((m) => m.toolCalls).map((tc) => tc.id);
146
+ expect(rowsAtB.map((tc) => tc.id).slice(0, gateAIds.length)).toEqual(gateAIds);
147
+ expect(atGateB.messages.length).toBeGreaterThanOrEqual(atGateA.messages.length);
148
+ await expect(statusJson(atGateB)).toMatchFileSnapshot("./goldens/sequential-gates.turn2.status.json");
149
+ expect(record.decideWaitingToolCalls(ApprovalAction.APPROVE, DECIDED_B_AT)).toBe(1);
150
+
151
+ // ── Turn 3: B runs, the run completes ────────────────────────────────────
152
+ const turn3 = await runDeepAgentTurn(scenario, { turnSeq: 2 });
153
+ expect(turn3.outcome.kind).toBe("returned");
154
+ expect((turn3.outcome as { value: Record<string, unknown> }).value.phase).toBe("EXECUTION_COMPLETED");
155
+ expect(record.persistedPhases.filter((p) => p === ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL)).toHaveLength(2);
156
+ const final = record.lastFullStatus!;
157
+ const rows = final.messages.flatMap((m) => m.toolCalls);
158
+ expect(rows.map((tc) => [tc.id, tc.status, tc.approvalAction, tc.approvalDecidedAt])).toEqual([
159
+ [EXECUTE_CALL_A.id, ToolCallStatus.TOOL_CALL_COMPLETED, ApprovalAction.APPROVE, DECIDED_A_AT],
160
+ [EXECUTE_CALL_B.id, ToolCallStatus.TOOL_CALL_COMPLETED, ApprovalAction.APPROVE, DECIDED_B_AT],
161
+ ]);
162
+ expect(rows[1].result).toContain("hermetic-b");
163
+
164
+ // ── Assert: hermeticity ──────────────────────────────────────────────────
165
+ expect(registry.urls.every((u) => u.includes("/model-registry"))).toBe(true);
166
+
167
+ // ── Assert: the golden ───────────────────────────────────────────────────
168
+ await expect(statusJson(final)).toMatchFileSnapshot("./goldens/sequential-gates.turn3.status.json");
169
+ });
170
+ });
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Hermetic goldens: an INLINE-PUBLISHED ARTIFACT survives a HITL gate — the
3
+ * arbiter for Q-S3-16 (the seed's breadth), two invocations on the durable
4
+ * (sqlite) checkpointer.
5
+ *
6
+ * Turn 1: the model writes a report (`write_file`; under capture mode in a
7
+ * non-git session workspace the write FLOWS into the CAS ledger), and
8
+ * `streaming-side-effects.ts` hands the path to `InlinePublisher`, which
9
+ * uploads the bytes to artifact storage and appends an `ExecutionArtifact`
10
+ * (`storageKey = artifacts/<executionId>/<name>`, a content hash, the
11
+ * scripted clock) to the status — a mid-turn publish the Cursor harness never
12
+ * performs. The model then proposes a gated `execute`; WAITING_FOR_APPROVAL.
13
+ *
14
+ * Turn 2: after APPROVE, the reinvocation seeds its status from the persisted
15
+ * one. On this harness `seedStatusFromExecution` clones the WHOLE status, so
16
+ * the artifact list arrives intact and the run's final status still lists the
17
+ * report. The server replaces `artifacts` wholesale whenever the request's
18
+ * list is non-empty (`update-status.ts`), so a runtime that seeded messages
19
+ * only and then published nothing new would carry an empty list — and the
20
+ * server would keep the old one, but a runtime that published one NEW
21
+ * artifact from an empty list would ERASE the prior one. Q-S3-16's
22
+ * `seedFromPersistedStatus` carries `artifacts` for exactly this reason;
23
+ * this pair of goldens (`inline-artifact.turn1`, `inline-artifact.turn2`) is
24
+ * what proves it did.
25
+ *
26
+ * Also recorded, F-M0-8 (a shape fact for the owner): the write's tool row
27
+ * shows `requiresApproval: true` with `approvalPolicySource` BUILTIN_CATEGORY
28
+ * — the category policy's verdict — while the row is COMPLETED because
29
+ * capture mode let it flow; the artifact carries no URL (the local store's
30
+ * serve URL never enters the record).
31
+ *
32
+ * What every reinvocation golden here shows since S3 M2a (the runtime over
33
+ * the native adapter; Q-S3-16 and Q-M2a-2): `startedAt` is THIS turn's own,
34
+ * not turn 1's carried by the orchestrator's whole-status clone; the
35
+ * `fileReviewEventStream` carries only the events THIS turn authored (the
36
+ * runtime seeds messages, sub-agents, artifacts, write-backs, todos, usage,
37
+ * the memory report and the structured output — never the server-owned
38
+ * ledgers, which the server appends to by event id in `update-status.ts`
39
+ * `appendRunnerEvents`; the hermetic record replaces wholesale, so a
40
+ * golden reads as what the runner SENT); and `streamingUsage` carries the
41
+ * runtime accountant's fields (`model`, `estimatedCostUsd`, the requested
42
+ * tier and thinking mode).
43
+ *
44
+ * Golden hunk since S3 M4 (Q-M4-7, ruled Q-M4-12 A): turn 1's
45
+ * `fileChangeProgress` is the TRUE snapshot at the WAITING write
46
+ * (`filesChanged: 1`, `report.md` ADD +3, `capturedAt` t=2) where it was a
47
+ * stale t=0 snapshot taken by the stream loop before the engine wrote the
48
+ * file — no later mid-stream persist crossed the floor, so the pause used
49
+ * to persist "0 files" beside a one-file change set. The runtime now
50
+ * refreshes the strip on every write, the terminal one included.
51
+ *
52
+ * Regenerate ONLY after a deliberate behavior change:
53
+ * npx vitest run src/activities/execute-deep-agent/__tests__/hermetic -u
54
+ */
55
+
56
+ import { existsSync } from "node:fs";
57
+ import { join } from "node:path";
58
+ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
59
+ import { toJson } from "@bufbuild/protobuf";
60
+ import {
61
+ AgentExecutionStatusSchema,
62
+ type AgentExecutionStatus,
63
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
64
+ import {
65
+ ApprovalAction,
66
+ ExecutionPhase,
67
+ ToolCallStatus,
68
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
69
+
70
+ vi.mock("../../../../shared/model-client.js", async () =>
71
+ (await import("../../__test-utils__/scripted-model-module.js")).scriptedModelClientModule(),
72
+ );
73
+ vi.mock("../../../../client/stigmer-client.js", async () =>
74
+ (await import("../../../../__test-utils__/hermetic-activity.js")).hermeticStigmerClientModule(),
75
+ );
76
+
77
+ import {
78
+ ScriptedClock,
79
+ createHermeticEnvironment,
80
+ type HermeticEnvironment,
81
+ } from "../../../../__test-utils__/hermetic-activity.js";
82
+ import { stubRegistryFetch } from "../../../../__test-utils__/model-registry-fixture.js";
83
+ import {
84
+ FIXTURE,
85
+ beginDeepAgentScenario,
86
+ deepAgentExecutionRecord,
87
+ runDeepAgentTurn,
88
+ } from "../../__test-utils__/hermetic-deep-agent.js";
89
+ import { CLOSING_TURN, EXECUTE_CALL_A } from "../../__test-utils__/hitl-script.js";
90
+
91
+ const REPORT = "report.md";
92
+ const REPORT_BODY = "# Report\n\nAll fixtures nominal.\n";
93
+ const WRITE_CALL_ID = "call-hermetic-write-0001";
94
+ const DECIDED_AT = "2026-01-01T00:00:30.000Z";
95
+
96
+ function statusJson(status: AgentExecutionStatus): string {
97
+ return JSON.stringify(toJson(AgentExecutionStatusSchema, status), null, 2) + "\n";
98
+ }
99
+
100
+ describe("ExecuteDeepAgent hermetic — inline artifact across a gate (sqlite)", () => {
101
+ let env: HermeticEnvironment;
102
+ let registry: ReturnType<typeof stubRegistryFetch>;
103
+ const clock = new ScriptedClock();
104
+
105
+ beforeAll(() => {
106
+ env = createHermeticEnvironment();
107
+ registry = stubRegistryFetch();
108
+ clock.install();
109
+ });
110
+
111
+ afterAll(() => {
112
+ clock.uninstall();
113
+ registry.restore();
114
+ env.dispose();
115
+ });
116
+
117
+ it("publishes the report inline in turn 1 and still lists it after the approved turn 2", async () => {
118
+ // ── Arrange ──────────────────────────────────────────────────────────────
119
+ const record = deepAgentExecutionRecord({ message: "Write the report, then run the command." });
120
+ const scenario = beginDeepAgentScenario({
121
+ env,
122
+ clock,
123
+ record,
124
+ checkpointer: "sqlite",
125
+ script: () => ({
126
+ turns: [
127
+ {
128
+ text: "Writing the report.",
129
+ toolCalls: [{ id: WRITE_CALL_ID, name: "write_file", args: { file_path: `/${REPORT}`, content: REPORT_BODY } }],
130
+ usage: { inputTokens: 1_300, outputTokens: 60 },
131
+ },
132
+ { text: "Now the command.", toolCalls: [EXECUTE_CALL_A], usage: { inputTokens: 1_500, outputTokens: 40 } },
133
+ CLOSING_TURN,
134
+ ],
135
+ }),
136
+ });
137
+
138
+ // ── Turn 1 ───────────────────────────────────────────────────────────────
139
+ const turn1 = await runDeepAgentTurn(scenario, { turnSeq: 0 });
140
+ expect(turn1.outcome.kind).toBe("returned");
141
+ expect(record.persistedPhases.at(-1)).toBe(ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL);
142
+ const run1 = record.lastFullStatus!;
143
+ expect(run1.artifacts.map((a) => [a.name, a.storageKey])).toEqual([
144
+ [REPORT, `artifacts/${FIXTURE.executionId}/${REPORT}`],
145
+ ]);
146
+ expect(run1.artifacts[0].contentHash, "sha256 of the bytes").toMatch(/^[0-9a-f]{64}$/);
147
+ expect(existsSync(join(env.artifactPath, run1.artifacts[0].storageKey)), "the bytes reached the store").toBe(true);
148
+ const writeRow = record.toolCalls().find((tc) => tc.id === WRITE_CALL_ID)!;
149
+ expect(writeRow.status, "capture mode let the write flow").toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
150
+ expect(record.waitingToolCalls().map((tc) => tc.id)).toEqual([EXECUTE_CALL_A.id]);
151
+ await expect(statusJson(run1)).toMatchFileSnapshot("./goldens/inline-artifact.turn1.status.json");
152
+
153
+ // ── Between turns ────────────────────────────────────────────────────────
154
+ expect(record.decideWaitingToolCalls(ApprovalAction.APPROVE, DECIDED_AT)).toBe(1);
155
+
156
+ // ── Turn 2 ───────────────────────────────────────────────────────────────
157
+ const turn2 = await runDeepAgentTurn(scenario, { turnSeq: 1 });
158
+ expect(turn2.outcome.kind).toBe("returned");
159
+ expect((turn2.outcome as { value: Record<string, unknown> }).value.phase).toBe("EXECUTION_COMPLETED");
160
+ const final = record.lastFullStatus!;
161
+ expect(final.artifacts.map((a) => [a.name, a.storageKey, a.contentHash]), "the seed carried the artifact").toEqual(
162
+ run1.artifacts.map((a) => [a.name, a.storageKey, a.contentHash]),
163
+ );
164
+ expect(final.messages.flatMap((m) => m.toolCalls).map((tc) => [tc.id, tc.status])).toEqual([
165
+ [WRITE_CALL_ID, ToolCallStatus.TOOL_CALL_COMPLETED],
166
+ [EXECUTE_CALL_A.id, ToolCallStatus.TOOL_CALL_COMPLETED],
167
+ ]);
168
+
169
+ // ── Assert: hermeticity ──────────────────────────────────────────────────
170
+ expect(registry.urls.every((u) => u.includes("/model-registry"))).toBe(true);
171
+ const finalJson = statusJson(final);
172
+ expect(finalJson, "no temp path may reach the golden").not.toContain(env.artifactPath);
173
+ await expect(finalJson).toMatchFileSnapshot("./goldens/inline-artifact.turn2.status.json");
174
+ });
175
+ });
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Hermetic goldens: the two ways a turn is INTERRUPTED from outside, and how
3
+ * the activity tells them apart — the throw-vs-return table the control
4
+ * plane's workflow keys on. Since S3 M2a the activity is the turn runtime
5
+ * over the native adapter, and the shapes below are the runtime's terminal
6
+ * table (`harness/terminal-table.ts`), ruled at Q-S3-7 (P-1: the pause copy
7
+ * is Cursor's) and stigmer#1071 ("persist a thrown terminal once").
8
+ *
9
+ * 1. USER PAUSE: the activity's `cancellationSignal` aborts. Whether the
10
+ * abort lands inside a model call (staged from the scripted model's
11
+ * `onTurn`) or while the loop is persisting (staged from the control
12
+ * plane's persist channel, the record's `controlSignal`), the runtime's
13
+ * stop controller aborts the adapter's signal, the adapter cancels the
14
+ * graph run and settles `interrupted`, and the runtime persists PAUSED
15
+ * ONCE — WITH the transcript and the row "Execution paused by user. Use
16
+ * resume to continue." (P-1: the runtime's pause copy is Cursor's; the
17
+ * orchestrator's said "… from this checkpoint.") — then throws
18
+ * `CancelledFailure("Activity paused by orchestrator")`. Until M2a the
19
+ * orchestrator persisted PAUSED TWICE, the second write bare and erasing
20
+ * the transcript (S3 M0 finding F-M0-6, stigmer#1054's double persist);
21
+ * `pause.bare.status.json` recorded that write and is gone with it. The
22
+ * one write differs by where the cancel landed — after the tool finished
23
+ * (`pause.loop.after-tool`) or while it was still RUNNING
24
+ * (`pause.loop.mid-tool`, the cancel arriving on the persist that carried
25
+ * the row).
26
+ * 2. WORKER SHUTDOWN: the queue's shutdown signal is aborted before the
27
+ * cancel, so the interruption is EXECUTION_FAILED with the interrupted
28
+ * copy and one row, thrown as
29
+ * `CancelledFailure("Activity cancelled (worker shutdown, not user pause)")`
30
+ * (#776) — the workflow re-invokes instead of waiting for a resume. The
31
+ * copy is unchanged; what changed at M2a (Q-S3-7, the runtime's arm
32
+ * wins) is that the runtime's arm KEEPS the transcript the turn produced
33
+ * and appends the row, where the orchestrator's `buildWorkerShutdownStatus`
34
+ * replaced the whole status with the row alone.
35
+ *
36
+ * Every interruption is staged deterministically — from a model turn or from
37
+ * a persist, never from a timer.
38
+ *
39
+ * Regenerate ONLY after a deliberate behavior change:
40
+ * npx vitest run src/activities/execute-deep-agent/__tests__/hermetic -u
41
+ */
42
+
43
+ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
44
+ import { toJson } from "@bufbuild/protobuf";
45
+ import {
46
+ AgentExecutionStatusSchema,
47
+ type AgentExecutionStatus,
48
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
49
+ import {
50
+ ExecutionControlSignal,
51
+ ExecutionPhase,
52
+ MessageType,
53
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
54
+
55
+ vi.mock("../../../../shared/model-client.js", async () =>
56
+ (await import("../../__test-utils__/scripted-model-module.js")).scriptedModelClientModule(),
57
+ );
58
+ vi.mock("../../../../client/stigmer-client.js", async () =>
59
+ (await import("../../../../__test-utils__/hermetic-activity.js")).hermeticStigmerClientModule(),
60
+ );
61
+
62
+ import {
63
+ ScriptedClock,
64
+ createHermeticEnvironment,
65
+ threwCancelledFailure,
66
+ type HermeticEnvironment,
67
+ type InvocationControls,
68
+ } from "../../../../__test-utils__/hermetic-activity.js";
69
+ import { stubRegistryFetch } from "../../../../__test-utils__/model-registry-fixture.js";
70
+ import {
71
+ beginDeepAgentScenario,
72
+ deepAgentExecutionRecord,
73
+ runDeepAgentTurn,
74
+ type DeepAgentScenario,
75
+ } from "../../__test-utils__/hermetic-deep-agent.js";
76
+ import type { RoleScript } from "../../__test-utils__/scripted-model.js";
77
+
78
+ const READ_CALL = { id: "call-hermetic-read-0001", name: "read_file", args: { file_path: "/notes.md" } };
79
+
80
+ /** A turn that reads, then would speak again — the interruption lands before it can. */
81
+ const SCRIPT: RoleScript = {
82
+ turns: [
83
+ { text: "Let me look.", toolCalls: [READ_CALL], usage: { inputTokens: 1_200, outputTokens: 30 } },
84
+ { text: "Never reaches the record.", usage: { inputTokens: 1_400, outputTokens: 10 } },
85
+ ],
86
+ };
87
+
88
+ describe("ExecuteDeepAgent hermetic — pause vs worker shutdown", () => {
89
+ let env: HermeticEnvironment;
90
+ let registry: ReturnType<typeof stubRegistryFetch>;
91
+ const clock = new ScriptedClock();
92
+ /** The scenario whose invocation is running — the persist-channel staging cancels it. */
93
+ let inFlight: DeepAgentScenario | undefined;
94
+
95
+ beforeAll(() => {
96
+ env = createHermeticEnvironment();
97
+ registry = stubRegistryFetch();
98
+ clock.install();
99
+ });
100
+
101
+ afterAll(() => {
102
+ clock.uninstall();
103
+ registry.restore();
104
+ env.dispose();
105
+ });
106
+
107
+ describe.each([
108
+ {
109
+ staging: "inside a model call",
110
+ begin: (record: ReturnType<typeof deepAgentExecutionRecord>) =>
111
+ beginDeepAgentScenario({
112
+ env,
113
+ clock,
114
+ record,
115
+ script: () => SCRIPT,
116
+ onTurn: (info, controls) => {
117
+ if (info.round === 1) controls.cancel();
118
+ },
119
+ }),
120
+ controlSignal: undefined as ((status: AgentExecutionStatus) => ExecutionControlSignal) | undefined,
121
+ loopGolden: "./goldens/pause.loop.after-tool.status.json",
122
+ },
123
+ {
124
+ staging: "between events",
125
+ begin: (record: ReturnType<typeof deepAgentExecutionRecord>) =>
126
+ beginDeepAgentScenario({ env, clock, record, script: () => SCRIPT }),
127
+ // The platform answers the persist that carries the first tool row by
128
+ // cancelling the activity (the workflow's pause).
129
+ controlSignal: (status: AgentExecutionStatus) => {
130
+ if (status.messages.some((m) => m.toolCalls.length > 0)) inFlight?.controls?.cancel();
131
+ return ExecutionControlSignal.UNSPECIFIED;
132
+ },
133
+ loopGolden: "./goldens/pause.loop.mid-tool.status.json",
134
+ },
135
+ ])("user pause $staging", ({ begin, controlSignal, loopGolden }) => {
136
+ it("persists PAUSED once, with the transcript and the row, and throws as a pause", async () => {
137
+ clock.reset();
138
+ const record = deepAgentExecutionRecord({ message: "Look at my notes.", controlSignal });
139
+ inFlight = begin(record);
140
+
141
+ const invocation = await runDeepAgentTurn(inFlight);
142
+
143
+ expect(threwCancelledFailure(invocation.outcome), "a pause THROWS CancelledFailure").toBe(true);
144
+ expect((invocation.outcome as { error: Error }).error.message).toBe("Activity paused by orchestrator");
145
+ expect(record.persistedPhases).toEqual([ExecutionPhase.EXECUTION_IN_PROGRESS, ExecutionPhase.EXECUTION_PAUSED]);
146
+
147
+ const pausedWrites = record.persisted.filter((s) => s.phase === ExecutionPhase.EXECUTION_PAUSED);
148
+ expect(pausedWrites, "PAUSED is persisted exactly once (stigmer#1071; F-M0-6 closed at M2a)").toHaveLength(1);
149
+ const [paused] = pausedWrites;
150
+ expect(paused.messages.filter((m) => m.type === MessageType.MESSAGE_SYSTEM).map((m) => m.content)).toEqual([
151
+ "Execution paused by user. Use resume to continue.",
152
+ ]);
153
+ expect(
154
+ paused.messages.flatMap((m) => m.toolCalls).map((tc) => tc.name),
155
+ "the read that landed before the pause is in the one write",
156
+ ).toEqual(["read_file"]);
157
+ expect(paused.completedAt).toBe("");
158
+ expect(record.lastFullStatus, "the wire ends on the write that carries the transcript").toEqual(paused);
159
+
160
+ const json = JSON.stringify(toJson(AgentExecutionStatusSchema, paused), null, 2) + "\n";
161
+ await expect(json).toMatchFileSnapshot(loopGolden);
162
+ });
163
+ });
164
+
165
+ it("worker shutdown: FAILED with the interrupted copy, thrown as a shutdown", async () => {
166
+ clock.reset();
167
+ const record = deepAgentExecutionRecord({ message: "Look at my notes." });
168
+ const scenario = beginDeepAgentScenario({
169
+ env,
170
+ clock,
171
+ record,
172
+ script: () => SCRIPT,
173
+ onTurn: (info, controls: InvocationControls) => {
174
+ if (info.round === 1) {
175
+ controls.signalWorkerShutdown();
176
+ controls.cancel();
177
+ }
178
+ },
179
+ });
180
+
181
+ const invocation = await runDeepAgentTurn(scenario);
182
+
183
+ expect(threwCancelledFailure(invocation.outcome), "a drain THROWS so the workflow re-invokes").toBe(true);
184
+ expect((invocation.outcome as { error: Error }).error.message).toBe(
185
+ "Activity cancelled (worker shutdown, not user pause)",
186
+ );
187
+ expect(record.persistedPhases.at(-1)).toBe(ExecutionPhase.EXECUTION_FAILED);
188
+ const final = record.lastFullStatus!;
189
+ expect(final.error).toBe("Execution interrupted: runner worker was shut down. Retry or resume.");
190
+ // The transcript the turn produced is kept and the row appended (the
191
+ // runtime's arm, Q-S3-7); the tool row rides its own empty AI message
192
+ // (F-M0-1, S4's).
193
+ expect(final.messages.map((m) => [m.type, m.content])).toEqual([
194
+ [MessageType.MESSAGE_AI, "Let me look."],
195
+ [MessageType.MESSAGE_AI, ""],
196
+ [
197
+ MessageType.MESSAGE_SYSTEM,
198
+ "Execution interrupted: the runner worker was shut down while the agent was still running. You can retry or resume.",
199
+ ],
200
+ ]);
201
+ expect(final.messages.flatMap((m) => m.toolCalls).map((tc) => tc.name), "the read that landed before the drain is kept").toEqual(["read_file"]);
202
+ expect(registry.urls.every((u) => u.includes("/model-registry"))).toBe(true);
203
+ const json = JSON.stringify(toJson(AgentExecutionStatusSchema, final), null, 2) + "\n";
204
+ await expect(json).toMatchFileSnapshot("./goldens/worker-shutdown.status.json");
205
+ });
206
+ });