@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,281 @@
1
+ /**
2
+ * ZIP validation and decompression for attachment archives — the byte-level
3
+ * half of an `extract` attachment, shared by every writer of one.
4
+ *
5
+ * Security model: attachments are untrusted user uploads. Archives are parsed
6
+ * from the central directory — the format's authoritative index — via the
7
+ * shared structural layer (@stigmer/zip-structure; issue #567, which killed
8
+ * the earlier local-header walk: it silently truncated stored streaming
9
+ * entries and rejected Go-default archives outright). Every entry is
10
+ * validated for path traversal, zip bombs, and format integrity BEFORE any
11
+ * extraction occurs, and because the central directory's sizes are
12
+ * declarations an attacker controls, decompression re-enforces them: an
13
+ * entry whose actual output disagrees with its declared size aborts the
14
+ * extraction.
15
+ *
16
+ * Error model: fail-hard through {@link AttachmentValidationError}. An
17
+ * archive that fails any check aborts the whole attachment resolution and
18
+ * propagates a descriptive, user-fixable message. This is deliberately the
19
+ * OPPOSITE policy from the skill-artifact reader (`zip-extract.ts`,
20
+ * non-fatal empty return): skill artifacts were structurally vouched for by
21
+ * the push gates, attachments never are.
22
+ *
23
+ * Validation and decompression share one record ({@link ValidatedZipEntry},
24
+ * still carrying its payload slice), so parsing happens exactly once and
25
+ * "the manifest promised a file extraction cannot find" is unrepresentable.
26
+ * The WRITE of an extracted entry is the caller's: the attachment resolver
27
+ * writes under the session's platform dir (`attachment-resolver.ts`), the
28
+ * one caller since S3 M2b retired the native injector. Moved here from that
29
+ * injector at S3 M1 (Q-S3-14) so the guards have one home.
30
+ */
31
+
32
+ import { createInflateRaw } from "node:zlib";
33
+ import { posix } from "node:path";
34
+ import {
35
+ EOCD_MIN_SIZE,
36
+ parseZipStructure,
37
+ type ZipStructuralEntry,
38
+ } from "@stigmer/zip-structure";
39
+
40
+ // ── Constants ────────────────────────────────────────────────────────
41
+
42
+ export const MAX_ZIP_FILES = 1000;
43
+ export const MAX_ZIP_EXTRACTED_SIZE = 100 * 1024 * 1024; // 100 MB
44
+
45
+ // ── Types ────────────────────────────────────────────────────────────
46
+
47
+ export interface ZipEntryInfo {
48
+ readonly relativePath: string;
49
+ readonly uncompressedSize: number;
50
+ }
51
+
52
+ /**
53
+ * A validated file entry, still carrying its payload slice. Validation and
54
+ * extraction share these records — parsing happens exactly once, so "the
55
+ * manifest promised a file extraction cannot find" is unrepresentable
56
+ * (the old two-walker design silently skipped such entries).
57
+ */
58
+ export interface ValidatedZipEntry extends ZipEntryInfo {
59
+ readonly compressionMethod: number;
60
+ readonly compressedData: Uint8Array;
61
+ }
62
+
63
+ // ── Errors ───────────────────────────────────────────────────────────
64
+
65
+ /** An archive the user handed us cannot be safely extracted; the reason is theirs to fix. */
66
+ export class AttachmentValidationError extends Error {
67
+ readonly attachmentFilename: string;
68
+ readonly reason: string;
69
+
70
+ constructor(attachmentFilename: string, reason: string) {
71
+ super(`Attachment '${attachmentFilename}': ${reason}`);
72
+ this.name = "AttachmentValidationError";
73
+ this.attachmentFilename = attachmentFilename;
74
+ this.reason = reason;
75
+ }
76
+ }
77
+
78
+ // ── ZIP Validation (pure functions) ──────────────────────────────────
79
+
80
+ /**
81
+ * Validate a ZIP archive for safe extraction. Returns the manifest of
82
+ * file entries (directories excluded) if the archive passes all checks.
83
+ *
84
+ * Security checks enforced:
85
+ * 1. Valid ZIP format (readable central directory; fail-hard, see module doc)
86
+ * 2. No absolute paths (entries starting with / or \)
87
+ * 3. No path traversal (.. components)
88
+ * 4. No null bytes in filenames
89
+ * 5. No duplicate entry paths (a contradictory manifest)
90
+ * 6. Only supported compression methods (stored, deflate) — checked here
91
+ * so an unsupported entry can never abort extraction after earlier
92
+ * entries were already written
93
+ * 7. Non-empty archive (at least one file entry)
94
+ * 8. File count within limits (max 1000)
95
+ * 9. Total declared uncompressed size within limits (max 100 MB) —
96
+ * declarations are re-enforced against actual output at decompression
97
+ */
98
+ export function validateZipForExtraction(
99
+ zipData: Buffer,
100
+ sourceFilename: string,
101
+ ): ZipEntryInfo[] {
102
+ return parseAndValidateZip(zipData, sourceFilename).map(
103
+ ({ relativePath, uncompressedSize }) => ({ relativePath, uncompressedSize }),
104
+ );
105
+ }
106
+
107
+ /** The same checks as {@link validateZipForExtraction}, keeping each entry's payload for the extraction that follows. */
108
+ export function parseAndValidateZip(
109
+ zipData: Buffer,
110
+ sourceFilename: string,
111
+ ): ValidatedZipEntry[] {
112
+ if (zipData.length < EOCD_MIN_SIZE) {
113
+ throw new AttachmentValidationError(
114
+ sourceFilename,
115
+ "not a valid ZIP archive (file too small)",
116
+ );
117
+ }
118
+
119
+ let structural: ZipStructuralEntry[];
120
+ try {
121
+ structural = parseZipStructure(zipData);
122
+ } catch (err) {
123
+ throw new AttachmentValidationError(
124
+ sourceFilename,
125
+ `not a valid ZIP archive (${err instanceof Error ? err.message : String(err)})`,
126
+ );
127
+ }
128
+
129
+ const entries: ValidatedZipEntry[] = [];
130
+ const seenPaths = new Set<string>();
131
+ let totalUncompressed = 0;
132
+
133
+ for (const entry of structural) {
134
+ if (entry.name.includes("\u0000")) {
135
+ throw new AttachmentValidationError(
136
+ sourceFilename,
137
+ "contains a filename with null bytes and cannot be safely extracted",
138
+ );
139
+ }
140
+
141
+ if (entry.isDirectory) continue;
142
+
143
+ if (entry.name.startsWith("/") || entry.name.startsWith("\\")) {
144
+ throw new AttachmentValidationError(
145
+ sourceFilename,
146
+ `contains an absolute path entry and cannot be safely extracted: ${entry.name}`,
147
+ );
148
+ }
149
+
150
+ if (hasPathTraversal(entry.name)) {
151
+ throw new AttachmentValidationError(
152
+ sourceFilename,
153
+ `contains a path traversal entry and cannot be safely extracted: ${entry.name}`,
154
+ );
155
+ }
156
+
157
+ if (seenPaths.has(entry.name)) {
158
+ throw new AttachmentValidationError(
159
+ sourceFilename,
160
+ `contains duplicate entry '${entry.name}' and cannot be safely extracted`,
161
+ );
162
+ }
163
+ seenPaths.add(entry.name);
164
+
165
+ if (entry.compressionMethod !== 0 && entry.compressionMethod !== 8) {
166
+ throw new AttachmentValidationError(
167
+ sourceFilename,
168
+ `entry '${entry.name}' uses unsupported compression method ${entry.compressionMethod}`,
169
+ );
170
+ }
171
+
172
+ entries.push({
173
+ relativePath: entry.name,
174
+ uncompressedSize: entry.uncompressedSize,
175
+ compressionMethod: entry.compressionMethod,
176
+ compressedData: entry.compressedData,
177
+ });
178
+ totalUncompressed += entry.uncompressedSize;
179
+ }
180
+
181
+ if (entries.length === 0) {
182
+ throw new AttachmentValidationError(
183
+ sourceFilename,
184
+ "is an empty ZIP archive (no file entries)",
185
+ );
186
+ }
187
+
188
+ if (entries.length > MAX_ZIP_FILES) {
189
+ throw new AttachmentValidationError(
190
+ sourceFilename,
191
+ `contains ${entries.length} files (limit: ${MAX_ZIP_FILES})`,
192
+ );
193
+ }
194
+
195
+ if (totalUncompressed > MAX_ZIP_EXTRACTED_SIZE) {
196
+ const sizeMb = (totalUncompressed / (1024 * 1024)).toFixed(1);
197
+ const limitMb = (MAX_ZIP_EXTRACTED_SIZE / (1024 * 1024)).toFixed(0);
198
+ throw new AttachmentValidationError(
199
+ sourceFilename,
200
+ `would extract to ${sizeMb} MB (limit: ${limitMb} MB)`,
201
+ );
202
+ }
203
+
204
+ return entries.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
205
+ }
206
+
207
+ // ── Decompression ────────────────────────────────────────────────────
208
+
209
+ /**
210
+ * Decompress a validated entry, enforcing its declared uncompressed size.
211
+ *
212
+ * The size cap in parseAndValidateZip budgets *declared* sizes, which the
213
+ * archive author controls — a crafted archive can declare 1 KB and inflate
214
+ * to gigabytes. Enforcement is therefore two-sided and fail-hard: inflation
215
+ * aborts the moment output exceeds the declaration, and an undershoot (or a
216
+ * stored payload whose length disagrees) fails too, because a manifest that
217
+ * misdescribes its own contents is exactly the corrupt-input class this
218
+ * module must never extract from.
219
+ */
220
+ export async function decompressEntry(
221
+ entry: ValidatedZipEntry,
222
+ sourceFilename: string,
223
+ ): Promise<Buffer> {
224
+ const declared = entry.uncompressedSize;
225
+
226
+ if (entry.compressionMethod === 0) {
227
+ if (entry.compressedData.length !== declared) {
228
+ throw new AttachmentValidationError(
229
+ sourceFilename,
230
+ `entry '${entry.relativePath}' payload is ${entry.compressedData.length} bytes ` +
231
+ `but the archive declares ${declared} — corrupt or crafted archive`,
232
+ );
233
+ }
234
+ return Buffer.from(entry.compressedData);
235
+ }
236
+
237
+ // Deflate — the only other method parseAndValidateZip admits.
238
+ return new Promise<Buffer>((resolve, reject) => {
239
+ const inflate = createInflateRaw();
240
+ const chunks: Buffer[] = [];
241
+ let produced = 0;
242
+ inflate.on("data", (chunk: Buffer) => {
243
+ produced += chunk.length;
244
+ if (produced > declared) {
245
+ inflate.destroy();
246
+ reject(new AttachmentValidationError(
247
+ sourceFilename,
248
+ `entry '${entry.relativePath}' decompresses past its declared size of ` +
249
+ `${declared} bytes — corrupt or crafted archive`,
250
+ ));
251
+ return;
252
+ }
253
+ chunks.push(chunk);
254
+ });
255
+ inflate.on("end", () => {
256
+ if (produced !== declared) {
257
+ reject(new AttachmentValidationError(
258
+ sourceFilename,
259
+ `entry '${entry.relativePath}' decompressed to ${produced} bytes ` +
260
+ `but the archive declares ${declared} — corrupt or crafted archive`,
261
+ ));
262
+ return;
263
+ }
264
+ resolve(Buffer.concat(chunks));
265
+ });
266
+ inflate.on("error", reject);
267
+ inflate.end(Buffer.from(entry.compressedData));
268
+ });
269
+ }
270
+
271
+ function hasPathTraversal(filePath: string): boolean {
272
+ const normalized = posix.normalize(filePath);
273
+ if (normalized.startsWith("../") || normalized === "..") return true;
274
+
275
+ const segments = filePath.split(/[/\\]/);
276
+ for (const seg of segments) {
277
+ if (seg === "..") return true;
278
+ }
279
+
280
+ return false;
281
+ }
@@ -21,6 +21,7 @@
21
21
  import type { ResolvedMcpServer } from "./mcp-resolver.js";
