@stigmer/runner 3.15.1 → 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
@@ -2,20 +2,25 @@
2
2
  * Pins the turn context's pure decisions — the three places `turn-context.ts`
3
3
  * decides something rather than fetching it:
4
4
  *
5
- * - `isReinvocation`: the one reading of "does the engine already hold state
6
- * for this execution", keyed on how the harness mints its state id. The
7
- * engine-minted arm is today's `!!threadId`; the deterministic arm refuses
8
- * until the native adapter lands (S3), and the refusal is pinned so a
9
- * future caller cannot get a silent `false`.
10
- * - `approvalDecisionsOf`: the runtime's reader of adjudicated approvals,
5
+ * - `isReinvocation`: the runtime's one reading of "does this invocation
6
+ * resume rather than begin", keyed on how the harness mints its state id.
7
+ * The engine-minted arm is `!!threadId`; the deterministic arm is the
8
+ * persisted transcript's non-emptiness, whatever the thread id says (the
9
+ * id exists on the first turn too), and never the live engine checkpoint.
10
+ * - `approvalDecisionsOf` (`approval-decisions.ts`): the runtime's reader of adjudicated approvals,
11
11
  * over a transcript that carries every row shape it must include or
12
12
  * exclude. Its agreement with the Cursor adapter's own reader
13
13
  * (`reconstructAdjudicatedApprovals`) is pinned from the adapter's side
14
14
  * (`activities/execute-cursor/__tests__/approval-state.test.ts`), because
15
15
  * `src/harness/` never imports `src/activities/`, tests included.
16
- * - `decideReinvocation`: the four ways a resume proceeds (reject fails,
17
- * approvals run, a clean file-review resume completes, a failed or
18
- * partially discarded one completes with its facts carried).
16
+ * - `decideReinvocation`: the three ways a resume proceeds (any decision
17
+ * runs the agent REJECT included since S3 M1 —, a clean file-review
18
+ * resume completes, a failed or partially discarded one completes with
19
+ * its facts carried).
20
+ * - `regeneratesApprovedWrites`: which pause primitives make the runner pin
21
+ * an approved whole-file write's bytes itself (the engine re-runs the
22
+ * model) and which never do (the engine applies the exact args it paused
23
+ * on). S3 M2a, Q-M2a-3.
19
24
  *
20
25
  * The phases that fetch and provision are exercised end to end by the
21
26
  * hermetic goldens (`activities/execute-cursor/__tests__/hermetic/`), which
@@ -25,30 +30,53 @@
25
30
 
26
31
  import { describe, expect, it } from "vitest";
27
32
  import { create } from "@bufbuild/protobuf";
