@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
@@ -14,31 +14,53 @@
14
14
  * the backend's create pipeline requires a storage key on every attachment
15
15
  * it accepts, and in local mode the storage reads directly off disk.
16
16
  *
17
- * Placement is always `inputs/{filename}` — the platform namespace this
18
- * harness can surface in the workspace. An attachment's `mountPath` is
19
- * honored by convention, not mechanism: the standard mounts (e.g. the
20
- * approved plan at `.stigmer/inputs/<slug>_<id>.plan.md`) resolve to exactly this
21
- * placement, and the prompt's `<input_files>` section plus any path-derived
22
- * directives are built from the RESOLVED paths, so prompt and filesystem can
23
- * never disagree.
17
+ * Placement is the `inputs/` namespace — the platform directory every
18
+ * harness surfaces in the workspace through the `.stigmer` link, and the
19
+ * only place an attachment may land. An attachment names its place INSIDE
20
+ * that namespace with `mountPath` (`inputs/<name>`, `inputs/<dir>/`,
21
+ * `.stigmer/inputs/<name>`; a leading slash is stripped), or takes the
22
+ * default `inputs/{filename}`. A `mountPath` outside the namespace is
23
+ * refused with an actionable error, never relocated silently: a file
24
+ * written into the user's own tree would be committed to the session's
25
+ * write-back branch on completion (`git add -A`), and every real producer —
26
+ * the CLI's directory attachments, the approved plan the console mounts —
27
+ * already stays inside `inputs/` (S3 M1, Q-M1-2; the proto's `/workspace/…`
28
+ * example is a doc issue). The prompt's `<input_files>` section and every
29
+ * path-derived directive are built from the RESOLVED paths, so prompt and
30
+ * filesystem can never disagree.
24
31
  *