22
22
  import { resolveMcpServers } from "./mcp-resolver.js";
23
23
  import type { McpTransportPosture } from "./mcp-transport-guard.js";
24
+ import { withTimeout } from "./with-timeout.js";
24
25
  import type { StigmerClient } from "../client/stigmer-client.js";
25
26
  import type { McpServerUsage } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
26
27
 
@@ -98,17 +99,14 @@ export async function backfillMcpServersIfNeeded(
98
99
  );
99
100
  onHeartbeat?.();
100
101
 
101
- const updated = await Promise.race([
102
- client.connectMcpServer(serverId, org, runtimeEnv),
103
- new Promise<never>((_, reject) =>
104
- setTimeout(
105
- () => reject(new Error(
106
- `Connect timed out after ${CONNECT_TIMEOUT_MS / 1000}s`,
107
- )),
108
- CONNECT_TIMEOUT_MS,
109
- ),
110
- ),
111
- ]);
102
+ // Bounded through the shared helper so the timer is cleared when the
103
+ // connect wins; a bare `setTimeout` in a `Promise.race` stays armed and
104
+ // holds the event loop open for the full bound (stigmer#1008).
105
+ const updated = await withTimeout(
106
+ CONNECT_TIMEOUT_MS,
107
+ `Connect timed out after ${CONNECT_TIMEOUT_MS / 1000}s`,
108
+ () => client.connectMcpServer(serverId, org, runtimeEnv),
109
+ );
112
110
 
