@stigmer/runner 3.0.9-dev.20260615153829 → 3.1.0
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/dist/.build-fingerprint +1 -1
- package/dist/__test-utils__/approval-contract/types.d.ts +174 -0
- package/dist/__test-utils__/approval-contract/types.js +24 -0
- package/dist/__test-utils__/approval-contract/types.js.map +1 -0
- package/dist/activities/call-agent-status.d.ts +19 -1
- package/dist/activities/call-agent-status.js +64 -5
- package/dist/activities/call-agent-status.js.map +1 -1
- package/dist/activities/call-llm.js +19 -53
- package/dist/activities/call-llm.js.map +1 -1
- package/dist/activities/classify-tool-approvals.d.ts +24 -0
- package/dist/activities/classify-tool-approvals.js +69 -17
- package/dist/activities/classify-tool-approvals.js.map +1 -1
- package/dist/activities/discover-mcp-server.d.ts +7 -0
- package/dist/activities/discover-mcp-server.js +11 -1
- package/dist/activities/discover-mcp-server.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +140 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +130 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -0
- package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.d.ts +18 -0
- package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +123 -0
- package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -0
- package/dist/activities/execute-cursor/approval-policy.d.ts +17 -61
- package/dist/activities/execute-cursor/approval-policy.js +8 -120
- package/dist/activities/execute-cursor/approval-policy.js.map +1 -1
- package/dist/activities/execute-cursor/approval-state.d.ts +219 -9
- package/dist/activities/execute-cursor/approval-state.js +233 -17
- package/dist/activities/execute-cursor/approval-state.js.map +1 -1
- package/dist/activities/execute-cursor/capture-flow.d.ts +127 -0
- package/dist/activities/execute-cursor/capture-flow.js +234 -0
- package/dist/activities/execute-cursor/capture-flow.js.map +1 -0
- package/dist/activities/execute-cursor/cas-observations.d.ts +96 -0
- package/dist/activities/execute-cursor/cas-observations.js +184 -0
- package/dist/activities/execute-cursor/cas-observations.js.map +1 -0
- package/dist/activities/execute-cursor/command-provenance.d.ts +62 -0
- package/dist/activities/execute-cursor/command-provenance.js +128 -0
- package/dist/activities/execute-cursor/command-provenance.js.map +1 -0
- package/dist/activities/execute-cursor/exact-apply.d.ts +110 -0
- package/dist/activities/execute-cursor/exact-apply.js +204 -0
- package/dist/activities/execute-cursor/exact-apply.js.map +1 -0
- package/dist/activities/execute-cursor/hook-script.d.ts +53 -24
- package/dist/activities/execute-cursor/hook-script.js +310 -47
- package/dist/activities/execute-cursor/hook-script.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +8 -1
- package/dist/activities/execute-cursor/index.js +500 -64
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/message-translator.d.ts +258 -11
- package/dist/activities/execute-cursor/message-translator.js +836 -90
- package/dist/activities/execute-cursor/message-translator.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +20 -23
- package/dist/activities/execute-cursor/prompt-builder.js +74 -15
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/workspace-setup.d.ts +17 -0
- package/dist/activities/execute-cursor/workspace-setup.js +212 -33
- package/dist/activities/execute-cursor/workspace-setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.d.ts +19 -0
- package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js +143 -0
- package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js.map +1 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.d.ts +88 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js +81 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js.map +1 -0
- package/dist/activities/execute-deep-agent/approval-file-change.d.ts +47 -0
- package/dist/activities/execute-deep-agent/approval-file-change.js +68 -0
- package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -0
- package/dist/activities/execute-deep-agent/attachment-injector.d.ts +8 -1
- package/dist/activities/execute-deep-agent/attachment-injector.js +7 -7
- package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
- package/dist/activities/execute-deep-agent/cas-capture-backend.d.ts +42 -0
- package/dist/activities/execute-deep-agent/cas-capture-backend.js +47 -0
- package/dist/activities/execute-deep-agent/cas-capture-backend.js.map +1 -0
- package/dist/activities/execute-deep-agent/cas-capture-observer.d.ts +79 -0
- package/dist/activities/execute-deep-agent/cas-capture-observer.js +112 -0
- package/dist/activities/execute-deep-agent/cas-capture-observer.js.map +1 -0
- package/dist/activities/execute-deep-agent/hitl.d.ts +16 -3
- package/dist/activities/execute-deep-agent/hitl.js +11 -3
- package/dist/activities/execute-deep-agent/hitl.js.map +1 -1
- package/dist/activities/execute-deep-agent/index.d.ts +2 -1
- package/dist/activities/execute-deep-agent/index.js +393 -33
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/inline-publisher.d.ts +7 -1
- package/dist/activities/execute-deep-agent/inline-publisher.js +23 -2
- package/dist/activities/execute-deep-agent/inline-publisher.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.d.ts +53 -2
- package/dist/activities/execute-deep-agent/setup.js +149 -92
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/stamp-flowed-rows.d.ts +36 -0
- package/dist/activities/execute-deep-agent/stamp-flowed-rows.js +56 -0
- package/dist/activities/execute-deep-agent/stamp-flowed-rows.js.map +1 -0
- package/dist/activities/execute-deep-agent/status-builder-shared.d.ts +34 -1
- package/dist/activities/execute-deep-agent/status-builder-shared.js +26 -25
- package/dist/activities/execute-deep-agent/status-builder-shared.js.map +1 -1
- package/dist/activities/execute-deep-agent/status-builder.d.ts +17 -5
- package/dist/activities/execute-deep-agent/status-builder.js +44 -3
- package/dist/activities/execute-deep-agent/status-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/streaming-side-effects.js +2 -19
- package/dist/activities/execute-deep-agent/streaming-side-effects.js.map +1 -1
- package/dist/activities/execute-deep-agent/streaming.js +3 -15
- package/dist/activities/execute-deep-agent/streaming.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +25 -7
- package/dist/activities/execute-deep-agent/subagent-transformer.js +23 -7
- package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +30 -3
- package/dist/activities/execute-deep-agent/subagent-wiring.js +29 -3
- package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
- package/dist/activities/execute-deep-agent/v3-status-builder.js +32 -2
- package/dist/activities/execute-deep-agent/v3-status-builder.js.map +1 -1
- package/dist/claimcheck/payload-codec.js +9 -5
- package/dist/claimcheck/payload-codec.js.map +1 -1
- package/dist/client/stigmer-client.d.ts +2 -0
- package/dist/client/stigmer-client.js +2 -0
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/middleware/approval-gate.d.ts +85 -4
- package/dist/middleware/approval-gate.js +165 -38
- package/dist/middleware/approval-gate.js.map +1 -1
- package/dist/middleware/types.d.ts +2 -5
- package/dist/shared/activity-input.d.ts +43 -0
- package/dist/shared/activity-input.js +17 -0
- package/dist/shared/activity-input.js.map +1 -0
- package/dist/shared/approval-canonicalize.d.ts +19 -0
- package/dist/shared/approval-canonicalize.js +119 -0
- package/dist/shared/approval-canonicalize.js.map +1 -0
- package/dist/shared/approval-fingerprint.d.ts +106 -0
- package/dist/shared/approval-fingerprint.js +113 -0
- package/dist/shared/approval-fingerprint.js.map +1 -0
- package/dist/shared/approval-policy.d.ts +182 -12
- package/dist/shared/approval-policy.js +213 -27
- package/dist/shared/approval-policy.js.map +1 -1
- package/dist/shared/args-preview.d.ts +52 -0
- package/dist/shared/args-preview.js +93 -0
- package/dist/shared/args-preview.js.map +1 -0
- package/dist/shared/artifact-storage.d.ts +19 -1
- package/dist/shared/artifact-storage.js +48 -11
- package/dist/shared/artifact-storage.js.map +1 -1
- package/dist/shared/file-change.d.ts +44 -0
- package/dist/shared/file-change.js +57 -0
- package/dist/shared/file-change.js.map +1 -0
- package/dist/shared/file-tools.d.ts +107 -0
- package/dist/shared/file-tools.js +168 -0
- package/dist/shared/file-tools.js.map +1 -0
- package/dist/shared/filereview/capture.d.ts +202 -0
- package/dist/shared/filereview/capture.js +498 -0
- package/dist/shared/filereview/capture.js.map +1 -0
- package/dist/shared/filereview/cas-substrate.d.ts +190 -0
- package/dist/shared/filereview/cas-substrate.js +284 -0
- package/dist/shared/filereview/cas-substrate.js.map +1 -0
- package/dist/shared/filereview/digest.d.ts +40 -0
- package/dist/shared/filereview/digest.js +66 -0
- package/dist/shared/filereview/digest.js.map +1 -0
- package/dist/shared/filereview/events.d.ts +170 -0
- package/dist/shared/filereview/events.js +298 -0
- package/dist/shared/filereview/events.js.map +1 -0
- package/dist/shared/filereview/git-substrate.d.ts +175 -0
- package/dist/shared/filereview/git-substrate.js +439 -0
- package/dist/shared/filereview/git-substrate.js.map +1 -0
- package/dist/shared/filereview/index.d.ts +11 -0
- package/dist/shared/filereview/index.js +12 -0
- package/dist/shared/filereview/index.js.map +1 -0
- package/dist/shared/filereview/secret-paths.d.ts +63 -0
- package/dist/shared/filereview/secret-paths.js +105 -0
- package/dist/shared/filereview/secret-paths.js.map +1 -0
- package/dist/shared/fingerprint-secret.d.ts +26 -0
- package/dist/shared/fingerprint-secret.js +47 -0
- package/dist/shared/fingerprint-secret.js.map +1 -0
- package/dist/shared/model-client.d.ts +51 -0
- package/dist/shared/model-client.js +77 -0
- package/dist/shared/model-client.js.map +1 -0
- package/dist/shared/plan-artifact.js +0 -2
- package/dist/shared/plan-artifact.js.map +1 -1
- package/dist/shared/status-offload.d.ts +83 -9
- package/dist/shared/status-offload.js +399 -79
- package/dist/shared/status-offload.js.map +1 -1
- package/dist/shared/status.js +14 -1
- package/dist/shared/status.js.map +1 -1
- package/dist/shared/tool-kind.d.ts +19 -0
- package/dist/shared/tool-kind.js +13 -0
- package/dist/shared/tool-kind.js.map +1 -1
- package/dist/shared/tool-row.d.ts +88 -0
- package/dist/shared/tool-row.js +127 -0
- package/dist/shared/tool-row.js.map +1 -0
- package/dist/shared/workspace/platform-dir.d.ts +25 -0
- package/dist/shared/workspace/platform-dir.js +38 -2
- package/dist/shared/workspace/platform-dir.js.map +1 -1
- package/dist/workflows/call-agent-orchestrator.js +56 -7
- package/dist/workflows/call-agent-orchestrator.js.map +1 -1
- package/dist/workflows/connect-mcp-server.d.ts +50 -0
- package/dist/workflows/connect-mcp-server.js +136 -15
- package/dist/workflows/connect-mcp-server.js.map +1 -1
- package/dist/workflows/types.d.ts +8 -0
- package/package.json +2 -2
- package/src/__test-utils__/approval-contract/contract.ts +224 -0
- package/src/__test-utils__/approval-contract/types.ts +179 -0
- package/src/__test-utils__/fake-artifact-storage.ts +72 -0
- package/src/__tests__/approval-gateway-contract.test.ts +29 -0
- package/src/__tests__/claimcheck-codec.test.ts +16 -53
- package/src/__tests__/golden-e2e.test.ts +2 -0
- package/src/__tests__/runner-token-coordinator.test.ts +3 -3
- package/src/activities/__tests__/call-agent-status.test.ts +135 -0
- package/src/activities/__tests__/call-llm.test.ts +1 -1
- package/src/activities/__tests__/classify-tool-approvals.test.ts +208 -1
- package/src/activities/__tests__/discover-mcp-server.test.ts +30 -0
- package/src/activities/__tests__/workflow-event-activities.test.ts +2 -1
- package/src/activities/call-agent-status.ts +74 -4
- package/src/activities/call-llm.ts +18 -63
- package/src/activities/classify-tool-approvals.ts +101 -19
- package/src/activities/discover-mcp-server.ts +29 -1
- package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +216 -0
- package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +148 -0
- package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +41 -9
- package/src/activities/execute-cursor/__tests__/approval-state.test.ts +292 -0
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +68 -1
- package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +1005 -0
- package/src/activities/execute-cursor/__tests__/cas-observations.test.ts +187 -0
- package/src/activities/execute-cursor/__tests__/coarse-fingerprint.test.ts +97 -0
- package/src/activities/execute-cursor/__tests__/command-provenance.test.ts +240 -0
- package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +203 -0
- package/src/activities/execute-cursor/__tests__/exact-apply.test.ts +375 -0
- package/src/activities/execute-cursor/__tests__/hitl-ledger.test.ts +1294 -24
- package/src/activities/execute-cursor/__tests__/hitl-resume-history.test.ts +446 -0
- package/src/activities/execute-cursor/__tests__/hook-script.test.ts +384 -110
- package/src/activities/execute-cursor/__tests__/message-translator.test.ts +171 -25
- package/src/activities/execute-cursor/__tests__/sequential-gate-resume.test.ts +189 -0
- package/src/activities/execute-cursor/__tests__/tool-result-image.test.ts +44 -23
- package/src/activities/execute-cursor/__tests__/workspace-setup.test.ts +190 -10
- package/src/activities/execute-cursor/approval-policy.ts +28 -159
- package/src/activities/execute-cursor/approval-state.ts +366 -18
- package/src/activities/execute-cursor/capture-flow.ts +323 -0
- package/src/activities/execute-cursor/cas-observations.ts +204 -0
- package/src/activities/execute-cursor/command-provenance.ts +168 -0
- package/src/activities/execute-cursor/exact-apply.ts +253 -0
- package/src/activities/execute-cursor/hook-script.ts +317 -51
- package/src/activities/execute-cursor/index.ts +575 -67
- package/src/activities/execute-cursor/message-translator.ts +963 -89
- package/src/activities/execute-cursor/prompt-builder.ts +80 -14
- package/src/activities/execute-cursor/workspace-setup.ts +257 -42
- package/src/activities/execute-deep-agent/__test-utils__/gateway-substrate.ts +180 -0
- package/src/activities/execute-deep-agent/__test-utils__/scripted-model.ts +134 -0
- package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +84 -0
- package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +11 -24
- package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +64 -0
- package/src/activities/execute-deep-agent/__tests__/cas-capture-observer.test.ts +163 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-integration.test.ts +8 -5
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +342 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +284 -0
- package/src/activities/execute-deep-agent/__tests__/hitl.test.ts +14 -36
- package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +31 -13
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +349 -0
- package/src/activities/execute-deep-agent/__tests__/stamp-flowed-rows.test.ts +119 -0
- package/src/activities/execute-deep-agent/__tests__/status-builder.test.ts +91 -10
- package/src/activities/execute-deep-agent/__tests__/streaming-v3.test.ts +9 -9
- package/src/activities/execute-deep-agent/__tests__/subagent-approval-propagation.test.ts +160 -0
- package/src/activities/execute-deep-agent/__tests__/subagent-gitignored-capture.test.ts +213 -0
- package/src/activities/execute-deep-agent/__tests__/subagent-transformer.test.ts +3 -6
- package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +84 -1
- package/src/activities/execute-deep-agent/__tests__/v3-status-builder.test.ts +4 -1
- package/src/activities/execute-deep-agent/approval-file-change.ts +80 -0
- package/src/activities/execute-deep-agent/attachment-injector.ts +20 -11
- package/src/activities/execute-deep-agent/cas-capture-backend.ts +66 -0
- package/src/activities/execute-deep-agent/cas-capture-observer.ts +125 -0
- package/src/activities/execute-deep-agent/hitl.ts +23 -7
- package/src/activities/execute-deep-agent/index.ts +463 -40
- package/src/activities/execute-deep-agent/inline-publisher.ts +27 -4
- package/src/activities/execute-deep-agent/setup.ts +223 -125
- package/src/activities/execute-deep-agent/stamp-flowed-rows.ts +64 -0
- package/src/activities/execute-deep-agent/status-builder-shared.ts +62 -23
- package/src/activities/execute-deep-agent/status-builder.ts +63 -8
- package/src/activities/execute-deep-agent/streaming-side-effects.ts +2 -16
- package/src/activities/execute-deep-agent/streaming.ts +3 -13
- package/src/activities/execute-deep-agent/subagent-transformer.ts +53 -13
- package/src/activities/execute-deep-agent/subagent-wiring.ts +50 -3
- package/src/activities/execute-deep-agent/v3-status-builder.ts +36 -2
- package/src/claimcheck/payload-codec.ts +8 -8
- package/src/client/stigmer-client.ts +9 -1
- package/src/middleware/__tests__/approval-gate.test.ts +488 -4
- package/src/middleware/approval-gate.ts +247 -38
- package/src/middleware/types.ts +5 -5
- package/src/shared/__tests__/activity-input.test.ts +78 -0
- package/src/shared/__tests__/approval-canonicalize.test.ts +106 -0
- package/src/shared/__tests__/approval-fingerprint.test.ts +115 -0
- package/src/shared/__tests__/approval-policy.test.ts +274 -40
- package/src/shared/__tests__/args-preview.test.ts +78 -0
- package/src/shared/__tests__/artifact-storage-extended.test.ts +62 -10
- package/src/shared/__tests__/artifact-storage.test.ts +123 -11
- package/src/shared/__tests__/file-change.test.ts +85 -0
- package/src/shared/__tests__/file-tools.test.ts +90 -0
- package/src/shared/__tests__/fingerprint-secret.test.ts +51 -0
- package/src/shared/__tests__/lease-scope-corpus.test.ts +56 -0
- package/src/shared/__tests__/model-client.test.ts +162 -0
- package/src/shared/__tests__/plan-artifact.test.ts +11 -26
- package/src/shared/__tests__/policy-source-corpus.test.ts +58 -0
- package/src/shared/__tests__/status-offload.test.ts +573 -16
- package/src/shared/__tests__/status.test.ts +4 -5
- package/src/shared/__tests__/tool-kind.test.ts +24 -1
- package/src/shared/__tests__/tool-row.test.ts +221 -0
- package/src/shared/activity-input.ts +57 -0
- package/src/shared/approval-canonicalize.ts +159 -0
- package/src/shared/approval-fingerprint.ts +148 -0
- package/src/shared/approval-policy.ts +303 -27
- package/src/shared/args-preview.ts +98 -0
- package/src/shared/artifact-storage.ts +62 -11
- package/src/shared/checkpointer/__tests__/http-saver.test.ts +1 -2
- package/src/shared/file-change.ts +64 -0
- package/src/shared/file-tools.ts +169 -0
- package/src/shared/filereview/__tests__/capture.test.ts +856 -0
- package/src/shared/filereview/__tests__/cas-substrate.test.ts +404 -0
- package/src/shared/filereview/__tests__/digest.test.ts +100 -0
- package/src/shared/filereview/__tests__/events.test.ts +245 -0
- package/src/shared/filereview/__tests__/git-substrate.test.ts +362 -0
- package/src/shared/filereview/__tests__/proxy-reconcile.test.ts +286 -0
- package/src/shared/filereview/__tests__/secret-paths.test.ts +121 -0
- package/src/shared/filereview/capture.ts +727 -0
- package/src/shared/filereview/cas-substrate.ts +401 -0
- package/src/shared/filereview/digest.ts +83 -0
- package/src/shared/filereview/events.ts +449 -0
- package/src/shared/filereview/git-substrate.ts +555 -0
- package/src/shared/filereview/index.ts +60 -0
- package/src/shared/filereview/secret-paths.ts +121 -0
- package/src/shared/fingerprint-secret.ts +53 -0
- package/src/shared/model-client.ts +122 -0
- package/src/shared/plan-artifact.ts +0 -2
- package/src/shared/status-offload.ts +433 -77
- package/src/shared/status.ts +13 -0
- package/src/shared/tool-kind.ts +33 -0
- package/src/shared/tool-row.ts +135 -0
- package/src/shared/workspace/platform-dir.ts +41 -2
- package/src/workflow-engine/__tests__/golden-execution.test.ts +35 -18
- package/src/workflow-engine/__tests__/tasks/try.test.ts +1 -1
- package/src/workflows/__tests__/connect-mcp-server.test.ts +304 -29
- package/src/workflows/call-agent-orchestrator.ts +53 -6
- package/src/workflows/connect-mcp-server.ts +179 -24
- package/src/workflows/types.ts +8 -0
|
@@ -5,76 +5,246 @@
|
|
|
5
5
|
* 1. McpServerStatus.tool_approvals — system-generated defaults
|
|
6
6
|
* 2. McpServerSpec.pinned_tool_approvals — manual overrides
|
|
7
7
|
* 3. McpServerUsage.tool_approval_overrides — per-agent customization
|
|
8
|
-
* 4.
|
|
8
|
+
* 4. Active approval leases — the runtime bypass, now SCOPED: the pre-armed
|
|
9
|
+
* spec.auto_approve_all is a whole-run global bypass, while an interactive
|
|
10
|
+
* APPROVE_ALL ("approve all of this kind") grants a run-lifetime lease for
|
|
11
|
+
* only that action's scope (its built-in category, or its MCP server). See
|
|
12
|
+
* {@link ActiveLeases}.
|
|
9
13
|
*
|
|
10
14
|
* Used by both ExecuteCursor (hook-deny model) and ExecuteDeepAgent
|
|
11
15
|
* (middleware interruptOn model) to determine which tools need approval.
|
|
12
16
|
*/
|
|
13
|
-
import { ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
17
|
+
import { ApprovalAction, ApprovalPolicySource } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
18
|
+
import { toolApprovalCategory } from "./tool-kind.js";
|
|
14
19
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
20
|
+
* Reduce a single tool call to the scope its APPROVE_ALL would lease — the core
|
|
21
|
+
* of {@link deriveActiveLeases}, extracted so the cross-edition lease-scope
|
|
22
|
+
* corpus (apis/testdata/hitl/lease-scope) can exercise it directly.
|
|
17
23
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
24
|
+
* The MCP server slug takes precedence over the built-in category and is used
|
|
25
|
+
* RAW (the server's identity, not case-folded), matching the Go
|
|
26
|
+
* {@link DeriveLeaseScope} and Java {@link LeaseScope.deriveKey} byte-for-byte.
|
|
27
|
+
* The category lookup reuses {@link toolApprovalCategory}, the shared oracle, so
|
|
28
|
+
* a built-in resolves to write/delete/shell (read-only built-ins are ungated and
|
|
29
|
+
* return `undefined`).
|
|
23
30
|
*/
|
|
24
|
-
export function
|
|
31
|
+
export function deriveLeaseScope(toolName, mcpServerSlug) {
|
|
32
|
+
if (mcpServerSlug) {
|
|
33
|
+
return { kind: "server", server: mcpServerSlug };
|
|
34
|
+
}
|
|
35
|
+
const category = toolApprovalCategory(toolName);
|
|
36
|
+
if (category) {
|
|
37
|
+
return { kind: "category", category };
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Derive the active approval leases for an execution.
|
|
43
|
+
*
|
|
44
|
+
* The scoped successor to the former all-or-nothing hasApproveAllDecision:
|
|
45
|
+
* instead of "any APPROVE_ALL anywhere disables the whole gate", each
|
|
46
|
+
* APPROVE_ALL decision is reduced (via {@link deriveLeaseScope}) to the SCOPE of
|
|
47
|
+
* the tool it was made on — the built-in category for a built-in tool (read-only
|
|
48
|
+
* tools are never gated, so a built-in lease is always write/delete/shell), or
|
|
49
|
+
* the MCP server slug for an MCP tool — and only that scope is auto-approved for
|
|
50
|
+
* the rest of the run.
|
|
51
|
+
*
|
|
52
|
+
* Scans root and sub-agent tool calls so a lease granted anywhere applies
|
|
53
|
+
* execution-wide (matching the prior cross-sub-agent behavior, now bounded by
|
|
54
|
+
* scope). Both harnesses call this so the contract is defined in exactly one
|
|
55
|
+
* place. The scope derivation reuses {@link toolApprovalCategory}, the same
|
|
56
|
+
* corpus-tested oracle the Go and Java editions mirror, so the backend's
|
|
57
|
+
* scope-aware bulk-approve and this runner-side evaluation can never disagree.
|
|
58
|
+
*/
|
|
59
|
+
export function deriveActiveLeases(execution) {
|
|
60
|
+
const categories = new Set();
|
|
61
|
+
const servers = new Set();
|
|
62
|
+
const addLease = (tc) => {
|
|
63
|
+
if (tc.approvalAction !== ApprovalAction.APPROVE_ALL)
|
|
64
|
+
return;
|
|
65
|
+
const scope = deriveLeaseScope(tc.name, tc.mcpServerSlug);
|
|
66
|
+
if (!scope)
|
|
67
|
+
return;
|
|
68
|
+
if (scope.kind === "server") {
|
|
69
|
+
servers.add(scope.server);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
categories.add(scope.category);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
25
75
|
const status = execution.status;
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (tc.approvalAction === ApprovalAction.APPROVE_ALL)
|
|
31
|
-
return true;
|
|
76
|
+
if (status) {
|
|
77
|
+
for (const message of status.messages) {
|
|
78
|
+
for (const tc of message.toolCalls)
|
|
79
|
+
addLease(tc);
|
|
32
80
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (tc.approvalAction === ApprovalAction.APPROVE_ALL)
|
|
38
|
-
return true;
|
|
81
|
+
for (const sa of status.subAgentExecutions) {
|
|
82
|
+
for (const message of sa.messages) {
|
|
83
|
+
for (const tc of message.toolCalls)
|
|
84
|
+
addLease(tc);
|
|
39
85
|
}
|
|
40
86
|
}
|
|
41
87
|
}
|
|
42
|
-
return
|
|
88
|
+
return {
|
|
89
|
+
global: execution.spec?.autoApproveAll ?? false,
|
|
90
|
+
categories,
|
|
91
|
+
servers,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Monotonic identifier of the policy-engine logic that produced a decision,
|
|
96
|
+
* persisted on `ToolCall.policy_engine_version`. Bumped when the
|
|
97
|
+
* merge/classification semantics change so decisions made by different engine
|
|
98
|
+
* versions remain distinguishable in audits. Phase 7 made
|
|
99
|
+
* `annotation_destructive_tighten` a distinct, persisted source.
|
|
100
|
+
*/
|
|
101
|
+
export const POLICY_ENGINE_VERSION = "phase-7";
|
|
102
|
+
/**
|
|
103
|
+
* Map the runner-internal {@link PolicySource} to the persisted proto
|
|
104
|
+
* {@link ApprovalPolicySource}. `undefined` (a tool no policy layer governs —
|
|
105
|
+
* e.g. a read-only built-in) maps to UNSPECIFIED, so the persisted field is left
|
|
106
|
+
* at its default exactly as an unclassified `tool_kind` is. The 1:1 mapping keeps
|
|
107
|
+
* the runner's union and the proto enum from drifting (asserted by the
|
|
108
|
+
* cross-edition corpus).
|
|
109
|
+
*/
|
|
110
|
+
export function toProtoPolicySource(source) {
|
|
111
|
+
switch (source) {
|
|
112
|
+
case "classifier_default":
|
|
113
|
+
return ApprovalPolicySource.CLASSIFIER_DEFAULT;
|
|
114
|
+
case "pinned_override":
|
|
115
|
+
return ApprovalPolicySource.PINNED_OVERRIDE;
|
|
116
|
+
case "agent_override":
|
|
117
|
+
return ApprovalPolicySource.AGENT_OVERRIDE;
|
|
118
|
+
case "auto_approve_all":
|
|
119
|
+
return ApprovalPolicySource.AUTO_APPROVE_ALL;
|
|
120
|
+
case "approval_lease":
|
|
121
|
+
return ApprovalPolicySource.APPROVAL_LEASE;
|
|
122
|
+
case "builtin_category":
|
|
123
|
+
return ApprovalPolicySource.BUILTIN_CATEGORY;
|
|
124
|
+
case "annotation_destructive_tighten":
|
|
125
|
+
return ApprovalPolicySource.ANNOTATION_DESTRUCTIVE_TIGHTEN;
|
|
126
|
+
case "file_capture":
|
|
127
|
+
// Capture-mode flow is never persisted on a gated tool call (the file tool
|
|
128
|
+
// is not gated — it has no WAITING_APPROVAL row); it exists only on the
|
|
129
|
+
// audit receipt. Map to UNSPECIFIED for the proto-persisted field.
|
|
130
|
+
return ApprovalPolicySource.UNSPECIFIED;
|
|
131
|
+
case undefined:
|
|
132
|
+
return ApprovalPolicySource.UNSPECIFIED;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Derive the authorization provenance — which policy layer governs this tool —
|
|
137
|
+
* for persisting on `ToolCall.approval_policy_source`.
|
|
138
|
+
*
|
|
139
|
+
* This is the read-side twin of the gate's decision logic: same layered
|
|
140
|
+
* precedence, but it answers "which layer governs this call?" for EVERY tool
|
|
141
|
+
* (gated or auto-approved), so the StatusBuilders can stamp provenance on the
|
|
142
|
+
* tool call exactly where they stamp `tool_kind`. It returns `undefined` for a
|
|
143
|
+
* plain read-only built-in that no policy layer touches (the proto's
|
|
144
|
+
* APPROVAL_POLICY_SOURCE_UNSPECIFIED).
|
|
145
|
+
*
|
|
146
|
+
* Precedence:
|
|
147
|
+
* 1. Whole-run global bypass (pre-armed auto_approve_all) governs everything —
|
|
148
|
+
* it is *why* anything ran ungated, so it wins.
|
|
149
|
+
* 2. MCP tool: the merged policy carries the responsible layer when gated; an
|
|
150
|
+
* absent entry means the four-level chain cleared it (classifier base). A
|
|
151
|
+
* server-scoped lease also surfaces as an absent entry — distinguishing it
|
|
152
|
+
* would need the lease set threaded here and is deferred with the rest of the
|
|
153
|
+
* per-server lease provenance, so a lease-cleared MCP tool reads
|
|
154
|
+
* classifier_default (matching the gate).
|
|
155
|
+
* 3. Built-in: a mutating category is governed (leased → approval_lease, else
|
|
156
|
+
* builtin_category); a read-only built-in is governed by no layer → undefined.
|
|
157
|
+
*/
|
|
158
|
+
export function resolveApprovalProvenance(toolName, serverSlug, policies, leasedCategories, globalBypass) {
|
|
159
|
+
if (globalBypass)
|
|
160
|
+
return "auto_approve_all";
|
|
161
|
+
if (serverSlug) {
|
|
162
|
+
const policy = policies.get(`${serverSlug}/${toolName}`);
|
|
163
|
+
if (policy)
|
|
164
|
+
return policy.source;
|
|
165
|
+
return "classifier_default";
|
|
166
|
+
}
|
|
167
|
+
const category = toolApprovalCategory(toolName);
|
|
168
|
+
if (!category)
|
|
169
|
+
return undefined;
|
|
170
|
+
if (leasedCategories.has(category))
|
|
171
|
+
return "approval_lease";
|
|
172
|
+
return "builtin_category";
|
|
43
173
|
}
|
|
44
174
|
/**
|
|
45
175
|
* Merge approval policies from all four levels into a single lookup map.
|
|
46
176
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
177
|
+
* Each MCP server contributes its own set of policies, so the map is keyed by
|
|
178
|
+
* "serverSlug/toolName" to avoid collisions between servers.
|
|
179
|
+
*
|
|
180
|
+
* Policy chain (each level overrides the previous):
|
|
181
|
+
* 1. status.toolApprovals — system-generated defaults; presence = requires approval
|
|
182
|
+
* 2. spec.pinnedToolApprovals — manual overrides; presence = requires approval
|
|
183
|
+
* 3. agent tool_approval_overrides — explicit boolean per tool (enable OR disable)
|
|
184
|
+
* 4. active leases — runtime bypass (highest priority), now scoped
|
|
185
|
+
*
|
|
186
|
+
* The map carries ONLY the tools that require approval — a tool's absence means
|
|
187
|
+
* "auto-approved". Leases shape that absence:
|
|
188
|
+
* - On a global pre-arm ({@link ActiveLeases.global}) the map is empty.
|
|
189
|
+
* - A server-scoped lease drops that server's tools from the map entirely. This
|
|
190
|
+
* single omission makes EVERY substrate treat the server as auto-approved with
|
|
191
|
+
* no extra code — the deep-agent gate and StatusBuilder read the map, and the
|
|
192
|
+
* Cursor hook's mcpToolPolicies is built from it (the hook is not itself
|
|
193
|
+
* server-aware, so omission is the only way to lease an MCP server there).
|
|
194
|
+
* Built-in CATEGORY leases are NOT applied here — built-ins are not in this map;
|
|
195
|
+
* they are cleared at the gate (deep-agent) and the hook (Cursor) instead.
|
|
196
|
+
*
|
|
197
|
+
* Used by both ExecuteCursor (hook-deny model) and ExecuteDeepAgent (middleware
|
|
198
|
+
* interruptOn model), so the four-level semantics are defined in exactly one
|
|
199
|
+
* place.
|
|
49
200
|
*/
|
|
50
|
-
export function mergeApprovalPolicies(resolvedServers, agentOverrides,
|
|
201
|
+
export function mergeApprovalPolicies(resolvedServers, agentOverrides, leases) {
|
|
51
202
|
const merged = new Map();
|
|
52
|
-
if (
|
|
203
|
+
if (leases.global)
|
|
53
204
|
return merged;
|
|
54
205
|
for (const server of resolvedServers) {
|
|
206
|
+
// Server-scoped lease: a prior APPROVE_ALL on one of this server's tools
|
|
207
|
+
// auto-approves the whole server for the run, so none of its tools enter the
|
|
208
|
+
// require-approval map (identical to how an already-auto-approved tool is
|
|
209
|
+
// absent — every consumer treats it as cleared).
|
|
210
|
+
if (leases.servers.has(server.slug))
|
|
211
|
+
continue;
|
|
55
212
|
const serverPolicies = new Map();
|
|
213
|
+
// Layer 1: system-generated defaults (presence = requires approval). A tool
|
|
214
|
+
// the connect-time tightener force-gated from its destructiveHint annotation
|
|
215
|
+
// carries that provenance (from_destructive_hint) so it is attributed to the
|
|
216
|
+
// annotation rather than the classifier — the only sub-case within layer 1.
|
|
56
217
|
for (const policy of server.toolApprovals) {
|
|
57
218
|
if (!policy.toolName)
|
|
58
219
|
continue;
|
|
59
220
|
serverPolicies.set(policy.toolName, {
|
|
60
221
|
requiresApproval: true,
|
|
61
222
|
message: policy.message || `Execute tool: ${policy.toolName}`,
|
|
223
|
+
source: policy.fromDestructiveHint
|
|
224
|
+
? "annotation_destructive_tighten"
|
|
225
|
+
: "classifier_default",
|
|
62
226
|
});
|
|
63
227
|
}
|
|
228
|
+
// Layer 2: manual overrides (presence = requires approval, overrides layer 1).
|
|
64
229
|
for (const pinned of server.pinnedToolApprovals) {
|
|
65
230
|
if (!pinned.toolName)
|
|
66
231
|
continue;
|
|
67
232
|
serverPolicies.set(pinned.toolName, {
|
|
68
233
|
requiresApproval: true,
|
|
69
234
|
message: pinned.message || serverPolicies.get(pinned.toolName)?.message || `Execute tool: ${pinned.toolName}`,
|
|
235
|
+
source: "pinned_override",
|
|
70
236
|
});
|
|
71
237
|
}
|
|
238
|
+
// Layer 3: per-agent overrides (explicit boolean, can enable or disable).
|
|
239
|
+
// Touching a tool here makes the per-agent layer the responsible source,
|
|
240
|
+
// whether it enables, disables, or re-messages the gate.
|
|
72
241
|
for (const override of agentOverrides) {
|
|
73
242
|
if (!override.toolName)
|
|
74
243
|
continue;
|
|
75
244
|
const existing = serverPolicies.get(override.toolName);
|
|
76
245
|
if (existing) {
|
|
77
246
|
existing.requiresApproval = override.requiresApproval;
|
|
247
|
+
existing.source = "agent_override";
|
|
78
248
|
if (override.message) {
|
|
79
249
|
existing.message = override.message;
|
|
80
250
|
}
|
|
@@ -83,9 +253,11 @@ export function mergeApprovalPolicies(resolvedServers, agentOverrides, autoAppro
|
|
|
83
253
|
serverPolicies.set(override.toolName, {
|
|
84
254
|
requiresApproval: true,
|
|
85
255
|
message: override.message || `Execute tool: ${override.toolName}`,
|
|
256
|
+
source: "agent_override",
|
|
86
257
|
});
|
|
87
258
|
}
|
|
88
259
|
}
|
|
260
|
+
// Emit only the tools that still require approval after all layers.
|
|
89
261
|
for (const [toolName, policy] of serverPolicies) {
|
|
90
262
|
if (!policy.requiresApproval)
|
|
91
263
|
continue;
|
|
@@ -95,17 +267,31 @@ export function mergeApprovalPolicies(resolvedServers, agentOverrides, autoAppro
|
|
|
95
267
|
mcpServerSlug: server.slug,
|
|
96
268
|
requiresApproval: true,
|
|
97
269
|
approvalMessage: policy.message,
|
|
270
|
+
source: policy.source,
|
|
98
271
|
});
|
|
99
272
|
}
|
|
100
273
|
}
|
|
101
274
|
return merged;
|
|
102
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* Look up whether an MCP tool requires approval.
|
|
278
|
+
*
|
|
279
|
+
* @param toolName - The actual MCP tool name (e.g., "apply_cloud_resource")
|
|
280
|
+
* @param mcpServerSlug - The MCP server slug (e.g., "planton")
|
|
281
|
+
* @param policies - The merged policy map from {@link mergeApprovalPolicies}
|
|
282
|
+
* @returns The policy if approval is required, undefined if auto-approved
|
|
283
|
+
*/
|
|
103
284
|
export function lookupMcpToolPolicy(toolName, mcpServerSlug, policies) {
|
|
104
285
|
return policies.get(`${mcpServerSlug}/${toolName}`);
|
|
105
286
|
}
|
|
106
287
|
/**
|
|
107
288
|
* Resolve {{args.field}} placeholders in an approval message using the
|
|
108
289
|
* tool's actual arguments.
|
|
290
|
+
*
|
|
291
|
+
* Placeholder syntax matches the proto-documented format:
|
|
292
|
+
* - {{args.field_name}} — replaced with the argument value
|
|
293
|
+
* - {{tool_name}} — replaced with the tool name
|
|
294
|
+
* - Missing fields are replaced with "<unknown>"
|
|
109
295
|
*/
|
|
110
296
|
export function resolveApprovalMessage(template, toolName, args) {
|
|
111
297
|
return template
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval-policy.js","sourceRoot":"","sources":["../../src/shared/approval-policy.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"approval-policy.js","sourceRoot":"","sources":["../../src/shared/approval-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,8DAA8D,CAAC;AACpH,OAAO,EAAE,oBAAoB,EAA6B,MAAM,gBAAgB,CAAC;AA8CjF;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAAgB,EAChB,aAAqB;IAErB,IAAI,aAAa,EAAE,CAAC;QAClB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;IACnD,CAAC;IACD,MAAM,QAAQ,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAChD,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;IACxC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAAyB;IAC1D,MAAM,UAAU,GAAG,IAAI,GAAG,EAAwB,CAAC;IACnD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAElC,MAAM,QAAQ,GAAG,CAAC,EAIjB,EAAQ,EAAE;QACT,IAAI,EAAE,CAAC,cAAc,KAAK,cAAc,CAAC,WAAW;YAAE,OAAO;QAC7D,MAAM,KAAK,GAAG,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC;QAC1D,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC;aAAM,CAAC;YACN,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACjC,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC;IAChC,IAAI,MAAM,EAAE,CAAC;QACX,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACtC,KAAK,MAAM,EAAE,IAAI,OAAO,CAAC,SAAS;gBAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;QACnD,CAAC;QACD,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,kBAAkB,EAAE,CAAC;YAC3C,KAAK,MAAM,OAAO,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAC;gBAClC,KAAK,MAAM,EAAE,IAAI,OAAO,CAAC,SAAS;oBAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YACnD,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,MAAM,EAAE,SAAS,CAAC,IAAI,EAAE,cAAc,IAAI,KAAK;QAC/C,UAAU;QACV,OAAO;KACR,CAAC;AACJ,CAAC;AA2BD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,SAAS,CAAC;AAE/C;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAgC;IAClE,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,oBAAoB;YACvB,OAAO,oBAAoB,CAAC,kBAAkB,CAAC;QACjD,KAAK,iBAAiB;YACpB,OAAO,oBAAoB,CAAC,eAAe,CAAC;QAC9C,KAAK,gBAAgB;YACnB,OAAO,oBAAoB,CAAC,cAAc,CAAC;QAC7C,KAAK,kBAAkB;YACrB,OAAO,oBAAoB,CAAC,gBAAgB,CAAC;QAC/C,KAAK,gBAAgB;YACnB,OAAO,oBAAoB,CAAC,cAAc,CAAC;QAC7C,KAAK,kBAAkB;YACrB,OAAO,oBAAoB,CAAC,gBAAgB,CAAC;QAC/C,KAAK,gCAAgC;YACnC,OAAO,oBAAoB,CAAC,8BAA8B,CAAC;QAC7D,KAAK,cAAc;YACjB,2EAA2E;YAC3E,wEAAwE;YACxE,mEAAmE;YACnE,OAAO,oBAAoB,CAAC,WAAW,CAAC;QAC1C,KAAK,SAAS;YACZ,OAAO,oBAAoB,CAAC,WAAW,CAAC;IAC5C,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,yBAAyB,CACvC,QAAgB,EAChB,UAAkB,EAClB,QAA+C,EAC/C,gBAAmD,EACnD,YAAqB;IAErB,IAAI,YAAY;QAAE,OAAO,kBAAkB,CAAC;IAE5C,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,UAAU,IAAI,QAAQ,EAAE,CAAC,CAAC;QACzD,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC,MAAM,CAAC;QACjC,OAAO,oBAAoB,CAAC;IAC9B,CAAC;IAED,MAAM,QAAQ,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAChD,IAAI,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChC,IAAI,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,gBAAgB,CAAC;IAC5D,OAAO,kBAAkB,CAAC;AAC5B,CAAC;AAgBD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,qBAAqB,CACnC,eAAoC,EACpC,cAAsC,EACtC,MAAoB;IAEpB,MAAM,MAAM,GAAG,IAAI,GAAG,EAA4B,CAAC;IAEnD,IAAI,MAAM,CAAC,MAAM;QAAE,OAAO,MAAM,CAAC;IAEjC,KAAK,MAAM,MAAM,IAAI,eAAe,EAAE,CAAC;QACrC,yEAAyE;QACzE,6EAA6E;QAC7E,0EAA0E;QAC1E,iDAAiD;QACjD,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;YAAE,SAAS;QAE9C,MAAM,cAAc,GAAG,IAAI,GAAG,EAAgF,CAAC;QAE/G,4EAA4E;QAC5E,6EAA6E;QAC7E,6EAA6E;QAC7E,4EAA4E;QAC5E,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;YAC1C,IAAI,CAAC,MAAM,CAAC,QAAQ;gBAAE,SAAS;YAC/B,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE;gBAClC,gBAAgB,EAAE,IAAI;gBACtB,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,iBAAiB,MAAM,CAAC,QAAQ,EAAE;gBAC7D,MAAM,EAAE,MAAM,CAAC,mBAAmB;oBAChC,CAAC,CAAC,gCAAgC;oBAClC,CAAC,CAAC,oBAAoB;aACzB,CAAC,CAAC;QACL,CAAC;QAED,+EAA+E;QAC/E,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,mBAAmB,EAAE,CAAC;YAChD,IAAI,CAAC,MAAM,CAAC,QAAQ;gBAAE,SAAS;YAC/B,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE;gBAClC,gBAAgB,EAAE,IAAI;gBACtB,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,OAAO,IAAI,iBAAiB,MAAM,CAAC,QAAQ,EAAE;gBAC7G,MAAM,EAAE,iBAAiB;aAC1B,CAAC,CAAC;QACL,CAAC;QAED,0EAA0E;QAC1E,yEAAyE;QACzE,yDAAyD;QACzD,KAAK,MAAM,QAAQ,IAAI,cAAc,EAAE,CAAC;YACtC,IAAI,CAAC,QAAQ,CAAC,QAAQ;gBAAE,SAAS;YACjC,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YACvD,IAAI,QAAQ,EAAE,CAAC;gBACb,QAAQ,CAAC,gBAAgB,GAAG,QAAQ,CAAC,gBAAgB,CAAC;gBACtD,QAAQ,CAAC,MAAM,GAAG,gBAAgB,CAAC;gBACnC,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;oBACrB,QAAQ,CAAC,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC;gBACtC,CAAC;YACH,CAAC;iBAAM,IAAI,QAAQ,CAAC,gBAAgB,EAAE,CAAC;gBACrC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,EAAE;oBACpC,gBAAgB,EAAE,IAAI;oBACtB,OAAO,EAAE,QAAQ,CAAC,OAAO,IAAI,iBAAiB,QAAQ,CAAC,QAAQ,EAAE;oBACjE,MAAM,EAAE,gBAAgB;iBACzB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,oEAAoE;QACpE,KAAK,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,cAAc,EAAE,CAAC;YAChD,IAAI,CAAC,MAAM,CAAC,gBAAgB;gBAAE,SAAS;YACvC,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,IAAI,IAAI,QAAQ,EAAE,CAAC;YACzC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE;gBACd,QAAQ;gBACR,aAAa,EAAE,MAAM,CAAC,IAAI;gBAC1B,gBAAgB,EAAE,IAAI;gBACtB,eAAe,EAAE,MAAM,CAAC,OAAO;gBAC/B,MAAM,EAAE,MAAM,CAAC,MAAM;aACtB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAAgB,EAChB,aAAqB,EACrB,QAAuC;IAEvC,OAAO,QAAQ,CAAC,GAAG,CAAC,GAAG,aAAa,IAAI,QAAQ,EAAE,CAAC,CAAC;AACtD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CACpC,QAAgB,EAChB,QAAgB,EAChB,IAA6B;IAE7B,OAAO,QAAQ;SACZ,OAAO,CAAC,oBAAoB,EAAE,QAAQ,CAAC;SACvC,OAAO,CAAC,sBAAsB,EAAE,CAAC,MAAM,EAAE,KAAa,EAAE,EAAE;QACzD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,WAAW,CAAC;QAC9D,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC5C,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness-agnostic sanitization of tool arguments for the `args_preview` field.
|
|
3
|
+
*
|
|
4
|
+
* `args_preview` is the human-readable, UI-facing projection of a tool call's
|
|
5
|
+
* arguments. It is surfaced on the approval card, the CLI, and the web console,
|
|
6
|
+
* and — for the Cursor harness — it is the field a resumed turn parses to rebuild
|
|
7
|
+
* an approval grant's salient identity. So it must always be SMALL and VALID
|
|
8
|
+
* JSON: the per-tool offload does not touch it, and the aggregate size backstop
|
|
9
|
+
* would replace an oversized preview with an unparseable marker.
|
|
10
|
+
*
|
|
11
|
+
* This module is the single home for arg sanitization shared by both harnesses
|
|
12
|
+
* (native re-exports {@link sanitizeArgsPreview} from its status builders; the
|
|
13
|
+
* Cursor gate path uses {@link buildElidedArgsPreview}).
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Argument keys whose values are secrets and must never appear in a preview.
|
|
17
|
+
* Matched case-insensitively against the top-level key name.
|
|
18
|
+
*/
|
|
19
|
+
export declare const SENSITIVE_ARG_KEYS: ReadonlySet<string>;
|
|
20
|
+
/** Whole-preview length cap for {@link sanitizeArgsPreview} (native). */
|
|
21
|
+
export declare const MAX_ARGS_PREVIEW_LENGTH = 500;
|
|
22
|
+
/**
|
|
23
|
+
* Sanitize args into a compact preview string (native harness behavior).
|
|
24
|
+
*
|
|
25
|
+
* Redacts sensitive keys, then truncates the whole JSON to a fixed length. The
|
|
26
|
+
* truncation can yield invalid JSON, which is acceptable for the native harness
|
|
27
|
+
* (its resume model keys on `tool_call_id`, never on a re-parsed preview). The
|
|
28
|
+
* Cursor gate path must instead use {@link buildElidedArgsPreview}, which keeps
|
|
29
|
+
* the JSON valid and preserves the salient identity fields.
|
|
30
|
+
*/
|
|
31
|
+
export declare function sanitizeArgsPreview(args: Record<string, unknown>): string;
|
|
32
|
+
/**
|
|
33
|
+
* Build a compact, ALWAYS-VALID `args_preview` from a tool call's full,
|
|
34
|
+
* authoritative arguments.
|
|
35
|
+
*
|
|
36
|
+
* Unlike {@link sanitizeArgsPreview} (which truncates the whole string, possibly
|
|
37
|
+
* to invalid JSON), this elides oversized string *values* in place — preserving
|
|
38
|
+
* every key and the JSON structure. Two invariants make it safe for the Cursor
|
|
39
|
+
* gate path:
|
|
40
|
+
* - It NEVER elides a salient field (the resume grant's identity — the file
|
|
41
|
+
* path or shell command — is parsed back out of this preview, so it must
|
|
42
|
+
* survive verbatim).
|
|
43
|
+
* - It redacts secret keys.
|
|
44
|
+
*
|
|
45
|
+
* The heavy content (a whole-file body, a large diff) lives on `file_changes`
|
|
46
|
+
* (offloaded to a ref when large) and `args` (bounded by the size backstop), so
|
|
47
|
+
* the preview itself stays small even for a multi-MB write.
|
|
48
|
+
*
|
|
49
|
+
* @param args the full tool arguments
|
|
50
|
+
* @param salientFields keys whose values must be preserved verbatim (identity)
|
|
51
|
+
*/
|
|
52
|
+
export declare function buildElidedArgsPreview(args: Record<string, unknown>, salientFields: readonly string[]): string;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness-agnostic sanitization of tool arguments for the `args_preview` field.
|
|
3
|
+
*
|
|
4
|
+
* `args_preview` is the human-readable, UI-facing projection of a tool call's
|
|
5
|
+
* arguments. It is surfaced on the approval card, the CLI, and the web console,
|
|
6
|
+
* and — for the Cursor harness — it is the field a resumed turn parses to rebuild
|
|
7
|
+
* an approval grant's salient identity. So it must always be SMALL and VALID
|
|
8
|
+
* JSON: the per-tool offload does not touch it, and the aggregate size backstop
|
|
9
|
+
* would replace an oversized preview with an unparseable marker.
|
|
10
|
+
*
|
|
11
|
+
* This module is the single home for arg sanitization shared by both harnesses
|
|
12
|
+
* (native re-exports {@link sanitizeArgsPreview} from its status builders; the
|
|
13
|
+
* Cursor gate path uses {@link buildElidedArgsPreview}).
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Argument keys whose values are secrets and must never appear in a preview.
|
|
17
|
+
* Matched case-insensitively against the top-level key name.
|
|
18
|
+
*/
|
|
19
|
+
export const SENSITIVE_ARG_KEYS = new Set([
|
|
20
|
+
"password", "token", "secret", "api_key", "apikey",
|
|
21
|
+
"credentials", "auth", "authorization",
|
|
22
|
+
]);
|
|
23
|
+
/** Whole-preview length cap for {@link sanitizeArgsPreview} (native). */
|
|
24
|
+
export const MAX_ARGS_PREVIEW_LENGTH = 500;
|
|
25
|
+
/**
|
|
26
|
+
* Sanitize args into a compact preview string (native harness behavior).
|
|
27
|
+
*
|
|
28
|
+
* Redacts sensitive keys, then truncates the whole JSON to a fixed length. The
|
|
29
|
+
* truncation can yield invalid JSON, which is acceptable for the native harness
|
|
30
|
+
* (its resume model keys on `tool_call_id`, never on a re-parsed preview). The
|
|
31
|
+
* Cursor gate path must instead use {@link buildElidedArgsPreview}, which keeps
|
|
32
|
+
* the JSON valid and preserves the salient identity fields.
|
|
33
|
+
*/
|
|
34
|
+
export function sanitizeArgsPreview(args) {
|
|
35
|
+
const sanitized = {};
|
|
36
|
+
for (const [key, value] of Object.entries(args)) {
|
|
37
|
+
sanitized[key] = SENSITIVE_ARG_KEYS.has(key.toLowerCase()) ? "[REDACTED]" : value;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const json = JSON.stringify(sanitized);
|
|
41
|
+
return json.length > MAX_ARGS_PREVIEW_LENGTH
|
|
42
|
+
? json.slice(0, MAX_ARGS_PREVIEW_LENGTH) + "…"
|
|
43
|
+
: json;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Per-string-value cap for {@link buildElidedArgsPreview}. */
|
|
50
|
+
const MAX_PREVIEW_VALUE_LENGTH = 200;
|
|
51
|
+
/**
|
|
52
|
+
* Build a compact, ALWAYS-VALID `args_preview` from a tool call's full,
|
|
53
|
+
* authoritative arguments.
|
|
54
|
+
*
|
|
55
|
+
* Unlike {@link sanitizeArgsPreview} (which truncates the whole string, possibly
|
|
56
|
+
* to invalid JSON), this elides oversized string *values* in place — preserving
|
|
57
|
+
* every key and the JSON structure. Two invariants make it safe for the Cursor
|
|
58
|
+
* gate path:
|
|
59
|
+
* - It NEVER elides a salient field (the resume grant's identity — the file
|
|
60
|
+
* path or shell command — is parsed back out of this preview, so it must
|
|
61
|
+
* survive verbatim).
|
|
62
|
+
* - It redacts secret keys.
|
|
63
|
+
*
|
|
64
|
+
* The heavy content (a whole-file body, a large diff) lives on `file_changes`
|
|
65
|
+
* (offloaded to a ref when large) and `args` (bounded by the size backstop), so
|
|
66
|
+
* the preview itself stays small even for a multi-MB write.
|
|
67
|
+
*
|
|
68
|
+
* @param args the full tool arguments
|
|
69
|
+
* @param salientFields keys whose values must be preserved verbatim (identity)
|
|
70
|
+
*/
|
|
71
|
+
export function buildElidedArgsPreview(args, salientFields) {
|
|
72
|
+
const out = {};
|
|
73
|
+
for (const [key, value] of Object.entries(args)) {
|
|
74
|
+
if (SENSITIVE_ARG_KEYS.has(key.toLowerCase())) {
|
|
75
|
+
out[key] = "[REDACTED]";
|
|
76
|
+
}
|
|
77
|
+
else if (!salientFields.includes(key) &&
|
|
78
|
+
typeof value === "string" &&
|
|
79
|
+
value.length > MAX_PREVIEW_VALUE_LENGTH) {
|
|
80
|
+
out[key] = `[${value.length} chars]`;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
out[key] = value;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
return JSON.stringify(out);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return "";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=args-preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args-preview.js","sourceRoot":"","sources":["../../src/shared/args-preview.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAwB,IAAI,GAAG,CAAC;IAC7D,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ;IAClD,aAAa,EAAE,MAAM,EAAE,eAAe;CACvC,CAAC,CAAC;AAEH,yEAAyE;AACzE,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAE3C;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAA6B;IAC/D,MAAM,SAAS,GAA4B,EAAE,CAAC;IAC9C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,SAAS,CAAC,GAAG,CAAC,GAAG,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC;IACpF,CAAC;IACD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QACvC,OAAO,IAAI,CAAC,MAAM,GAAG,uBAAuB;YAC1C,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,uBAAuB,CAAC,GAAG,GAAG;YAC9C,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,+DAA+D;AAC/D,MAAM,wBAAwB,GAAG,GAAG,CAAC;AAErC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,sBAAsB,CACpC,IAA6B,EAC7B,aAAgC;IAEhC,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,IAAI,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YAC9C,GAAG,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC;QAC1B,CAAC;aAAM,IACL,CAAC,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC;YAC5B,OAAO,KAAK,KAAK,QAAQ;YACzB,KAAK,CAAC,MAAM,GAAG,wBAAwB,EACvC,CAAC;YACD,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,KAAK,CAAC,MAAM,SAAS,CAAC;QACvC,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACnB,CAAC;IACH,CAAC;IACD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
* Artifact storage abstraction for the unified runner.
|
|
3
3
|
*
|
|
4
4
|
* Two backends:
|
|
5
|
-
* - Local: writes to the filesystem
|
|
5
|
+
* - Local: writes to the filesystem (OSS mode). The runner reads its own
|
|
6
|
+
* artifacts straight back off disk via {@link ArtifactStorage.download} — the
|
|
7
|
+
* exact inverse of {@link ArtifactStorage.upload}. `getDownloadUrl` still
|
|
8
|
+
* returns the stigmer-server serve URL, but that is for OTHER consumers (the
|
|
9
|
+
* web console fetching an artifact for display), not the runner's own reads.
|
|
6
10
|
* - Proxy: uses presigned URLs from the Stigmer Side-Channel Proxy (cloud mode).
|
|
11
|
+
* Here `download` resolves a presigned URL and fetches it over HTTPS.
|
|
7
12
|
*
|
|
8
13
|
* The runner never holds R2/S3 credentials — in cloud mode it calls the proxy
|
|
9
14
|
* to obtain a presigned upload URL, then PUTs content over plain HTTPS.
|
|
@@ -14,6 +19,17 @@ import type { Config } from "../config.js";
|
|
|
14
19
|
export interface ArtifactStorage {
|
|
15
20
|
upload(key: string, content: Buffer, contentType?: string): Promise<string>;
|
|
16
21
|
getDownloadUrl(key: string): Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
* Read an artifact's raw bytes by key — the inverse of {@link upload} and the
|
|
24
|
+
* single read path for all runner read-back (CAS reconcile, exact-apply,
|
|
25
|
+
* claimcheck decode, attachment injection).
|
|
26
|
+
*
|
|
27
|
+
* Returns the exact stored bytes, or throws a descriptive, key-scoped `Error`
|
|
28
|
+
* when the object is missing or the transport fails. Per-backend semantics:
|
|
29
|
+
* local reads directly off disk; proxy resolves a presigned URL and fetches
|
|
30
|
+
* it. Callers own their own error-wrapping / degradation policy.
|
|
31
|
+
*/
|
|
32
|
+
download(key: string): Promise<Buffer>;
|
|
17
33
|
exists(key: string): Promise<boolean>;
|
|
18
34
|
}
|
|
19
35
|
export type ArtifactStorageType = "local" | "proxy";
|
|
@@ -23,6 +39,7 @@ export declare class LocalArtifactStorage implements ArtifactStorage {
|
|
|
23
39
|
constructor(basePath: string, serveUrlBase: string);
|
|
24
40
|
upload(key: string, content: Buffer, _contentType?: string): Promise<string>;
|
|
25
41
|
getDownloadUrl(key: string): Promise<string>;
|
|
42
|
+
download(key: string): Promise<Buffer>;
|
|
26
43
|
exists(key: string): Promise<boolean>;
|
|
27
44
|
}
|
|
28
45
|
export declare class ProxyArtifactStorage implements ArtifactStorage {
|
|
@@ -31,6 +48,7 @@ export declare class ProxyArtifactStorage implements ArtifactStorage {
|
|
|
31
48
|
constructor(proxyEndpoint: string, authToken: string);
|
|
32
49
|
upload(key: string, content: Buffer, contentType?: string): Promise<string>;
|
|
33
50
|
getDownloadUrl(key: string): Promise<string>;
|
|
51
|
+
download(key: string): Promise<Buffer>;
|
|
34
52
|
exists(key: string): Promise<boolean>;
|
|
35
53
|
}
|
|
36
54
|
export interface ArtifactStorageConfig {
|
|
@@ -2,15 +2,20 @@
|
|
|
2
2
|
* Artifact storage abstraction for the unified runner.
|
|
3
3
|
*
|
|
4
4
|
* Two backends:
|
|
5
|
-
* - Local: writes to the filesystem
|
|
5
|
+
* - Local: writes to the filesystem (OSS mode). The runner reads its own
|
|
6
|
+
* artifacts straight back off disk via {@link ArtifactStorage.download} — the
|
|
7
|
+
* exact inverse of {@link ArtifactStorage.upload}. `getDownloadUrl` still
|
|
8
|
+
* returns the stigmer-server serve URL, but that is for OTHER consumers (the
|
|
9
|
+
* web console fetching an artifact for display), not the runner's own reads.
|
|
6
10
|
* - Proxy: uses presigned URLs from the Stigmer Side-Channel Proxy (cloud mode).
|
|
11
|
+
* Here `download` resolves a presigned URL and fetches it over HTTPS.
|
|
7
12
|
*
|
|
8
13
|
* The runner never holds R2/S3 credentials — in cloud mode it calls the proxy
|
|
9
14
|
* to obtain a presigned upload URL, then PUTs content over plain HTTPS.
|
|
10
15
|
*
|
|
11
16
|
* DD-6: No direct R2 backend. Local + Proxy only.
|
|
12
17
|
*/
|
|
13
|
-
import { mkdir, writeFile, access } from "node:fs/promises";
|
|
18
|
+
import { mkdir, writeFile, readFile, access } from "node:fs/promises";
|
|
14
19
|
import { dirname, join } from "node:path";
|
|
15
20
|
// ── Local Backend ────────────────────────────────────────────────────
|
|
16
21
|
export class LocalArtifactStorage {
|
|
@@ -29,6 +34,18 @@ export class LocalArtifactStorage {
|
|
|
29
34
|
async getDownloadUrl(key) {
|
|
30
35
|
return `${this.serveUrlBase}/${key}`;
|
|
31
36
|
}
|
|
37
|
+
async download(key) {
|
|
38
|
+
// Direct disk read — the exact inverse of `upload`. The runner wrote these
|
|
39
|
+
// bytes to `basePath`, so it reads them back without a self-HTTP round-trip
|
|
40
|
+
// and without depending on the serve URL being set or reachable.
|
|
41
|
+
try {
|
|
42
|
+
return await readFile(join(this.basePath, key));
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
46
|
+
throw new Error(`Artifact not found for key '${key}': ${reason}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
32
49
|
async exists(key) {
|
|
33
50
|
try {
|
|
34
51
|
await access(join(this.basePath, key));
|
|
@@ -112,21 +129,41 @@ export class ProxyArtifactStorage {
|
|
|
112
129
|
const data = await resp.json();
|
|
113
130
|
return data.url;
|
|
114
131
|
}
|
|
132
|
+
async download(key) {
|
|
133
|
+
const url = await this.getDownloadUrl(key);
|
|
134
|
+
const resp = await fetch(url);
|
|
135
|
+
if (!resp.ok) {
|
|
136
|
+
throw new Error(`Artifact download failed (HTTP ${resp.status}) for key '${key}': ` +
|
|
137
|
+
await resp.text());
|
|
138
|
+
}
|
|
139
|
+
return Buffer.from(await resp.arrayBuffer());
|
|
140
|
+
}
|
|
115
141
|
async exists(key) {
|
|
142
|
+
// A presigned URL is minted for ANY key — the presign endpoint does not check
|
|
143
|
+
// the object — so "did presign succeed?" is NOT existence (that bug turned a
|
|
144
|
+
// git-only file-review reconcile into a doomed manifest download + 404 crash).
|
|
145
|
+
// Probe the object itself with a 1-byte ranged GET: the presigned URL is
|
|
146
|
+
// SigV4-signed for GET (a HEAD would break the signature), and `Range:
|
|
147
|
+
// bytes=0-0` transfers at most one byte. Missing => 404; present => 200/206
|
|
148
|
+
// (or 416 for a 0-byte object, whose range is unsatisfiable yet it exists).
|
|
149
|
+
let url;
|
|
116
150
|
try {
|
|
117
|
-
|
|
118
|
-
method: "POST",
|
|
119
|
-
headers: {
|
|
120
|
-
"Authorization": `Bearer ${this.authToken}`,
|
|
121
|
-
"Content-Type": "application/json",
|
|
122
|
-
},
|
|
123
|
-
body: JSON.stringify({ key }),
|
|
124
|
-
});
|
|
125
|
-
return resp.ok;
|
|
151
|
+
url = await this.getDownloadUrl(key);
|
|
126
152
|
}
|
|
127
153
|
catch {
|
|
154
|
+
// Presign endpoint unreachable: report absent. The sole caller (content-
|
|
155
|
+
// addressed CAS blob dedup) then re-uploads, which is idempotent.
|
|
128
156
|
return false;
|
|
129
157
|
}
|
|
158
|
+
const resp = await fetch(url, { headers: { Range: "bytes=0-0" } });
|
|
159
|
+
await resp.arrayBuffer().catch(() => undefined); // drain the <=1-byte body
|
|
160
|
+
if (resp.status === 404)
|
|
161
|
+
return false;
|
|
162
|
+
if (resp.status === 200 || resp.status === 206 || resp.status === 416)
|
|
163
|
+
return true;
|
|
164
|
+
// Any other status (e.g. 403 expired/misconfigured, 5xx) is a real fault, not
|
|
165
|
+
// an existence answer — surface it rather than silently mis-reporting.
|
|
166
|
+
throw new Error(`Artifact existence check failed (HTTP ${resp.status}) for key '${key}'`);
|
|
130
167
|
}
|
|
131
168
|
}
|
|
132
169
|
export function loadArtifactStorageConfig(config) {
|