@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.
- package/README.md +2 -4
- package/dist/.build-fingerprint +1 -1
- package/dist/__test-utils__/execution-record-fixture.d.ts +14 -0
- package/dist/__test-utils__/execution-record-fixture.js +4 -0
- package/dist/__test-utils__/execution-record-fixture.js.map +1 -1
- package/dist/__test-utils__/file-review-projection.d.ts +40 -0
- package/dist/__test-utils__/file-review-projection.js +136 -0
- package/dist/__test-utils__/file-review-projection.js.map +1 -0
- package/dist/__test-utils__/harness-contract/types.d.ts +23 -1
- package/dist/__test-utils__/harness-contract/types.js +15 -0
- package/dist/__test-utils__/harness-contract/types.js.map +1 -1
- package/dist/__test-utils__/hermetic-activity.d.ts +44 -12
- package/dist/__test-utils__/hermetic-activity.js +52 -14
- package/dist/__test-utils__/hermetic-activity.js.map +1 -1
- package/dist/__test-utils__/model-registry-fixture.d.ts +32 -7
- package/dist/__test-utils__/model-registry-fixture.js +31 -7
- package/dist/__test-utils__/model-registry-fixture.js.map +1 -1
- package/dist/__test-utils__/turn-input-fixture.d.ts +1 -1
- package/dist/__test-utils__/turn-input-fixture.js +1 -1
- package/dist/__test-utils__/turn-input-fixture.js.map +1 -1
- package/dist/activities/call-agent.js +2 -2
- package/dist/activities/call-agent.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/contract-subject.js +45 -3
- package/dist/activities/execute-cursor/__test-utils__/contract-subject.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.d.ts +0 -9
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +2 -41
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -1
- package/dist/activities/execute-cursor/adapter.d.ts +7 -5
- package/dist/activities/execute-cursor/adapter.js +7 -5
- package/dist/activities/execute-cursor/adapter.js.map +1 -1
- package/dist/activities/execute-cursor/approval-policy.d.ts +5 -2
- package/dist/activities/execute-cursor/approval-policy.js +5 -2
- package/dist/activities/execute-cursor/approval-policy.js.map +1 -1
- package/dist/activities/execute-cursor/approval-state.d.ts +16 -8
- package/dist/activities/execute-cursor/approval-state.js +6 -3
- package/dist/activities/execute-cursor/approval-state.js.map +1 -1
- package/dist/activities/execute-cursor/cas-observations.d.ts +14 -2
- package/dist/activities/execute-cursor/cas-observations.js +19 -2
- package/dist/activities/execute-cursor/cas-observations.js.map +1 -1
- package/dist/activities/execute-cursor/cursor-capabilities.d.ts +8 -4
- package/dist/activities/execute-cursor/cursor-capabilities.js +21 -4
- package/dist/activities/execute-cursor/cursor-capabilities.js.map +1 -1
- package/dist/activities/execute-cursor/message-translator.d.ts +12 -20
- package/dist/activities/execute-cursor/message-translator.js +19 -32
- package/dist/activities/execute-cursor/message-translator.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +48 -58
- package/dist/activities/execute-cursor/prompt-builder.js +76 -95
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/turn-boundary.d.ts +27 -55
- package/dist/activities/execute-cursor/turn-boundary.js +30 -90
- package/dist/activities/execute-cursor/turn-boundary.js.map +1 -1
- package/dist/activities/execute-cursor/turn-settle.d.ts +11 -7
- package/dist/activities/execute-cursor/turn-settle.js +37 -49
- package/dist/activities/execute-cursor/turn-settle.js.map +1 -1
- package/dist/activities/execute-cursor/turn-setup.d.ts +16 -24
- package/dist/activities/execute-cursor/turn-setup.js +29 -59
- package/dist/activities/execute-cursor/turn-setup.js.map +1 -1
- package/dist/activities/execute-cursor/turn-stream.d.ts +3 -8
- package/dist/activities/execute-cursor/turn-stream.js +9 -22
- package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
- package/dist/activities/execute-cursor/turn.js +5 -1
- package/dist/activities/execute-cursor/turn.js.map +1 -1
- package/dist/activities/execute-deep-agent/__test-utils__/contract-subject.d.ts +93 -0
- package/dist/activities/execute-deep-agent/__test-utils__/contract-subject.js +348 -0
- package/dist/activities/execute-deep-agent/__test-utils__/contract-subject.js.map +1 -0
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.d.ts +170 -0
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js +225 -0
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js.map +1 -0
- package/dist/activities/execute-deep-agent/__test-utils__/hitl-script.d.ts +24 -0
- package/dist/activities/execute-deep-agent/__test-utils__/hitl-script.js +41 -0
- package/dist/activities/execute-deep-agent/__test-utils__/hitl-script.js.map +1 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.d.ts +54 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.js +72 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.js.map +1 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.d.ts +187 -23
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js +238 -27
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js.map +1 -1
- package/dist/activities/execute-deep-agent/adapter.d.ts +41 -0
- package/dist/activities/execute-deep-agent/adapter.js +78 -0
- package/dist/activities/execute-deep-agent/adapter.js.map +1 -0
- package/dist/activities/execute-deep-agent/cas-capture-observer.d.ts +8 -0
- package/dist/activities/execute-deep-agent/cas-capture-observer.js +9 -0
- package/dist/activities/execute-deep-agent/cas-capture-observer.js.map +1 -1
- package/dist/activities/execute-deep-agent/deep-agent-capabilities.d.ts +31 -0
- package/dist/activities/execute-deep-agent/deep-agent-capabilities.js +40 -0
- package/dist/activities/execute-deep-agent/deep-agent-capabilities.js.map +1 -0
- package/dist/activities/execute-deep-agent/execution-state.d.ts +4 -11
- package/dist/activities/execute-deep-agent/execution-state.js +4 -12
- package/dist/activities/execute-deep-agent/execution-state.js.map +1 -1
- package/dist/activities/execute-deep-agent/hitl.d.ts +49 -47
- package/dist/activities/execute-deep-agent/hitl.js +77 -103
- package/dist/activities/execute-deep-agent/hitl.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +51 -23
- package/dist/activities/execute-deep-agent/prompt-builder.js +115 -76
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/status-builder-shared.d.ts +18 -32
- package/dist/activities/execute-deep-agent/status-builder-shared.js +10 -65
- package/dist/activities/execute-deep-agent/status-builder-shared.js.map +1 -1
- package/dist/activities/execute-deep-agent/streaming-side-effects.d.ts +10 -8
- package/dist/activities/execute-deep-agent/streaming-side-effects.js +12 -14
- package/dist/activities/execute-deep-agent/streaming-side-effects.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-tracker.d.ts +36 -13
- package/dist/activities/execute-deep-agent/subagent-tracker.js +76 -39
- package/dist/activities/execute-deep-agent/subagent-tracker.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +25 -28
- package/dist/activities/execute-deep-agent/subagent-transformer.js +29 -82
- package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +3 -2
- package/dist/activities/execute-deep-agent/subagent-wiring.js +2 -2
- package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
- package/dist/activities/execute-deep-agent/turn-settle.d.ts +42 -0
- package/dist/activities/execute-deep-agent/turn-settle.js +146 -0
- package/dist/activities/execute-deep-agent/turn-settle.js.map +1 -0
- package/dist/activities/execute-deep-agent/turn-setup.d.ts +212 -0
- package/dist/activities/execute-deep-agent/turn-setup.js +430 -0
- package/dist/activities/execute-deep-agent/turn-setup.js.map +1 -0
- package/dist/activities/execute-deep-agent/turn-stream.d.ts +97 -0
- package/dist/activities/execute-deep-agent/turn-stream.js +242 -0
- package/dist/activities/execute-deep-agent/turn-stream.js.map +1 -0
- package/dist/activities/execute-deep-agent/turn.d.ts +42 -0
- package/dist/activities/execute-deep-agent/turn.js +116 -0
- package/dist/activities/execute-deep-agent/turn.js.map +1 -0
- package/dist/activities/execute-deep-agent/v3-event-recorder.d.ts +6 -5
- package/dist/activities/execute-deep-agent/v3-event-recorder.js +6 -5
- package/dist/activities/execute-deep-agent/v3-event-recorder.js.map +1 -1
- package/dist/activities/execute-deep-agent/v3-events.d.ts +1 -1
- package/dist/activities/execute-deep-agent/v3-events.js +1 -1
- package/dist/activities/execute-deep-agent/v3-status-builder.d.ts +60 -17
- package/dist/activities/execute-deep-agent/v3-status-builder.js +72 -57
- package/dist/activities/execute-deep-agent/v3-status-builder.js.map +1 -1
- package/dist/harness/approval-decisions.d.ts +61 -0
- package/dist/harness/approval-decisions.js +102 -0
- package/dist/harness/approval-decisions.js.map +1 -0
- package/dist/harness/capabilities.d.ts +16 -5
- package/dist/harness/capabilities.js +9 -0
- package/dist/harness/capabilities.js.map +1 -1
- package/dist/harness/capture.d.ts +144 -0
- package/dist/harness/capture.js +219 -0
- package/dist/harness/capture.js.map +1 -0
- package/dist/harness/persist-chokepoint.d.ts +19 -4
- package/dist/harness/persist-chokepoint.js +17 -4
- package/dist/harness/persist-chokepoint.js.map +1 -1
- package/dist/harness/run-turn.js +115 -29
- package/dist/harness/run-turn.js.map +1 -1
- package/dist/harness/terminal-table.d.ts +40 -11
- package/dist/harness/terminal-table.js +58 -19
- package/dist/harness/terminal-table.js.map +1 -1
- package/dist/harness/turn-context.d.ts +141 -76
- package/dist/harness/turn-context.js +194 -99
- package/dist/harness/turn-context.js.map +1 -1
- package/dist/harness/types.d.ts +65 -12
- package/dist/harness/types.js +8 -2
- package/dist/harness/types.js.map +1 -1
- package/dist/harness-adapters.d.ts +12 -8
- package/dist/harness-adapters.js +17 -9
- package/dist/harness-adapters.js.map +1 -1
- package/dist/middleware/approval-gate.d.ts +1 -1
- package/dist/middleware/approval-gate.js +1 -1
- package/dist/middleware/approval-gate.js.map +1 -1
- package/dist/middleware/cost-advisory.d.ts +39 -0
- package/dist/middleware/{cost-cap.js → cost-advisory.js} +37 -55
- package/dist/middleware/cost-advisory.js.map +1 -0
- package/dist/middleware/index.d.ts +23 -12
- package/dist/middleware/index.js +26 -18
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/types.d.ts +3 -2
- package/dist/runner-manager.js +1 -5
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.js +1 -5
- package/dist/runner.js.map +1 -1
- package/dist/shared/attachment-resolver.d.ts +41 -18
- package/dist/shared/attachment-resolver.js +178 -46
- package/dist/shared/attachment-resolver.js.map +1 -1
- package/dist/shared/attachment-zip.d.ts +85 -0
- package/dist/shared/attachment-zip.js +185 -0
- package/dist/shared/attachment-zip.js.map +1 -0
- package/dist/shared/connect-backfill.js +5 -4
- package/dist/shared/connect-backfill.js.map +1 -1
- package/dist/shared/cost-guard.d.ts +3 -3
- package/dist/shared/cost-guard.js +3 -3
- package/dist/shared/execution-status-writer.d.ts +5 -5
- package/dist/shared/execution-status-writer.js +5 -5
- package/dist/shared/extract-structured-output.d.ts +8 -7
- package/dist/shared/extract-structured-output.js +8 -7
- package/dist/shared/extract-structured-output.js.map +1 -1
- package/dist/shared/filereview/cas-progress.d.ts +1 -18
- package/dist/shared/filereview/cas-progress.js +4 -0
- package/dist/shared/filereview/cas-progress.js.map +1 -1
- package/dist/shared/filereview/cas-touched.d.ts +56 -0
- package/dist/shared/filereview/cas-touched.js +63 -0
- package/dist/shared/filereview/cas-touched.js.map +1 -0
- package/dist/shared/filereview/index.d.ts +2 -1
- package/dist/shared/filereview/index.js +2 -1
- package/dist/shared/filereview/index.js.map +1 -1
- package/dist/shared/mcp-resolver.d.ts +4 -5
- package/dist/shared/mcp-resolver.js +4 -5
- package/dist/shared/mcp-resolver.js.map +1 -1
- package/dist/shared/persist-decision.d.ts +3 -4
- package/dist/shared/persist-decision.js +3 -4
- package/dist/shared/persist-decision.js.map +1 -1
- package/dist/shared/plan-mode-permissions.d.ts +2 -2
- package/dist/shared/plan-mode-permissions.js +2 -2
- package/dist/shared/plan-mode-prompt.d.ts +1 -1
- package/dist/shared/plan-mode-prompt.js +1 -1
- package/dist/shared/prompt-sections.d.ts +163 -0
- package/dist/shared/prompt-sections.js +164 -0
- package/dist/shared/prompt-sections.js.map +1 -0
- package/dist/shared/skill-resolver.d.ts +4 -2
- package/dist/shared/skill-resolver.js +4 -2
- package/dist/shared/skill-resolver.js.map +1 -1
- package/dist/shared/tool-rounds.d.ts +32 -0
- package/dist/shared/tool-rounds.js +43 -0
- package/dist/shared/tool-rounds.js.map +1 -1
- package/dist/shared/tool-row.d.ts +34 -0
- package/dist/shared/tool-row.js +52 -0
- package/dist/shared/tool-row.js.map +1 -1
- package/dist/shared/workspace/session-provision.d.ts +3 -3
- package/dist/shared/workspace/session-provision.js +3 -3
- package/dist/shared/workspace/stigmer-link.d.ts +3 -3
- package/dist/shared/workspace/stigmer-link.js +3 -3
- package/dist/shared/workspace/writeback-coordinator.d.ts +15 -22
- package/dist/shared/workspace/writeback-coordinator.js +16 -53
- package/dist/shared/workspace/writeback-coordinator.js.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +1 -1
- package/package.json +4 -4
- package/src/__test-utils__/__tests__/harness-contract-self-check.test.ts +1 -0
- package/src/__test-utils__/execution-record-fixture.ts +18 -2
- package/src/__test-utils__/file-review-projection.ts +156 -0
- package/src/__test-utils__/git-workspace-fixture.ts +58 -0
- package/src/__test-utils__/harness-boot-order-child.ts +7 -2
- package/src/__test-utils__/harness-contract/contract.ts +33 -20
- package/src/__test-utils__/harness-contract/recording-sink.ts +11 -1
- package/src/__test-utils__/harness-contract/runtime-contract.ts +328 -10
- package/src/__test-utils__/harness-contract/scripted-adapter.ts +38 -6
- package/src/__test-utils__/harness-contract/types.ts +27 -3
- package/src/__test-utils__/hermetic-activity.ts +52 -14
- package/src/__test-utils__/model-registry-fixture.ts +32 -7
- package/src/__test-utils__/module-specifiers.ts +24 -0
- package/src/__test-utils__/turn-input-fixture.ts +2 -2
- package/src/__tests__/harness-boot-order.test.ts +9 -2
- package/src/activities/call-agent.ts +2 -2
- package/src/activities/execute-cursor/__test-utils__/contract-subject.ts +47 -3
- package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +2 -48
- package/src/activities/execute-cursor/__tests__/approval-decisions-agree.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +6 -1
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +52 -27
- package/src/activities/execute-cursor/__tests__/cas-observations.test.ts +25 -0
- package/src/activities/execute-cursor/__tests__/goldens/prompt.enhanced.everything.prompt.md +166 -0
- package/src/activities/execute-cursor/__tests__/goldens/prompt.enhanced.plan-mode.prompt.md +186 -0
- package/src/activities/execute-cursor/__tests__/goldens/prompt.hitl.recovery.prompt.md +191 -0
- package/src/activities/execute-cursor/__tests__/goldens/prompt.hitl.reinvocation.prompt.md +13 -0
- package/src/activities/execute-cursor/__tests__/goldens/prompt.resumed.prefixed.prompt.md +32 -0
- package/src/activities/execute-cursor/__tests__/goldens/prompt.resumed.raw.prompt.md +1 -0
- package/src/activities/execute-cursor/__tests__/hermetic/file-review-capture.test.ts +1 -2
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/sdk-error-at-create.status.json +4 -0
- package/src/activities/execute-cursor/__tests__/hermetic/harness-contract.test.ts +24 -4
- package/src/activities/execute-cursor/__tests__/hermetic/stream-self-stop-arms.test.ts +6 -5
- package/src/activities/execute-cursor/__tests__/hermetic/thrown-error-arms.test.ts +9 -0
- package/src/activities/execute-cursor/__tests__/hitl-resume-history.test.ts +11 -8
- package/src/activities/execute-cursor/__tests__/prompt-goldens.test.ts +229 -0
- package/src/activities/execute-cursor/__tests__/same-identity-reproposal.test.ts +3 -3
- package/src/activities/execute-cursor/__tests__/sequential-gate-resume.test.ts +2 -2
- package/src/activities/execute-cursor/__tests__/turn-boundary.test.ts +78 -234
- package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +1 -26
- package/src/activities/execute-cursor/adapter.ts +7 -5
- package/src/activities/execute-cursor/approval-policy.ts +5 -2
- package/src/activities/execute-cursor/approval-state.ts +16 -8
- package/src/activities/execute-cursor/cas-observations.ts +20 -2
- package/src/activities/execute-cursor/cursor-capabilities.ts +24 -5
- package/src/activities/execute-cursor/message-translator.ts +29 -41
- package/src/activities/execute-cursor/prompt-builder.ts +99 -157
- package/src/activities/execute-cursor/turn-boundary.ts +34 -140
- package/src/activities/execute-cursor/turn-settle.ts +37 -51
- package/src/activities/execute-cursor/turn-setup.ts +30 -70
- package/src/activities/execute-cursor/turn-stream.ts +8 -31
- package/src/activities/execute-cursor/turn.ts +5 -1
- package/src/activities/execute-deep-agent/__test-utils__/contract-subject.ts +391 -0
- package/src/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.ts +313 -0
- package/src/activities/execute-deep-agent/__test-utils__/hitl-script.ts +48 -0
- package/src/activities/execute-deep-agent/__test-utils__/scripted-model-module.ts +85 -0
- package/src/activities/execute-deep-agent/__test-utils__/scripted-model.ts +383 -36
- package/src/activities/execute-deep-agent/__tests__/adapter-graph-is-sdk-free.test.ts +76 -0
- package/src/activities/execute-deep-agent/__tests__/adapter-is-temporal-free.test.ts +47 -0
- package/src/activities/execute-deep-agent/__tests__/adapter.test.ts +80 -0
- package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +1 -1
- package/src/activities/execute-deep-agent/__tests__/execution-state-extended.test.ts +0 -2
- package/src/activities/execute-deep-agent/__tests__/execution-state.test.ts +0 -3
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.build-from-plan.prompt.md +172 -0
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.everything.prompt.md +167 -0
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.minimal.prompt.md +36 -0
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.plan-mode.prompt.md +178 -0
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.skills-below-threshold.prompt.md +158 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/file-review-capture.test.ts +210 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve-all-lease.turn1.status.json +74 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve-all-lease.turn2.status.json +104 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve.turn2.status.json +78 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/file-review-capture.status.json +138 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/gate.turn1.status.json +68 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/inline-artifact.turn1.status.json +154 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/inline-artifact.turn2.status.json +116 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/pause.loop.after-tool.status.json +69 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/pause.loop.mid-tool.status.json +67 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/plain-turn.status.json +47 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/platform-stop.status.json +68 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/recursion-limit.persisted.status.json +280 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/reject.turn2.status.json +77 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/resolution-error.status.json +18 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sequential-gates.turn2.status.json +99 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sequential-gates.turn3.status.json +109 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/skip.turn2.status.json +76 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/structured-output.text-fallback.status.json +51 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/structured-output.tier2.status.json +51 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/structured-output.tool-strategy.status.json +56 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sub-agent-delegation.status.json +90 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/tool-call.status.json +70 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/worker-shutdown.status.json +71 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/harness-contract.test.ts +144 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-approve-all-lease.test.ts +184 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-round-trips.test.ts +203 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-sequential-gates.test.ts +170 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/inline-artifact-across-gate.test.ts +175 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/pause-vs-shutdown.test.ts +206 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/plain-turn.test.ts +142 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/platform-stop.test.ts +126 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/recursion-limit.test.ts +139 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/resolution-error.test.ts +122 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/structured-output.test.ts +236 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/sub-agent-delegation.test.ts +161 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/tool-call.test.ts +157 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/workspace-lock-timeout.test.ts +152 -0
- package/src/activities/execute-deep-agent/__tests__/hitl.test.ts +50 -122
- package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +8 -6
- package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +2 -2
- package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +35 -35
- package/src/activities/execute-deep-agent/__tests__/prompt-goldens.test.ts +247 -0
- package/src/activities/execute-deep-agent/__tests__/scripted-model.test.ts +329 -0
- package/src/activities/execute-deep-agent/__tests__/status-builder-shared.test.ts +1 -20
- package/src/activities/execute-deep-agent/__tests__/streaming-side-effects.test.ts +78 -0
- package/src/activities/execute-deep-agent/__tests__/subagent-gitignored-capture.test.ts +1 -1
- package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +2 -2
- package/src/activities/execute-deep-agent/__tests__/subagent-tracker.test.ts +57 -31
- package/src/activities/execute-deep-agent/__tests__/subagent-transformer.test.ts +19 -79
- package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +15 -15
- package/src/activities/execute-deep-agent/__tests__/summarization-verification.test.ts +1 -1
- package/src/activities/execute-deep-agent/__tests__/turn-stream.test.ts +391 -0
- package/src/activities/execute-deep-agent/__tests__/v3-status-builder.test.ts +343 -38
- package/src/activities/execute-deep-agent/__tests__/vision-input.test.ts +5 -5
- package/src/activities/execute-deep-agent/adapter.ts +94 -0
- package/src/activities/execute-deep-agent/cas-capture-observer.ts +11 -0
- package/src/activities/execute-deep-agent/deep-agent-capabilities.ts +43 -0
- package/src/activities/execute-deep-agent/execution-state.ts +4 -13
- package/src/activities/execute-deep-agent/hitl.ts +100 -123
- package/src/activities/execute-deep-agent/prompt-builder.ts +141 -115
- package/src/activities/execute-deep-agent/status-builder-shared.ts +21 -84
- package/src/activities/execute-deep-agent/streaming-side-effects.ts +13 -19
- package/src/activities/execute-deep-agent/subagent-tracker.ts +77 -42
- package/src/activities/execute-deep-agent/subagent-transformer.ts +46 -105
- package/src/activities/execute-deep-agent/subagent-wiring.ts +5 -4
- package/src/activities/execute-deep-agent/turn-settle.ts +164 -0
- package/src/activities/execute-deep-agent/turn-setup.ts +541 -0
- package/src/activities/execute-deep-agent/turn-stream.ts +313 -0
- package/src/activities/execute-deep-agent/turn.ts +131 -0
- package/src/activities/execute-deep-agent/v3-event-recorder.ts +6 -5
- package/src/activities/execute-deep-agent/v3-events.ts +1 -1
- package/src/activities/execute-deep-agent/v3-status-builder.ts +84 -63
- package/src/harness/__tests__/approval-decisions.test.ts +116 -0
- package/src/harness/__tests__/capture.test.ts +268 -0
- package/src/harness/__tests__/mount-skills.test.ts +169 -0
- package/src/harness/__tests__/persist-chokepoint.test.ts +38 -2
- package/src/harness/__tests__/run-turn-deterministic.test.ts +94 -0
- package/src/harness/__tests__/run-turn.test.ts +39 -23
- package/src/harness/__tests__/turn-attachments-vision.test.ts +80 -0
- package/src/harness/__tests__/turn-context.test.ts +73 -26
- package/src/harness/approval-decisions.ts +104 -0
- package/src/harness/capabilities.ts +16 -5
- package/src/harness/capture.ts +301 -0
- package/src/harness/persist-chokepoint.ts +24 -4
- package/src/harness/run-turn.ts +120 -29
- package/src/harness/terminal-table.ts +61 -20
- package/src/harness/turn-context.ts +208 -111
- package/src/harness/types.ts +66 -12
- package/src/harness-adapters.ts +17 -9
- package/src/middleware/__tests__/approval-gate.test.ts +1 -1
- package/src/middleware/__tests__/cost-advisory.test.ts +113 -0
- package/src/middleware/approval-gate.ts +2 -2
- package/src/middleware/{cost-cap.ts → cost-advisory.ts} +40 -70
- package/src/middleware/index.ts +29 -29
- package/src/middleware/types.ts +3 -2
- package/src/runner-manager.ts +0 -8
- package/src/runner.ts +0 -8
- package/src/shared/__tests__/attachment-naming.test.ts +6 -19
- package/src/shared/__tests__/attachment-resolver.test.ts +158 -8
- package/src/shared/__tests__/attachment-zip.test.ts +236 -0
- package/src/shared/__tests__/bedrock-adapter.test.ts +1 -1
- package/src/shared/__tests__/blueprint-resolver.test.ts +43 -0
- package/src/shared/__tests__/cost-guard.test.ts +3 -3
- package/src/shared/__tests__/prompt-sections.test.ts +167 -0
- package/src/shared/__tests__/secret-leak-scan.test.ts +2 -2
- package/src/{activities/execute-deep-agent → shared}/__tests__/stamp-flowed-rows.test.ts +33 -10
- package/src/shared/__tests__/vertex-adapter.test.ts +1 -1
- package/src/shared/attachment-resolver.ts +218 -55
- package/src/shared/attachment-zip.ts +281 -0
- package/src/shared/connect-backfill.ts +9 -11
- package/src/shared/cost-guard.ts +3 -3
- package/src/shared/execution-status-writer.ts +5 -5
- package/src/shared/extract-structured-output.ts +8 -7
- package/src/shared/filereview/__tests__/capture.test.ts +86 -4
- package/src/shared/filereview/__tests__/cas-progress.test.ts +2 -1
- package/src/shared/filereview/cas-progress.ts +5 -19
- package/src/shared/filereview/cas-touched.ts +89 -0
- package/src/shared/filereview/index.ts +4 -2
- package/src/shared/mcp-resolver.ts +4 -5
- package/src/shared/persist-decision.ts +3 -4
- package/src/shared/plan-mode-permissions.ts +2 -2
- package/src/shared/plan-mode-prompt.ts +1 -1
- package/src/shared/prompt-sections.ts +256 -0
- package/src/shared/skill-resolver.ts +4 -2
- package/src/shared/tool-rounds.ts +48 -0
- package/src/shared/tool-row.ts +61 -0
- package/src/shared/workspace/__tests__/stigmer-link.test.ts +7 -22
- package/src/shared/workspace/__tests__/writeback-coordinator.test.ts +20 -61
- package/src/shared/workspace/session-provision.ts +3 -3
- package/src/shared/workspace/stigmer-link.ts +3 -3
- package/src/shared/workspace/writeback-coordinator.ts +16 -64
- package/src/tools/index.ts +1 -1
- package/dist/activities/execute-cursor/capture-flow.d.ts +0 -142
- package/dist/activities/execute-cursor/capture-flow.js +0 -285
- package/dist/activities/execute-cursor/capture-flow.js.map +0 -1
- package/dist/activities/execute-cursor/command-provenance.d.ts +0 -48
- package/dist/activities/execute-cursor/command-provenance.js +0 -38
- package/dist/activities/execute-cursor/command-provenance.js.map +0 -1
- package/dist/activities/execute-deep-agent/attachment-injector.d.ts +0 -134
- package/dist/activities/execute-deep-agent/attachment-injector.js +0 -386
- package/dist/activities/execute-deep-agent/attachment-injector.js.map +0 -1
- package/dist/activities/execute-deep-agent/command-provenance.d.ts +0 -61
- package/dist/activities/execute-deep-agent/command-provenance.js +0 -72
- package/dist/activities/execute-deep-agent/command-provenance.js.map +0 -1
- package/dist/activities/execute-deep-agent/environment.d.ts +0 -24
- package/dist/activities/execute-deep-agent/environment.js +0 -58
- package/dist/activities/execute-deep-agent/environment.js.map +0 -1
- package/dist/activities/execute-deep-agent/event-recorder.d.ts +0 -21
- package/dist/activities/execute-deep-agent/event-recorder.js +0 -67
- package/dist/activities/execute-deep-agent/event-recorder.js.map +0 -1
- package/dist/activities/execute-deep-agent/index.d.ts +0 -15
- package/dist/activities/execute-deep-agent/index.js +0 -918
- package/dist/activities/execute-deep-agent/index.js.map +0 -1
- package/dist/activities/execute-deep-agent/mcp-gate.d.ts +0 -28
- package/dist/activities/execute-deep-agent/mcp-gate.js +0 -22
- package/dist/activities/execute-deep-agent/mcp-gate.js.map +0 -1
- package/dist/activities/execute-deep-agent/post-stream.d.ts +0 -23
- package/dist/activities/execute-deep-agent/post-stream.js +0 -71
- package/dist/activities/execute-deep-agent/post-stream.js.map +0 -1
- package/dist/activities/execute-deep-agent/setup.d.ts +0 -136
- package/dist/activities/execute-deep-agent/setup.js +0 -767
- package/dist/activities/execute-deep-agent/setup.js.map +0 -1
- package/dist/activities/execute-deep-agent/stamp-flowed-rows.d.ts +0 -36
- package/dist/activities/execute-deep-agent/stamp-flowed-rows.js +0 -56
- package/dist/activities/execute-deep-agent/stamp-flowed-rows.js.map +0 -1
- package/dist/activities/execute-deep-agent/status-builder.d.ts +0 -93
- package/dist/activities/execute-deep-agent/status-builder.js +0 -365
- package/dist/activities/execute-deep-agent/status-builder.js.map +0 -1
- package/dist/activities/execute-deep-agent/streaming-terminal.d.ts +0 -21
- package/dist/activities/execute-deep-agent/streaming-terminal.js +0 -80
- package/dist/activities/execute-deep-agent/streaming-terminal.js.map +0 -1
- package/dist/activities/execute-deep-agent/streaming-v3.d.ts +0 -13
- package/dist/activities/execute-deep-agent/streaming-v3.js +0 -174
- package/dist/activities/execute-deep-agent/streaming-v3.js.map +0 -1
- package/dist/activities/execute-deep-agent/streaming.d.ts +0 -81
- package/dist/activities/execute-deep-agent/streaming.js +0 -160
- package/dist/activities/execute-deep-agent/streaming.js.map +0 -1
- package/dist/middleware/cost-cap.d.ts +0 -22
- package/dist/middleware/cost-cap.js.map +0 -1
- package/dist/middleware/graceful-stop.d.ts +0 -17
- package/dist/middleware/graceful-stop.js +0 -63
- package/dist/middleware/graceful-stop.js.map +0 -1
- package/dist/shared/skill-writer.d.ts +0 -75
- package/dist/shared/skill-writer.js +0 -207
- package/dist/shared/skill-writer.js.map +0 -1
- package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +0 -1032
- package/src/activities/execute-cursor/__tests__/command-provenance.test.ts +0 -240
- package/src/activities/execute-cursor/__tests__/progress-substrate.test.ts +0 -169
- package/src/activities/execute-cursor/capture-flow.ts +0 -379
- package/src/activities/execute-cursor/command-provenance.ts +0 -73
- package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +0 -1161
- package/src/activities/execute-deep-agent/__tests__/command-provenance.test.ts +0 -252
- package/src/activities/execute-deep-agent/__tests__/environment.test.ts +0 -108
- package/src/activities/execute-deep-agent/__tests__/event-recorder.test.ts +0 -150
- package/src/activities/execute-deep-agent/__tests__/hitl-integration.test.ts +0 -226
- package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +0 -318
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +0 -351
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +0 -293
- package/src/activities/execute-deep-agent/__tests__/index.test.ts +0 -99
- package/src/activities/execute-deep-agent/__tests__/mcp-gate.test.ts +0 -44
- package/src/activities/execute-deep-agent/__tests__/post-stream.test.ts +0 -112
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +0 -358
- package/src/activities/execute-deep-agent/__tests__/status-builder.test.ts +0 -1933
- package/src/activities/execute-deep-agent/__tests__/streaming-terminal.test.ts +0 -40
- package/src/activities/execute-deep-agent/__tests__/streaming-v3.test.ts +0 -597
- package/src/activities/execute-deep-agent/__tests__/streaming.test.ts +0 -508
- package/src/activities/execute-deep-agent/attachment-injector.ts +0 -627
- package/src/activities/execute-deep-agent/command-provenance.ts +0 -102
- package/src/activities/execute-deep-agent/environment.ts +0 -76
- package/src/activities/execute-deep-agent/event-recorder.ts +0 -95
- package/src/activities/execute-deep-agent/index.ts +0 -1072
- package/src/activities/execute-deep-agent/mcp-gate.ts +0 -37
- package/src/activities/execute-deep-agent/post-stream.ts +0 -109
- package/src/activities/execute-deep-agent/setup.ts +0 -1081
- package/src/activities/execute-deep-agent/stamp-flowed-rows.ts +0 -64
- package/src/activities/execute-deep-agent/status-builder.ts +0 -481
- package/src/activities/execute-deep-agent/streaming-terminal.ts +0 -106
- package/src/activities/execute-deep-agent/streaming-v3.ts +0 -277
- package/src/activities/execute-deep-agent/streaming.ts +0 -313
- package/src/middleware/__tests__/cost-cap.test.ts +0 -192
- package/src/middleware/__tests__/graceful-stop.test.ts +0 -105
- package/src/middleware/graceful-stop.ts +0 -86
- package/src/shared/__tests__/skill-writer.test.ts +0 -372
- 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
|
|
18
|
-
* harness
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* never
|
|
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
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
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
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
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
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
const
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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,
|
|
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":"AAAA
|
|
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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"}
|