113
111
  const toolCount = updated.status?.discoveredCapabilities?.tools.length ?? 0;
114
112
  const approvalCount = updated.status?.toolApprovals?.length ?? 0;
@@ -13,8 +13,8 @@
13
13
  * same clean-cancel pattern the stall watchdog and first-denial stop use.
14
14
  * Slightly harsher than native by construction; both harnesses terminate with
15
15
  * the same honesty semantics (EXECUTION_TERMINATED, work checkpointed, the
16
- * conversation continues on the next message — the recursion-limit precedent
17
- * in execute-deep-agent/streaming-terminal.ts).
16
+ * conversation continues on the next message — the recursion-limit precedent,
17
+ * `harness/terminal-table.ts` `toolCallLimitArm`).
18
18
  *
19
19
  * The running figure is the usage accumulator's local pricing-table estimate
20
20
  * (authoritative billing is the BiDi proxy). That is the same estimation
@@ -34,7 +34,7 @@ export function costCapExceeded(maxCostUsd: number, estimatedCostUsd: number): b
34
34
 
35
35
  /**
36
36
  * Stable prefix of the cost-limit terminal error. Mirrors the cross-repo
37
- * pattern of TOOL_CALL_LIMIT_ERROR_PREFIX (streaming-terminal.ts): consumers
37
+ * pattern of TOOL_CALL_LIMIT_ERROR_PREFIX (tool-rounds.ts): consumers
38
38
  * that need to distinguish "ran out of cost budget" from other TERMINATED
39
39
  * causes can match on this prefix, because AgentExecutionStatus carries no
40
40
  * structured termination reason. Do not reword without checking consumers.
@@ -2,10 +2,10 @@
2
2
  * Shared interface for proto mutation consumed by side-effect classes
3
3
  * (InlinePublisher, WriteBackCoordinator) and streaming loops.
4
4
  *
5
- * The deep-agent harness implements it on its v2 StatusBuilder and
6
- * V3StatusBuilder; the Cursor harness — which mutates a bare status proto
7
- * with no builder — uses {@link statusProtoWriter}. Side-effect classes
8
- * depend only on this interface, never on a specific implementation.
5
+ * The deep-agent harness implements it on its `V3StatusBuilder`; the Cursor
6
+ * harness — which mutates a bare status proto with no builder — uses
7
+ * {@link statusProtoWriter}. Side-effect classes depend only on this
8
+ * interface, never on a specific implementation.
9
9
  */
