@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
|
@@ -44,9 +44,9 @@ describe("costCapExceeded", () => {
|
|
|
44
44
|
|
|
45
45
|
describe("cost-limit terminal copy", () => {
|
|
46
46
|
it("pins the error prefix consumers match on (no structured termination reason exists)", () => {
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
// matching this prefix to generic error copy.
|
|
47
|
+
// The twin of TOOL_CALL_LIMIT_ERROR_PREFIX's pin (`shared/tool-rounds.ts`,
|
|
48
|
+
// matched by the cloud's reply-extractor): a reword silently downgrades
|
|
49
|
+
// any consumer matching this prefix to generic error copy.
|
|
50
50
|
expect(COST_LIMIT_ERROR_PREFIX).toBe("Agent reached the cost limit");
|
|
51
51
|
});
|
|
52
52
|
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared prompt glue (`shared/prompt-sections.ts`): the standing order,
|
|
3
|
+
* the input-files lines, the skill selection and the also-available sentence
|
|
4
|
+
* (the sentence's arms came from `skill-writer.test.ts`, retired with its
|
|
5
|
+
* module at S3 M5). What each harness renders AROUND these is pinned whole by
|
|
6
|
+
* the two builders' prompt goldens (`execute-deep-agent/__tests__/
|
|
7
|
+
* prompt-goldens.test.ts`, `execute-cursor/__tests__/prompt-goldens.test.ts`);
|
|
8
|
+
* this file pins the glue's own contract, harness-free.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect } from "vitest";
|
|
12
|
+
import {
|
|
13
|
+
alsoAvailableSkillsNote,
|
|
14
|
+
inputFileLines,
|
|
15
|
+
selectSkillsForPrompt,
|
|
16
|
+
standingContextSections,
|
|
17
|
+
visionPromptInfoOf,
|
|
18
|
+
type StandingSectionKind,
|
|
19
|
+
} from "../prompt-sections.js";
|
|
20
|
+
import type { ResolvedAttachment } from "../attachment-resolver.js";
|
|
21
|
+
import type { SkillMetadata } from "../skill-resolver.js";
|
|
22
|
+
import { SKILL_COUNT_THRESHOLD } from "../skill-relevance.js";
|
|
23
|
+
import { formatDeclaredPreferencesText } from "../declared-preferences.js";
|
|
24
|
+
|
|
25
|
+
describe("standingContextSections", () => {
|
|
26
|
+
const ORDER: readonly StandingSectionKind[] = [
|
|
27
|
+
"conversation-sender",
|
|
28
|
+
"declared-preferences",
|
|
29
|
+
"recalled-memories",
|
|
30
|
+
"session-context",
|
|
31
|
+
"previous-conversation",
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
it("renders every present section in the one ruled order, whatever order the fields arrive in", () => {
|
|
35
|
+
const sections = standingContextSections({
|
|
36
|
+
contextBridge: "bridge",
|
|
37
|
+
sessionContext: "session",
|
|
38
|
+
recalledMemories: { facts: ["fact"] },
|
|
39
|
+
declaredPreferences: { orgContext: "org" },
|
|
40
|
+
senderIdentity: { value: "15550001111", kind: "whatsapp_phone" },
|
|
41
|
+
});
|
|
42
|
+
expect(sections.map((s) => s.kind)).toEqual(ORDER);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("omits an absent section and keeps the relative order of the rest", () => {
|
|
46
|
+
const sections = standingContextSections({ contextBridge: "bridge", senderIdentity: { value: "U1", kind: "slack_user_id" } });
|
|
47
|
+
expect(sections.map((s) => s.kind)).toEqual(["conversation-sender", "previous-conversation"]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("renders nothing for an empty context", () => {
|
|
51
|
+
expect(standingContextSections({})).toEqual([]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("carries each body from the fact's own module, unframed", () => {
|
|
55
|
+
const [section] = standingContextSections({ declaredPreferences: { userContext: "Keep answers terse." } });
|
|
56
|
+
expect(section?.body).toBe(formatDeclaredPreferencesText({ userContext: "Keep answers terse." }));
|
|
57
|
+
expect(section?.body.startsWith("<")).toBe(false);
|
|
58
|
+
expect(section?.body.startsWith("#")).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("inputFileLines", () => {
|
|
63
|
+
const file = (relativePath: string, extra: Partial<ResolvedAttachment> = {}): ResolvedAttachment => ({
|
|
64
|
+
filename: relativePath.split("/").pop()!,
|
|
65
|
+
relativePath,
|
|
66
|
+
sizeBytes: 1024,
|
|
67
|
+
...extra,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("one bullet per file with the size, the rename disclosure and the download URL", () => {
|
|
71
|
+
const { entries } = inputFileLines(
|
|
72
|
+
[
|
|
73
|
+
file(".stigmer/inputs/spec.pdf"),
|
|
74
|
+
file(".stigmer/inputs/report-2.pdf", { renamedFrom: "report.pdf" }),
|
|
75
|
+
file(".stigmer/inputs/diagram.png", { downloadUrl: "https://r2.example/d?sig=1" }),
|
|
76
|
+
],
|
|
77
|
+
undefined,
|
|
78
|
+
undefined,
|
|
79
|
+
);
|
|
80
|
+
expect(entries).toEqual([
|
|
81
|
+
"- `.stigmer/inputs/spec.pdf` (1024 bytes)",
|
|
82
|
+
"- `.stigmer/inputs/report-2.pdf` (1024 bytes) (renamed from duplicate 'report.pdf')",
|
|
83
|
+
"- `.stigmer/inputs/diagram.png` (1024 bytes) — download URL: https://r2.example/d?sig=1",
|
|
84
|
+
]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("renders the URL hand-off line only when a listed file carries a URL AND the storage's kind is known", () => {
|
|
88
|
+
const withUrl = [file("a.pdf", { downloadUrl: "https://r2.example/a" })];
|
|
89
|
+
expect(inputFileLines(withUrl, undefined, "presigned").urlHandoff).toBeDefined();
|
|
90
|
+
expect(inputFileLines(withUrl, undefined, undefined).urlHandoff).toBeUndefined();
|
|
91
|
+
expect(inputFileLines([file("a.pdf")], undefined, "presigned").urlHandoff).toBeUndefined();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("renders the vision lines only when the turn carries vision facts", () => {
|
|
95
|
+
expect(inputFileLines([file("a.png")], undefined, undefined).vision).toEqual([]);
|
|
96
|
+
const { vision } = inputFileLines(
|
|
97
|
+
[file("a.png")],
|
|
98
|
+
{ inlineFilenames: ["a.png"], notViewable: [{ path: ".stigmer/inputs/big.png", reason: "too_large" }] },
|
|
99
|
+
undefined,
|
|
100
|
+
);
|
|
101
|
+
expect(vision.some((l) => l.includes("a.png"))).toBe(true);
|
|
102
|
+
expect(vision.some((l) => l.includes("big.png"))).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("visionPromptInfoOf", () => {
|
|
107
|
+
it("is undefined when the turn has neither inline images nor degraded ones", () => {
|
|
108
|
+
expect(visionPromptInfoOf({ visionImages: [], visionNotViewable: [] })).toBeUndefined();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("names the inline images in send order and carries the degraded entries", () => {
|
|
112
|
+
const info = visionPromptInfoOf({
|
|
113
|
+
visionImages: [{ filename: "b.png" }, { filename: "a.jpg" }],
|
|
114
|
+
visionNotViewable: [{ path: ".stigmer/inputs/big.png", reason: "too_large" }],
|
|
115
|
+
});
|
|
116
|
+
expect(info).toEqual({
|
|
117
|
+
inlineFilenames: ["b.png", "a.jpg"],
|
|
118
|
+
notViewable: [{ path: ".stigmer/inputs/big.png", reason: "too_large" }],
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("selectSkillsForPrompt", () => {
|
|
124
|
+
const skill = (name: string, description: string): SkillMetadata => ({ name, description, path: `.stigmer/skills/${name}/SKILL.md` });
|
|
125
|
+
|
|
126
|
+
it("highlights every skill below the threshold, in mount order, and names none as also-available", () => {
|
|
127
|
+
const skills = [skill("alpha", "Deploy to kubernetes"), skill("beta", "Reshape CSV exports")];
|
|
128
|
+
const selection = selectSkillsForPrompt("deploy the service", skills);
|
|
129
|
+
expect(selection.highlighted).toEqual(skills);
|
|
130
|
+
expect(selection.alsoAvailable).toEqual([]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("at the threshold, highlights the skills the message reaches and names the rest", () => {
|
|
134
|
+
const skills = Array.from({ length: SKILL_COUNT_THRESHOLD }, (_, i) =>
|
|
135
|
+
i === 0 ? skill("k8s-deploy", "Deploy services to kubernetes clusters") : skill(`other-${i}`, `Unrelated thing number ${i}`),
|
|
136
|
+
);
|
|
137
|
+
const selection = selectSkillsForPrompt("Deploy the payments service to kubernetes", skills);
|
|
138
|
+
expect(selection.highlighted.map((s) => s.name)).toContain("k8s-deploy");
|
|
139
|
+
expect(selection.alsoAvailable.length).toBeGreaterThan(0);
|
|
140
|
+
expect(selection.highlighted.length + selection.alsoAvailable.length).toBe(SKILL_COUNT_THRESHOLD);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("selects nothing from nothing", () => {
|
|
144
|
+
expect(selectSkillsForPrompt("anything", [])).toEqual({ highlighted: [], alsoAvailable: [] });
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe("alsoAvailableSkillsNote", () => {
|
|
149
|
+
it("returns empty for no excluded names", () => {
|
|
150
|
+
expect(alsoAvailableSkillsNote([])).toBe("");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("lists excluded skills with backtick formatting", () => {
|
|
154
|
+
const note = alsoAvailableSkillsNote(["alpha", "beta", "gamma"]);
|
|
155
|
+
expect(note).toContain("`alpha`");
|
|
156
|
+
expect(note).toContain("`beta`");
|
|
157
|
+
expect(note).toContain("`gamma`");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("includes activation instructions that name no tool, so any harness can carry it", () => {
|
|
161
|
+
const note = alsoAvailableSkillsNote(["some-skill"]);
|
|
162
|
+
expect(note).toContain(".stigmer/skills/<name>/SKILL.md");
|
|
163
|
+
expect(note).toContain("relevant to your task");
|
|
164
|
+
expect(note).not.toMatch(/\b(read|Read) tool\b/);
|
|
165
|
+
expect(note.startsWith("#")).toBe(false);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* An offline end-to-end test is structurally infeasible for this path (DD-23 /
|
|
11
11
|
* DD-26 F3: the offline harness always runs a git workspace with LocalArtifactDir,
|
|
12
12
|
* so deriveCaptureMode is always true and the no-storage deny-gate is unreachable).
|
|
13
|
-
* This test exercises the exact function
|
|
14
|
-
*
|
|
13
|
+
* This test exercises the exact function the turn runtime's persist chokepoint
|
|
14
|
+
* invokes for both harnesses, over a full AgentExecutionStatus.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { describe, it, expect } from "vitest";
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Unit tests for the
|
|
3
|
-
* (`
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Unit tests for the shared turn-boundary flowed-row stamping
|
|
3
|
+
* (`shared/tool-row.ts` `stampFlowedFileEditRows` /
|
|
4
|
+
* `stampFlowedSubAgentFileEditRows`; the deep-agent's copy until S3 M4), with
|
|
5
|
+
* emphasis on the sub-agent path added by the DD-24 follow-up: sub-agent edit
|
|
6
|
+
* rows fold into the parent turn's change set, so they carry the parent change
|
|
7
|
+
* set id, scoped to this turn by tool-call-id novelty (they lack the
|
|
8
|
+
* already-stamped/hidden shields the top-level pass has). The `flowed`
|
|
9
|
+
* predicate is the one seam a deny-gate harness narrows the pass with.
|
|
7
10
|
*/
|
|
8
11
|
|
|
9
12
|
import { describe, it, expect } from "vitest";
|
|
@@ -15,11 +18,12 @@ import {
|
|
|
15
18
|
import type { AgentMessage } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
16
19
|
import { SubAgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
|
|
17
20
|
import { ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
18
|
-
import { collectSubAgentToolCallIds } from "../../../shared/tool-row.js";
|
|
19
21
|
import {
|
|
22
|
+
collectSubAgentToolCallIds,
|
|
23
|
+
hideToolCallRow,
|
|
20
24
|
stampFlowedFileEditRows,
|
|
21
25
|
stampFlowedSubAgentFileEditRows,
|
|
22
|
-
} from "../
|
|
26
|
+
} from "../tool-row.js";
|
|
23
27
|
|
|
24
28
|
const CHANGE_SET_ID = "exec-1:0";
|
|
25
29
|
|
|
@@ -42,7 +46,7 @@ function subAgent(subId: string, ...messages: AgentMessage[]) {
|
|
|
42
46
|
return create(SubAgentExecutionSchema, { id: subId, name: "code_editor", messages });
|
|
43
47
|
}
|
|
44
48
|
|
|
45
|
-
describe("stampFlowedFileEditRows
|
|
49
|
+
describe("stampFlowedFileEditRows", () => {
|
|
46
50
|
it("stamps a flowed write/delete row and leaves a non-file row untouched", () => {
|
|
47
51
|
const write = editMessage("tc-1", "src/a.ts", "write_file");
|
|
48
52
|
const shell = create(AgentMessageSchema, {
|
|
@@ -60,7 +64,7 @@ describe("stampFlowedFileEditRows (deep-agent)", () => {
|
|
|
60
64
|
const prior = editMessage("tc-prior", "src/old.ts");
|
|
61
65
|
const current = editMessage("tc-current", "src/new.ts");
|
|
62
66
|
|
|
63
|
-
stampFlowedFileEditRows([prior, current], CHANGE_SET_ID, new Set(["tc-prior"]));
|
|
67
|
+
stampFlowedFileEditRows([prior, current], CHANGE_SET_ID, { skipToolCallIds: new Set(["tc-prior"]) });
|
|
64
68
|
|
|
65
69
|
expect(prior.toolCalls[0].fileChangeSetId).toBe(""); // skipped
|
|
66
70
|
expect(current.toolCalls[0].fileChangeSetId).toBe(CHANGE_SET_ID);
|
|
@@ -74,9 +78,28 @@ describe("stampFlowedFileEditRows (deep-agent)", () => {
|
|
|
74
78
|
|
|
75
79
|
expect(row.toolCalls[0].fileChangeSetId).toBe("exec-1:prior");
|
|
76
80
|
});
|
|
81
|
+
|
|
82
|
+
it("skips a legacy hidden row (a pre-stamping session's collapsed edit): it belongs to an earlier turn's set", () => {
|
|
83
|
+
const legacy = editMessage("tc-legacy", "src/old.ts");
|
|
84
|
+
hideToolCallRow(legacy.toolCalls[0]!);
|
|
85
|
+
|
|
86
|
+
stampFlowedFileEditRows([legacy], CHANGE_SET_ID);
|
|
87
|
+
|
|
88
|
+
expect(legacy.toolCalls[0]!.fileChangeSetId).toBe("");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("leaves a row the caller's `flowed` predicate refuses (a denied identity, a non-terminal status) unstamped", () => {
|
|
92
|
+
const flowed = editMessage("tc-flowed", "src/a.ts");
|
|
93
|
+
const denied = editMessage("tc-denied", "src/b.ts");
|
|
94
|
+
|
|
95
|
+
stampFlowedFileEditRows([flowed, denied], CHANGE_SET_ID, { flowed: (tc) => tc.id !== "tc-denied" });
|
|
96
|
+
|
|
97
|
+
expect(flowed.toolCalls[0].fileChangeSetId).toBe(CHANGE_SET_ID);
|
|
98
|
+
expect(denied.toolCalls[0].fileChangeSetId).toBe("");
|
|
99
|
+
});
|
|
77
100
|
});
|
|
78
101
|
|
|
79
|
-
describe("stampFlowedSubAgentFileEditRows
|
|
102
|
+
describe("stampFlowedSubAgentFileEditRows", () => {
|
|
80
103
|
it("stamps a current-turn sub-agent's edit row with the parent change set id", () => {
|
|
81
104
|
const sub = subAgent("sa-1", editMessage("sa-tc-1", "src/sub.ts"));
|
|
82
105
|
|
|
@@ -119,7 +119,7 @@ describe("buildChatModel vertex adapter", () => {
|
|
|
119
119
|
});
|
|
120
120
|
|
|
121
121
|
it("translated ids inherit the same default maxTokens as canonical ids", () => {
|
|
122
|
-
// setup.ts deliberately omits maxTokens, so the LangChain per-model
|
|
122
|
+
// turn-setup.ts deliberately omits maxTokens, so the LangChain per-model
|
|
123
123
|
// default applies. That default prefix-matches the model string — and the
|
|
124
124
|
// vertex branch hands ChatAnthropic the TRANSLATED string. This pins the
|
|
125
125
|
// equality the zero-behavior-change criterion rests on (verified 16384 ==
|
|
@@ -14,34 +14,56 @@
|
|
|
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
|
|
|
39
60
|
import { mkdir, copyFile, readFile, stat, writeFile } from "node:fs/promises";
|
|
40
|
-
import { join, basename } from "node:path";
|
|
61
|
+
import { join, basename, dirname, posix } from "node:path";
|
|
41
62
|
import type { Attachment } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/spec_pb";
|
|
42
63
|
import type { ArtifactStorage } from "./artifact-storage.js";
|
|
43
64
|
import { mintAttachmentDownloadUrl } from "./attachment-download-urls.js";
|
|
44
65
|
import { allocateUniqueName } from "./attachment-naming.js";
|
|
66
|
+
import { decompressEntry, parseAndValidateZip } from "./attachment-zip.js";
|
|
45
67
|
import {
|
|
46
68
|
isVisionCandidate,
|
|
47
69
|
type VisionBudget,
|
|
@@ -59,6 +81,8 @@ export interface ResolvedAttachment {
|
|
|
59
81
|
filename: string;
|
|
60
82
|
/** Workspace-relative path the agent reads (`.stigmer/inputs/{filename}`). */
|
|
61
83
|
relativePath: string;
|
|
84
|
+
/** The bytes on disk; a prompt discloses it beside the path so the agent can weigh a read. */
|
|
85
|
+
sizeBytes: number;
|
|
62
86
|
/**
|
|
63
87
|
* The attachment's original filename, present only when a duplicate name
|
|
64
88
|
* was renamed (shared/attachment-naming.ts) — rendered as disclosure in
|
|
@@ -138,13 +162,18 @@ export async function resolveAttachments(
|
|
|
138
162
|
// it, but only when the agent has skills).
|
|
139
163
|
await ensureStigmerSymlink(options.primaryWorkspaceDir, platformDir);
|
|
140
164
|
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
const
|
|
165
|
+
// Every place is decided before any bytes move: an explicit-path
|
|
166
|
+
// contradiction refuses up front, and a default name never lands on a path
|
|
167
|
+
// an explicit one claims (see module doc on duplicate handling).
|
|
168
|
+
const places = resolvePlacements(attachments);
|
|
145
169
|
const results: ResolvedAttachment[] = [];
|
|
146
170
|
for (const attachment of attachments) {
|
|
147
|
-
|
|
171
|
+
const place = places.get(attachment)!;
|
|
172
|
+
if (attachment.extract) {
|
|
173
|
+
results.push(...(await extractArchive(attachment, place, inputsDir, options)));
|
|
174
|
+
} else {
|
|
175
|
+
results.push(await resolveAttachment(attachment, place, inputsDir, options));
|
|
176
|
+
}
|
|
148
177
|
}
|
|
149
178
|
|
|
150
179
|
console.log(
|
|
@@ -157,19 +186,22 @@ export async function resolveAttachments(
|
|
|
157
186
|
|
|
158
187
|
async function resolveAttachment(
|
|
159
188
|
attachment: Attachment,
|
|
189
|
+
place: Placement,
|
|
160
190
|
inputsDir: string,
|
|
161
|
-
takenNames: Set<string>,
|
|
162
191
|
options: AttachmentResolverOptions,
|
|
163
192
|
): Promise<ResolvedAttachment> {
|
|
193
|
+
const { relative, renamedFrom } = place;
|
|
194
|
+
// The final basename is the canonical filename: after a duplicate rename
|
|
195
|
+
// the original attachment.filename no longer names the file on disk, and
|
|
196
|
+
// the vision label must match what the prompt lists.
|
|
197
|
+
const filename = posix.basename(relative);
|
|
198
|
+
await mkdir(dirname(join(inputsDir, relative)), { recursive: true });
|
|
199
|
+
|
|
164
200
|
// Local-mode fast path: the file is already on this machine's disk.
|
|
165
201
|
if (options.mode === "local" && attachment.localPath) {
|
|
166
|
-
const { name: filename, renamedFrom } = allocateUniqueName(
|
|
167
|
-
safeInputName(attachment.filename || attachment.localPath),
|
|
168
|
-
takenNames,
|
|
169
|
-
);
|
|
170
202
|
let vision: VisionOutcome | undefined;
|
|
171
203
|
try {
|
|
172
|
-
vision = await materializeLocalFile(attachment, filename, inputsDir, options.visionBudget);
|
|
204
|
+
vision = await materializeLocalFile(attachment, filename, join(inputsDir, relative), options.visionBudget);
|
|
173
205
|
} catch (err) {
|
|
174
206
|
throw new AttachmentResolutionError(
|
|
175
207
|
attachment.filename,
|
|
@@ -185,7 +217,8 @@ async function resolveAttachment(
|
|
|
185
217
|
);
|
|
186
218
|
return {
|
|
187
219
|
filename,
|
|
188
|
-
relativePath: join(STIGMER_LOCAL_STATE_DIR, INPUTS_SUBDIR,
|
|
220
|
+
relativePath: join(STIGMER_LOCAL_STATE_DIR, INPUTS_SUBDIR, relative),
|
|
221
|
+
sizeBytes: (await stat(join(inputsDir, relative))).size,
|
|
189
222
|
...(renamedFrom !== undefined ? { renamedFrom } : {}),
|
|
190
223
|
...visionOutcomeFields(vision),
|
|
191
224
|
...(downloadUrl !== undefined ? { downloadUrl } : {}),
|
|
@@ -193,6 +226,77 @@ async function resolveAttachment(
|
|
|
193
226
|
}
|
|
194
227
|
|
|
195
228
|
// Universal path: download the uploaded content by storage key.
|
|
229
|
+
const content = await downloadFromStorage(attachment, options);
|
|
230
|
+
await writeFile(join(inputsDir, relative), content);
|
|
231
|
+
|
|
232
|
+
// The bytes are already in hand for the file write — offer them to the
|
|
233
|
+
// vision budget before they go out of scope (the sniff decides eligibility;
|
|
234
|
+
// no pre-filter needed on this branch).
|
|
235
|
+
const vision = options.visionBudget?.offer(filename, attachment.contentType, content);
|
|
236
|
+
|
|
237
|
+
const downloadUrl = await mintAttachmentDownloadUrl(
|
|
238
|
+
options.storage, attachment.storageKey, filename,
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
filename,
|
|
243
|
+
relativePath: join(STIGMER_LOCAL_STATE_DIR, INPUTS_SUBDIR, relative),
|
|
244
|
+
sizeBytes: content.byteLength,
|
|
245
|
+
...(renamedFrom !== undefined ? { renamedFrom } : {}),
|
|
246
|
+
...visionOutcomeFields(vision),
|
|
247
|
+
...(downloadUrl !== undefined ? { downloadUrl } : {}),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Extract an `extract` attachment's archive under its mount directory. The
|
|
253
|
+
* bytes are read whole (an archive is never a vision candidate, so the local
|
|
254
|
+
* fast path's lazy read has nothing to save), validated and decompressed by
|
|
255
|
+
* the shared guards, and written entry by entry; the entries are the
|
|
256
|
+
* resolved attachments (see the module doc for what they do not carry).
|
|
257
|
+
*/
|
|
258
|
+
async function extractArchive(
|
|
259
|
+
attachment: Attachment,
|
|
260
|
+
place: Placement,
|
|
261
|
+
inputsDir: string,
|
|
262
|
+
options: AttachmentResolverOptions,
|
|
263
|
+
): Promise<ResolvedAttachment[]> {
|
|
264
|
+
const content = await bytesOf(attachment, options);
|
|
265
|
+
const entries = parseAndValidateZip(content, attachment.filename);
|
|
266
|
+
const mountDir = place.relative.replace(/\/+$/, "");
|
|
267
|
+
const results: ResolvedAttachment[] = [];
|
|
268
|
+
for (const entry of entries) {
|
|
269
|
+
const bytes = await decompressEntry(entry, attachment.filename);
|
|
270
|
+
const relative = posix.join(mountDir, entry.relativePath);
|
|
271
|
+
const dest = join(inputsDir, relative);
|
|
272
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
273
|
+
await writeFile(dest, bytes);
|
|
274
|
+
results.push({
|
|
275
|
+
filename: posix.basename(entry.relativePath),
|
|
276
|
+
relativePath: join(STIGMER_LOCAL_STATE_DIR, INPUTS_SUBDIR, relative),
|
|
277
|
+
sizeBytes: bytes.byteLength,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
return results;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** The attachment's bytes, from the local fast path or from storage. */
|
|
284
|
+
async function bytesOf(attachment: Attachment, options: AttachmentResolverOptions): Promise<Buffer> {
|
|
285
|
+
if (options.mode === "local" && attachment.localPath) {
|
|
286
|
+
try {
|
|
287
|
+
return await readFile(attachment.localPath);
|
|
288
|
+
} catch (err) {
|
|
289
|
+
throw new AttachmentResolutionError(
|
|
290
|
+
attachment.filename,
|
|
291
|
+
`failed to read local file '${attachment.localPath}': ` +
|
|
292
|
+
`${err instanceof Error ? err.message : String(err)}`,
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return downloadFromStorage(attachment, options);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function downloadFromStorage(attachment: Attachment, options: AttachmentResolverOptions): Promise<Buffer> {
|
|
196
300
|
if (!attachment.storageKey) {
|
|
197
301
|
throw new AttachmentResolutionError(
|
|
198
302
|
attachment.filename,
|
|
@@ -206,14 +310,8 @@ async function resolveAttachment(
|
|
|
206
310
|
`(key: ${attachment.storageKey}) cannot be downloaded`,
|
|
207
311
|
);
|
|
208
312
|
}
|
|
209
|
-
|
|
210
|
-
const { name: filename, renamedFrom } = allocateUniqueName(
|
|
211
|
-
safeInputName(attachment.filename || attachment.storageKey),
|
|
212
|
-
takenNames,
|
|
213
|
-
);
|
|
214
|
-
let content: Buffer;
|
|
215
313
|
try {
|
|
216
|
-
|
|
314
|
+
return await options.storage.download(attachment.storageKey);
|
|
217
315
|
} catch (err) {
|
|
218
316
|
throw new AttachmentResolutionError(
|
|
219
317
|
attachment.filename,
|
|
@@ -221,24 +319,90 @@ async function resolveAttachment(
|
|
|
221
319
|
`${err instanceof Error ? err.message : String(err)}`,
|
|
222
320
|
);
|
|
223
321
|
}
|
|
224
|
-
|
|
322
|
+
}
|
|
225
323
|
|
|
226
|
-
|
|
227
|
-
// vision budget before they go out of scope (the sniff decides eligibility;
|
|
228
|
-
// no pre-filter needed on this branch).
|
|
229
|
-
const vision = options.visionBudget?.offer(filename, attachment.contentType, content);
|
|
324
|
+
// ── Placement ────────────────────────────────────────────────────────
|
|
230
325
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
326
|
+
/** Where an attachment lands, relative to the `inputs/` directory. */
|
|
327
|
+
interface Placement {
|
|
328
|
+
readonly relative: string;
|
|
329
|
+
/** Present when a default-derived name was uniquified (issue #364). */
|
|
330
|
+
readonly renamedFrom?: string;
|
|
331
|
+
}
|
|
234
332
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
333
|
+
/**
|
|
334
|
+
* The two-pass placement (see module doc): explicit mount paths first, each
|
|
335
|
+
* resolved into the namespace and refused on a contradiction; then the
|
|
336
|
+
* default-derived names, uniquified around every name already taken directly
|
|
337
|
+
* under `inputs/`.
|
|
338
|
+
*/
|
|
339
|
+
function resolvePlacements(attachments: readonly Attachment[]): Map<Attachment, Placement> {
|
|
340
|
+
const places = new Map<Attachment, Placement>();
|
|
341
|
+
const claimed = new Map<string, Attachment>();
|
|
342
|
+
|
|
343
|
+
for (const attachment of attachments) {
|
|
344
|
+
if (!attachment.mountPath) continue;
|
|
345
|
+
const relative = explicitPlacement(attachment);
|
|
346
|
+
const existing = claimed.get(relative);
|
|
347
|
+
if (existing) {
|
|
348
|
+
throw new AttachmentResolutionError(
|
|
349
|
+
attachment.filename,
|
|
350
|
+
`mount path '${attachment.mountPath}' collides with attachment '${existing.filename}'. ` +
|
|
351
|
+
"Set distinct mountPath values on the attachments to resolve this conflict.",
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
claimed.set(relative, attachment);
|
|
355
|
+
places.set(attachment, { relative });
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const takenNames = new Set<string>();
|
|
359
|
+
for (const relative of claimed.keys()) {
|
|
360
|
+
if (!relative.includes("/")) takenNames.add(relative);
|
|
361
|
+
}
|
|
362
|
+
for (const attachment of attachments) {
|
|
363
|
+
if (attachment.mountPath) continue;
|
|
364
|
+
const { name, renamedFrom } = allocateUniqueName(
|
|
365
|
+
safeInputName(attachment.filename || attachment.localPath || attachment.storageKey),
|
|
366
|
+
takenNames,
|
|
367
|
+
);
|
|
368
|
+
places.set(attachment, renamedFrom !== undefined ? { relative: name, renamedFrom } : { relative: name });
|
|
369
|
+
}
|
|
370
|
+
return places;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** The namespace prefixes an explicit `mountPath` may name, longest first. */
|
|
374
|
+
const INPUTS_NAMESPACE_PREFIXES = [`${STIGMER_LOCAL_STATE_DIR}/${INPUTS_SUBDIR}`, INPUTS_SUBDIR] as const;
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Resolve an explicit `mountPath` into a path relative to `inputs/`, or
|
|
378
|
+
* refuse: empty after cleaning, outside the namespace, or climbing out of it.
|
|
379
|
+
* A caller-supplied path is untrusted, so `..` is judged after normalization.
|
|
380
|
+
*/
|
|
381
|
+
function explicitPlacement(attachment: Attachment): string {
|
|
382
|
+
const cleaned = attachment.mountPath.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
383
|
+
const prefix = INPUTS_NAMESPACE_PREFIXES.find((p) => cleaned === p || cleaned.startsWith(`${p}/`));
|
|
384
|
+
if (prefix === undefined) {
|
|
385
|
+
throw new AttachmentResolutionError(
|
|
386
|
+
attachment.filename,
|
|
387
|
+
`mount path '${attachment.mountPath}' is outside the attachment namespace. ` +
|
|
388
|
+
`Attachments land under '${INPUTS_SUBDIR}/' (surfaced as '${STIGMER_LOCAL_STATE_DIR}/${INPUTS_SUBDIR}/'); ` +
|
|
389
|
+
`set mountPath to '${INPUTS_SUBDIR}/<name>' or leave it empty for the default placement.`,
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
const relative = posix.normalize(cleaned.slice(prefix.length).replace(/^\/+/, ""));
|
|
393
|
+
if (relative === "" || relative === ".") {
|
|
394
|
+
throw new AttachmentResolutionError(
|
|
395
|
+
attachment.filename,
|
|
396
|
+
`mount path '${attachment.mountPath}' names the inputs directory itself, not a place inside it`,
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
if (relative === ".." || relative.startsWith("../") || posix.isAbsolute(relative)) {
|
|
400
|
+
throw new AttachmentResolutionError(
|
|
401
|
+
attachment.filename,
|
|
402
|
+
`mount path '${attachment.mountPath}' escapes the attachment namespace`,
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
return relative;
|
|
242
406
|
}
|
|
243
407
|
|
|
244
408
|
/**
|
|
@@ -251,10 +415,9 @@ async function resolveAttachment(
|
|
|
251
415
|
async function materializeLocalFile(
|
|
252
416
|
attachment: Attachment,
|
|
253
417
|
filename: string,
|
|
254
|
-
|
|
418
|
+
dest: string,
|
|
255
419
|
visionBudget: VisionBudget | undefined,
|
|
256
420
|
): Promise<VisionOutcome | undefined> {
|
|
257
|
-
const dest = join(inputsDir, filename);
|
|
258
421
|
if (!visionBudget || !isVisionCandidate(attachment.contentType, filename)) {
|
|
259
422
|
await copyFile(attachment.localPath, dest);
|
|
260
423
|
return undefined;
|