@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
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZIP validation and decompression for attachment archives — the byte-level
|
|
3
|
+
* half of an `extract` attachment, shared by every writer of one.
|
|
4
|
+
*
|
|
5
|
+
* Security model: attachments are untrusted user uploads. Archives are parsed
|
|
6
|
+
* from the central directory — the format's authoritative index — via the
|
|
7
|
+
* shared structural layer (@stigmer/zip-structure; issue #567, which killed
|
|
8
|
+
* the earlier local-header walk: it silently truncated stored streaming
|
|
9
|
+
* entries and rejected Go-default archives outright). Every entry is
|
|
10
|
+
* validated for path traversal, zip bombs, and format integrity BEFORE any
|
|
11
|
+
* extraction occurs, and because the central directory's sizes are
|
|
12
|
+
* declarations an attacker controls, decompression re-enforces them: an
|
|
13
|
+
* entry whose actual output disagrees with its declared size aborts the
|
|
14
|
+
* extraction.
|
|
15
|
+
*
|
|
16
|
+
* Error model: fail-hard through {@link AttachmentValidationError}. An
|
|
17
|
+
* archive that fails any check aborts the whole attachment resolution and
|
|
18
|
+
* propagates a descriptive, user-fixable message. This is deliberately the
|
|
19
|
+
* OPPOSITE policy from the skill-artifact reader (`zip-extract.ts`,
|
|
20
|
+
* non-fatal empty return): skill artifacts were structurally vouched for by
|
|
21
|
+
* the push gates, attachments never are.
|
|
22
|
+
*
|
|
23
|
+
* Validation and decompression share one record ({@link ValidatedZipEntry},
|
|
24
|
+
* still carrying its payload slice), so parsing happens exactly once and
|
|
25
|
+
* "the manifest promised a file extraction cannot find" is unrepresentable.
|
|
26
|
+
* The WRITE of an extracted entry is the caller's: the attachment resolver
|
|
27
|
+
* writes under the session's platform dir (`attachment-resolver.ts`), the
|
|
28
|
+
* one caller since S3 M2b retired the native injector. Moved here from that
|
|
29
|
+
* injector at S3 M1 (Q-S3-14) so the guards have one home.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { createInflateRaw } from "node:zlib";
|
|
33
|
+
import { posix } from "node:path";
|
|
34
|
+
import {
|
|
35
|
+
EOCD_MIN_SIZE,
|
|
36
|
+
parseZipStructure,
|
|
37
|
+
type ZipStructuralEntry,
|
|
38
|
+
} from "@stigmer/zip-structure";
|
|
39
|
+
|
|
40
|
+
// ── Constants ────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
export const MAX_ZIP_FILES = 1000;
|
|
43
|
+
export const MAX_ZIP_EXTRACTED_SIZE = 100 * 1024 * 1024; // 100 MB
|
|
44
|
+
|
|
45
|
+
// ── Types ────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
export interface ZipEntryInfo {
|
|
48
|
+
readonly relativePath: string;
|
|
49
|
+
readonly uncompressedSize: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A validated file entry, still carrying its payload slice. Validation and
|
|
54
|
+
* extraction share these records — parsing happens exactly once, so "the
|
|
55
|
+
* manifest promised a file extraction cannot find" is unrepresentable
|
|
56
|
+
* (the old two-walker design silently skipped such entries).
|
|
57
|
+
*/
|
|
58
|
+
export interface ValidatedZipEntry extends ZipEntryInfo {
|
|
59
|
+
readonly compressionMethod: number;
|
|
60
|
+
readonly compressedData: Uint8Array;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Errors ───────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
/** An archive the user handed us cannot be safely extracted; the reason is theirs to fix. */
|
|
66
|
+
export class AttachmentValidationError extends Error {
|
|
67
|
+
readonly attachmentFilename: string;
|
|
68
|
+
readonly reason: string;
|
|
69
|
+
|
|
70
|
+
constructor(attachmentFilename: string, reason: string) {
|
|
71
|
+
super(`Attachment '${attachmentFilename}': ${reason}`);
|
|
72
|
+
this.name = "AttachmentValidationError";
|
|
73
|
+
this.attachmentFilename = attachmentFilename;
|
|
74
|
+
this.reason = reason;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── ZIP Validation (pure functions) ──────────────────────────────────
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Validate a ZIP archive for safe extraction. Returns the manifest of
|
|
82
|
+
* file entries (directories excluded) if the archive passes all checks.
|
|
83
|
+
*
|
|
84
|
+
* Security checks enforced:
|
|
85
|
+
* 1. Valid ZIP format (readable central directory; fail-hard, see module doc)
|
|
86
|
+
* 2. No absolute paths (entries starting with / or \)
|
|
87
|
+
* 3. No path traversal (.. components)
|
|
88
|
+
* 4. No null bytes in filenames
|
|
89
|
+
* 5. No duplicate entry paths (a contradictory manifest)
|
|
90
|
+
* 6. Only supported compression methods (stored, deflate) — checked here
|
|
91
|
+
* so an unsupported entry can never abort extraction after earlier
|
|
92
|
+
* entries were already written
|
|
93
|
+
* 7. Non-empty archive (at least one file entry)
|
|
94
|
+
* 8. File count within limits (max 1000)
|
|
95
|
+
* 9. Total declared uncompressed size within limits (max 100 MB) —
|
|
96
|
+
* declarations are re-enforced against actual output at decompression
|
|
97
|
+
*/
|
|
98
|
+
export function validateZipForExtraction(
|
|
99
|
+
zipData: Buffer,
|
|
100
|
+
sourceFilename: string,
|
|
101
|
+
): ZipEntryInfo[] {
|
|
102
|
+
return parseAndValidateZip(zipData, sourceFilename).map(
|
|
103
|
+
({ relativePath, uncompressedSize }) => ({ relativePath, uncompressedSize }),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** The same checks as {@link validateZipForExtraction}, keeping each entry's payload for the extraction that follows. */
|
|
108
|
+
export function parseAndValidateZip(
|
|
109
|
+
zipData: Buffer,
|
|
110
|
+
sourceFilename: string,
|
|
111
|
+
): ValidatedZipEntry[] {
|
|
112
|
+
if (zipData.length < EOCD_MIN_SIZE) {
|
|
113
|
+
throw new AttachmentValidationError(
|
|
114
|
+
sourceFilename,
|
|
115
|
+
"not a valid ZIP archive (file too small)",
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let structural: ZipStructuralEntry[];
|
|
120
|
+
try {
|
|
121
|
+
structural = parseZipStructure(zipData);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
throw new AttachmentValidationError(
|
|
124
|
+
sourceFilename,
|
|
125
|
+
`not a valid ZIP archive (${err instanceof Error ? err.message : String(err)})`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const entries: ValidatedZipEntry[] = [];
|
|
130
|
+
const seenPaths = new Set<string>();
|
|
131
|
+
let totalUncompressed = 0;
|
|
132
|
+
|
|
133
|
+
for (const entry of structural) {
|
|
134
|
+
if (entry.name.includes("\u0000")) {
|
|
135
|
+
throw new AttachmentValidationError(
|
|
136
|
+
sourceFilename,
|
|
137
|
+
"contains a filename with null bytes and cannot be safely extracted",
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (entry.isDirectory) continue;
|
|
142
|
+
|
|
143
|
+
if (entry.name.startsWith("/") || entry.name.startsWith("\\")) {
|
|
144
|
+
throw new AttachmentValidationError(
|
|
145
|
+
sourceFilename,
|
|
146
|
+
`contains an absolute path entry and cannot be safely extracted: ${entry.name}`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (hasPathTraversal(entry.name)) {
|
|
151
|
+
throw new AttachmentValidationError(
|
|
152
|
+
sourceFilename,
|
|
153
|
+
`contains a path traversal entry and cannot be safely extracted: ${entry.name}`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (seenPaths.has(entry.name)) {
|
|
158
|
+
throw new AttachmentValidationError(
|
|
159
|
+
sourceFilename,
|
|
160
|
+
`contains duplicate entry '${entry.name}' and cannot be safely extracted`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
seenPaths.add(entry.name);
|
|
164
|
+
|
|
165
|
+
if (entry.compressionMethod !== 0 && entry.compressionMethod !== 8) {
|
|
166
|
+
throw new AttachmentValidationError(
|
|
167
|
+
sourceFilename,
|
|
168
|
+
`entry '${entry.name}' uses unsupported compression method ${entry.compressionMethod}`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
entries.push({
|
|
173
|
+
relativePath: entry.name,
|
|
174
|
+
uncompressedSize: entry.uncompressedSize,
|
|
175
|
+
compressionMethod: entry.compressionMethod,
|
|
176
|
+
compressedData: entry.compressedData,
|
|
177
|
+
});
|
|
178
|
+
totalUncompressed += entry.uncompressedSize;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (entries.length === 0) {
|
|
182
|
+
throw new AttachmentValidationError(
|
|
183
|
+
sourceFilename,
|
|
184
|
+
"is an empty ZIP archive (no file entries)",
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (entries.length > MAX_ZIP_FILES) {
|
|
189
|
+
throw new AttachmentValidationError(
|
|
190
|
+
sourceFilename,
|
|
191
|
+
`contains ${entries.length} files (limit: ${MAX_ZIP_FILES})`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (totalUncompressed > MAX_ZIP_EXTRACTED_SIZE) {
|
|
196
|
+
const sizeMb = (totalUncompressed / (1024 * 1024)).toFixed(1);
|
|
197
|
+
const limitMb = (MAX_ZIP_EXTRACTED_SIZE / (1024 * 1024)).toFixed(0);
|
|
198
|
+
throw new AttachmentValidationError(
|
|
199
|
+
sourceFilename,
|
|
200
|
+
`would extract to ${sizeMb} MB (limit: ${limitMb} MB)`,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return entries.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ── Decompression ────────────────────────────────────────────────────
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Decompress a validated entry, enforcing its declared uncompressed size.
|
|
211
|
+
*
|
|
212
|
+
* The size cap in parseAndValidateZip budgets *declared* sizes, which the
|
|
213
|
+
* archive author controls — a crafted archive can declare 1 KB and inflate
|
|
214
|
+
* to gigabytes. Enforcement is therefore two-sided and fail-hard: inflation
|
|
215
|
+
* aborts the moment output exceeds the declaration, and an undershoot (or a
|
|
216
|
+
* stored payload whose length disagrees) fails too, because a manifest that
|
|
217
|
+
* misdescribes its own contents is exactly the corrupt-input class this
|
|
218
|
+
* module must never extract from.
|
|
219
|
+
*/
|
|
220
|
+
export async function decompressEntry(
|
|
221
|
+
entry: ValidatedZipEntry,
|
|
222
|
+
sourceFilename: string,
|
|
223
|
+
): Promise<Buffer> {
|
|
224
|
+
const declared = entry.uncompressedSize;
|
|
225
|
+
|
|
226
|
+
if (entry.compressionMethod === 0) {
|
|
227
|
+
if (entry.compressedData.length !== declared) {
|
|
228
|
+
throw new AttachmentValidationError(
|
|
229
|
+
sourceFilename,
|
|
230
|
+
`entry '${entry.relativePath}' payload is ${entry.compressedData.length} bytes ` +
|
|
231
|
+
`but the archive declares ${declared} — corrupt or crafted archive`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
return Buffer.from(entry.compressedData);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Deflate — the only other method parseAndValidateZip admits.
|
|
238
|
+
return new Promise<Buffer>((resolve, reject) => {
|
|
239
|
+
const inflate = createInflateRaw();
|
|
240
|
+
const chunks: Buffer[] = [];
|
|
241
|
+
let produced = 0;
|
|
242
|
+
inflate.on("data", (chunk: Buffer) => {
|
|
243
|
+
produced += chunk.length;
|
|
244
|
+
if (produced > declared) {
|
|
245
|
+
inflate.destroy();
|
|
246
|
+
reject(new AttachmentValidationError(
|
|
247
|
+
sourceFilename,
|
|
248
|
+
`entry '${entry.relativePath}' decompresses past its declared size of ` +
|
|
249
|
+
`${declared} bytes — corrupt or crafted archive`,
|
|
250
|
+
));
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
chunks.push(chunk);
|
|
254
|
+
});
|
|
255
|
+
inflate.on("end", () => {
|
|
256
|
+
if (produced !== declared) {
|
|
257
|
+
reject(new AttachmentValidationError(
|
|
258
|
+
sourceFilename,
|
|
259
|
+
`entry '${entry.relativePath}' decompressed to ${produced} bytes ` +
|
|
260
|
+
`but the archive declares ${declared} — corrupt or crafted archive`,
|
|
261
|
+
));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
resolve(Buffer.concat(chunks));
|
|
265
|
+
});
|
|
266
|
+
inflate.on("error", reject);
|
|
267
|
+
inflate.end(Buffer.from(entry.compressedData));
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function hasPathTraversal(filePath: string): boolean {
|
|
272
|
+
const normalized = posix.normalize(filePath);
|
|
273
|
+
if (normalized.startsWith("../") || normalized === "..") return true;
|
|
274
|
+
|
|
275
|
+
const segments = filePath.split(/[/\\]/);
|
|
276
|
+
for (const seg of segments) {
|
|
277
|
+
if (seg === "..") return true;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import type { ResolvedMcpServer } from "./mcp-resolver.js";
|
|
22
22
|
import { resolveMcpServers } from "./mcp-resolver.js";
|
|
23
23
|
import type { McpTransportPosture } from "./mcp-transport-guard.js";
|
|
24
|
+
import { withTimeout } from "./with-timeout.js";
|
|
24
25
|
import type { StigmerClient } from "../client/stigmer-client.js";
|
|
25
26
|
import type { McpServerUsage } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
|
|
26
27
|
|
|
@@ -98,17 +99,14 @@ export async function backfillMcpServersIfNeeded(
|
|
|
98
99
|
);
|
|
99
100
|
onHeartbeat?.();
|
|
100
101
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
),
|
|
110
|
-
),
|
|
111
|
-
]);
|
|
102
|
+
// Bounded through the shared helper so the timer is cleared when the
|
|
103
|
+
// connect wins; a bare `setTimeout` in a `Promise.race` stays armed and
|
|
104
|
+
// holds the event loop open for the full bound (stigmer#1008).
|
|
105
|
+
const updated = await withTimeout(
|
|
106
|
+
CONNECT_TIMEOUT_MS,
|
|
107
|
+
`Connect timed out after ${CONNECT_TIMEOUT_MS / 1000}s`,
|
|
108
|
+
() => client.connectMcpServer(serverId, org, runtimeEnv),
|
|
109
|
+
);
|
|
112
110
|
|
|
113
111
|
const toolCount = updated.status?.discoveredCapabilities?.tools.length ?? 0;
|
|
114
112
|
const approvalCount = updated.status?.toolApprovals?.length ?? 0;
|
package/src/shared/cost-guard.ts
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* same clean-cancel pattern the stall watchdog and first-denial stop use.
|
|
14
14
|
* Slightly harsher than native by construction; both harnesses terminate with
|
|
15
15
|
* the same honesty semantics (EXECUTION_TERMINATED, work checkpointed, the
|
|
16
|
-
* conversation continues on the next message — the recursion-limit precedent
|
|
17
|
-
*
|
|
16
|
+
* conversation continues on the next message — the recursion-limit precedent,
|
|
17
|
+
* `harness/terminal-table.ts` `toolCallLimitArm`).
|
|
18
18
|
*
|
|
19
19
|
* The running figure is the usage accumulator's local pricing-table estimate
|
|
20
20
|
* (authoritative billing is the BiDi proxy). That is the same estimation
|
|
@@ -34,7 +34,7 @@ export function costCapExceeded(maxCostUsd: number, estimatedCostUsd: number): b
|
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* Stable prefix of the cost-limit terminal error. Mirrors the cross-repo
|
|
37
|
-
* pattern of TOOL_CALL_LIMIT_ERROR_PREFIX (
|
|
37
|
+
* pattern of TOOL_CALL_LIMIT_ERROR_PREFIX (tool-rounds.ts): consumers
|
|
38
38
|
* that need to distinguish "ran out of cost budget" from other TERMINATED
|
|
39
39
|
* causes can match on this prefix, because AgentExecutionStatus carries no
|
|
40
40
|
* structured termination reason. Do not reword without checking consumers.
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* Shared interface for proto mutation consumed by side-effect classes
|
|
3
3
|
* (InlinePublisher, WriteBackCoordinator) and streaming loops.
|
|
4
4
|
*
|
|
5
|
-
* The deep-agent harness implements it on its
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* The deep-agent harness implements it on its `V3StatusBuilder`; the Cursor
|
|
6
|
+
* harness — which mutates a bare status proto with no builder — uses
|
|
7
|
+
* {@link statusProtoWriter}. Side-effect classes depend only on this
|
|
8
|
+
* interface, never on a specific implementation.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
@@ -28,7 +28,7 @@ export interface ExecutionStatusWriter {
|
|
|
28
28
|
* concern the direct-mutation style has no use for, so it stays `false`.
|
|
29
29
|
*
|
|
30
30
|
* Write-backs upsert by `workspaceEntryName` — the same contract as the
|
|
31
|
-
* deep-agent `
|
|
31
|
+
* deep-agent `V3StatusBuilder.addWriteBack`: each git-backed workspace entry
|
|
32
32
|
* carries at most one record, progressing through phases.
|
|
33
33
|
*/
|
|
34
34
|
export function statusProtoWriter(status: AgentExecutionStatus): ExecutionStatusWriter {
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tier-2 structured-output extraction for a harness turn.
|
|
3
3
|
*
|
|
4
|
-
* Harness-agnostic
|
|
5
|
-
*
|
|
4
|
+
* Harness-agnostic, and called from one place: the turn runtime's epilogue
|
|
5
|
+
* (`harness/run-turn.ts` `extractStructuredOutputFromText`), so both
|
|
6
|
+
* harnesses have it — Cursor since S2 M3, native since S3 M2a (pinned by the
|
|
7
|
+
* native hermetic `structured-output` tier-2 arm since M2b, Q-S3-7).
|
|
6
8
|
*
|
|
7
9
|
* When tier-1 text extraction (shared/extract-json.ts) cannot find JSON in
|
|
8
10
|
* the agent's free-text response, this tier asks an economy-tier LLM to
|
|
9
11
|
* extract it via withStructuredOutput (function-calling), which guarantees
|
|
10
12
|
* schema-conformant output through the API's tool-use mechanism.
|
|
11
13
|
*
|
|
12
|
-
* Lives in its own module
|
|
13
|
-
* the
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* bundle-slim's deferred evaluation preserves.
|
|
14
|
+
* Lives in its own module so the LangChain construction path stays out of
|
|
15
|
+
* the runtime's module graph until a run actually needs tier 2 — the
|
|
16
|
+
* runtime imports this module lazily at the call site, mirroring its tier-1
|
|
17
|
+
* import, which is what bundle-slim's deferred evaluation preserves.
|
|
17
18
|
*/
|
|
18
19
|
|
|
19
20
|
import type { Config } from "../config.js";
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @regression file-hitl-phase0 — pins file-edit HITL fixes #7, #8, #9 (see _projects/2026-06/20260630.01.file-change-hitl-redesign/tasks/T01_3_regression-manifest.md)
|
|
3
3
|
*
|
|
4
|
-
* Tests the harness-agnostic capture orchestration directly
|
|
5
|
-
* harness id (the
|
|
6
|
-
*
|
|
7
|
-
* Cursor
|
|
4
|
+
* Tests the harness-agnostic capture orchestration directly, under one
|
|
5
|
+
* harness id (since S3 M4 the runtime is the ONE caller for every harness,
|
|
6
|
+
* `harness/capture.ts`; until then each adapter called this seam and the
|
|
7
|
+
* Cursor side had its own test, `execute-cursor/capture-flow.test.ts`, whose
|
|
8
|
+
* three reconcile arms no other file carried moved here). Confirms that the
|
|
9
|
+
* ledger entries depend on nothing but `harness_id` and that with no
|
|
8
10
|
* excludePaths every changed file is captured. Runs against a REAL temp git repo.
|
|
9
11
|
*/
|
|
10
12
|
|
|
@@ -333,6 +335,86 @@ function candidateCompleteness(status: AgentExecutionStatus): DiffCompleteness |
|
|
|
333
335
|
: undefined;
|
|
334
336
|
}
|
|
335
337
|
|
|
338
|
+
describe("capture reconcile — the decision rules the resume enforces (moved from execute-cursor/capture-flow.test at S3 M4)", () => {
|
|
339
|
+
async function captureTwoFileTurn(status: AgentExecutionStatus): Promise<void> {
|
|
340
|
+
const baseline = await captureBaselineToLedger({ status, gitRoot: repo, executionId: EXEC_ID, changeSetId: CHANGE_SET_ID, harnessId: HARNESS });
|
|
341
|
+
await write("notes.md", "planton notes\n");
|
|
342
|
+
await write("src/main.ts", "export const x = 99;\n");
|
|
343
|
+
await captureCandidateToLedger({ status, gitRoot: repo, executionId: EXEC_ID, changeSetId: CHANGE_SET_ID, baselineTree: baseline, harnessId: HARNESS });
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
it("reconstructs an approved file from the ref even if the tree was reset (retry-safe)", async () => {
|
|
347
|
+
const status = newStatus();
|
|
348
|
+
const baseline = await captureBaselineToLedger({ status, gitRoot: repo, executionId: EXEC_ID, changeSetId: CHANGE_SET_ID, harnessId: HARNESS });
|
|
349
|
+
await write("notes.md", "planton notes\n");
|
|
350
|
+
await captureCandidateToLedger({ status, gitRoot: repo, executionId: EXEC_ID, changeSetId: CHANGE_SET_ID, baselineTree: baseline, harnessId: HARNESS });
|
|
351
|
+
const changeSet = decidedChangeSet(status, { "notes.md": FileDecisionAction.APPROVE });
|
|
352
|
+
|
|
353
|
+
// A tree reset before the resume (a Temporal retry, a sandbox recycle):
|
|
354
|
+
// the approved bytes are gone from disk, the pinned refs survive.
|
|
355
|
+
await write("notes.md", "platon notes\n");
|
|
356
|
+
|
|
357
|
+
const result = await applyCaptureDecisions({ status, gitRoot: repo, executionId: EXEC_ID, changeSet, harnessId: HARNESS });
|
|
358
|
+
|
|
359
|
+
expect(result.approvedPaths).toEqual(["notes.md"]);
|
|
360
|
+
// Reconcile-from-refs re-asserts the approved "after" bytes whatever the tree holds.
|
|
361
|
+
expect(await read("notes.md")).toBe("planton notes\n");
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it("CHANGE_SET-scoped approval covers all files; a FILE decision overrides it (most-specific-wins)", async () => {
|
|
365
|
+
const status = newStatus();
|
|
366
|
+
await captureTwoFileTurn(status);
|
|
367
|
+
const changes = candidateChanges(status);
|
|
368
|
+
const mainChange = changes.find((c) => c.pathAfter === "src/main.ts")!;
|
|
369
|
+
const changeSet = create(FileChangeSetSchema, {
|
|
370
|
+
id: CHANGE_SET_ID,
|
|
371
|
+
changes,
|
|
372
|
+
status: FileChangeSetStatus.DECIDED,
|
|
373
|
+
decisions: [
|
|
374
|
+
// CHANGE_SET: approve everything…
|
|
375
|
+
create(FileDecisionSchema, { id: "d-cs", changeSetId: CHANGE_SET_ID, scope: FileDecisionScope.CHANGE_SET, action: FileDecisionAction.APPROVE }),
|
|
376
|
+
// …but a FILE decision rejects src/main.ts (more specific wins).
|
|
377
|
+
create(FileDecisionSchema, {
|
|
378
|
+
id: "d-file",
|
|
379
|
+
changeSetId: CHANGE_SET_ID,
|
|
380
|
+
scope: FileDecisionScope.FILE,
|
|
381
|
+
fileChangeId: mainChange.id,
|
|
382
|
+
action: FileDecisionAction.REJECT,
|
|
383
|
+
expectedDigest: mainChange.fileDigest,
|
|
384
|
+
}),
|
|
385
|
+
],
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
const result = await applyCaptureDecisions({ status, gitRoot: repo, executionId: EXEC_ID, changeSet, harnessId: HARNESS });
|
|
389
|
+
|
|
390
|
+
expect(result.approvedPaths).toEqual(["notes.md"]);
|
|
391
|
+
expect(result.rejectedPaths).toEqual(["src/main.ts"]);
|
|
392
|
+
expect(await read("notes.md")).toBe("planton notes\n");
|
|
393
|
+
expect(await read("src/main.ts")).toBe("export const x = 1;\n");
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it("refuses to apply and authors FAILED(HASH_MISMATCH) when on-disk bytes diverge from the approved digest", async () => {
|
|
397
|
+
const status = newStatus();
|
|
398
|
+
await captureTwoFileTurn(status);
|
|
399
|
+
// The reviewer approved both, but the recorded after_sha256 for notes.md no
|
|
400
|
+
// longer matches the captured bytes (what-you-approve-is-what-applies fails).
|
|
401
|
+
const changeSet = decidedChangeSet(status, { "notes.md": FileDecisionAction.APPROVE, "src/main.ts": FileDecisionAction.APPROVE });
|
|
402
|
+
changeSet.changes.find((c) => c.pathAfter === "notes.md")!.afterSha256 = "deadbeef".repeat(8);
|
|
403
|
+
|
|
404
|
+
const result = await applyCaptureDecisions({ status, gitRoot: repo, executionId: EXEC_ID, changeSet, harnessId: HARNESS });
|
|
405
|
+
|
|
406
|
+
expect(result.failed).toBe(true);
|
|
407
|
+
expect(result.failureDetail).toContain("notes.md");
|
|
408
|
+
// NOTHING was applied (a partial apply under a verification failure is
|
|
409
|
+
// worse than none): src/main.ts still holds its un-reconciled (after) bytes.
|
|
410
|
+
expect(await read("src/main.ts")).toBe("export const x = 99;\n");
|
|
411
|
+
const failed = eventsOfType(status, FileReviewEventType.FAILED);
|
|
412
|
+
expect(failed).toHaveLength(1);
|
|
413
|
+
if (failed[0]!.payload.case === "failed") expect(failed[0]!.payload.value.detail).toContain("notes.md");
|
|
414
|
+
expect(eventsOfType(status, FileReviewEventType.RECONCILED)).toHaveLength(0);
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
336
418
|
describe("capture orchestration — hybrid git + CAS (Phase 3)", () => {
|
|
337
419
|
it("composes git-tracked and CAS captures into one HYBRID change set", async () => {
|
|
338
420
|
const status = newStatus();
|
|
@@ -16,7 +16,8 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
|
16
16
|
import { join, dirname } from "node:path";
|
|
17
17
|
import { tmpdir } from "node:os";
|
|
18
18
|
import { FileChangeKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
19
|
-
import { createCasProgressSubstrate
|
|
19
|
+
import { createCasProgressSubstrate } from "../cas-progress.js";
|
|
20
|
+
import type { CasTouchedSnapshot } from "../cas-touched.js";
|
|
20
21
|
import { LINE_COUNT_MAX_BYTES } from "../line-counts.js";
|
|
21
22
|
import type { ProgressEntry } from "../progress.js";
|
|
22
23
|
|
|
@@ -43,26 +43,12 @@ import {
|
|
|
43
43
|
type ProgressSubstrate,
|
|
44
44
|
} from "./progress.js";
|
|
45
45
|
import { partitionIgnoredPathsBySecret } from "./secret-paths.js";
|
|
46
|
+
import type { CasTouchedReader } from "./cas-touched.js";
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
* - `blockedSecretPaths`: paths the gate hard-blocked as secret-like — excluded
|
|
52
|
-
* from capture.
|
|
53
|
-
*
|
|
54
|
-
* Deep-agent supplies a synchronous copy of its live `CasCaptureObserver`; Cursor
|
|
55
|
-
* supplies a reader over the hook's on-disk sidecar. The reader MUST return an
|
|
56
|
-
* atomic snapshot (copy the live map before any await) so a concurrent sub-agent
|
|
57
|
-
* write cannot mutate it mid-capture.
|
|
58
|
-
*/
|
|
59
|
-
export interface CasTouchedSnapshot {
|
|
60
|
-
readonly before: ReadonlyMap<string, Uint8Array | null>;
|
|
61
|
-
readonly blockedSecretPaths: ReadonlySet<string>;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Produces a {@link CasTouchedSnapshot} for one capture. */
|
|
65
|
-
export type CasTouchedReader = () => CasTouchedSnapshot | Promise<CasTouchedSnapshot>;
|
|
48
|
+
// The snapshot this substrate reads (`CasTouchedSnapshot`) and the reader that
|
|
49
|
+
// produces it live in `cas-touched.ts`, the one home of "what the engine
|
|
50
|
+
// observed touching CAS-owned paths": the turn-boundary capture reads the same
|
|
51
|
+
// snapshot, so neither consumer owns the shape.
|
|
66
52
|
|
|
67
53
|
/**
|
|
68
54
|
* The CAS progress substrate. Reads the touched set on each capture, computes
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the engine observed touching CAS-owned paths this turn — the one fact
|
|
3
|
+
* about a turn's file changes that the runtime cannot read from the tree and
|
|
4
|
+
* the engine cannot help knowing.
|
|
5
|
+
*
|
|
6
|
+
* In a git work tree the tracked changes are a `git diff` against the pinned
|
|
7
|
+
* baseline; the gitignored ones are invisible to git, and in a non-git
|
|
8
|
+
* workspace EVERY change is. For those paths the only record of the
|
|
9
|
+
* pre-turn bytes is the one taken at mutation time, by whatever sits between
|
|
10
|
+
* the engine and the disk: the deep-agent's `CasCaptureObserver` (fed by its
|
|
11
|
+
* filesystem backend and its approval gate) or the Cursor hook's on-disk
|
|
12
|
+
* sidecar. Both produce this one snapshot shape, and both consumers read it:
|
|
13
|
+
* the mid-run progress slice (`cas-progress.ts`) and the turn-boundary
|
|
14
|
+
* capture ({@link buildCasTurnCaptures}), which composes it into the
|
|
15
|
+
* {@link CasPathCapture}s the CAS substrate stores.
|
|
16
|
+
*
|
|
17
|
+
* Until S3 M4 each harness carried its own copy of `buildCasTurnCaptures`
|
|
18
|
+
* beside its observation source; the copies were byte-near-identical
|
|
19
|
+
* (F-M4-P6) and now live here once. The AFTER bytes are always re-read from
|
|
20
|
+
* disk at composition time (the authoritative net result of the turn), so
|
|
21
|
+
* many edits to one path collapse to one before/after.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { readFile } from "node:fs/promises";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
import type { FileCaptureClass } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
27
|
+
import type { CasPathCapture } from "./cas-substrate.js";
|
|
28
|
+
import { partitionIgnoredPathsBySecret } from "./secret-paths.js";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The CAS-owned paths touched so far this turn: pre-turn bytes per
|
|
32
|
+
* first-touched path (`null` = the path did not exist → an ADD) and the paths
|
|
33
|
+
* the gate hard-blocked as secret-like (never applied, never captured; the
|
|
34
|
+
* boundary authors a content-less entry for each). Keyed workspace-root-
|
|
35
|
+
* relative. A reader MUST return an atomic snapshot (copy the live map before
|
|
36
|
+
* any await) so a concurrent sub-agent write cannot mutate it mid-capture.
|
|
37
|
+
*/
|
|
38
|
+
export interface CasTouchedSnapshot {
|
|
39
|
+
readonly before: ReadonlyMap<string, Uint8Array | null>;
|
|
40
|
+
readonly blockedSecretPaths: ReadonlySet<string>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Produces a {@link CasTouchedSnapshot} for one capture. */
|
|
44
|
+
export type CasTouchedReader = () => CasTouchedSnapshot | Promise<CasTouchedSnapshot>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Compose the turn's CAS captures from what the engine observed: for each
|
|
48
|
+
* observed path the before-bytes come from the snapshot and the after-bytes
|
|
49
|
+
* are re-read from disk now (`null` = the file is gone). Secret-like paths are
|
|
50
|
+
* diverted to `unreviewablePaths` and their before-bytes dropped — the
|
|
51
|
+
* fail-closed backstop that holds even under the global bypass, where the
|
|
52
|
+
* gate did not run to block them up front ({@link partitionIgnoredPathsBySecret}
|
|
53
|
+
* is the one decision; this shell performs only the IO for the capturable
|
|
54
|
+
* set). `captureClass` is the turn's CAS substrate class — GIT_IGNORED_CAPTURED
|
|
55
|
+
* for a git work tree's ignored paths, NON_GIT_CAS for a non-git workspace
|
|
56
|
+
* (where these ARE the whole change set) — so each captured path carries its
|
|
57
|
+
* true provenance. An empty snapshot yields empty results and leaves the
|
|
58
|
+
* shared capture unchanged.
|
|
59
|
+
*/
|
|
60
|
+
export async function buildCasTurnCaptures(
|
|
61
|
+
snapshot: CasTouchedSnapshot,
|
|
62
|
+
workspaceRoot: string,
|
|
63
|
+
captureClass: FileCaptureClass,
|
|
64
|
+
): Promise<{ casCaptures: CasPathCapture[]; unreviewablePaths: string[] }> {
|
|
65
|
+
const { capturablePaths, unreviewablePaths } = partitionIgnoredPathsBySecret(
|
|
66
|
+
snapshot.before.keys(),
|
|
67
|
+
snapshot.blockedSecretPaths,
|
|
68
|
+
);
|
|
69
|
+
const casCaptures: CasPathCapture[] = [];
|
|
70
|
+
for (const relPath of capturablePaths) {
|
|
71
|
+
const after = await readFileOrNull(join(workspaceRoot, relPath));
|
|
72
|
+
casCaptures.push({
|
|
73
|
+
path: relPath,
|
|
74
|
+
before: snapshot.before.get(relPath) ?? null,
|
|
75
|
+
after,
|
|
76
|
+
captureClass,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return { casCaptures, unreviewablePaths: [...unreviewablePaths] };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Raw bytes of a file, or `null` when it does not exist (a DELETE, or an ADD since removed). */
|
|
83
|
+
async function readFileOrNull(absolutePath: string): Promise<Uint8Array | null> {
|
|
84
|
+
try {
|
|
85
|
+
return await readFile(absolutePath);
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -56,11 +56,13 @@ export {
|
|
|
56
56
|
type ProgressSubstrate,
|
|
57
57
|
} from "./progress.js";
|
|
58
58
|
|
|
59
|
+
export { createCasProgressSubstrate } from "./cas-progress.js";
|
|
60
|
+
|
|
59
61
|
export {
|
|
60
|
-
|
|
62
|
+
buildCasTurnCaptures,
|
|
61
63
|
type CasTouchedReader,
|
|
62
64
|
type CasTouchedSnapshot,
|
|
63
|
-
} from "./cas-
|
|
65
|
+
} from "./cas-touched.js";
|
|
64
66
|
|
|
65
67
|
export {
|
|
66
68
|
countLineChanges,
|