10
10
 
11
11
  import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
@@ -28,7 +28,7 @@ export interface ExecutionStatusWriter {
28
28
  * concern the direct-mutation style has no use for, so it stays `false`.
29
29
  *
30
30
  * Write-backs upsert by `workspaceEntryName` — the same contract as the
31
- * deep-agent `StatusBuilder.addWriteBack`: each git-backed workspace entry
31
+ * deep-agent `V3StatusBuilder.addWriteBack`: each git-backed workspace entry
32
32
  * carries at most one record, progressing through phases.
33
33
  */
34
34
  export function statusProtoWriter(status: AgentExecutionStatus): ExecutionStatusWriter {
@@ -1,19 +1,20 @@
1
1
  /**
2
2
  * Tier-2 structured-output extraction for a harness turn.
3
3
  *
4
- * Harness-agnostic: the Cursor activity is today's only caller and the turn
5
- * runtime takes it over in S2 M3 (the native harness has no tier 2 today).
4
+ * Harness-agnostic, and called from one place: the turn runtime's epilogue
5
+ * (`harness/run-turn.ts` `extractStructuredOutputFromText`), so both
6
+ * harnesses have it — Cursor since S2 M3, native since S3 M2a (pinned by the
7
+ * native hermetic `structured-output` tier-2 arm since M2b, Q-S3-7).
6
8
  *
7
9
  * When tier-1 text extraction (shared/extract-json.ts) cannot find JSON in
8
10
  * the agent's free-text response, this tier asks an economy-tier LLM to
9
11
  * extract it via withStructuredOutput (function-calling), which guarantees
10
12
  * schema-conformant output through the API's tool-use mechanism.
11
13
  *
12
- * Lives in its own module (rather than inside execute-cursor/index.ts) so
13
- * the LangChain construction path stays out of the Cursor activity's module
14
- * graph until a run actually needs tier 2 index.ts imports this module
15
- * lazily at the call site, mirroring its tier-1 import, which is what
16
- * bundle-slim's deferred evaluation preserves.
14
+ * Lives in its own module so the LangChain construction path stays out of
15
+ * the runtime's module graph until a run actually needs tier 2 — the
16
+ * runtime imports this module lazily at the call site, mirroring its tier-1
17
+ * import, which is what bundle-slim's deferred evaluation preserves.
17
18
  */
18
19
 
19
20
  import type { Config } from "../config.js";
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * @regression file-hitl-phase0 — pins file-edit HITL fixes #7, #8, #9 (see _projects/2026-06/20260630.01.file-change-hitl-redesign/tasks/T01_3_regression-manifest.md)
3
3
  *
4
- * Tests the harness-agnostic capture orchestration directly with the deep-agent
5
- * harness id (the Cursor adapter is covered by execute-cursor/capture-flow.test).
6
- * Confirms producer parity: the deep-agent authors IDENTICAL ledger entries to
7
- * Cursor through this seam only `harness_id` differs — and that with no
4
+ * Tests the harness-agnostic capture orchestration directly, under one
5
+ * harness id (since S3 M4 the runtime is the ONE caller for every harness,
6
+ * `harness/capture.ts`; until then each adapter called this seam and the
7
+ * Cursor side had its own test, `execute-cursor/capture-flow.test.ts`, whose
8
+ * three reconcile arms no other file carried moved here). Confirms that the
9
+ * ledger entries depend on nothing but `harness_id` and that with no
8
10
  * excludePaths every changed file is captured. Runs against a REAL temp git repo.
9
11
  */
10
12
 
@@ -333,6 +335,86 @@ function candidateCompleteness(status: AgentExecutionStatus): DiffCompleteness |
333
335
  : undefined;
334
336
  }
335
337
 
338
+ describe("capture reconcile — the decision rules the resume enforces (moved from execute-cursor/capture-flow.test at S3 M4)", () => {
339
+ async function captureTwoFileTurn(status: AgentExecutionStatus): Promise<void> {
340
+ const baseline = await captureBaselineToLedger({ status, gitRoot: repo, executionId: EXEC_ID, changeSetId: CHANGE_SET_ID, harnessId: HARNESS });
341
+ await write("notes.md", "planton notes\n");
342
+ await write("src/main.ts", "export const x = 99;\n");
343
+ await captureCandidateToLedger({ status, gitRoot: repo, executionId: EXEC_ID, changeSetId: CHANGE_SET_ID, baselineTree: baseline, harnessId: HARNESS });
344
+ }
345
+
346
+ it("reconstructs an approved file from the ref even if the tree was reset (retry-safe)", async () => {
347
+ const status = newStatus();
348
+ const baseline = await captureBaselineToLedger({ status, gitRoot: repo, executionId: EXEC_ID, changeSetId: CHANGE_SET_ID, harnessId: HARNESS });
349
+ await write("notes.md", "planton notes\n");
350
+ await captureCandidateToLedger({ status, gitRoot: repo, executionId: EXEC_ID, changeSetId: CHANGE_SET_ID, baselineTree: baseline, harnessId: HARNESS });
351
+ const changeSet = decidedChangeSet(status, { "notes.md": FileDecisionAction.APPROVE });
352
+
353
+ // A tree reset before the resume (a Temporal retry, a sandbox recycle):
354
+ // the approved bytes are gone from disk, the pinned refs survive.
355
+ await write("notes.md", "platon notes\n");
356
+
357
+ const result = await applyCaptureDecisions({ status, gitRoot: repo, executionId: EXEC_ID, changeSet, harnessId: HARNESS });
358
+
359
+ expect(result.approvedPaths).toEqual(["notes.md"]);
360
+ // Reconcile-from-refs re-asserts the approved "after" bytes whatever the tree holds.
361
+ expect(await read("notes.md")).toBe("planton notes\n");
362
+ });
363
+
364
+ it("CHANGE_SET-scoped approval covers all files; a FILE decision overrides it (most-specific-wins)", async () => {
365
+ const status = newStatus();
366
+ await captureTwoFileTurn(status);
367
+ const changes = candidateChanges(status);
368
+ const mainChange = changes.find((c) => c.pathAfter === "src/main.ts")!;
369
+ const changeSet = create(FileChangeSetSchema, {
370
+ id: CHANGE_SET_ID,
371
+ changes,
372
+ status: FileChangeSetStatus.DECIDED,
373
+ decisions: [
374
+ // CHANGE_SET: approve everything…
375
+ create(FileDecisionSchema, { id: "d-cs", changeSetId: CHANGE_SET_ID, scope: FileDecisionScope.CHANGE_SET, action: FileDecisionAction.APPROVE }),
376
+ // …but a FILE decision rejects src/main.ts (more specific wins).
377
+ create(FileDecisionSchema, {
378
+ id: "d-file",
379
+ changeSetId: CHANGE_SET_ID,
380
+ scope: FileDecisionScope.FILE,
381
+ fileChangeId: mainChange.id,
382
+ action: FileDecisionAction.REJECT,
383
+ expectedDigest: mainChange.fileDigest,
384
+ }),
385
+ ],
386
+ });
387
+
388
+ const result = await applyCaptureDecisions({ status, gitRoot: repo, executionId: EXEC_ID, changeSet, harnessId: HARNESS });
389
+
390
+ expect(result.approvedPaths).toEqual(["notes.md"]);
391
+ expect(result.rejectedPaths).toEqual(["src/main.ts"]);
392
+ expect(await read("notes.md")).toBe("planton notes\n");
393
+ expect(await read("src/main.ts")).toBe("export const x = 1;\n");
394
+ });
395
+
396
+ it("refuses to apply and authors FAILED(HASH_MISMATCH) when on-disk bytes diverge from the approved digest", async () => {
397
+ const status = newStatus();
398
+ await captureTwoFileTurn(status);
399
+ // The reviewer approved both, but the recorded after_sha256 for notes.md no
400
+ // longer matches the captured bytes (what-you-approve-is-what-applies fails).
401
+ const changeSet = decidedChangeSet(status, { "notes.md": FileDecisionAction.APPROVE, "src/main.ts": FileDecisionAction.APPROVE });
402
+ changeSet.changes.find((c) => c.pathAfter === "notes.md")!.afterSha256 = "deadbeef".repeat(8);
403
+
404
+ const result = await applyCaptureDecisions({ status, gitRoot: repo, executionId: EXEC_ID, changeSet, harnessId: HARNESS });
405
+
406
+ expect(result.failed).toBe(true);
407
+ expect(result.failureDetail).toContain("notes.md");
408
+ // NOTHING was applied (a partial apply under a verification failure is
409
+ // worse than none): src/main.ts still holds its un-reconciled (after) bytes.
410
+ expect(await read("src/main.ts")).toBe("export const x = 99;\n");
411
+ const failed = eventsOfType(status, FileReviewEventType.FAILED);
412
+ expect(failed).toHaveLength(1);
413
+ if (failed[0]!.payload.case === "failed") expect(failed[0]!.payload.value.detail).toContain("notes.md");
414
+ expect(eventsOfType(status, FileReviewEventType.RECONCILED)).toHaveLength(0);
415
+ });
416
+ });
417
+
336
418
  describe("capture orchestration — hybrid git + CAS (Phase 3)", () => {
337
419
  it("composes git-tracked and CAS captures into one HYBRID change set", async () => {
338
420
  const status = newStatus();
@@ -16,7 +16,8 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
16
16
  import { join, dirname } from "node:path";
17
17
  import { tmpdir } from "node:os";
18
18
  import { FileChangeKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
19
- import { createCasProgressSubstrate, type CasTouchedSnapshot } from "../cas-progress.js";
19
+ import { createCasProgressSubstrate } from "../cas-progress.js";
20
+ import type { CasTouchedSnapshot } from "../cas-touched.js";
20
21
  import { LINE_COUNT_MAX_BYTES } from "../line-counts.js";
21
22
  import type { ProgressEntry } from "../progress.js";
22
23
 
@@ -43,26 +43,12 @@ import {
43
43
  type ProgressSubstrate,
44
44
  } from "./progress.js";
45
45
  import { partitionIgnoredPathsBySecret } from "./secret-paths.js";
46
+ import type { CasTouchedReader } from "./cas-touched.js";
46
47
 
47
- /**
48
- * A snapshot of the turn's CAS-observed paths, read on each capture:
49
- * - `before`: first-touched paths' pre-turn bytes (`null` = the path did not
50
- * exist before an ADD). The KEYS are the touched-path set.
51
- * - `blockedSecretPaths`: paths the gate hard-blocked as secret-like — excluded
52
- * from capture.
53
- *
54
- * Deep-agent supplies a synchronous copy of its live `CasCaptureObserver`; Cursor
55
- * supplies a reader over the hook's on-disk sidecar. The reader MUST return an
56
- * atomic snapshot (copy the live map before any await) so a concurrent sub-agent
57
- * write cannot mutate it mid-capture.
58
- */
59
- export interface CasTouchedSnapshot {
60
- readonly before: ReadonlyMap<string, Uint8Array | null>;
61
- readonly blockedSecretPaths: ReadonlySet<string>;
62
- }
63
-
64
- /** Produces a {@link CasTouchedSnapshot} for one capture. */
65
- export type CasTouchedReader = () => CasTouchedSnapshot | Promise<CasTouchedSnapshot>;
48
+ // The snapshot this substrate reads (`CasTouchedSnapshot`) and the reader that
49
+ // produces it live in `cas-touched.ts`, the one home of "what the engine
50
+ // observed touching CAS-owned paths": the turn-boundary capture reads the same
51
+ // snapshot, so neither consumer owns the shape.
66
52
 
67
53
  /**
68
54
  * The CAS progress substrate. Reads the touched set on each capture, computes
@@ -0,0 +1,89 @@
1
+ /**
2
+ * What the engine observed touching CAS-owned paths this turn — the one fact
3
+ * about a turn's file changes that the runtime cannot read from the tree and
4
+ * the engine cannot help knowing.
5
+ *
6
+ * In a git work tree the tracked changes are a `git diff` against the pinned
7
+ * baseline; the gitignored ones are invisible to git, and in a non-git
8
+ * workspace EVERY change is. For those paths the only record of the
9
+ * pre-turn bytes is the one taken at mutation time, by whatever sits between
10
+ * the engine and the disk: the deep-agent's `CasCaptureObserver` (fed by its
11
+ * filesystem backend and its approval gate) or the Cursor hook's on-disk
12
+ * sidecar. Both produce this one snapshot shape, and both consumers read it:
13
+ * the mid-run progress slice (`cas-progress.ts`) and the turn-boundary
14
+ * capture ({@link buildCasTurnCaptures}), which composes it into the
15
+ * {@link CasPathCapture}s the CAS substrate stores.
16
+ *
17
+ * Until S3 M4 each harness carried its own copy of `buildCasTurnCaptures`
18
+ * beside its observation source; the copies were byte-near-identical
19
+ * (F-M4-P6) and now live here once. The AFTER bytes are always re-read from
20
+ * disk at composition time (the authoritative net result of the turn), so
21
+ * many edits to one path collapse to one before/after.
22
+ */
23
+
24
+ import { readFile } from "node:fs/promises";
25
+ import { join } from "node:path";
26
+ import type { FileCaptureClass } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
27
+ import type { CasPathCapture } from "./cas-substrate.js";
28
+ import { partitionIgnoredPathsBySecret } from "./secret-paths.js";
29
+
30
+ /**
31
+ * The CAS-owned paths touched so far this turn: pre-turn bytes per
32
+ * first-touched path (`null` = the path did not exist → an ADD) and the paths
33
+ * the gate hard-blocked as secret-like (never applied, never captured; the
34
+ * boundary authors a content-less entry for each). Keyed workspace-root-
35
+ * relative. A reader MUST return an atomic snapshot (copy the live map before
36
+ * any await) so a concurrent sub-agent write cannot mutate it mid-capture.
37
+ */
38
+ export interface CasTouchedSnapshot {
39
+ readonly before: ReadonlyMap<string, Uint8Array | null>;
40
+ readonly blockedSecretPaths: ReadonlySet<string>;
41
+ }
42
+
43
+ /** Produces a {@link CasTouchedSnapshot} for one capture. */
44
+ export type CasTouchedReader = () => CasTouchedSnapshot | Promise<CasTouchedSnapshot>;
45
+
46
+ /**
47
+ * Compose the turn's CAS captures from what the engine observed: for each
48
+ * observed path the before-bytes come from the snapshot and the after-bytes
49
+ * are re-read from disk now (`null` = the file is gone). Secret-like paths are
50
+ * diverted to `unreviewablePaths` and their before-bytes dropped — the
51
+ * fail-closed backstop that holds even under the global bypass, where the
52
+ * gate did not run to block them up front ({@link partitionIgnoredPathsBySecret}
53
+ * is the one decision; this shell performs only the IO for the capturable
54
+ * set). `captureClass` is the turn's CAS substrate class — GIT_IGNORED_CAPTURED
55
+ * for a git work tree's ignored paths, NON_GIT_CAS for a non-git workspace
56
+ * (where these ARE the whole change set) — so each captured path carries its
57
+ * true provenance. An empty snapshot yields empty results and leaves the
58
+ * shared capture unchanged.
59
+ */
60
+ export async function buildCasTurnCaptures(
61
+ snapshot: CasTouchedSnapshot,
62
+ workspaceRoot: string,
63
+ captureClass: FileCaptureClass,
64
+ ): Promise<{ casCaptures: CasPathCapture[]; unreviewablePaths: string[] }> {
65
+ const { capturablePaths, unreviewablePaths } = partitionIgnoredPathsBySecret(
66
+ snapshot.before.keys(),
67
+ snapshot.blockedSecretPaths,
68
+ );
69
+ const casCaptures: CasPathCapture[] = [];
70
+ for (const relPath of capturablePaths) {
71
+ const after = await readFileOrNull(join(workspaceRoot, relPath));
72
+ casCaptures.push({
73
+ path: relPath,
74
+ before: snapshot.before.get(relPath) ?? null,
75
+ after,
76
+ captureClass,
77
+ });
78
+ }
79
+ return { casCaptures, unreviewablePaths: [...unreviewablePaths] };
80
+ }
81
+
82
+ /** Raw bytes of a file, or `null` when it does not exist (a DELETE, or an ADD since removed). */
83
+ async function readFileOrNull(absolutePath: string): Promise<Uint8Array | null> {
84
+ try {
85
+ return await readFile(absolutePath);
86
+ } catch {
87
+ return null;
88
+ }
89
+ }
@@ -56,11 +56,13 @@ export {
56
56
  type ProgressSubstrate,
57
57
  } from "./progress.js";
58
58
 
59
+ export { createCasProgressSubstrate } from "./cas-progress.js";
60
+
59
61
  export {
60
- createCasProgressSubstrate,
62
+ buildCasTurnCaptures,
61
63
  type CasTouchedReader,
62
64
  type CasTouchedSnapshot,
63
- } from "./cas-progress.js";
65
+ } from "./cas-touched.js";
64
66
 
65
67
  export {
66
68
  countLineChanges,