25
- * Duplicate filenames are renamed, never overwritten (issue #364): because
26
- * placement keys purely on the filename, two attachments with the same name
27
- * contend for one path — the later one takes the platform's `stem-2.ext`
28
- * rename (shared/attachment-naming.ts, same semantics as the deep-agent
29
- * injector and the React composer) and the rename is disclosed in the
30
- * prompt's `<input_files>` section via {@link ResolvedAttachment.renamedFrom}.
32
+ * Archives: an attachment marked `extract` is a ZIP whose entries land under
33
+ * its mount as a DIRECTORY (`inputs/<mount>/<entry>`), validated and
34
+ * decompressed by `attachment-zip.ts` (the #567 guards). Each entry is its
35
+ * own {@link ResolvedAttachment} with no vision, no `renamedFrom` and no
36
+ * `downloadUrl`: an image inside a ZIP has no attachment-level bytes, a
37
+ * renamed mount directory is visible through every entry path, and the
38
+ * stored object is the ZIP, not any listed file.
31
39
  *
32
- * Error model: fail-hard, matching the native harness's attachment injector.
33
- * Attachments are explicit user inputs an execution that silently runs
34
- * without one produces silently incorrect results (the "plan file wasn't
35
- * found" class of failure). Any attachment that cannot be materialized aborts
36
- * the execution with an actionable error.
40
+ * Duplicate names are renamed, never overwritten (issue #364), in two passes:
41
+ * explicit mount paths claim their exact targets first two attachments
42
+ * pinning the SAME path is a user contradiction no rename can honestly
43
+ * resolve, so that alone still refuses and default-derived names then
44
+ * uniquify around everything already taken with the platform's `stem-2.ext`
45
+ * rename (shared/attachment-naming.ts, the React composer's semantics),
46
+ * disclosed in the prompt via {@link ResolvedAttachment.renamedFrom}.
47
+ *
48
+ * Error model: fail-hard. Attachments are explicit user inputs — an
49
+ * execution that silently runs without one produces silently incorrect
50
+ * results (the "plan file wasn't found" class of failure). Any attachment
51
+ * that cannot be materialized aborts the execution with an actionable
52
+ * {@link AttachmentResolutionError}; an archive that fails its guards
53
+ * aborts with the guard's own `AttachmentValidationError`.
54
+ *
55
+ * Until S3 M1 this resolver ignored `mountPath` and could not extract; the
56
+ * native harness's `attachment-injector.ts` did both and was retired at S3
57
+ * M2b for this one pipeline (Q-S3-14).
37
58
  */
38
59
  import { mkdir, copyFile, readFile, stat, writeFile } from "node:fs/promises";
39
- import { join, basename } from "node:path";
60
+ import { join, basename, dirname, posix } from "node:path";
40
61
  import { mintAttachmentDownloadUrl } from "./attachment-download-urls.js";
41
62
  import { allocateUniqueName } from "./attachment-naming.js";
63
+ import { decompressEntry, parseAndValidateZip } from "./attachment-zip.js";
42
64
  import { isVisionCandidate, } from "./attachment-vision.js";
43
65
  import { getPlatformDir } from "./workspace/platform-dir.js";
44
66
  import { ensureStigmerSymlink, STIGMER_LOCAL_STATE_DIR } from "./workspace/stigmer-link.js";
@@ -69,25 +91,36 @@ export async function resolveAttachments(attachments, options) {
69
91
  // every resolved path below would dangle (the skill resolver also ensures
70
92
  // it, but only when the agent has skills).
71
93
  await ensureStigmerSymlink(options.primaryWorkspaceDir, platformDir);
72
- // Placement keys purely on the filename, so this set is the whole
73
- // collision domain sequential resolution means each attachment sees
74
- // every name claimed before it (see module doc on duplicate handling).
75
- const takenNames = new Set();
94
+ // Every place is decided before any bytes move: an explicit-path
95
+ // contradiction refuses up front, and a default name never lands on a path
96
+ // an explicit one claims (see module doc on duplicate handling).
97
+ const places = resolvePlacements(attachments);
76
98
  const results = [];
77
99
  for (const attachment of attachments) {
78
- results.push(await resolveAttachment(attachment, inputsDir, takenNames, options));
100
+ const place = places.get(attachment);
101
+ if (attachment.extract) {
102
+ results.push(...(await extractArchive(attachment, place, inputsDir, options)));
103
+ }
104
+ else {
105
+ results.push(await resolveAttachment(attachment, place, inputsDir, options));
106
+ }
79
107
  }
80
108
  console.log(`[attachment-resolver] resolved ${results.length} attachment(s): ` +
81
109
  results.map((r) => r.relativePath).join(", "));
82
110
  return results;
83
111
  }
84
- async function resolveAttachment(attachment, inputsDir, takenNames, options) {
112
+ async function resolveAttachment(attachment, place, inputsDir, options) {
113
+ const { relative, renamedFrom } = place;
114
+ // The final basename is the canonical filename: after a duplicate rename
115
+ // the original attachment.filename no longer names the file on disk, and
116
+ // the vision label must match what the prompt lists.
117
+ const filename = posix.basename(relative);
118
+ await mkdir(dirname(join(inputsDir, relative)), { recursive: true });
85
119
  // Local-mode fast path: the file is already on this machine's disk.
86
120
  if (options.mode === "local" && attachment.localPath) {
87
- const { name: filename, renamedFrom } = allocateUniqueName(safeInputName(attachment.filename || attachment.localPath), takenNames);
88
121
  let vision;
89
122
  try {
90
- vision = await materializeLocalFile(attachment, filename, inputsDir, options.visionBudget);
123
+ vision = await materializeLocalFile(attachment, filename, join(inputsDir, relative), options.visionBudget);
91
124
  }
92
125
  catch (err) {
93
126
  throw new AttachmentResolutionError(attachment.filename, `failed to copy local file '${attachment.localPath}': ` +
@@ -99,13 +132,70 @@ async function resolveAttachment(attachment, inputsDir, takenNames, options) {
99
132
  const downloadUrl = await mintAttachmentDownloadUrl(options.storage, attachment.storageKey, filename);
100
133
  return {
101
134
  filename,
102
- relativePath: join(STIGMER_LOCAL_STATE_DIR, INPUTS_SUBDIR, filename),
135
+ relativePath: join(STIGMER_LOCAL_STATE_DIR, INPUTS_SUBDIR, relative),
136
+ sizeBytes: (await stat(join(inputsDir, relative))).size,
103
137
  ...(renamedFrom !== undefined ? { renamedFrom } : {}),
104
138
  ...visionOutcomeFields(vision),
105
139
  ...(downloadUrl !== undefined ? { downloadUrl } : {}),
106
140
  };
107
141
  }
108
142
  // Universal path: download the uploaded content by storage key.
143
+ const content = await downloadFromStorage(attachment, options);
144
+ await writeFile(join(inputsDir, relative), content);
145
+ // The bytes are already in hand for the file write — offer them to the
146
+ // vision budget before they go out of scope (the sniff decides eligibility;
147
+ // no pre-filter needed on this branch).
148
+ const vision = options.visionBudget?.offer(filename, attachment.contentType, content);
149
+ const downloadUrl = await mintAttachmentDownloadUrl(options.storage, attachment.storageKey, filename);
150
+ return {
151
+ filename,
152
+ relativePath: join(STIGMER_LOCAL_STATE_DIR, INPUTS_SUBDIR, relative),
153
+ sizeBytes: content.byteLength,
154
+ ...(renamedFrom !== undefined ? { renamedFrom } : {}),
155
+ ...visionOutcomeFields(vision),
156
+ ...(downloadUrl !== undefined ? { downloadUrl } : {}),
157
+ };
158
+ }
159
+ /**
160
+ * Extract an `extract` attachment's archive under its mount directory. The
161
+ * bytes are read whole (an archive is never a vision candidate, so the local
162
+ * fast path's lazy read has nothing to save), validated and decompressed by
163
+ * the shared guards, and written entry by entry; the entries are the
164
+ * resolved attachments (see the module doc for what they do not carry).
165
+ */
166
+ async function extractArchive(attachment, place, inputsDir, options) {
167
+ const content = await bytesOf(attachment, options);
168
+ const entries = parseAndValidateZip(content, attachment.filename);
169
+ const mountDir = place.relative.replace(/\/+$/, "");
170
+ const results = [];
171
+ for (const entry of entries) {
172
+ const bytes = await decompressEntry(entry, attachment.filename);
173
+ const relative = posix.join(mountDir, entry.relativePath);
174
+ const dest = join(inputsDir, relative);
175
+ await mkdir(dirname(dest), { recursive: true });
176
+ await writeFile(dest, bytes);
177
+ results.push({
178
+ filename: posix.basename(entry.relativePath),
179
+ relativePath: join(STIGMER_LOCAL_STATE_DIR, INPUTS_SUBDIR, relative),
180
+ sizeBytes: bytes.byteLength,
181
+ });
182
+ }
183
+ return results;
184
+ }
185
+ /** The attachment's bytes, from the local fast path or from storage. */
186
+ async function bytesOf(attachment, options) {
187
+ if (options.mode === "local" && attachment.localPath) {
188
+ try {
189
+ return await readFile(attachment.localPath);
190
+ }
191
+ catch (err) {
192
+ throw new AttachmentResolutionError(attachment.filename, `failed to read local file '${attachment.localPath}': ` +
193
+ `${err instanceof Error ? err.message : String(err)}`);
194
+ }
195
+ }
196
+ return downloadFromStorage(attachment, options);
197
+ }
198
+ async function downloadFromStorage(attachment, options) {
109
199
  if (!attachment.storageKey) {
110
200
  throw new AttachmentResolutionError(attachment.filename, "missing storageKey — cannot download attachment from storage");
111
201
  }
@@ -113,28 +203,71 @@ async function resolveAttachment(attachment, inputsDir, takenNames, options) {
113
203
  throw new AttachmentResolutionError(attachment.filename, `artifact storage is unavailable, so this attachment ` +
114
204
  `(key: ${attachment.storageKey}) cannot be downloaded`);
115
205
  }
116
- const { name: filename, renamedFrom } = allocateUniqueName(safeInputName(attachment.filename || attachment.storageKey), takenNames);
117
- let content;
118
206
  try {
119
- content = await options.storage.download(attachment.storageKey);
207
+ return await options.storage.download(attachment.storageKey);
120
208
  }
121
209
  catch (err) {
122
210
  throw new AttachmentResolutionError(attachment.filename, `failed to download from storage (key: ${attachment.storageKey}): ` +
123
211
  `${err instanceof Error ? err.message : String(err)}`);
124
212
  }
125
- await writeFile(join(inputsDir, filename), content);
126
- // The bytes are already in hand for the file write — offer them to the
127
- // vision budget before they go out of scope (the sniff decides eligibility;
128
- // no pre-filter needed on this branch).
129
- const vision = options.visionBudget?.offer(filename, attachment.contentType, content);
130
- const downloadUrl = await mintAttachmentDownloadUrl(options.storage, attachment.storageKey, filename);
131
- return {
132
- filename,
133
- relativePath: join(STIGMER_LOCAL_STATE_DIR, INPUTS_SUBDIR, filename),
134
- ...(renamedFrom !== undefined ? { renamedFrom } : {}),
135
- ...visionOutcomeFields(vision),
136
- ...(downloadUrl !== undefined ? { downloadUrl } : {}),
137
- };
213
+ }
214
+ /**
215
+ * The two-pass placement (see module doc): explicit mount paths first, each
216
+ * resolved into the namespace and refused on a contradiction; then the
217
+ * default-derived names, uniquified around every name already taken directly
218
+ * under `inputs/`.
219
+ */
220
+ function resolvePlacements(attachments) {
221
+ const places = new Map();
222
+ const claimed = new Map();
223
+ for (const attachment of attachments) {
224
+ if (!attachment.mountPath)
225
+ continue;
226
+ const relative = explicitPlacement(attachment);
227
+ const existing = claimed.get(relative);
228
+ if (existing) {
229
+ throw new AttachmentResolutionError(attachment.filename, `mount path '${attachment.mountPath}' collides with attachment '${existing.filename}'. ` +
230
+ "Set distinct mountPath values on the attachments to resolve this conflict.");
231
+ }
232
+ claimed.set(relative, attachment);
233
+ places.set(attachment, { relative });
234
+ }
235
+ const takenNames = new Set();
236
+ for (const relative of claimed.keys()) {
237
+ if (!relative.includes("/"))
238
+ takenNames.add(relative);
239
+ }
240
+ for (const attachment of attachments) {
241
+ if (attachment.mountPath)
242
+ continue;
243
+ const { name, renamedFrom } = allocateUniqueName(safeInputName(attachment.filename || attachment.localPath || attachment.storageKey), takenNames);
244
+ places.set(attachment, renamedFrom !== undefined ? { relative: name, renamedFrom } : { relative: name });
245
+ }
246
+ return places;
247
+ }
248
+ /** The namespace prefixes an explicit `mountPath` may name, longest first. */
249
+ const INPUTS_NAMESPACE_PREFIXES = [`${STIGMER_LOCAL_STATE_DIR}/${INPUTS_SUBDIR}`, INPUTS_SUBDIR];
250
+ /**
251
+ * Resolve an explicit `mountPath` into a path relative to `inputs/`, or
252
+ * refuse: empty after cleaning, outside the namespace, or climbing out of it.
253
+ * A caller-supplied path is untrusted, so `..` is judged after normalization.
254
+ */
255
+ function explicitPlacement(attachment) {
256
+ const cleaned = attachment.mountPath.replace(/^\/+/, "").replace(/\/+$/, "");
257
+ const prefix = INPUTS_NAMESPACE_PREFIXES.find((p) => cleaned === p || cleaned.startsWith(`${p}/`));
258
+ if (prefix === undefined) {
259
+ throw new AttachmentResolutionError(attachment.filename, `mount path '${attachment.mountPath}' is outside the attachment namespace. ` +
260
+ `Attachments land under '${INPUTS_SUBDIR}/' (surfaced as '${STIGMER_LOCAL_STATE_DIR}/${INPUTS_SUBDIR}/'); ` +
261
+ `set mountPath to '${INPUTS_SUBDIR}/<name>' or leave it empty for the default placement.`);
262
+ }
263
+ const relative = posix.normalize(cleaned.slice(prefix.length).replace(/^\/+/, ""));
264
+ if (relative === "" || relative === ".") {
265
+ throw new AttachmentResolutionError(attachment.filename, `mount path '${attachment.mountPath}' names the inputs directory itself, not a place inside it`);
266
+ }
267
+ if (relative === ".." || relative.startsWith("../") || posix.isAbsolute(relative)) {
268
+ throw new AttachmentResolutionError(attachment.filename, `mount path '${attachment.mountPath}' escapes the attachment namespace`);
269
+ }
270
+ return relative;
138
271
  }
139
272
  /**
140
273
  * Materialize a local-path attachment, reading the bytes only when they are
@@ -143,8 +276,7 @@ async function resolveAttachment(attachment, inputsDir, takenNames, options) {
143
276
  * enters memory. Returns the vision outcome, or `undefined` when vision is
144
277
  * disabled or the file is not a candidate.
145
278
  */
146
- async function materializeLocalFile(attachment, filename, inputsDir, visionBudget) {
147
- const dest = join(inputsDir, filename);
279
+ async function materializeLocalFile(attachment, filename, dest, visionBudget) {
148
280
  if (!visionBudget || !isVisionCandidate(attachment.contentType, filename)) {
149
281
  await copyFile(attachment.localPath, dest);
150
282
  return undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"attachment-resolver.js","sourceRoot":"","sources":["../../src/shared/attachment-resolver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"attachment-resolver.js","sourceRoot":"","sources":["../../src/shared/attachment-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AAEH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9E,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAG3D,OAAO,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EACL,iBAAiB,GAKlB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAE5F,MAAM,aAAa,GAAG,QAAQ,CAAC;AAuD/B,MAAM,OAAO,yBAA0B,SAAQ,KAAK;IACzC,kBAAkB,CAAS;IAC3B,MAAM,CAAS;IAExB,YAAY,kBAA0B,EAAE,MAAc;QACpD,KAAK,CAAC,eAAe,kBAAkB,MAAM,MAAM,EAAE,CAAC,CAAC;QACvD,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;QACxC,IAAI,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;QAC7C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,WAAyB,EACzB,OAAkC;IAElC,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAExC,MAAM,WAAW,GAAG,cAAc,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IACnD,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE5C,2EAA2E;IAC3E,0EAA0E;IAC1E,2CAA2C;IAC3C,MAAM,oBAAoB,CAAC,OAAO,CAAC,mBAAmB,EAAE,WAAW,CAAC,CAAC;IAErE,iEAAiE;IACjE,2EAA2E;IAC3E,iEAAiE;IACjE,MAAM,MAAM,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAyB,EAAE,CAAC;IACzC,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,CAAE,CAAC;QACtC,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;YACvB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,cAAc,CAAC,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;QACjF,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC,MAAM,iBAAiB,CAAC,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;QAC/E,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,CACT,kCAAkC,OAAO,CAAC,MAAM,kBAAkB;QAClE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAC9C,CAAC;IAEF,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,KAAK,UAAU,iBAAiB,CAC9B,UAAsB,EACtB,KAAgB,EAChB,SAAiB,EACjB,OAAkC;IAElC,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,KAAK,CAAC;IACxC,yEAAyE;IACzE,yEAAyE;IACzE,qDAAqD;IACrD,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAErE,oEAAoE;IACpE,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,IAAI,UAAU,CAAC,SAAS,EAAE,CAAC;QACrD,IAAI,MAAiC,CAAC;QACtC,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,oBAAoB,CAAC,UAAU,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;QAC7G,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,yBAAyB,CACjC,UAAU,CAAC,QAAQ,EACnB,8BAA8B,UAAU,CAAC,SAAS,KAAK;gBACvD,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACtD,CAAC;QACJ,CAAC;QACD,sEAAsE;QACtE,wEAAwE;QACxE,sDAAsD;QACtD,MAAM,WAAW,GAAG,MAAM,yBAAyB,CACjD,OAAO,CAAC,OAAO,EAAE,UAAU,CAAC,UAAU,EAAE,QAAQ,CACjD,CAAC;QACF,OAAO;YACL,QAAQ;YACR,YAAY,EAAE,IAAI,CAAC,uBAAuB,EAAE,aAAa,EAAE,QAAQ,CAAC;YACpE,SAAS,EAAE,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI;YACvD,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrD,GAAG,mBAAmB,CAAC,MAAM,CAAC;YAC9B,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACtD,CAAC;IACJ,CAAC;IAED,gEAAgE;IAChE,MAAM,OAAO,GAAG,MAAM,mBAAmB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC/D,MAAM,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;IAEpD,uEAAuE;IACvE,4EAA4E;IAC5E,wCAAwC;IACxC,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,KAAK,CAAC,QAAQ,EAAE,UAAU,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAEtF,MAAM,WAAW,GAAG,MAAM,yBAAyB,CACjD,OAAO,CAAC,OAAO,EAAE,UAAU,CAAC,UAAU,EAAE,QAAQ,CACjD,CAAC;IAEF,OAAO;QACL,QAAQ;QACR,YAAY,EAAE,IAAI,CAAC,uBAAuB,EAAE,aAAa,EAAE,QAAQ,CAAC;QACpE,SAAS,EAAE,OAAO,CAAC,UAAU;QAC7B,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrD,GAAG,mBAAmB,CAAC,MAAM,CAAC;QAC9B,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtD,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,cAAc,CAC3B,UAAsB,EACtB,KAAgB,EAChB,SAAiB,EACjB,OAAkC;IAElC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,mBAAmB,CAAC,OAAO,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;IAClE,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACpD,MAAM,OAAO,GAAyB,EAAE,CAAC;IACzC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,KAAK,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;QAChE,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;QAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QACvC,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,MAAM,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC7B,OAAO,CAAC,IAAI,CAAC;YACX,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC;YAC5C,YAAY,EAAE,IAAI,CAAC,uBAAuB,EAAE,aAAa,EAAE,QAAQ,CAAC;YACpE,SAAS,EAAE,KAAK,CAAC,UAAU;SAC5B,CAAC,CAAC;IACL,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,wEAAwE;AACxE,KAAK,UAAU,OAAO,CAAC,UAAsB,EAAE,OAAkC;IAC/E,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,IAAI,UAAU,CAAC,SAAS,EAAE,CAAC;QACrD,IAAI,CAAC;YACH,OAAO,MAAM,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAC9C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,yBAAyB,CACjC,UAAU,CAAC,QAAQ,EACnB,8BAA8B,UAAU,CAAC,SAAS,KAAK;gBACvD,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACtD,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,mBAAmB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AAClD,CAAC;AAED,KAAK,UAAU,mBAAmB,CAAC,UAAsB,EAAE,OAAkC;IAC3F,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;QAC3B,MAAM,IAAI,yBAAyB,CACjC,UAAU,CAAC,QAAQ,EACnB,8DAA8D,CAC/D,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACrB,MAAM,IAAI,yBAAyB,CACjC,UAAU,CAAC,QAAQ,EACnB,sDAAsD;YACtD,SAAS,UAAU,CAAC,UAAU,wBAAwB,CACvD,CAAC;IACJ,CAAC;IACD,IAAI,CAAC;QACH,OAAO,MAAM,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;IAC/D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,yBAAyB,CACjC,UAAU,CAAC,QAAQ,EACnB,yCAAyC,UAAU,CAAC,UAAU,KAAK;YACnE,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACtD,CAAC;IACJ,CAAC;AACH,CAAC;AAWD;;;;;GAKG;AACH,SAAS,iBAAiB,CAAC,WAAkC;IAC3D,MAAM,MAAM,GAAG,IAAI,GAAG,EAAyB,CAAC;IAChD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAsB,CAAC;IAE9C,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,IAAI,CAAC,UAAU,CAAC,SAAS;YAAE,SAAS;QACpC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAC;QAC/C,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,IAAI,yBAAyB,CACjC,UAAU,CAAC,QAAQ,EACnB,eAAe,UAAU,CAAC,SAAS,+BAA+B,QAAQ,CAAC,QAAQ,KAAK;gBACxF,4EAA4E,CAC7E,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QAClC,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;IACvC,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,KAAK,MAAM,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QACtC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACxD,CAAC;IACD,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,IAAI,UAAU,CAAC,SAAS;YAAE,SAAS;QACnC,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,kBAAkB,CAC9C,aAAa,CAAC,UAAU,CAAC,QAAQ,IAAI,UAAU,CAAC,SAAS,IAAI,UAAU,CAAC,UAAU,CAAC,EACnF,UAAU,CACX,CAAC;QACF,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3G,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,8EAA8E;AAC9E,MAAM,yBAAyB,GAAG,CAAC,GAAG,uBAAuB,IAAI,aAAa,EAAE,EAAE,aAAa,CAAU,CAAC;AAE1G;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,UAAsB;IAC/C,MAAM,OAAO,GAAG,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC7E,MAAM,MAAM,GAAG,yBAAyB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,KAAK,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACnG,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,IAAI,yBAAyB,CACjC,UAAU,CAAC,QAAQ,EACnB,eAAe,UAAU,CAAC,SAAS,yCAAyC;YAC5E,2BAA2B,aAAa,oBAAoB,uBAAuB,IAAI,aAAa,OAAO;YAC3G,qBAAqB,aAAa,uDAAuD,CAC1F,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;IACnF,IAAI,QAAQ,KAAK,EAAE,IAAI,QAAQ,KAAK,GAAG,EAAE,CAAC;QACxC,MAAM,IAAI,yBAAyB,CACjC,UAAU,CAAC,QAAQ,EACnB,eAAe,UAAU,CAAC,SAAS,4DAA4D,CAChG,CAAC;IACJ,CAAC;IACD,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAClF,MAAM,IAAI,yBAAyB,CACjC,UAAU,CAAC,QAAQ,EACnB,eAAe,UAAU,CAAC,SAAS,oCAAoC,CACxE,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,oBAAoB,CACjC,UAAsB,EACtB,QAAgB,EAChB,IAAY,EACZ,YAAsC;IAEtC,IAAI,CAAC,YAAY,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC1E,MAAM,QAAQ,CAAC,UAAU,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QAC3C,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,2EAA2E;IAC3E,2EAA2E;IAC3E,gEAAgE;IAChE,IAAI,YAAY,CAAC,cAAc,EAAE,EAAE,CAAC;QAClC,MAAM,QAAQ,CAAC,UAAU,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QAC3C,OAAO,YAAY,CAAC,UAAU,EAAE,CAAC;IACnC,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAC9C,IAAI,YAAY,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5C,MAAM,QAAQ,CAAC,UAAU,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QAC3C,OAAO,YAAY,CAAC,cAAc,EAAE,CAAC;IACvC,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IACrD,MAAM,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC/B,OAAO,YAAY,CAAC,KAAK,CAAC,QAAQ,EAAE,UAAU,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;AACvE,CAAC;AAED,SAAS,mBAAmB,CAC1B,OAAkC;IAElC,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACnE,OAAO,OAAO,CAAC,IAAI,KAAK,UAAU;QAChC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,KAAK,EAAE;QAC3B,CAAC,CAAC,EAAE,cAAc,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;AACzC,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,aAAa,CAAC,GAAW;IAChC,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QACjD,MAAM,IAAI,yBAAyB,CACjC,GAAG,EACH,IAAI,GAAG,wDAAwD,CAChE,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,85 @@
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
+ export declare const MAX_ZIP_FILES = 1000;
32
+ export declare const MAX_ZIP_EXTRACTED_SIZE: number;
33
+ export interface ZipEntryInfo {
34
+ readonly relativePath: string;
35
+ readonly uncompressedSize: number;
36
+ }
37
+ /**
38
+ * A validated file entry, still carrying its payload slice. Validation and
39
+ * extraction share these records — parsing happens exactly once, so "the
40
+ * manifest promised a file extraction cannot find" is unrepresentable
41
+ * (the old two-walker design silently skipped such entries).
42
+ */
43
+ export interface ValidatedZipEntry extends ZipEntryInfo {
44
+ readonly compressionMethod: number;
45
+ readonly compressedData: Uint8Array;
46
+ }
47
+ /** An archive the user handed us cannot be safely extracted; the reason is theirs to fix. */
48
+ export declare class AttachmentValidationError extends Error {
49
+ readonly attachmentFilename: string;
50
+ readonly reason: string;
51
+ constructor(attachmentFilename: string, reason: string);
52
+ }
53
+ /**
54
+ * Validate a ZIP archive for safe extraction. Returns the manifest of
55
+ * file entries (directories excluded) if the archive passes all checks.
56
+ *
57
+ * Security checks enforced:
58
+ * 1. Valid ZIP format (readable central directory; fail-hard, see module doc)
59
+ * 2. No absolute paths (entries starting with / or \)
60
+ * 3. No path traversal (.. components)
61
+ * 4. No null bytes in filenames
62
+ * 5. No duplicate entry paths (a contradictory manifest)
63
+ * 6. Only supported compression methods (stored, deflate) — checked here
64
+ * so an unsupported entry can never abort extraction after earlier
65
+ * entries were already written
66
+ * 7. Non-empty archive (at least one file entry)
67
+ * 8. File count within limits (max 1000)
68
+ * 9. Total declared uncompressed size within limits (max 100 MB) —
69
+ * declarations are re-enforced against actual output at decompression
70
+ */
71
+ export declare function validateZipForExtraction(zipData: Buffer, sourceFilename: string): ZipEntryInfo[];
72
+ /** The same checks as {@link validateZipForExtraction}, keeping each entry's payload for the extraction that follows. */
73
+ export declare function parseAndValidateZip(zipData: Buffer, sourceFilename: string): ValidatedZipEntry[];
74
+ /**
75
+ * Decompress a validated entry, enforcing its declared uncompressed size.
76
+ *
77
+ * The size cap in parseAndValidateZip budgets *declared* sizes, which the
78
+ * archive author controls — a crafted archive can declare 1 KB and inflate
79
+ * to gigabytes. Enforcement is therefore two-sided and fail-hard: inflation
80
+ * aborts the moment output exceeds the declaration, and an undershoot (or a
81
+ * stored payload whose length disagrees) fails too, because a manifest that
82
+ * misdescribes its own contents is exactly the corrupt-input class this
83
+ * module must never extract from.
84
+ */
85
+ export declare function decompressEntry(entry: ValidatedZipEntry, sourceFilename: string): Promise<Buffer>;
@@ -0,0 +1,185 @@
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
+ import { createInflateRaw } from "node:zlib";
32
+ import { posix } from "node:path";
33
+ import { EOCD_MIN_SIZE, parseZipStructure, } from "@stigmer/zip-structure";
34
+ // ── Constants ────────────────────────────────────────────────────────
35
+ export const MAX_ZIP_FILES = 1000;
36
+ export const MAX_ZIP_EXTRACTED_SIZE = 100 * 1024 * 1024; // 100 MB
37
+ // ── Errors ───────────────────────────────────────────────────────────
38
+ /** An archive the user handed us cannot be safely extracted; the reason is theirs to fix. */
39
+ export class AttachmentValidationError extends Error {
40
+ attachmentFilename;
41
+ reason;
42
+ constructor(attachmentFilename, reason) {
43
+ super(`Attachment '${attachmentFilename}': ${reason}`);
44
+ this.name = "AttachmentValidationError";
45
+ this.attachmentFilename = attachmentFilename;
46
+ this.reason = reason;
47
+ }
48
+ }
49
+ // ── ZIP Validation (pure functions) ──────────────────────────────────
50
+ /**
51
+ * Validate a ZIP archive for safe extraction. Returns the manifest of
52
+ * file entries (directories excluded) if the archive passes all checks.
53
+ *
54
+ * Security checks enforced:
55
+ * 1. Valid ZIP format (readable central directory; fail-hard, see module doc)
56
+ * 2. No absolute paths (entries starting with / or \)
57
+ * 3. No path traversal (.. components)
58
+ * 4. No null bytes in filenames
59
+ * 5. No duplicate entry paths (a contradictory manifest)
60
+ * 6. Only supported compression methods (stored, deflate) — checked here
61
+ * so an unsupported entry can never abort extraction after earlier
62
+ * entries were already written
63
+ * 7. Non-empty archive (at least one file entry)
64
+ * 8. File count within limits (max 1000)
65
+ * 9. Total declared uncompressed size within limits (max 100 MB) —
66
+ * declarations are re-enforced against actual output at decompression
67
+ */
68
+ export function validateZipForExtraction(zipData, sourceFilename) {
69
+ return parseAndValidateZip(zipData, sourceFilename).map(({ relativePath, uncompressedSize }) => ({ relativePath, uncompressedSize }));
70
+ }
71
+ /** The same checks as {@link validateZipForExtraction}, keeping each entry's payload for the extraction that follows. */
72
+ export function parseAndValidateZip(zipData, sourceFilename) {
73
+ if (zipData.length < EOCD_MIN_SIZE) {
74
+ throw new AttachmentValidationError(sourceFilename, "not a valid ZIP archive (file too small)");
75
+ }
76
+ let structural;
77
+ try {
78
+ structural = parseZipStructure(zipData);
79
+ }
80
+ catch (err) {
81
+ throw new AttachmentValidationError(sourceFilename, `not a valid ZIP archive (${err instanceof Error ? err.message : String(err)})`);
82
+ }
83
+ const entries = [];
84
+ const seenPaths = new Set();
85
+ let totalUncompressed = 0;
86
+ for (const entry of structural) {
87
+ if (entry.name.includes("\u0000")) {
88
+ throw new AttachmentValidationError(sourceFilename, "contains a filename with null bytes and cannot be safely extracted");
89
+ }
90
+ if (entry.isDirectory)
91
+ continue;
92
+ if (entry.name.startsWith("/") || entry.name.startsWith("\\")) {
93
+ throw new AttachmentValidationError(sourceFilename, `contains an absolute path entry and cannot be safely extracted: ${entry.name}`);
94
+ }
95
+ if (hasPathTraversal(entry.name)) {
96
+ throw new AttachmentValidationError(sourceFilename, `contains a path traversal entry and cannot be safely extracted: ${entry.name}`);
97
+ }
98
+ if (seenPaths.has(entry.name)) {
99
+ throw new AttachmentValidationError(sourceFilename, `contains duplicate entry '${entry.name}' and cannot be safely extracted`);
100
+ }
101
+ seenPaths.add(entry.name);
102
+ if (entry.compressionMethod !== 0 && entry.compressionMethod !== 8) {
103
+ throw new AttachmentValidationError(sourceFilename, `entry '${entry.name}' uses unsupported compression method ${entry.compressionMethod}`);
104
+ }
105
+ entries.push({
106
+ relativePath: entry.name,
107
+ uncompressedSize: entry.uncompressedSize,
108
+ compressionMethod: entry.compressionMethod,
109
+ compressedData: entry.compressedData,
110
+ });
111
+ totalUncompressed += entry.uncompressedSize;
112
+ }
113
+ if (entries.length === 0) {
114
+ throw new AttachmentValidationError(sourceFilename, "is an empty ZIP archive (no file entries)");
115
+ }
116
+ if (entries.length > MAX_ZIP_FILES) {
117
+ throw new AttachmentValidationError(sourceFilename, `contains ${entries.length} files (limit: ${MAX_ZIP_FILES})`);
118
+ }
119
+ if (totalUncompressed > MAX_ZIP_EXTRACTED_SIZE) {
120
+ const sizeMb = (totalUncompressed / (1024 * 1024)).toFixed(1);
121
+ const limitMb = (MAX_ZIP_EXTRACTED_SIZE / (1024 * 1024)).toFixed(0);
122
+ throw new AttachmentValidationError(sourceFilename, `would extract to ${sizeMb} MB (limit: ${limitMb} MB)`);
123
+ }
124
+ return entries.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
125
+ }
126
+ // ── Decompression ────────────────────────────────────────────────────
127
+ /**
128
+ * Decompress a validated entry, enforcing its declared uncompressed size.
129
+ *
130
+ * The size cap in parseAndValidateZip budgets *declared* sizes, which the
131
+ * archive author controls — a crafted archive can declare 1 KB and inflate
132
+ * to gigabytes. Enforcement is therefore two-sided and fail-hard: inflation
133
+ * aborts the moment output exceeds the declaration, and an undershoot (or a
134
+ * stored payload whose length disagrees) fails too, because a manifest that
135
+ * misdescribes its own contents is exactly the corrupt-input class this
136
+ * module must never extract from.
137
+ */
138
+ export async function decompressEntry(entry, sourceFilename) {
139
+ const declared = entry.uncompressedSize;
140
+ if (entry.compressionMethod === 0) {
141
+ if (entry.compressedData.length !== declared) {
142
+ throw new AttachmentValidationError(sourceFilename, `entry '${entry.relativePath}' payload is ${entry.compressedData.length} bytes ` +
143
+ `but the archive declares ${declared} — corrupt or crafted archive`);
144
+ }
145
+ return Buffer.from(entry.compressedData);
146
+ }
147
+ // Deflate — the only other method parseAndValidateZip admits.
148
+ return new Promise((resolve, reject) => {
149
+ const inflate = createInflateRaw();
150
+ const chunks = [];
151
+ let produced = 0;
152
+ inflate.on("data", (chunk) => {
153
+ produced += chunk.length;
154
+ if (produced > declared) {
155
+ inflate.destroy();
156
+ reject(new AttachmentValidationError(sourceFilename, `entry '${entry.relativePath}' decompresses past its declared size of ` +
157
+ `${declared} bytes — corrupt or crafted archive`));
158
+ return;
159
+ }
160
+ chunks.push(chunk);
161
+ });
162
+ inflate.on("end", () => {
163
+ if (produced !== declared) {
164
+ reject(new AttachmentValidationError(sourceFilename, `entry '${entry.relativePath}' decompressed to ${produced} bytes ` +
165
+ `but the archive declares ${declared} — corrupt or crafted archive`));
166
+ return;
167
+ }
168
+ resolve(Buffer.concat(chunks));
169
+ });
170
+ inflate.on("error", reject);
171
+ inflate.end(Buffer.from(entry.compressedData));
172
+ });
173
+ }
174
+ function hasPathTraversal(filePath) {
175
+ const normalized = posix.normalize(filePath);
176
+ if (normalized.startsWith("../") || normalized === "..")
177
+ return true;
178
+ const segments = filePath.split(/[/\\]/);
179
+ for (const seg of segments) {
180
+ if (seg === "..")
181
+ return true;
182
+ }
183
+ return false;
184
+ }
185
+ //# sourceMappingURL=attachment-zip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachment-zip.js","sourceRoot":"","sources":["../../src/shared/attachment-zip.ts"],"names":[],"mappings":"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"}