28
- import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
33
+ import { AgentExecutionSchema, AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
29
34
  import { AgentMessageSchema, ToolCallSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
30
35
  import { SubAgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
31
36
  import { ApprovalAction, MessageType, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
32
37
 
33
- import { approvalDecisionsOf, decideReinvocation, isReinvocation, type ReinvocationFacts } from "../turn-context.js";
38
+ import { approvalDecisionsOf } from "../approval-decisions.js";
39
+ import { decideReinvocation, isReinvocation, regeneratesApprovedWrites, type ReinvocationFacts } from "../turn-context.js";
34
40
 
35
41
  const INPUT = { executionId: "aex_1", threadId: "", turnSeq: 0 } as const;
36
42
 
43
+ /** An execution with no persisted history (a first run) or with `n` persisted AI rows (a continuation). */
44
+ function executionWithTranscript(rows: number) {
45
+ return create(AgentExecutionSchema, {
46
+ status: {
47
+ messages: Array.from({ length: rows }, (_, i) =>
48
+ create(AgentMessageSchema, { type: MessageType.MESSAGE_AI, content: `row ${i}` }),
49
+ ),
50
+ },
51
+ });
52
+ }
53
+ const FIRST_RUN = executionWithTranscript(0);
54
+ const CONTINUATION = executionWithTranscript(1);
55
+
37
56
  describe("isReinvocation", () => {
38
57
  it("engine-minted: an empty state id is a first turn, any id is a resume", () => {
39
- expect(isReinvocation("engine-minted", INPUT)).toBe(false);
40
- expect(isReinvocation("engine-minted", { ...INPUT, threadId: "agent-42" })).toBe(true);
58
+ expect(isReinvocation("engine-minted", INPUT, FIRST_RUN)).toBe(false);
59
+ expect(isReinvocation("engine-minted", { ...INPUT, threadId: "agent-42" }, FIRST_RUN)).toBe(true);
41
60
  });
42
61
 
43
62
  it("engine-minted: turnSeq is NOT the signal (a pure-reconcile turn does not advance it; the legacy wire shape yields 0)", () => {
44
- expect(isReinvocation("engine-minted", { ...INPUT, turnSeq: 3 })).toBe(false);
45
- expect(isReinvocation("engine-minted", { ...INPUT, threadId: "agent-42", turnSeq: 0 })).toBe(true);
63
+ expect(isReinvocation("engine-minted", { ...INPUT, turnSeq: 3 }, FIRST_RUN)).toBe(false);
64
+ expect(isReinvocation("engine-minted", { ...INPUT, threadId: "agent-42", turnSeq: 0 }, FIRST_RUN)).toBe(true);
46
65
  });
47
66
 
48
- it("deterministic: refuses rather than guess until the native adapter lands (S3)", () => {
49
- expect(() => isReinvocation("deterministic", { ...INPUT, threadId: "thread-1" })).toThrow(
50
- /deterministic arm lands with the native adapter \(S3\)/,
51
- );
67
+ it("engine-minted: the transcript is NOT the signal (the bound id is; a follow-up turn with a bound id and an empty transcript still resumes the engine)", () => {
68
+ expect(isReinvocation("engine-minted", { ...INPUT, threadId: "agent-42" }, FIRST_RUN)).toBe(true);
69
+ expect(isReinvocation("engine-minted", INPUT, CONTINUATION)).toBe(false);
70
+ });
71
+
72
+ it("deterministic: the persisted transcript is the signal — empty is a first run, any committed history is a continuation", () => {
73
+ expect(isReinvocation("deterministic", { ...INPUT, threadId: "thread-ses_1" }, FIRST_RUN)).toBe(false);
74
+ expect(isReinvocation("deterministic", { ...INPUT, threadId: "thread-ses_1" }, CONTINUATION)).toBe(true);
75
+ });
76
+
77
+ it("deterministic: the thread id is NOT the signal (a deterministic harness has one on its first turn too)", () => {
78
+ expect(isReinvocation("deterministic", { ...INPUT, threadId: "thread-ses_1", turnSeq: 0 }, FIRST_RUN)).toBe(false);
79
+ expect(isReinvocation("deterministic", { ...INPUT, threadId: "", turnSeq: 0 }, CONTINUATION)).toBe(true);
52
80
  });
53
81
  });
54
82
 
@@ -135,21 +163,22 @@ describe("decideReinvocation", () => {
135
163
  expect(decideReinvocation(facts())).toBeUndefined();
136
164
  });
137
165
 
138
- it("any REJECT among the adjudicated decisions fails the turn, whatever else was decided", () => {
166
+ it("a REJECT runs the agent like any other decision — the run continues without the tool (stigmer#197; S3 M1, Q-S3-2)", () => {
167
+ // Until S3 M1 this returned `{ kind: "rejected-by-user" }` and the runtime
168
+ // FAILED the execution before any engine ran, contradicting the proto, the
169
+ // conformance suite and the native harness (S2 M4 finding F1).
139
170
  const decisions = new Map([
140
171
  ["tc-1", ApprovalAction.APPROVE],
141
172
  ["tc-2", ApprovalAction.REJECT],
142
173
  ]);
143
- expect(decideReinvocation(facts({ approvalDecisions: decisions }))).toEqual({ kind: "rejected-by-user" });
174
+ expect(decideReinvocation(facts({ approvalDecisions: decisions }))).toBeUndefined();
175
+ expect(decideReinvocation(facts({ approvalDecisions: new Map([["tc-2", ApprovalAction.REJECT]]) }))).toBeUndefined();
144
176
  });
145
177
 
146
- it("approvals without a reject run the agent, even when file review was also reconciled", () => {
178
+ it("any adjudicated decision runs the agent, even when file review was also reconciled", () => {
147
179
  const decisions = new Map([["tc-1", ApprovalAction.APPROVE]]);
148
180
  expect(decideReinvocation(facts({ approvalDecisions: decisions, reconciledFileReview: true }))).toBeUndefined();
149
- expect(
150
- decideReinvocation(facts({ approvalDecisions: new Map([["tc-1", ApprovalAction.SKIP]]) })),
151
- "a SKIP is not a reject",
152
- ).toBeUndefined();
181
+ expect(decideReinvocation(facts({ approvalDecisions: new Map([["tc-1", ApprovalAction.SKIP]]) }))).toBeUndefined();
153
182
  });
154
183
 
155
184
  it("a pure file-review resume completes, carrying the reconcile's facts", () => {
@@ -176,3 +205,21 @@ describe("decideReinvocation", () => {
176
205
  });
177
206
  });
178
207
  });
208
+
209
+ describe("regeneratesApprovedWrites", () => {
210
+ it("a deny-and-retry engine re-prompts the model with grants, so the runner pins the approved bytes", () => {
211
+ expect(regeneratesApprovedWrites("deny-and-retry")).toBe(true);
212
+ });
213
+
214
+ it("a callback engine's resumed session re-attempts the call, so the runner pins the approved bytes", () => {
215
+ expect(regeneratesApprovedWrites("callback")).toBe(true);
216
+ });
217
+
218
+ it("an interrupt engine applies the exact args it paused on — a runner-side write would land twice and pre-empt the row", () => {
219
+ expect(regeneratesApprovedWrites("interrupt")).toBe(false);
220
+ });
221
+
222
+ it("a gate-less engine has nothing approved to apply", () => {
223
+ expect(regeneratesApprovedWrites("none")).toBe(false);
224
+ });
225
+ });
@@ -0,0 +1,104 @@
1
+ /**
2
+ * The runtime's one reader and one settler of a user's approval decision.
3
+ *
4
+ * A decision lives on the tool-call row the server owns
5
+ * (`ToolCall.approval_action`, written by `SubmitApproval`); the runtime
6
+ * reads it at the start of a reinvocation and settles the rows that never
7
+ * run at the end of it. Both acts are here so the two halves of one
8
+ * transition cannot drift apart:
9
+ *
10
+ * - {@link approvalDecisionsOf} reads every top-level row still
11
+ * WAITING_APPROVAL whose action is set. The WAITING filter is what keeps a
12
+ * decided row from being re-gated on the NEXT reinvocation (its row is
13
+ * COMPLETED or SKIPPED by then) and what lets the adapter still describe
14
+ * the decision to its engine on THIS one (Cursor's reinvocation prompt
15
+ * names the skipped and rejected actions in human terms).
16
+ * - {@link terminalizeNonExecutingDecisions} settles SKIP and REJECT rows to
17
+ * TOOL_CALL_SKIPPED (REJECT also carries `error: "Rejected by user"`),
18
+ * root and sub-agent transcripts alike. The runtime calls it once,
19
+ * immediately after `adapter.runTurn` returns and before any persist of
20
+ * the outcome, for EVERY outcome: a decision that never runs the tool has
21
+ * no engine event to flip its row, so the row would otherwise persist
22
+ * WAITING on a completed execution. APPROVE and APPROVE_ALL are not
23
+ * touched — the tool executes, and the adapter's own events settle it.
24
+ *
25
+ * Why after the adapter, not before: both readers above filter on WAITING, so
26
+ * stamping first would hide the decision from the reinvocation prompt and
27
+ * from the exact-apply. Why by decision and not by row status: the outcome
28
+ * of SKIP and REJECT is fully determined by the recorded decision, whatever
29
+ * transient status an engine left (a denial ToolMessage with no tool events,
30
+ * or a replay that never re-drove the gate), so the stamp is idempotent and
31
+ * checkpointer-independent by construction. REJECT and SKIP share SKIPPED as
32
+ * the terminal status (the tool did not run) and stay distinguishable by
33
+ * `approval_action` and by the server's append-only approval-event stream.
34
+ *
35
+ * This is the contract as of stigmer#197: REJECT denies the tool and the run
36
+ * CONTINUES; it never fails the execution. The native harness proved it
37
+ * (`hitl.ts` `reconcileNonExecutingDecisions`, moved here at S3 M1) and the
38
+ * conformance suite (`agentexecution-approval.conformance.test.ts`) is its
39
+ * arbiter for both harnesses.
40
+ */
41
+
42
+ import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
43
+ import type { AgentMessage } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
44
+ import { ApprovalAction, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
45
+
46
+ /** The row's error for a REJECT; byte-pinned by the native goldens (`reject.turn2.status.json`) and the conformance suite. */
47
+ export const REJECTED_BY_USER_ERROR = "Rejected by user";
48
+
49
+ /**
50
+ * Every top-level tool-call row still WAITING_APPROVAL whose `approvalAction`
51
+ * the server has set, keyed by tool-call id, in transcript order. Derived
52
+ * from the status on every invocation, never stored, so it cannot drift from
53
+ * the rows. The same rows, in the same order, that the Cursor adapter's
54
+ * `reconstructAdjudicatedApprovals` reads for its own facts (the
55
+ * pending-approval protos and content digests); a test pins the two
56
+ * readers' agreement.
57
+ */
58
+ export function approvalDecisionsOf(status: AgentExecutionStatus): ReadonlyMap<string, ApprovalAction> {
59
+ const decisions = new Map<string, ApprovalAction>();
60
+ for (const message of status.messages) {
61
+ for (const row of message.toolCalls) {
62
+ if (row.status !== ToolCallStatus.TOOL_CALL_WAITING_APPROVAL) continue;
63
+ if (row.approvalAction === ApprovalAction.UNSPECIFIED) continue;
64
+ decisions.set(row.id, row.approvalAction);
65
+ }
66
+ }
67
+ return decisions;
68
+ }
69
+
70
+ /**
71
+ * Settle every SKIP and REJECT row to TOOL_CALL_SKIPPED, root and sub-agent
72
+ * transcripts, in place. Idempotent: a row already settled carries the same
73
+ * decision and re-settles identically. A REJECT row's `error` is set only
74
+ * when empty, so an engine's own denial text, if any, stands.
75
+ */
76
+ export function terminalizeNonExecutingDecisions(status: AgentExecutionStatus): void {
77
+ const settle = (messages: readonly AgentMessage[]): void => {
78
+ for (const message of messages) {
79
+ for (const row of message.toolCalls) {
80
+ switch (row.approvalAction) {
81
+ case ApprovalAction.SKIP:
82
+ row.status = ToolCallStatus.TOOL_CALL_SKIPPED;
83
+ break;
84
+ case ApprovalAction.REJECT:
85
+ row.status = ToolCallStatus.TOOL_CALL_SKIPPED;
86
+ if (!row.error) row.error = REJECTED_BY_USER_ERROR;
87
+ break;
88
+ case ApprovalAction.UNSPECIFIED:
89
+ case ApprovalAction.APPROVE:
90
+ case ApprovalAction.APPROVE_ALL:
91
+ break;
92
+ default: {
93
+ const exhaustive: never = row.approvalAction;
94
+ throw new Error(`terminalizeNonExecutingDecisions: unknown approval action ${String(exhaustive)}`);
95
+ }
96
+ }
97
+ }
98
+ }
99
+ };
100
+ settle(status.messages);
101
+ for (const subAgent of status.subAgentExecutions) {
102
+ settle(subAgent.messages);
103
+ }
104
+ }
@@ -9,6 +9,15 @@
9
9
  * Every row of the matrix below is either a flag here or internal to one
10
10
  * adapter; nothing in it needed a third kind of thing.
11
11
  *
12
+ * One flag is declared for the matrix and read by no runtime phase today:
13
+ * `systemPrompt`. Its readers are the adapters' own prompt builders, by
14
+ * placement (the native builder renders a system prompt rebuilt every turn;
15
+ * the Cursor builder renders the session's first user message), and the
16
+ * shared prompt glue (`shared/prompt-sections.ts`) is written so that
17
+ * placement stays each adapter's. The runtime would branch on it only if it
18
+ * ever composed prompts itself — the S4/S5 unification question, not a phase
19
+ * that exists (S3 M5, Q-M5-6).
20
+ *
12
21
  * The matrix the contract was designed against (2026-09; Claude and Codex are
13
22
  * the surveyed SDKs, not built harnesses):
14
23
  *
@@ -63,13 +72,15 @@ export type PausePrimitive = "interrupt" | "deny-and-retry" | "callback" | "none
63
72
  export type StateIdSource = "deterministic" | "engine-minted";
64
73
 
65
74
  /**
66
- * The two facts the runtime's file-review reconcile needs from the harness
67
- * and cannot know: the id the projection reads from the BASELINE payload
68
- * (`shared/filereview/capture.ts` `applyCaptureDecisions`), and the
75
+ * The two facts the runtime's file-review capture and reconcile need from
76
+ * the harness and cannot know: the id the projection reads from the
77
+ * BASELINE payload (`shared/filereview/capture.ts`), and the
69
78
  * workspace-relative paths the harness writes into the repo that a turn's
70
79
  * diff must never show (Cursor's transient `.cursor/hooks.json`). Declared
71
- * once per adapter, read by `turn-context.ts` (ruled Q-M2-6 as an argument,
72
- * homed here at M3).
80
+ * once per adapter, read by `harness/capture.ts` (the pin, the progress
81
+ * slice, the candidate) and `turn-context.ts` (the reconcile). Ruled Q-M2-6
82
+ * as an argument, homed here at S2 M3; the capture joined the reconcile as a
83
+ * reader at S3 M4.
73
84
  */
74
85
  export interface FileReviewIdentity {
75
86
  readonly harnessId: string;
@@ -0,0 +1,301 @@
1
+ /**
2
+ * The runtime's file-review capture — apply-then-review for every harness,
3
+ * written once: the baseline pinned before the engine runs, the mid-run
4
+ * progress the chokepoint refreshes on every write, the candidate captured
5
+ * after the engine's turn, the DD-28 approved-command provenance, the flowed
6
+ * transcript rows stamped with the change set, and the one question the
7
+ * outcome table asks afterwards: is a review pending?
8
+ *
9
+ * ONE CAPTURE, ONE MOMENT, ONE OWNER
10
+ * ----------------------------------
11
+ * A turn's reviewable change is a property of the TREE, not of the engine:
12
+ * whatever sits on the working tree when the engine's turn is over is what
13
+ * the user reviews, whoever wrote it — the engine, a sub-agent, a recovery
14
+ * retry, a stdio MCP server the engine spawned. So the runtime pins the
15
+ * baseline as its LAST act before `adapter.runTurn` (after its own writes
16
+ * into the tree — the reconcile, the skill and attachment mounts — so they
17
+ * cancel out of the diff) and captures ONCE after `runTurn` returns, over
18
+ * the whole turn. Anything written into the tree after the pin is the
19
+ * turn's change. An adapter needs neither the baseline nor the change-set
20
+ * id and never learns whether a review is pending; the one fact it holds
21
+ * that the runtime cannot read is what its engine observed touching
22
+ * CAS-owned paths (`TurnSink.bindCasObservations`).
23
+ *
24
+ * Until S3 M4 both adapters carried this whole act (Cursor in its
25
+ * `turn-boundary.ts` between the stream and `run.wait()`, the deep-agent in
26
+ * its `turn-settle.ts`), each with its own evidence source and its own
27
+ * scoping rules for the provenance and the stamp. The two rules here are the
28
+ * one reading both engines already produce:
29
+ *
30
+ * - PROVENANCE (DD-28, `shared/filereview/command-provenance.ts` is the
31
+ * rule; this is its two inputs). "This turn's commands" are the top-level
32
+ * rows whose id had not SETTLED before the engine ran — never a message
33
+ * position, because on BOTH engines an approved command executes on the
34
+ * row the runtime seeded for it (the deep-agent's builder and Cursor's
35
+ * translator both reconcile the re-attempt onto the seeded WAITING row),
36
+ * at its prior position; a positional scope misses it (F-M4-P3: Cursor's
37
+ * did, so its auto-keep never qualified a gated-then-approved command).
38
+ * "Executed" is a COMPLETED row. "Consented" is the row's own
39
+ * server-authored `approval_action` — the seeded row keeps its id and its
40
+ * verdict when it executes on resume, so the executed command IS its
41
+ * consent row. Any sub-agent activity fails closed (DD-28 D1).
42
+ * - THE STAMP (`shared/tool-row.ts` `stampFlowedFileEditRows`). A write or
43
+ * delete row created this turn that reached COMPLETED flowed onto the
44
+ * tree and is badged with the change set; a WAITING (denied, gated) or
45
+ * FAILED row did not and is not. The row's own status is the evidence,
46
+ * not a ledger of denial tokens.
47
+ *
48
+ * WHAT IS NOT HERE
49
+ * ----------------
50
+ * The substrate and the ledger authoring (`shared/filereview/capture.ts`,
51
+ * `cas-touched.ts`, `progress.ts`); the resume-time reconcile
52
+ * (`turn-context.ts` `reconcileReinvocation`, already the runtime's since
53
+ * S2); the outcome matrix that turns "a review is pending" into a phase
54
+ * (`run-turn.ts`, through `terminal-table.ts` `awaitingReviewArm`).
55
+ *
56
+ * TWO GAPS THIS MODULE DOES NOT CLOSE (recorded for S5, S3 M4 F-M4-P-gaps)
57
+ * ----------------------------------------------------------------------
58
+ * A turn that ends `interrupted` is not captured (a stop fired; the tree may
59
+ * be mid-edit). So a platform STOP returns COMPLETED with unreviewed edits
60
+ * on the tree, and a pause or worker shutdown reinvokes the same turn, whose
61
+ * new pin absorbs the pre-pause edits into its baseline and authors a second
62
+ * BASELINE for the same change set. Both predate the lift on both harnesses.
63
+ */
64
+
65
+ import { ApprovalAction, FileCaptureClass, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
66
+ import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
67
+ import type { ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
68
+ import type { TurnCommandProvenance } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
69
+
70
+ import type { ArtifactStorage } from "../shared/artifact-storage.js";
71
+ import { captureBaselineToLedger, captureCandidateToLedger } from "../shared/filereview/capture.js";
72
+ import { createCasProgressSubstrate } from "../shared/filereview/cas-progress.js";
73
+ import { buildCasTurnCaptures, type CasTouchedReader, type CasTouchedSnapshot } from "../shared/filereview/cas-touched.js";
74
+ import { qualifyTurnCommandProvenance } from "../shared/filereview/command-provenance.js";
75
+ import { hasCandidateCaptured } from "../shared/filereview/events.js";
76
+ import {
77
+ createGitProgressSubstrate,
78
+ createHybridProgressSubstrate,
79
+ newProgressCaptureState,
80
+ type ProgressCaptureState,
81
+ type ProgressSubstrate,
82
+ } from "../shared/filereview/progress.js";
83
+ import { collectSettledToolCallIds, collectSubAgentToolCallIds, stampFlowedFileEditRows, stampFlowedSubAgentFileEditRows } from "../shared/tool-row.js";
84
+ import type { FileReviewIdentity } from "./capabilities.js";
85
+ import type { TurnWorkspace } from "./types.js";
86
+
87
+ /** The mid-run progress the chokepoint refreshes on every write: the substrate and the floor it throttles by. */
88
+ export interface TurnProgress {
89
+ readonly changeSetId: string;
90
+ readonly substrate: ProgressSubstrate;
91
+ readonly state: ProgressCaptureState;
92
+ }
93
+
94
+ /**
95
+ * One turn's capture as the runtime holds it between the pin and the
96
+ * boundary — a `runTurn` closure value beside the usage accumulator and the
97
+ * watchdog, on neither `TurnInput` (the adapter needs none of it) nor the
98
+ * frame (nothing here is a resource to release). Absent outside capture
99
+ * mode.
100
+ */
101
+ export interface TurnCapture {
102
+ readonly changeSetId: string;
103
+ /** The pre-turn tree on a git work tree; `""` on a non-git one (the CAS manifest is the baseline there). */
104
+ readonly baselineTree: string;
105
+ readonly progress: TurnProgress;
106
+ /** Top-level rows already settled before the engine ran: the complement is "this turn's" (the provenance scope). */
107
+ readonly priorSettledToolCallIds: ReadonlySet<string>;
108
+ /** Sub-agent rows present before the engine ran: the stamp's scope, and any growth fails provenance closed. */
109
+ readonly priorSubAgentToolCallIds: ReadonlySet<string>;
110
+ /**
111
+ * What the engine observed touching CAS-owned paths, once the adapter has
112
+ * bound it (`TurnSink.bindCasObservations`); read at each progress capture
113
+ * and at the boundary. Unbound reads as nothing observed: git-only capture.
114
+ */
115
+ casObservations: CasTouchedReader | undefined;
116
+ }
117
+
118
+ const NOTHING_OBSERVED: CasTouchedSnapshot = { before: new Map(), blockedSecretPaths: new Set() };
119
+
120
+ /**
121
+ * Turn start, in capture mode: snapshot the pre-turn ids, pin the pre-turn
122
+ * tree behind the baseline ref and author BASELINE_CAPTURED under the
123
+ * harness's identity (the event rides the next persist), and build the
124
+ * progress substrate for this workspace shape — the git slice over the
125
+ * baseline and the harness's excluded paths, the CAS slice over whatever
126
+ * observations the adapter later binds (a delegating reader, since the bind
127
+ * happens inside `runTurn`), hybrid when both apply. The CAS slice exists
128
+ * only when artifact storage does: without it a gitignored write is
129
+ * deny-gated by the harness (`captureIgnored`), never captured, and the
130
+ * ledger could not store its blobs anyway.
131
+ *
132
+ * Returns `undefined` outside capture mode: nothing to pin, nothing to
133
+ * capture, the classic deny-gate governs writes.
134
+ */
135
+ export async function pinCaptureBaseline(args: {
136
+ readonly status: AgentExecutionStatus;
137
+ readonly executionId: string;
138
+ readonly workspace: TurnWorkspace;
139
+ readonly fileReview: FileReviewIdentity;
140
+ readonly artifactStorage: ArtifactStorage | undefined;
141
+ }): Promise<TurnCapture | undefined> {
142
+ const { status, executionId, workspace, fileReview, artifactStorage } = args;
143
+ if (!workspace.captureMode) return undefined;
144
+ const { primaryDir, gitWorkspace, changeSetId } = workspace;
145
+
146
+ const priorSettledToolCallIds = collectSettledToolCallIds(status.messages);
147
+ const priorSubAgentToolCallIds = collectSubAgentToolCallIds(status.subAgentExecutions);
148
+
149
+ const baselineTree = await captureBaselineToLedger({
150
+ status,
151
+ gitRoot: primaryDir,
152
+ executionId,
153
+ changeSetId,
154
+ harnessId: fileReview.harnessId,
155
+ excludePaths: fileReview.excludePaths,
156
+ gitWorkspace,
157
+ });
158
+
159
+ const capture: TurnCapture = {
160
+ changeSetId,
161
+ baselineTree,
162
+ progress: {
163
+ changeSetId,
164
+ state: newProgressCaptureState(),
165
+ substrate: buildProgressSubstrate({
166
+ executionId,
167
+ workspace,
168
+ baselineTree,
169
+ excludePaths: fileReview.excludePaths,
170
+ casSlice: artifactStorage !== undefined,
171
+ // Delegates to whatever the adapter binds later; `capture` is
172
+ // assigned before any capture() can run.
173
+ readObservations: () => capture.casObservations?.() ?? NOTHING_OBSERVED,
174
+ }),
175
+ },
176
+ priorSettledToolCallIds,
177
+ priorSubAgentToolCallIds,
178
+ casObservations: undefined,
179
+ };
180
+ return capture;
181
+ }
182
+
183
+ /**
184
+ * The progress substrate for this workspace shape (DD-32 / DD-33): a git
185
+ * tree diffs its tracked paths by `--numstat` against the baseline; the CAS
186
+ * slice reads the engine's observations for the gitignored (git tree) or
187
+ * every (non-git) path; a git tree with storage composes both. The truth
188
+ * table is `deriveCaptureMode`'s: capture mode holds only for a git tree or
189
+ * a workspace with storage, so the fourth cell is unreachable and says so.
190
+ */
191
+ function buildProgressSubstrate(args: {
192
+ readonly executionId: string;
193
+ readonly workspace: TurnWorkspace;
194
+ readonly baselineTree: string;
195
+ readonly excludePaths: readonly string[];
196
+ readonly casSlice: boolean;
197
+ readonly readObservations: CasTouchedReader;
198
+ }): ProgressSubstrate {
199
+ const { executionId, workspace, baselineTree, excludePaths, casSlice, readObservations } = args;
200
+ const git = workspace.gitWorkspace
201
+ ? createGitProgressSubstrate({ workspaceRoot: workspace.primaryDir, executionId, baselineTree, excludePaths })
202
+ : undefined;
203
+ const cas = casSlice ? createCasProgressSubstrate({ workspaceRoot: workspace.primaryDir, read: readObservations }) : undefined;
204
+ if (git && cas) return createHybridProgressSubstrate(git, cas);
205
+ const one = git ?? cas;
206
+ if (!one) throw new Error(`capture: capture mode on a non-git tree without storage (execution=${executionId}); deriveCaptureMode forbids this`);
207
+ return one;
208
+ }
209
+
210
+ /**
211
+ * Turn end: compose the CAS captures from the bound observations, derive the
212
+ * approved-command provenance, author CANDIDATE_CAPTURED (the seam authors
213
+ * nothing for a turn that changed nothing), and — iff a candidate exists —
214
+ * stamp the flowed rows. The working tree is LEFT applied: the user reviews
215
+ * the real change, and a reject snaps files back on the resume
216
+ * (`reconcileReinvocation`). Returns whether a review is now pending.
217
+ */
218
+ export async function captureCandidate(args: {
219
+ readonly status: AgentExecutionStatus;
220
+ readonly executionId: string;
221
+ readonly workspace: TurnWorkspace;
222
+ readonly fileReview: FileReviewIdentity;
223
+ readonly artifactStorage: ArtifactStorage | undefined;
224
+ readonly capture: TurnCapture;
225
+ /** `spec.auto_approve_all`, the one whole-run bypass: qualifies every executed command as consented (DD-28). */
226
+ readonly globalBypass: boolean;
227
+ }): Promise<boolean> {
228
+ const { status, executionId, workspace, fileReview, artifactStorage, capture, globalBypass } = args;
229
+ const { primaryDir, gitWorkspace } = workspace;
230
+ const { changeSetId } = capture;
231
+
232
+ const casCaptureClass = gitWorkspace ? FileCaptureClass.GIT_IGNORED_CAPTURED : FileCaptureClass.NON_GIT_CAS;
233
+ const observed = artifactStorage && capture.casObservations ? await capture.casObservations() : NOTHING_OBSERVED;
234
+ const { casCaptures, unreviewablePaths } = await buildCasTurnCaptures(observed, primaryDir, casCaptureClass);
235
+
236
+ const commandProvenance = deriveCommandProvenance(status, capture, globalBypass);
237
+ if (commandProvenance) {
238
+ console.log(
239
+ `capture: turn qualifies for approved-command auto-keep ` +
240
+ `(consent rows: ${commandProvenance.consentToolCallIds.join(",") || "(auto_approve_all)"}); ` +
241
+ `attaching provenance to candidate (execution=${executionId})`,
242
+ );
243
+ }
244
+
245
+ await captureCandidateToLedger({
246
+ status,
247
+ gitRoot: primaryDir,
248
+ executionId,
249
+ changeSetId,
250
+ baselineTree: capture.baselineTree,
251
+ harnessId: fileReview.harnessId,
252
+ excludePaths: fileReview.excludePaths,
253
+ casCaptures,
254
+ storage: artifactStorage,
255
+ unreviewablePaths,
256
+ unreviewableCaptureClass: casCaptureClass,
257
+ gitWorkspace,
258
+ commandProvenance,
259
+ });
260
+
261
+ const pending = hasCandidateCaptured(status, changeSetId);
262
+ if (pending) {
263
+ // A row must never reference a change set that does not exist, so the
264
+ // stamp is gated on the authored candidate.
265
+ stampFlowedFileEditRows(status.messages, changeSetId, { flowed: isCompletedRow });
266
+ stampFlowedSubAgentFileEditRows(status.subAgentExecutions, changeSetId, capture.priorSubAgentToolCallIds, isCompletedRow);
267
+ console.log(`capture: change set ${changeSetId} authored to the file_review ledger; working tree left applied for review (execution=${executionId})`);
268
+ }
269
+ return pending;
270
+ }
271
+
272
+ /** The row's own evidence that its effect landed on the tree. */
273
+ function isCompletedRow(tc: ToolCall): boolean {
274
+ return tc.status === ToolCallStatus.TOOL_CALL_COMPLETED;
275
+ }
276
+
277
+ /**
278
+ * The runtime's two inputs to the shared DD-28 rule (see the header). Pure
279
+ * over the status and the pre-turn snapshots; exported for its own tests.
280
+ */
281
+ export function deriveCommandProvenance(
282
+ status: AgentExecutionStatus,
283
+ capture: Pick<TurnCapture, "priorSettledToolCallIds" | "priorSubAgentToolCallIds">,
284
+ globalBypass: boolean,
285
+ ): TurnCommandProvenance | undefined {
286
+ // DD-28 D1: any sub-agent activity this turn disqualifies — a sub-agent
287
+ // that ran contributes at least one row id absent from the pre-turn set,
288
+ // and its writes fold into this change set without a consented command.
289
+ for (const id of collectSubAgentToolCallIds(status.subAgentExecutions)) {
290
+ if (!capture.priorSubAgentToolCallIds.has(id)) return undefined;
291
+ }
292
+ const turnToolCalls = status.messages.flatMap((m) => m.toolCalls).filter((tc) => !capture.priorSettledToolCallIds.has(tc.id));
293
+ return qualifyTurnCommandProvenance({
294
+ turnToolCalls,
295
+ messages: status.messages,
296
+ isExecutedCommand: isCompletedRow,
297
+ resolveDirectConsent: (tc) =>
298
+ tc.approvalAction === ApprovalAction.APPROVE || tc.approvalAction === ApprovalAction.APPROVE_ALL ? tc.id : undefined,
299
+ globalBypass,
300
+ });
301
+ }
@@ -10,13 +10,21 @@
10
10
  * any built-in write row targeting a secret-like path, top-level and
11
11
  * sub-agent. The single airtight choke point for a deny-gate harness and
12
12
  * the only guarantee under `auto_approve_all`. Idempotent.
13
- * 2. `streaming_usage` is refreshed from the accumulator when it has turns,
13
+ * 2. `file_change_progress` is refreshed from the turn's capture (the "N
14
+ * files changed so far" strip, DD-32) when the turn is in capture mode:
15
+ * the tree is read at most once per floor interval and only re-attached
16
+ * when it moved (`shared/filereview/progress.ts`), so a write that
17
+ * changed nothing on disk carries the snapshot it already had. Before
18
+ * S3 M4 each adapter's stream loop did this before its own persists; the
19
+ * runtime owns the capture now, so the display field it derives is
20
+ * refreshed where every other runtime-owned field is — here.
21
+ * 3. `streaming_usage` is refreshed from the accumulator when it has turns,
14
22
  * so the summary the UI shows is the one the write carries.
15
- * 3. `persistStatus` (`shared/status.ts`): tool-output offload, the
23
+ * 4. `persistStatus` (`shared/status.ts`): tool-output offload, the
16
24
  * aggregate size cap, transient retry, oversize recovery. Never rejects.
17
- * 4. A Temporal heartbeat, so a long streaming turn's liveness is proven by
25
+ * 5. A Temporal heartbeat, so a long streaming turn's liveness is proven by
18
26
  * the writes it makes (Q-S2-10).
19
- * 5. The control plane's answer: STOP becomes the runtime's platform-stop
27
+ * 6. The control plane's answer: STOP becomes the runtime's platform-stop
20
28
  * cause on the stop controller, which aborts the adapter's signal.
21
29
  *
22
30
  * Single-flight with coalescing: an adapter's `requestPersist()` while a
@@ -39,9 +47,11 @@ import { ExecutionControlSignal } from "@stigmer/protos/ai/stigmer/agentic/agent
39
47
  import { StreamingUsageSummarySchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/usage_pb";
40
48
 
41
49
  import type { StigmerClient } from "../client/stigmer-client.js";
50
+ import { captureFileChangeProgress } from "../shared/filereview/progress.js";
42
51
  import { persistStatus } from "../shared/status.js";
43
52
  import type { ToolOutputOffloadContext } from "../shared/status-offload.js";
44
53
  import { withholdSecretContentFromMessages } from "../shared/tool-row.js";
54
+ import type { TurnProgress } from "./capture.js";
45
55
  import type { UsageAccumulator } from "./usage-accumulator.js";
46
56
 
47
57
  export interface PersistChokepointDeps {
@@ -56,6 +66,12 @@ export interface PersistChokepointDeps {
56
66
  * and is `undefined` for a write made before then (no usage yet anyway).
57
67
  */
58
68
  readonly usage: () => UsageAccumulator | undefined;
69
+ /**
70
+ * The turn's mid-run progress, read at WRITE time for the same reason as
71
+ * `usage`: it exists only once the runtime has pinned the capture baseline,
72
+ * after resolution, and is `undefined` outside capture mode.
73
+ */
74
+ readonly progress: () => TurnProgress | undefined;
59
75
  /** The Temporal heartbeat, pulsed after each write. */
60
76
  readonly heartbeat: () => void;
61
77
  /** The control plane answered STOP to this write. */
@@ -99,6 +115,10 @@ export class PersistChokepoint {
99
115
  async write(): Promise<void> {
100
116
  const { client, executionId, status, offload, heartbeat } = this.deps;
101
117
  withholdSecretContentFromMessages(status.messages, status.subAgentExecutions);
118
+ const progress = this.deps.progress();
119
+ if (progress) {
120
+ await captureFileChangeProgress({ status, changeSetId: progress.changeSetId, substrate: progress.substrate, state: progress.state });
121
+ }
102
122
  const usage = this.deps.usage();
103
123
  if (usage?.hasTurns) {
104
124
  status.streamingUsage = create(StreamingUsageSummarySchema, usage.snapshot());