@stigmer/runner 3.15.1 → 3.15.2-dev.20260913223433
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
package/src/runner-manager.ts
CHANGED
|
@@ -226,11 +226,6 @@ export async function createStigmerRunnerManager(
|
|
|
226
226
|
// in-process embedders like the desktop never execute main.ts.
|
|
227
227
|
captureRunnerSecrets();
|
|
228
228
|
|
|
229
|
-
const { registerStigmerDeepagentsProfiles } = await import(
|
|
230
|
-
"./activities/execute-deep-agent/deepagents-profiles.js"
|
|
231
|
-
);
|
|
232
|
-
registerStigmerDeepagentsProfiles();
|
|
233
|
-
|
|
234
229
|
// `tokenRef` holds the control-plane token (the host's durable credential,
|
|
235
230
|
// e.g. the desktop's Auth0 token) and is shared with activity clients via
|
|
236
231
|
// config.stigmerTokenRef. The proxy credential (x-stigmer-auth) is managed
|
|
@@ -745,7 +740,6 @@ async function createAllActivities(config: Config): Promise<WorkerActivities> {
|
|
|
745
740
|
const [
|
|
746
741
|
{ HARNESS_ADAPTERS },
|
|
747
742
|
{ createHarnessActivities },
|
|
748
|
-
{ createDeepAgentActivities },
|
|
749
743
|
{ createEnsureThreadActivities },
|
|
750
744
|
{ createGenerateSessionSubjectActivities },
|
|
751
745
|
{ createClassifyToolApprovalsActivities },
|
|
@@ -765,7 +759,6 @@ async function createAllActivities(config: Config): Promise<WorkerActivities> {
|
|
|
765
759
|
] = await Promise.all([
|
|
766
760
|
import("./harness-adapters.js"),
|
|
767
761
|
import("./harness/registry.js"),
|
|
768
|
-
import("./activities/execute-deep-agent/index.js"),
|
|
769
762
|
import("./activities/ensure-thread.js"),
|
|
770
763
|
import("./activities/generate-session-subject.js"),
|
|
771
764
|
import("./activities/classify-tool-approvals.js"),
|
|
@@ -786,7 +779,6 @@ async function createAllActivities(config: Config): Promise<WorkerActivities> {
|
|
|
786
779
|
|
|
787
780
|
return {
|
|
788
781
|
...(await createHarnessActivities(HARNESS_ADAPTERS, config)),
|
|
789
|
-
...createDeepAgentActivities(config),
|
|
790
782
|
...createEnsureThreadActivities(),
|
|
791
783
|
...createGenerateSessionSubjectActivities(config),
|
|
792
784
|
...createClassifyToolApprovalsActivities(config),
|
package/src/runner.ts
CHANGED
|
@@ -201,11 +201,6 @@ export async function createStigmerRunner(
|
|
|
201
201
|
// for in-process embedders.
|
|
202
202
|
captureRunnerSecrets();
|
|
203
203
|
|
|
204
|
-
const { registerStigmerDeepagentsProfiles } = await import(
|
|
205
|
-
"./activities/execute-deep-agent/deepagents-profiles.js"
|
|
206
|
-
);
|
|
207
|
-
registerStigmerDeepagentsProfiles();
|
|
208
|
-
|
|
209
204
|
const baseConfig = mapOptionsToConfig(options);
|
|
210
205
|
|
|
211
206
|
assertLlmBackendsPreflight(baseConfig.proxyEndpoint);
|
|
@@ -462,7 +457,6 @@ async function createAllActivities(config: Config): Promise<WorkerActivities> {
|
|
|
462
457
|
const [
|
|
463
458
|
{ HARNESS_ADAPTERS },
|
|
464
459
|
{ createHarnessActivities },
|
|
465
|
-
{ createDeepAgentActivities },
|
|
466
460
|
{ createEnsureThreadActivities },
|
|
467
461
|
{ createGenerateSessionSubjectActivities },
|
|
468
462
|
{ createClassifyToolApprovalsActivities },
|
|
@@ -482,7 +476,6 @@ async function createAllActivities(config: Config): Promise<WorkerActivities> {
|
|
|
482
476
|
] = await Promise.all([
|
|
483
477
|
import("./harness-adapters.js"),
|
|
484
478
|
import("./harness/registry.js"),
|
|
485
|
-
import("./activities/execute-deep-agent/index.js"),
|
|
486
479
|
import("./activities/ensure-thread.js"),
|
|
487
480
|
import("./activities/generate-session-subject.js"),
|
|
488
481
|
import("./activities/classify-tool-approvals.js"),
|
|
@@ -503,7 +496,6 @@ async function createAllActivities(config: Config): Promise<WorkerActivities> {
|
|
|
503
496
|
|
|
504
497
|
return {
|
|
505
498
|
...(await createHarnessActivities(HARNESS_ADAPTERS, config)),
|
|
506
|
-
...createDeepAgentActivities(config),
|
|
507
499
|
...createEnsureThreadActivities(),
|
|
508
500
|
...createGenerateSessionSubjectActivities(config),
|
|
509
501
|
...createClassifyToolApprovalsActivities(config),
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import { describe, it, expect,
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
2
|
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { uniquifyFilename, allocateUniqueName } from "../attachment-naming.js";
|
|
6
|
-
import { injectAttachments } from "../../activities/execute-deep-agent/attachment-injector.js";
|
|
7
6
|
import { resolveAttachments } from "../attachment-resolver.js";
|
|
8
7
|
import { getPlatformDir } from "../workspace/platform-dir.js";
|
|
9
8
|
import { makeInMemoryArtifactStorage } from "../../__test-utils__/fake-artifact-storage.js";
|
|
10
|
-
import type { WorkspaceBackend } from "../workspace/types.js";
|
|
11
9
|
|
|
12
10
|
describe("uniquifyFilename", () => {
|
|
13
11
|
// This table is the byte-for-byte twin of the React SDK suite
|
|
@@ -118,7 +116,7 @@ describe("cross-harness naming parity", () => {
|
|
|
118
116
|
} as any;
|
|
119
117
|
}
|
|
120
118
|
|
|
121
|
-
it("
|
|
119
|
+
it("the resolver produces the React SDK's basenames for the same duplicate-heavy list", async () => {
|
|
122
120
|
const { storage } = makeInMemoryArtifactStorage();
|
|
123
121
|
await storage.upload("attachments/01A/data.csv", Buffer.from("a"), "text/plain");
|
|
124
122
|
await storage.upload("attachments/01B/data.csv", Buffer.from("b"), "text/plain");
|
|
@@ -131,14 +129,8 @@ describe("cross-harness naming parity", () => {
|
|
|
131
129
|
makeAttachment("Makefile", "attachments/01D/Makefile"),
|
|
132
130
|
];
|
|
133
131
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
backend,
|
|
137
|
-
attachments,
|
|
138
|
-
storage,
|
|
139
|
-
isLocalMode: false,
|
|
140
|
-
});
|
|
141
|
-
|
|
132
|
+
// One attachment pipeline serves both harnesses since S3 M2b (Q-S3-14);
|
|
133
|
+
// the names it lands are the ones the React SDK's table above predicts.
|
|
142
134
|
const resolved = await resolveAttachments(attachments, {
|
|
143
135
|
sessionId,
|
|
144
136
|
primaryWorkspaceDir: workspaceDir,
|
|
@@ -146,13 +138,8 @@ describe("cross-harness naming parity", () => {
|
|
|
146
138
|
storage,
|
|
147
139
|
});
|
|
148
140
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
expect(injectedNames).toEqual(["data.csv", "data-2.csv", "Makefile", "Makefile-2"]);
|
|
152
|
-
expect(resolvedNames).toEqual(injectedNames);
|
|
153
|
-
expect(injected.map((f) => f.renamedFrom)).toEqual(
|
|
154
|
-
resolved.map((r) => r.renamedFrom),
|
|
155
|
-
);
|
|
141
|
+
expect(resolved.map((r) => r.filename)).toEqual(["data.csv", "data-2.csv", "Makefile", "Makefile-2"]);
|
|
142
|
+
expect(resolved.map((r) => r.renamedFrom)).toEqual([undefined, "data.csv", undefined, "Makefile"]);
|
|
156
143
|
// The resolver's files really land under the renamed paths.
|
|
157
144
|
expect(readFileSync(join(platformDir, "inputs", "data-2.csv"), "utf-8")).toBe("b");
|
|
158
145
|
});
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for the attachment resolver
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Tests for the attachment resolver — the turn runtime's attachment phase,
|
|
3
|
+
* the one pipeline for both harnesses since S3 M2b (the native twin,
|
|
4
|
+
* `attachment-injector.ts`, retired then; its ZIP guards moved to
|
|
5
|
+
* `attachment-zip.ts` at S3 M1).
|
|
5
6
|
*
|
|
6
7
|
* The load-bearing behaviors: storage-key attachments materialize under the
|
|
7
8
|
* platform inputs dir (the universal path — every server-created attachment
|
|
8
9
|
* carries a storage key), the workspace `.stigmer` symlink exists even when
|
|
9
10
|
* the agent has no skills, and any attachment that cannot be materialized
|
|
10
11
|
* fails the resolution loudly (the silent-skip regression behind "plan file
|
|
11
|
-
* wasn't found").
|
|
12
|
+
* wasn't found"). Since S3 M1 (Q-S3-14, Q-M1-2) the resolver is the one
|
|
13
|
+
* attachment pipeline for every harness, so it also pins the two behaviors
|
|
14
|
+
* lifted from the native injector: `extract` archives land under their mount
|
|
15
|
+
* directory through the shared #567 guards, and an explicit `mountPath` is
|
|
16
|
+
* honoured inside the `inputs/` namespace and refused outside it.
|
|
12
17
|
*/
|
|
13
18
|
|
|
14
19
|
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
@@ -16,6 +21,8 @@ import { mkdtempSync, writeFileSync, readFileSync, rmSync, lstatSync, readlinkSy
|
|
|
16
21
|
import { join } from "node:path";
|
|
17
22
|
import { tmpdir } from "node:os";
|
|
18
23
|
import { resolveAttachments, AttachmentResolutionError } from "../attachment-resolver.js";
|
|
24
|
+
import { AttachmentValidationError } from "../attachment-zip.js";
|
|
25
|
+
import { buildZip } from "@stigmer/zip-structure/testing";
|
|
19
26
|
import { getPlatformDir } from "../workspace/platform-dir.js";
|
|
20
27
|
import { makeInMemoryArtifactStorage } from "../../__test-utils__/fake-artifact-storage.js";
|
|
21
28
|
import { CURSOR_VISION_PROFILE, VisionBudget } from "../attachment-vision.js";
|
|
@@ -83,6 +90,7 @@ describe("resolveAttachments", () => {
|
|
|
83
90
|
{
|
|
84
91
|
filename: "plan.md",
|
|
85
92
|
relativePath: ".stigmer/inputs/plan.md",
|
|
93
|
+
sizeBytes: 10,
|
|
86
94
|
downloadUrl: "mem://attachments/01ABC/plan.md",
|
|
87
95
|
},
|
|
88
96
|
]);
|
|
@@ -115,7 +123,7 @@ describe("resolveAttachments", () => {
|
|
|
115
123
|
);
|
|
116
124
|
|
|
117
125
|
expect(result).toEqual([
|
|
118
|
-
{ filename: "data.csv", relativePath: ".stigmer/inputs/data.csv" },
|
|
126
|
+
{ filename: "data.csv", relativePath: ".stigmer/inputs/data.csv", sizeBytes: 5 },
|
|
119
127
|
]);
|
|
120
128
|
expect(readFileSync(join(platformDir, "inputs", "data.csv"), "utf-8")).toBe("a,b,c");
|
|
121
129
|
});
|
|
@@ -139,11 +147,13 @@ describe("resolveAttachments", () => {
|
|
|
139
147
|
{
|
|
140
148
|
filename: "report.pdf",
|
|
141
149
|
relativePath: ".stigmer/inputs/report.pdf",
|
|
150
|
+
sizeBytes: 11,
|
|
142
151
|
downloadUrl: "mem://attachments/01AAA/report.pdf",
|
|
143
152
|
},
|
|
144
153
|
{
|
|
145
154
|
filename: "report-2.pdf",
|
|
146
155
|
relativePath: ".stigmer/inputs/report-2.pdf",
|
|
156
|
+
sizeBytes: 12,
|
|
147
157
|
renamedFrom: "report.pdf",
|
|
148
158
|
downloadUrl: "mem://attachments/01BBB/report.pdf",
|
|
149
159
|
},
|
|
@@ -169,10 +179,11 @@ describe("resolveAttachments", () => {
|
|
|
169
179
|
// The key-less local file lists no URL; the storage twin does — the
|
|
170
180
|
// per-file split the prompt renders.
|
|
171
181
|
expect(result).toEqual([
|
|
172
|
-
{ filename: "notes.md", relativePath: ".stigmer/inputs/notes.md" },
|
|
182
|
+
{ filename: "notes.md", relativePath: ".stigmer/inputs/notes.md", sizeBytes: 10 },
|
|
173
183
|
{
|
|
174
184
|
filename: "notes-2.md",
|
|
175
185
|
relativePath: ".stigmer/inputs/notes-2.md",
|
|
186
|
+
sizeBytes: 13,
|
|
176
187
|
renamedFrom: "notes.md",
|
|
177
188
|
downloadUrl: "mem://attachments/01ABC/notes.md",
|
|
178
189
|
},
|
|
@@ -242,6 +253,7 @@ describe("resolveAttachments", () => {
|
|
|
242
253
|
{
|
|
243
254
|
filename: "evil.md",
|
|
244
255
|
relativePath: ".stigmer/inputs/evil.md",
|
|
256
|
+
sizeBytes: 9,
|
|
245
257
|
downloadUrl: "mem://attachments/01ABC/x",
|
|
246
258
|
},
|
|
247
259
|
]);
|
|
@@ -260,11 +272,148 @@ describe("resolveAttachments", () => {
|
|
|
260
272
|
);
|
|
261
273
|
|
|
262
274
|
expect(result).toEqual([
|
|
263
|
-
{ filename: "evil.txt", relativePath: ".stigmer/inputs/evil.txt" },
|
|
275
|
+
{ filename: "evil.txt", relativePath: ".stigmer/inputs/evil.txt", sizeBytes: 15 },
|
|
264
276
|
]);
|
|
265
277
|
expect(readFileSync(join(platformDir, "inputs", "evil.txt"), "utf-8")).toBe("local-contained");
|
|
266
278
|
});
|
|
267
279
|
|
|
280
|
+
// ── Explicit mountPath: honoured inside the inputs namespace, refused outside ──
|
|
281
|
+
|
|
282
|
+
describe("mountPath (S3 M1, Q-M1-2)", () => {
|
|
283
|
+
it("places a file at its explicit `inputs/…` path, in every spelling the producers use", async () => {
|
|
284
|
+
const { storage } = makeInMemoryArtifactStorage();
|
|
285
|
+
await storage.upload("k/a", Buffer.from("A"), "text/plain");
|
|
286
|
+
await storage.upload("k/b", Buffer.from("B"), "text/plain");
|
|
287
|
+
await storage.upload("k/c", Buffer.from("C"), "text/plain");
|
|
288
|
+
|
|
289
|
+
const result = await resolveAttachments(
|
|
290
|
+
[
|
|
291
|
+
makeAttachment({ filename: "a.txt", storageKey: "k/a", mountPath: "inputs/data/a.txt" }),
|
|
292
|
+
makeAttachment({ filename: "b.txt", storageKey: "k/b", mountPath: ".stigmer/inputs/b-renamed.txt" }),
|
|
293
|
+
makeAttachment({ filename: "c.txt", storageKey: "k/c", mountPath: "/inputs/c.txt" }),
|
|
294
|
+
],
|
|
295
|
+
options({ storage }),
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
expect(result.map((r) => r.relativePath)).toEqual([
|
|
299
|
+
".stigmer/inputs/data/a.txt",
|
|
300
|
+
".stigmer/inputs/b-renamed.txt",
|
|
301
|
+
".stigmer/inputs/c.txt",
|
|
302
|
+
]);
|
|
303
|
+
expect(result.map((r) => r.filename), "the on-disk basename, not the attachment's").toEqual(["a.txt", "b-renamed.txt", "c.txt"]);
|
|
304
|
+
expect(readFileSync(join(platformDir, "inputs", "data", "a.txt"), "utf-8")).toBe("A");
|
|
305
|
+
expect(readFileSync(join(platformDir, "inputs", "b-renamed.txt"), "utf-8")).toBe("B");
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("a default-named attachment uniquifies around a name an explicit path claims directly under inputs/", async () => {
|
|
309
|
+
const { storage } = makeInMemoryArtifactStorage();
|
|
310
|
+
await storage.upload("k/explicit", Buffer.from("explicit"), "text/plain");
|
|
311
|
+
await storage.upload("k/default", Buffer.from("default"), "text/plain");
|
|
312
|
+
|
|
313
|
+
const result = await resolveAttachments(
|
|
314
|
+
[
|
|
315
|
+
makeAttachment({ filename: "other.txt", storageKey: "k/explicit", mountPath: "inputs/plan.md" }),
|
|
316
|
+
makeAttachment({ filename: "plan.md", storageKey: "k/default" }),
|
|
317
|
+
],
|
|
318
|
+
options({ storage }),
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
expect(result.map(({ filename, relativePath, renamedFrom }) => ({ filename, relativePath, renamedFrom }))).toEqual([
|
|
322
|
+
{ filename: "plan.md", relativePath: ".stigmer/inputs/plan.md", renamedFrom: undefined },
|
|
323
|
+
{ filename: "plan-2.md", relativePath: ".stigmer/inputs/plan-2.md", renamedFrom: "plan.md" },
|
|
324
|
+
]);
|
|
325
|
+
expect(readFileSync(join(platformDir, "inputs", "plan.md"), "utf-8")).toBe("explicit");
|
|
326
|
+
expect(readFileSync(join(platformDir, "inputs", "plan-2.md"), "utf-8")).toBe("default");
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("refuses two attachments explicitly pinning the same path (a contradiction no rename resolves), before any bytes move", async () => {
|
|
330
|
+
const { storage } = makeInMemoryArtifactStorage();
|
|
331
|
+
await expect(
|
|
332
|
+
resolveAttachments(
|
|
333
|
+
[
|
|
334
|
+
makeAttachment({ filename: "a.csv", storageKey: "k/a", mountPath: "inputs/data.csv" }),
|
|
335
|
+
makeAttachment({ filename: "b.csv", storageKey: "k/b", mountPath: "inputs/data.csv" }),
|
|
336
|
+
],
|
|
337
|
+
options({ storage }),
|
|
338
|
+
),
|
|
339
|
+
).rejects.toThrow(/mount path 'inputs\/data\.csv' collides with attachment 'a\.csv'/);
|
|
340
|
+
expect(storage.download).not.toHaveBeenCalled();
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it("refuses a mountPath outside the inputs namespace with the namespace named (a file in the user's tree would ride the write-back commit)", async () => {
|
|
344
|
+
const { storage } = makeInMemoryArtifactStorage();
|
|
345
|
+
const attempt = resolveAttachments(
|
|
346
|
+
[makeAttachment({ filename: "data.yaml", storageKey: "k/d", mountPath: "/workspace/data/data.yaml" })],
|
|
347
|
+
options({ storage }),
|
|
348
|
+
);
|
|
349
|
+
await expect(attempt).rejects.toBeInstanceOf(AttachmentResolutionError);
|
|
350
|
+
await expect(attempt).rejects.toThrow(/outside the attachment namespace.*set mountPath to 'inputs\/<name>'/);
|
|
351
|
+
expect(storage.download).not.toHaveBeenCalled();
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it("refuses a mountPath that climbs out of the namespace, and one that names the inputs directory itself", async () => {
|
|
355
|
+
const { storage } = makeInMemoryArtifactStorage();
|
|
356
|
+
await expect(
|
|
357
|
+
resolveAttachments([makeAttachment({ storageKey: "k/e", mountPath: "inputs/../../evil.md" })], options({ storage })),
|
|
358
|
+
).rejects.toThrow(/escapes the attachment namespace/);
|
|
359
|
+
await expect(
|
|
360
|
+
resolveAttachments([makeAttachment({ storageKey: "k/e", mountPath: "inputs/" })], options({ storage })),
|
|
361
|
+
).rejects.toThrow(/names the inputs directory itself/);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// ── Archives: `extract` lands the entries under the mount directory ──────
|
|
366
|
+
|
|
367
|
+
describe("extract (S3 M1, Q-S3-14)", () => {
|
|
368
|
+
const archive = () =>
|
|
369
|
+
Buffer.from(
|
|
370
|
+
buildZip([
|
|
371
|
+
{ name: "notes/readme.md", content: "# notes" },
|
|
372
|
+
{ name: "data.csv", content: "a,b\n1,2" },
|
|
373
|
+
]),
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
it("extracts a storage-backed archive under `inputs/<archive filename>/`, one resolved attachment per entry, with no vision, rename or URL", async () => {
|
|
377
|
+
const { storage } = makeInMemoryArtifactStorage();
|
|
378
|
+
await storage.upload("k/bundle", archive(), "application/zip");
|
|
379
|
+
|
|
380
|
+
const result = await resolveAttachments(
|
|
381
|
+
[makeAttachment({ filename: "bundle.zip", storageKey: "k/bundle", extract: true, contentType: "application/zip" })],
|
|
382
|
+
options({ storage, visionBudget: new VisionBudget(CURSOR_VISION_PROFILE, { modelVision: true }) }),
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
expect(result).toEqual([
|
|
386
|
+
{ filename: "data.csv", relativePath: ".stigmer/inputs/bundle.zip/data.csv", sizeBytes: 7 },
|
|
387
|
+
{ filename: "readme.md", relativePath: ".stigmer/inputs/bundle.zip/notes/readme.md", sizeBytes: 7 },
|
|
388
|
+
]);
|
|
389
|
+
expect(readFileSync(join(platformDir, "inputs", "bundle.zip", "notes", "readme.md"), "utf-8")).toBe("# notes");
|
|
390
|
+
expect(storage.getDownloadUrl, "no URL for entries: the stored object is the ZIP, not any listed file").not.toHaveBeenCalled();
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it("extracts a local archive at the CLI's `inputs/<dirname>/` mount (the directory-attachment shape)", async () => {
|
|
394
|
+
const srcPath = join(workspaceDir, "docs.zip");
|
|
395
|
+
writeFileSync(srcPath, archive());
|
|
396
|
+
|
|
397
|
+
const result = await resolveAttachments(
|
|
398
|
+
[makeAttachment({ filename: "docs.zip", storageKey: "", localPath: srcPath, extract: true, mountPath: "inputs/docs/" })],
|
|
399
|
+
options(),
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
expect(result.map((r) => r.relativePath)).toEqual([".stigmer/inputs/docs/data.csv", ".stigmer/inputs/docs/notes/readme.md"]);
|
|
403
|
+
expect(readFileSync(join(platformDir, "inputs", "docs", "data.csv"), "utf-8")).toBe("a,b\n1,2");
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("an archive that fails the shared guards aborts the resolution with the guard's own error", async () => {
|
|
407
|
+
const srcPath = join(workspaceDir, "evil.zip");
|
|
408
|
+
writeFileSync(srcPath, Buffer.from(buildZip([{ name: "../escape.txt", content: "x" }])));
|
|
409
|
+
|
|
410
|
+
await expect(
|
|
411
|
+
resolveAttachments([makeAttachment({ filename: "evil.zip", storageKey: "", localPath: srcPath, extract: true })], options()),
|
|
412
|
+
).rejects.toBeInstanceOf(AttachmentValidationError);
|
|
413
|
+
expect(() => readFileSync(join(platformDir, "inputs", "evil.zip"))).toThrow();
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
|
|
268
417
|
// ── Download-URL hand-off (issue #532) ───────────────────────────────────
|
|
269
418
|
// The URL is strictly additive (shared/attachment-download-urls.ts): every
|
|
270
419
|
// case below also asserts the file materialized exactly as it would
|
|
@@ -279,7 +428,7 @@ describe("resolveAttachments", () => {
|
|
|
279
428
|
const result = await resolveAttachments([makeAttachment()], options({ storage }));
|
|
280
429
|
|
|
281
430
|
expect(result).toEqual([
|
|
282
|
-
{ filename: "plan.md", relativePath: ".stigmer/inputs/plan.md" },
|
|
431
|
+
{ filename: "plan.md", relativePath: ".stigmer/inputs/plan.md", sizeBytes: 10 },
|
|
283
432
|
]);
|
|
284
433
|
expect(readFileSync(join(platformDir, "inputs", "plan.md"), "utf-8")).toBe("# The Plan");
|
|
285
434
|
});
|
|
@@ -300,6 +449,7 @@ describe("resolveAttachments", () => {
|
|
|
300
449
|
{
|
|
301
450
|
filename: "data.csv",
|
|
302
451
|
relativePath: ".stigmer/inputs/data.csv",
|
|
452
|
+
sizeBytes: 5,
|
|
303
453
|
downloadUrl: "mem://attachments/01ABC/data.csv",
|
|
304
454
|
},
|
|
305
455
|
]);
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ZIP guards an `extract` attachment passes before a byte is written
|
|
3
|
+
* (`shared/attachment-zip.ts`): the manifest checks (paths, traversal, null
|
|
4
|
+
* bytes, duplicates, methods, emptiness, count and declared-size caps) and
|
|
5
|
+
* the central-directory parsing that issue #567 made authoritative. Moved
|
|
6
|
+
* from the native injector's test file at S3 M1 with the code; every
|
|
7
|
+
* archive comes from the shared real-shape builder
|
|
8
|
+
* (@stigmer/zip-structure/testing). The write of an admitted entry is each
|
|
9
|
+
* caller's and is pinned in that caller's tests (`attachment-resolver.test.ts`).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, it, expect } from "vitest";
|
|
13
|
+
import { buildZip, type ZipFixtureFile } from "@stigmer/zip-structure/testing";
|
|
14
|
+
import {
|
|
15
|
+
AttachmentValidationError,
|
|
16
|
+
MAX_ZIP_EXTRACTED_SIZE,
|
|
17
|
+
MAX_ZIP_FILES,
|
|
18
|
+
validateZipForExtraction,
|
|
19
|
+
} from "../attachment-zip.js";
|
|
20
|
+
|
|
21
|
+
function makeZip(entries: Record<string, string | Buffer>): Buffer {
|
|
22
|
+
return makeZipWith(entries, { method: "deflated" });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function makeStoredZip(entries: Record<string, Buffer>): Buffer {
|
|
26
|
+
return makeZipWith(entries, { method: "stored" });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function makeZipWith(
|
|
30
|
+
entries: Record<string, string | Buffer>,
|
|
31
|
+
shape: Pick<ZipFixtureFile, "method" | "streaming">,
|
|
32
|
+
): Buffer {
|
|
33
|
+
const files: ZipFixtureFile[] = Object.entries(entries).map(([name, content]) => ({
|
|
34
|
+
name,
|
|
35
|
+
content: typeof content === "string" ? content : new Uint8Array(content),
|
|
36
|
+
...shape,
|
|
37
|
+
}));
|
|
38
|
+
return Buffer.from(buildZip(files));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function makeDirectoryOnlyZip(): Buffer {
|
|
42
|
+
return Buffer.from(buildZip([{ name: "empty_dir/", content: "" }]));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
46
|
+
// validateZipForExtraction
|
|
47
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
48
|
+
|
|
49
|
+
describe("validateZipForExtraction", () => {
|
|
50
|
+
it("returns sorted manifest for valid ZIP with multiple files", () => {
|
|
51
|
+
const zip = makeZip({ "b.txt": "beta", "a.txt": "alpha", "c.txt": "charlie" });
|
|
52
|
+
const result = validateZipForExtraction(zip, "test.zip");
|
|
53
|
+
|
|
54
|
+
expect(result).toHaveLength(3);
|
|
55
|
+
expect(result.map(e => e.relativePath)).toEqual(["a.txt", "b.txt", "c.txt"]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("preserves nested directory paths in entries", () => {
|
|
59
|
+
const zip = makeZip({
|
|
60
|
+
"src/main.py": "print('hi')",
|
|
61
|
+
"README.md": "# Hello",
|
|
62
|
+
"src/lib/util.py": "pass",
|
|
63
|
+
});
|
|
64
|
+
const result = validateZipForExtraction(zip, "project.zip");
|
|
65
|
+
const paths = result.map(e => e.relativePath);
|
|
66
|
+
|
|
67
|
+
expect(paths).toContain("src/main.py");
|
|
68
|
+
expect(paths).toContain("src/lib/util.py");
|
|
69
|
+
expect(paths).toContain("README.md");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("reports correct uncompressed sizes", () => {
|
|
73
|
+
const contentA = "hello world";
|
|
74
|
+
const contentB = "x".repeat(500);
|
|
75
|
+
const zip = makeZip({ "a.txt": contentA, "b.txt": contentB });
|
|
76
|
+
|
|
77
|
+
const result = validateZipForExtraction(zip, "test.zip");
|
|
78
|
+
const sizeMap = new Map(result.map(e => [e.relativePath, e.uncompressedSize]));
|
|
79
|
+
|
|
80
|
+
expect(sizeMap.get("a.txt")).toBe(Buffer.from(contentA).length);
|
|
81
|
+
expect(sizeMap.get("b.txt")).toBe(Buffer.from(contentB).length);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("excludes directory-only entries", () => {
|
|
85
|
+
const zip = makeDirectoryOnlyZip();
|
|
86
|
+
expect(() => validateZipForExtraction(zip, "dirs.zip")).toThrow(
|
|
87
|
+
AttachmentValidationError,
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("rejects invalid ZIP format (random bytes)", () => {
|
|
92
|
+
expect(() => validateZipForExtraction(Buffer.from("not-a-zip"), "bad.zip"))
|
|
93
|
+
.toThrow(AttachmentValidationError);
|
|
94
|
+
expect(() => validateZipForExtraction(Buffer.from("not-a-zip"), "bad.zip"))
|
|
95
|
+
.toThrow(/not a valid ZIP archive/);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("rejects file too small to be a ZIP", () => {
|
|
99
|
+
expect(() => validateZipForExtraction(Buffer.from([0x50, 0x4b]), "tiny.zip"))
|
|
100
|
+
.toThrow(/too small/);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("rejects absolute path entries (forward slash)", () => {
|
|
104
|
+
const zip = makeZip({ "/etc/passwd": "root:x:0:0" });
|
|
105
|
+
expect(() => validateZipForExtraction(zip, "evil.zip"))
|
|
106
|
+
.toThrow(/absolute path/);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("rejects absolute path entries (backslash)", () => {
|
|
110
|
+
const zip = makeZip({ "\\windows\\system32\\evil.dll": "payload" });
|
|
111
|
+
expect(() => validateZipForExtraction(zip, "evil.zip"))
|
|
112
|
+
.toThrow(/absolute path/);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("rejects path traversal with leading ..", () => {
|
|
116
|
+
const zip = makeZip({ "../../etc/passwd": "root:x:0:0" });
|
|
117
|
+
expect(() => validateZipForExtraction(zip, "evil.zip"))
|
|
118
|
+
.toThrow(/path traversal/);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("rejects path traversal with embedded ..", () => {
|
|
122
|
+
const zip = makeZip({ "foo/../../etc/passwd": "root:x:0:0" });
|
|
123
|
+
expect(() => validateZipForExtraction(zip, "evil.zip"))
|
|
124
|
+
.toThrow(/path traversal/);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("rejects null bytes in filenames", () => {
|
|
128
|
+
const zip = makeZip({ "file\u0000.txt": "test" });
|
|
129
|
+
expect(() => validateZipForExtraction(zip, "null.zip"))
|
|
130
|
+
.toThrow(/null bytes/);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("rejects empty archive (valid ZIP, zero file entries)", () => {
|
|
134
|
+
const zip = makeDirectoryOnlyZip();
|
|
135
|
+
expect(() => validateZipForExtraction(zip, "empty.zip"))
|
|
136
|
+
.toThrow(/empty ZIP archive/);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("rejects file count exceeding limit", () => {
|
|
140
|
+
const entries: Record<string, Buffer> = {};
|
|
141
|
+
for (let i = 0; i < MAX_ZIP_FILES + 1; i++) {
|
|
142
|
+
entries[`file_${String(i).padStart(4, "0")}.txt`] = Buffer.from("x");
|
|
143
|
+
}
|
|
144
|
+
const zip = makeStoredZip(entries);
|
|
145
|
+
expect(() => validateZipForExtraction(zip, "bomb.zip"))
|
|
146
|
+
.toThrow(new RegExp(`limit: ${MAX_ZIP_FILES}`));
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("rejects total uncompressed size exceeding limit", () => {
|
|
150
|
+
const overLimit = MAX_ZIP_EXTRACTED_SIZE + 1;
|
|
151
|
+
const zip = makeStoredZip({ "big.bin": Buffer.alloc(overLimit, 0) });
|
|
152
|
+
expect(() => validateZipForExtraction(zip, "bomb.zip"))
|
|
153
|
+
.toThrow(/limit: 100 MB/);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("accepts ZIP at exactly the file count limit", () => {
|
|
157
|
+
const entries: Record<string, Buffer> = {};
|
|
158
|
+
for (let i = 0; i < MAX_ZIP_FILES; i++) {
|
|
159
|
+
entries[`f${i}.txt`] = Buffer.from("x");
|
|
160
|
+
}
|
|
161
|
+
const zip = makeStoredZip(entries);
|
|
162
|
+
const result = validateZipForExtraction(zip, "ok.zip");
|
|
163
|
+
expect(result).toHaveLength(MAX_ZIP_FILES);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
168
|
+
// Central-directory parsing (issue #567)
|
|
169
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
170
|
+
// The archive shapes the old local-header walk corrupted or rejected:
|
|
171
|
+
// stored streaming entries (silent manifest truncation) and Go-default
|
|
172
|
+
// deflated streaming entries (rejected outright). Sizes come from the
|
|
173
|
+
// central directory — the format's authoritative index — and structural
|
|
174
|
+
// failures are fail-hard, unlike the skill-artifact reader's non-fatal
|
|
175
|
+
// empty return: nothing upstream vouches for an attachment.
|
|
176
|
+
|
|
177
|
+
describe("validateZipForExtraction — central-directory parsing (issue #567)", () => {
|
|
178
|
+
it("validates every entry of a stored streaming archive (no silent truncation)", () => {
|
|
179
|
+
// Method 0 + flag bit 3 + zeroed local sizes: the old walk admitted the
|
|
180
|
+
// first entry with size 0, landed mid-payload, and quietly dropped the
|
|
181
|
+
// rest of the manifest.
|
|
182
|
+
const zip = Buffer.from(buildZip([
|
|
183
|
+
{ name: "first.txt", content: "first file content", streaming: true },
|
|
184
|
+
{ name: "second.txt", content: "second file content", streaming: true },
|
|
185
|
+
]));
|
|
186
|
+
|
|
187
|
+
const result = validateZipForExtraction(zip, "streamed.zip");
|
|
188
|
+
expect(result.map((e) => e.relativePath)).toEqual(["first.txt", "second.txt"]);
|
|
189
|
+
expect(result.map((e) => e.uncompressedSize)).toEqual([
|
|
190
|
+
"first file content".length,
|
|
191
|
+
"second file content".length,
|
|
192
|
+
]);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("accepts a Go-default archive (deflated streaming entries)", () => {
|
|
196
|
+
const zip = Buffer.from(buildZip([
|
|
197
|
+
{ name: "main.go", content: "package main", method: "deflated", streaming: true },
|
|
198
|
+
{ name: "go.mod", content: "module example", method: "deflated", streaming: true },
|
|
199
|
+
]));
|
|
200
|
+
|
|
201
|
+
const result = validateZipForExtraction(zip, "go-built.zip");
|
|
202
|
+
expect(result.map((e) => e.relativePath)).toEqual(["go.mod", "main.go"]);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("rejects an archive with no central directory (fail-hard, unlike skill extraction)", () => {
|
|
206
|
+
const zip = Buffer.from(buildZip(
|
|
207
|
+
[{ name: "a.txt", content: "aaa" }],
|
|
208
|
+
{ omitCentralDirectory: true },
|
|
209
|
+
));
|
|
210
|
+
|
|
211
|
+
expect(() => validateZipForExtraction(zip, "truncated.zip"))
|
|
212
|
+
.toThrow(AttachmentValidationError);
|
|
213
|
+
expect(() => validateZipForExtraction(zip, "truncated.zip"))
|
|
214
|
+
.toThrow(/not a valid ZIP archive/);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("rejects duplicate entry paths (a contradictory manifest)", () => {
|
|
218
|
+
const zip = Buffer.from(buildZip([
|
|
219
|
+
{ name: "dup.txt", content: "one" },
|
|
220
|
+
{ name: "dup.txt", content: "two" },
|
|
221
|
+
]));
|
|
222
|
+
|
|
223
|
+
expect(() => validateZipForExtraction(zip, "dup.zip"))
|
|
224
|
+
.toThrow(/duplicate entry/);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("locates the central directory behind a trailing archive comment", () => {
|
|
228
|
+
const zip = Buffer.from(buildZip(
|
|
229
|
+
[{ name: "a.txt", content: "aaa" }],
|
|
230
|
+
{ comment: "release archive — built by tooling" },
|
|
231
|
+
));
|
|
232
|
+
|
|
233
|
+
const result = validateZipForExtraction(zip, "commented.zip");
|
|
234
|
+
expect(result.map((e) => e.relativePath)).toEqual(["a.txt"]);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
@@ -152,7 +152,7 @@ describe("buildChatModel bedrock adapter", () => {
|
|
|
152
152
|
it("injects the CANONICAL id's default maxTokens when the caller omits it (the 4096 landmine)", async () => {
|
|
153
153
|
// LangChain's per-model default table prefix-matches the model string.
|
|
154
154
|
// `anthropic.…` ids match nothing and silently fall back to 4096 —
|
|
155
|
-
// setup.ts omits maxTokens, so every deep-agent call would be capped.
|
|
155
|
+
// turn-setup.ts omits maxTokens, so every deep-agent call would be capped.
|
|
156
156
|
// The adapter must inject what the canonical id would have received on
|
|
157
157
|
// the public API or vertex, whatever that is at the installed LangChain
|
|
158
158
|
// version (16384 for 4.5/4.6-generation ids at 1.4.0).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `shared/blueprint-resolver.ts` `mergeSkillRefs`: the one merge of the
|
|
3
|
+
* agent's and the session's skill refs the turn runtime resolves from
|
|
4
|
+
* (`resolveAgentBlueprint`). Union, deduplicated by slug, the session's ref
|
|
5
|
+
* winning a collision (it may pin a different version).
|
|
6
|
+
*
|
|
7
|
+
* Carried from `skill-writer.test.ts` at S3 M2b, when the native
|
|
8
|
+
* orchestrator's byte-twin of this function was deleted with its module;
|
|
9
|
+
* until then the runtime's copy was tested only through the twin.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, it, expect } from "vitest";
|
|
13
|
+
import type { ApiResourceReference } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
|
|
14
|
+
import { mergeSkillRefs } from "../blueprint-resolver.js";
|
|
15
|
+
|
|
16
|
+
function makeRef(slug: string, org = "test-org"): ApiResourceReference {
|
|
17
|
+
return { slug, org, $typeName: "ai.stigmer.commons.apiresource.ApiResourceReference" } as ApiResourceReference;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("mergeSkillRefs", () => {
|
|
21
|
+
it("returns empty for no refs", () => {
|
|
22
|
+
expect(mergeSkillRefs([], [])).toEqual([]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("returns the agent's refs when the session names none", () => {
|
|
26
|
+
const result = mergeSkillRefs([makeRef("skill-a"), makeRef("skill-b")], []);
|
|
27
|
+
expect(result.map((r) => r.slug)).toEqual(["skill-a", "skill-b"]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("deduplicates by slug with the session's ref winning", () => {
|
|
31
|
+
const result = mergeSkillRefs([makeRef("skill-a", "org1")], [makeRef("skill-a", "org2")]);
|
|
32
|
+
expect(result).toHaveLength(1);
|
|
33
|
+
expect(result[0].org).toBe("org2");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("unions distinct skills from both sources", () => {
|
|
37
|
+
expect(mergeSkillRefs([makeRef("skill-a")], [makeRef("skill-b")])).toHaveLength(2);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("ignores a ref with no slug (nothing to key it by)", () => {
|
|
41
|
+
expect(mergeSkillRefs([makeRef("")], [])).toEqual([]);
|
|
42
|
+
});
|
|
43
|
+
});
|