@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
@@ -1,627 +0,0 @@
1
- /**
2
- * Attachment injection for deep agent execution.
3
- *
4
- * Downloads attachments from artifact storage (cloud) or local filesystem,
5
- * validates ZIP archives with security guards, and writes files into the
6
- * workspace via the platform mount namespace (.stigmer/inputs/).
7
- *
8
- * Security model: attachments are untrusted user uploads. ZIP archives are
9
- * parsed from the central directory — the format's authoritative index —
10
- * via the shared structural layer (@stigmer/zip-structure; issue #567,
11
- * which killed this module's local-header walk: it silently truncated
12
- * stored streaming entries and rejected Go-default archives outright).
13
- * Every entry is validated for path traversal, zip bombs, and format
14
- * integrity BEFORE any extraction occurs, and because the central
15
- * directory's sizes are declarations an attacker controls, decompression
16
- * re-enforces them: an entry whose actual output disagrees with its
17
- * declared size aborts the injection.
18
- *
19
- * Error model: fail-hard. Any attachment failure aborts the entire injection
20
- * and propagates a descriptive error. Attachments are explicit user inputs —
21
- * running with partial inputs produces silently incorrect results. This is
22
- * deliberately the OPPOSITE policy from the skill-artifact reader
23
- * (shared/zip-extract.ts, non-fatal empty return): skill artifacts were
24
- * structurally vouched for by the push gates, attachments never are.
25
- *
26
- * Duplicate names are NOT a failure (issue #364): a default-derived mount
27
- * path that collides is renamed with the platform's `stem-2.ext` semantics
28
- * (shared/attachment-naming.ts) and the rename is disclosed in the prompt's
29
- * Input Files section. Only two attachments EXPLICITLY pinning the same
30
- * `mountPath` still abort — that is a user contradiction no rename can
31
- * honestly resolve.
32
- */
33
-
34
- import { readFile } from "node:fs/promises";
35
- import { createInflateRaw } from "node:zlib";
36
- import { posix } from "node:path";
37
- import type { Attachment } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/spec_pb";
38
- import type { WorkspaceBackend } from "../../shared/workspace/types.js";
39
- import type { ArtifactStorage } from "../../shared/artifact-storage.js";
40
- import { mintAttachmentDownloadUrl } from "../../shared/attachment-download-urls.js";
41
- import { allocateUniqueName } from "../../shared/attachment-naming.js";
42
- import type {
43
- VisionBudget,
44
- VisionDegradedReason,
45
- VisionImage,
46
- } from "../../shared/attachment-vision.js";
47
- import {
48
- EOCD_MIN_SIZE,
49
- parseZipStructure,
50
- type ZipStructuralEntry,
51
- } from "@stigmer/zip-structure";
52
-
53
- // ── Constants ────────────────────────────────────────────────────────
54
-
55
- const MAX_ZIP_FILES = 1000;
56
- const MAX_ZIP_EXTRACTED_SIZE = 100 * 1024 * 1024; // 100 MB
57
- const DEFAULT_INPUTS_PREFIX = ".stigmer/inputs";
58
-
59
- export { MAX_ZIP_FILES, MAX_ZIP_EXTRACTED_SIZE };
60
-
61
- // ── Types ────────────────────────────────────────────────────────────
62
-
63
- export interface InjectedFile {
64
- /** The final on-disk basename — after any duplicate rename, so it always
65
- * agrees with {@link path} and with the vision payload's image label. */
66
- readonly filename: string;
67
- readonly path: string;
68
- readonly sizeBytes: number;
69
- /**
70
- * The attachment's original filename, present only when a duplicate name
71
- * was renamed (shared/attachment-naming.ts) — rendered as disclosure in
72
- * the prompt's Input Files section.
73
- */
74
- readonly renamedFrom?: string;
75
- /** Present when the attachment was accepted into the turn's vision payload. */
76
- readonly vision?: VisionImage;
77
- /**
78
- * Present when the attachment was plausibly an image but could not ride
79
- * inline (see {@link VisionDegradedReason}) — disclosed in the system prompt
80
- * so the agent never silently ignores a photo the user believes it can see.
81
- * Attachments that were never image-shaped carry neither field.
82
- */
83
- readonly visionDegraded?: VisionDegradedReason;
84
- /**
85
- * A download URL for the attachment's stored object, listed in the system
86
- * prompt's Input Files section so the agent can hand the file to a tool
87
- * whose backend cannot read the sandbox filesystem (issue #532). Minted
88
- * whenever the attachment has a `storageKey` and a usable storage; never
89
- * present on extracted ZIP entries (no attachment-level object). Absent
90
- * when there is nothing to mint or the mint failed (non-fatal — policy,
91
- * degrade log, and prompt wording live in
92
- * shared/attachment-download-urls.ts).
93
- */
94
- readonly downloadUrl?: string;
95
- }
96
-
97
- export interface InjectAttachmentsOptions {
98
- readonly backend: WorkspaceBackend;
99
- readonly attachments: readonly Attachment[];
100
- /**
101
- * Artifact storage for cloud-mode attachment download. `undefined` when the
102
- * runner could not build a store (proxy misconfig). With no attachments this is
103
- * never touched (the function early-returns); a cloud-mode attachment that
104
- * genuinely needs storage surfaces a clear {@link AttachmentInjectionError}
105
- * rather than dereferencing undefined.
106
- */
107
- readonly storage: ArtifactStorage | undefined;
108
- readonly isLocalMode: boolean;
109
- /**
110
- * The turn's vision selector (attachment-vision.ts owns all policy).
111
- * `undefined` disables inline image delivery; file materialization is
112
- * identical either way — vision is strictly additive. Archives (`extract`)
113
- * are never offered: an image inside a ZIP has no attachment-level bytes.
114
- */
115
- readonly visionBudget?: VisionBudget;
116
- }
117
-
118
- export interface ZipEntryInfo {
119
- readonly relativePath: string;
120
- readonly uncompressedSize: number;
121
- }
122
-
123
- // ── Errors ───────────────────────────────────────────────────────────
124
-
125
- export class AttachmentInjectionError extends Error {
126
- readonly attachmentFilename: string;
127
- readonly reason: string;
128
-
129
- constructor(attachmentFilename: string, reason: string) {
130
- super(`Attachment '${attachmentFilename}': ${reason}`);
131
- this.name = "AttachmentInjectionError";
132
- this.attachmentFilename = attachmentFilename;
133
- this.reason = reason;
134
- }
135
- }
136
-
137
- export class AttachmentValidationError extends Error {
138
- readonly attachmentFilename: string;
139
- readonly reason: string;
140
-
141
- constructor(attachmentFilename: string, reason: string) {
142
- super(`Attachment '${attachmentFilename}': ${reason}`);
143
- this.name = "AttachmentValidationError";
144
- this.attachmentFilename = attachmentFilename;
145
- this.reason = reason;
146
- }
147
- }
148
-
149
- // ── ZIP Validation (pure functions) ──────────────────────────────────
150
-
151
- /**
152
- * Validate a ZIP archive for safe extraction. Returns the manifest of
153
- * file entries (directories excluded) if the archive passes all checks.
154
- *
155
- * Security checks enforced:
156
- * 1. Valid ZIP format (readable central directory; fail-hard, see module doc)
157
- * 2. No absolute paths (entries starting with / or \)
158
- * 3. No path traversal (.. components)
159
- * 4. No null bytes in filenames
160
- * 5. No duplicate entry paths (a contradictory manifest)
161
- * 6. Only supported compression methods (stored, deflate) — checked here
162
- * so an unsupported entry can never abort extraction after earlier
163
- * entries were already written
164
- * 7. Non-empty archive (at least one file entry)
165
- * 8. File count within limits (max 1000)
166
- * 9. Total declared uncompressed size within limits (max 100 MB) —
167
- * declarations are re-enforced against actual output at decompression
168
- */
169
- export function validateZipForExtraction(
170
- zipData: Buffer,
171
- sourceFilename: string,
172
- ): ZipEntryInfo[] {
173
- return parseAndValidateZip(zipData, sourceFilename).map(
174
- ({ relativePath, uncompressedSize }) => ({ relativePath, uncompressedSize }),
175
- );
176
- }
177
-
178
- /**
179
- * A validated file entry, still carrying its payload slice. Validation and
180
- * extraction share these records — parsing happens exactly once, so "the
181
- * manifest promised a file extraction cannot find" is unrepresentable
182
- * (the old two-walker design silently skipped such entries).
183
- */
184
- interface ValidatedZipEntry extends ZipEntryInfo {
185
- readonly compressionMethod: number;
186
- readonly compressedData: Uint8Array;
187
- }
188
-
189
- function parseAndValidateZip(
190
- zipData: Buffer,
191
- sourceFilename: string,
192
- ): ValidatedZipEntry[] {
193
- if (zipData.length < EOCD_MIN_SIZE) {
194
- throw new AttachmentValidationError(
195
- sourceFilename,
196
- "not a valid ZIP archive (file too small)",
197
- );
198
- }
199
-
200
- let structural: ZipStructuralEntry[];
201
- try {
202
- structural = parseZipStructure(zipData);
203
- } catch (err) {
204
- throw new AttachmentValidationError(
205
- sourceFilename,
206
- `not a valid ZIP archive (${err instanceof Error ? err.message : String(err)})`,
207
- );
208
- }
209
-
210
- const entries: ValidatedZipEntry[] = [];
211
- const seenPaths = new Set<string>();
212
- let totalUncompressed = 0;
213
-
214
- for (const entry of structural) {
215
- if (entry.name.includes("\u0000")) {
216
- throw new AttachmentValidationError(
217
- sourceFilename,
218
- "contains a filename with null bytes and cannot be safely extracted",
219
- );
220
- }
221
-
222
- if (entry.isDirectory) continue;
223
-
224
- if (entry.name.startsWith("/") || entry.name.startsWith("\\")) {
225
- throw new AttachmentValidationError(
226
- sourceFilename,
227
- `contains an absolute path entry and cannot be safely extracted: ${entry.name}`,
228
- );
229
- }
230
-
231
- if (hasPathTraversal(entry.name)) {
232
- throw new AttachmentValidationError(
233
- sourceFilename,
234
- `contains a path traversal entry and cannot be safely extracted: ${entry.name}`,
235
- );
236
- }
237
-
238
- if (seenPaths.has(entry.name)) {
239
- throw new AttachmentValidationError(
240
- sourceFilename,
241
- `contains duplicate entry '${entry.name}' and cannot be safely extracted`,
242
- );
243
- }
244
- seenPaths.add(entry.name);
245
-
246
- if (entry.compressionMethod !== 0 && entry.compressionMethod !== 8) {
247
- throw new AttachmentValidationError(
248
- sourceFilename,
249
- `entry '${entry.name}' uses unsupported compression method ${entry.compressionMethod}`,
250
- );
251
- }
252
-
253
- entries.push({
254
- relativePath: entry.name,
255
- uncompressedSize: entry.uncompressedSize,
256
- compressionMethod: entry.compressionMethod,
257
- compressedData: entry.compressedData,
258
- });
259
- totalUncompressed += entry.uncompressedSize;
260
- }
261
-
262
- if (entries.length === 0) {
263
- throw new AttachmentValidationError(
264
- sourceFilename,
265
- "is an empty ZIP archive (no file entries)",
266
- );
267
- }
268
-
269
- if (entries.length > MAX_ZIP_FILES) {
270
- throw new AttachmentValidationError(
271
- sourceFilename,
272
- `contains ${entries.length} files (limit: ${MAX_ZIP_FILES})`,
273
- );
274
- }
275
-
276
- if (totalUncompressed > MAX_ZIP_EXTRACTED_SIZE) {
277
- const sizeMb = (totalUncompressed / (1024 * 1024)).toFixed(1);
278
- const limitMb = (MAX_ZIP_EXTRACTED_SIZE / (1024 * 1024)).toFixed(0);
279
- throw new AttachmentValidationError(
280
- sourceFilename,
281
- `would extract to ${sizeMb} MB (limit: ${limitMb} MB)`,
282
- );
283
- }
284
-
285
- return entries.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
286
- }
287
-
288
- // ── Attachment Injection ─────────────────────────────────────────────
289
-
290
- /**
291
- * Download, validate, and inject all attachments into the workspace.
292
- *
293
- * Resolves all mount paths before any downloads begin: duplicate
294
- * default-derived names are renamed (never fatal — see module doc), and an
295
- * explicit-mountPath contradiction throws before any bytes move.
296
- * On any failure, throws AttachmentInjectionError with an actionable message.
297
- */
298
- export async function injectAttachments(opts: InjectAttachmentsOptions): Promise<InjectedFile[]> {
299
- const { backend, attachments, storage, isLocalMode, visionBudget } = opts;
300
-
301
- if (attachments.length === 0) return [];
302
-
303
- // Phase 1: Pre-compute mount paths and detect collisions
304
- const mountPaths = resolveMountPaths(attachments);
305
-
306
- // Phase 2: Download, validate, and write each attachment
307
- const injectedFiles: InjectedFile[] = [];
308
-
309
- for (const attachment of attachments) {
310
- const content = await downloadAttachment(attachment, storage, isLocalMode);
311
- const { path: mountPath, renamedFrom } = mountPaths.get(attachment)!;
312
-
313
- if (attachment.extract) {
314
- const entries = parseAndValidateZip(content, attachment.filename);
315
- const extracted = await extractZipToWorkspace(
316
- entries, mountPath, backend, attachment.filename,
317
- );
318
- // A renamed mount DIR is visible through every extracted path; the
319
- // entries themselves were not renamed, so they carry no renamedFrom
320
- // (per-file disclosure would misattribute the rename). Extracted
321
- // entries also carry no downloadUrl: the stored object is the ZIP, not
322
- // any listed file, and a URL to bytes that differ from the listing
323
- // would be a false promise.
324
- injectedFiles.push(...extracted);
325
- } else {
326
- await backend.writeFileBuffer(mountPath, content);
327
- // The final basename is the canonical filename: after a duplicate
328
- // rename the original attachment.filename no longer names the file on
329
- // disk, and the vision label must match what the prompt lists or the
330
- // agent sees two images with one indistinguishable name.
331
- const filename = posix.basename(mountPath);
332
- // The bytes are already in hand for the workspace write — offer them to
333
- // the vision budget before they go out of scope (the sniff decides
334
- // eligibility; the budget owns every size/count rule).
335
- const vision = visionBudget?.offer(filename, attachment.contentType, content);
336
- const downloadUrl = await mintAttachmentDownloadUrl(
337
- storage, attachment.storageKey, filename,
338
- );
339
- injectedFiles.push({
340
- filename,
341
- path: mountPath,
342
- sizeBytes: content.length,
343
- ...(renamedFrom !== undefined ? { renamedFrom } : {}),
344
- ...(vision?.kind === "accepted" ? { vision: vision.image } : {}),
345
- ...(vision?.kind === "degraded" ? { visionDegraded: vision.reason } : {}),
346
- ...(downloadUrl !== undefined ? { downloadUrl } : {}),
347
- });
348
- }
349
- }
350
-
351
- console.log(
352
- `[attachment-injector] Injected ${injectedFiles.length} file(s) into workspace`,
353
- );
354
-
355
- return injectedFiles;
356
- }
357
-
358
- // ── Internal Helpers ─────────────────────────────────────────────────
359
-
360
- interface ResolvedMountPath {
361
- readonly path: string;
362
- /** Present when a default-derived name was uniquified (issue #364). */
363
- readonly renamedFrom?: string;
364
- }
365
-
366
- function resolveMountPaths(
367
- attachments: readonly Attachment[],
368
- ): Map<Attachment, ResolvedMountPath> {
369
- const result = new Map<Attachment, ResolvedMountPath>();
370
- const pathToAttachment = new Map<string, Attachment>();
371
-
372
- // Pass 1: explicit mount paths claim their exact targets first. Two
373
- // attachments explicitly pinning the SAME path is a user contradiction no
374
- // rename can honestly resolve — keep rejecting with the actionable message.
375
- for (const attachment of attachments) {
376
- if (!attachment.mountPath) continue;
377
- const mountPath = resolveExplicitMountPath(attachment);
378
- const existing = pathToAttachment.get(mountPath);
379
-
380
- if (existing) {
381
- throw new AttachmentInjectionError(
382
- attachment.filename,
383
- `mount path '${mountPath}' collides with attachment '${existing.filename}'. ` +
384
- "Set distinct mountPath values on the attachments to resolve this conflict.",
385
- );
386
- }
387
-
388
- pathToAttachment.set(mountPath, attachment);
389
- result.set(attachment, { path: mountPath });
390
- }
391
-
392
- // Pass 2: default-derived names (`.stigmer/inputs/{filename}`) uniquify
393
- // around everything already taken — other defaults AND explicit paths that
394
- // landed inside the inputs prefix — instead of failing the execution
395
- // (issue #364). The taken-set is seeded with the basenames the explicit
396
- // pass claimed directly under the prefix.
397
- const takenNames = new Set<string>();
398
- for (const path of pathToAttachment.keys()) {
399
- if (path.startsWith(`${DEFAULT_INPUTS_PREFIX}/`)) {
400
- const rest = path.slice(DEFAULT_INPUTS_PREFIX.length + 1);
401
- if (rest.length > 0 && !rest.includes("/")) takenNames.add(rest);
402
- }
403
- }
404
- for (const attachment of attachments) {
405
- if (attachment.mountPath) continue;
406
- const { name, renamedFrom } = allocateUniqueName(
407
- deriveDefaultFilename(attachment),
408
- takenNames,
409
- );
410
- const path = `${DEFAULT_INPUTS_PREFIX}/${name}`;
411
- pathToAttachment.set(path, attachment);
412
- result.set(
413
- attachment,
414
- renamedFrom !== undefined ? { path, renamedFrom } : { path },
415
- );
416
- }
417
-
418
- return result;
419
- }
420
-
421
- function resolveExplicitMountPath(attachment: Attachment): string {
422
- const cleaned = attachment.mountPath.replace(/^\/+/, "");
423
- if (cleaned.length === 0) {
424
- throw new AttachmentInjectionError(
425
- attachment.filename,
426
- "mountPath resolves to an empty path after removing leading slashes",
427
- );
428
- }
429
- // A caller-supplied mount path is untrusted. Stripping leading slashes does
430
- // not stop `..` segments from climbing out of the workspace root on the
431
- // non-`.stigmer/` branch (the `.stigmer/`-routed branch is already guarded
432
- // by LocalWorkspaceBackend.resolvePath). Reject any path that normalizes to
433
- // an escape before it reaches the backend write.
434
- if (escapesRoot(cleaned)) {
435
- throw new AttachmentInjectionError(
436
- attachment.filename,
437
- `mount path '${attachment.mountPath}' escapes the workspace root`,
438
- );
439
- }
440
- return cleaned;
441
- }
442
-
443
- // Derives the single-component filename an attachment without an explicit
444
- // mountPath materializes under (the name that pass 2 uniquifies).
445
- function deriveDefaultFilename(attachment: Attachment): string {
446
- const rawName = attachment.filename || deriveFilename(attachment.storageKey);
447
- if (!rawName) {
448
- throw new AttachmentInjectionError(
449
- "(unknown)",
450
- "attachment has neither filename nor storageKey — cannot determine mount path",
451
- );
452
- }
453
-
454
- // The filename is untrusted; reduce it to a single path component so it lands
455
- // directly under the inputs prefix rather than steering the write elsewhere.
456
- const filename = posix.basename(rawName);
457
- if (filename === "" || filename === "." || filename === "..") {
458
- throw new AttachmentInjectionError(
459
- rawName,
460
- `filename '${rawName}' does not yield a usable name for materialization`,
461
- );
462
- }
463
-
464
- return filename;
465
- }
466
-
467
- // escapesRoot reports whether a workspace-relative mount path would climb out
468
- // of the workspace root once normalized. Mount paths are forward-slash,
469
- // workspace-relative strings, so posix normalization is the correct semantics.
470
- function escapesRoot(relPath: string): boolean {
471
- const normalized = posix.normalize(relPath);
472
- return (
473
- normalized === ".." ||
474
- normalized.startsWith("../") ||
475
- posix.isAbsolute(normalized)
476
- );
477
- }
478
-
479
- function deriveFilename(storageKey: string): string {
480
- if (!storageKey) return "";
481
- const parts = storageKey.split("/");
482
- return parts[parts.length - 1] || "";
483
- }
484
-
485
- async function downloadAttachment(
486
- attachment: Attachment,
487
- storage: ArtifactStorage | undefined,
488
- isLocalMode: boolean,
489
- ): Promise<Buffer> {
490
- // Local mode fast path: read directly from filesystem
491
- if (isLocalMode && attachment.localPath) {
492
- try {
493
- return await readFile(attachment.localPath);
494
- } catch (err) {
495
- throw new AttachmentInjectionError(
496
- attachment.filename,
497
- `failed to read local file '${attachment.localPath}': ${err instanceof Error ? err.message : String(err)}`,
498
- );
499
- }
500
- }
501
-
502
- // Cloud mode: download via artifact storage
503
- if (!attachment.storageKey) {
504
- throw new AttachmentInjectionError(
505
- attachment.filename,
506
- "missing storageKey — cannot download attachment from storage",
507
- );
508
- }
509
-
510
- // The attachment must come from storage, but the runner could not build one
511
- // (proxy misconfig). Fail with an actionable message rather than dereferencing
512
- // undefined — consistent with the degraded posture, never a silent skip.
513
- if (!storage) {
514
- throw new AttachmentInjectionError(
515
- attachment.filename,
516
- `artifact storage is unavailable, so this attachment (key: ${attachment.storageKey}) cannot be downloaded`,
517
- );
518
- }
519
-
520
- try {
521
- return await storage.download(attachment.storageKey);
522
- } catch (err) {
523
- if (err instanceof AttachmentInjectionError) throw err;
524
- throw new AttachmentInjectionError(
525
- attachment.filename,
526
- `failed to download from storage (key: ${attachment.storageKey}): ${err instanceof Error ? err.message : String(err)}`,
527
- );
528
- }
529
- }
530
-
531
- async function extractZipToWorkspace(
532
- entries: readonly ValidatedZipEntry[],
533
- mountDir: string,
534
- backend: WorkspaceBackend,
535
- sourceFilename: string,
536
- ): Promise<InjectedFile[]> {
537
- const cleanMountDir = mountDir.replace(/\/+$/, "");
538
- const injected: InjectedFile[] = [];
539
-
540
- for (const entry of entries) {
541
- const content = await decompressEntry(entry, sourceFilename);
542
- const targetPath = `${cleanMountDir}/${entry.relativePath}`;
543
-
544
- await backend.writeFileBuffer(targetPath, content);
545
- injected.push({
546
- filename: posix.basename(entry.relativePath),
547
- path: targetPath,
548
- sizeBytes: content.length,
549
- });
550
- }
551
-
552
- return injected;
553
- }
554
-
555
- /**
556
- * Decompress a validated entry, enforcing its declared uncompressed size.
557
- *
558
- * The size cap in parseAndValidateZip budgets *declared* sizes, which the
559
- * archive author controls — a crafted archive can declare 1 KB and inflate
560
- * to gigabytes. Enforcement is therefore two-sided and fail-hard: inflation
561
- * aborts the moment output exceeds the declaration, and an undershoot (or a
562
- * stored payload whose length disagrees) fails too, because a manifest that
563
- * misdescribes its own contents is exactly the corrupt-input class this
564
- * module must never extract from.
565
- */
566
- async function decompressEntry(
567
- entry: ValidatedZipEntry,
568
- sourceFilename: string,
569
- ): Promise<Buffer> {
570
- const declared = entry.uncompressedSize;
571
-
572
- if (entry.compressionMethod === 0) {
573
- if (entry.compressedData.length !== declared) {
574
- throw new AttachmentValidationError(
575
- sourceFilename,
576
- `entry '${entry.relativePath}' payload is ${entry.compressedData.length} bytes ` +
577
- `but the archive declares ${declared} — corrupt or crafted archive`,
578
- );
579
- }
580
- return Buffer.from(entry.compressedData);
581
- }
582
-
583
- // Deflate — the only other method parseAndValidateZip admits.
584
- return new Promise<Buffer>((resolve, reject) => {
585
- const inflate = createInflateRaw();
586
- const chunks: Buffer[] = [];
587
- let produced = 0;
588
- inflate.on("data", (chunk: Buffer) => {
589
- produced += chunk.length;
590
- if (produced > declared) {
591
- inflate.destroy();
592
- reject(new AttachmentValidationError(
593
- sourceFilename,
594
- `entry '${entry.relativePath}' decompresses past its declared size of ` +
595
- `${declared} bytes — corrupt or crafted archive`,
596
- ));
597
- return;
598
- }
599
- chunks.push(chunk);
600
- });
601
- inflate.on("end", () => {
602
- if (produced !== declared) {
603
- reject(new AttachmentValidationError(
604
- sourceFilename,
605
- `entry '${entry.relativePath}' decompressed to ${produced} bytes ` +
606
- `but the archive declares ${declared} — corrupt or crafted archive`,
607
- ));
608
- return;
609
- }
610
- resolve(Buffer.concat(chunks));
611
- });
612
- inflate.on("error", reject);
613
- inflate.end(Buffer.from(entry.compressedData));
614
- });
615
- }
616
-
617
- function hasPathTraversal(filePath: string): boolean {
618
- const normalized = posix.normalize(filePath);
619
- if (normalized.startsWith("../") || normalized === "..") return true;
620
-
621
- const segments = filePath.split(/[/\\]/);
622
- for (const seg of segments) {
623
- if (seg === "..") return true;
624
- }
625
-
626
- return false;
627
- }