@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
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approved-command turn provenance (DD-28): the runner-owned qualification for
|
|
3
|
+
* the auto-keep policy.
|
|
4
|
+
*
|
|
5
|
+
* A turn whose ONLY mutation source was shell commands the human had already
|
|
6
|
+
* authorized should not re-gate its file effects at the turn-boundary review —
|
|
7
|
+
* the user consented to the command, and the command's file effects are the
|
|
8
|
+
* consented outcome. The server cannot derive this itself (tool calls carry no
|
|
9
|
+
* turn marker), so the runner — the only component that owns turn scoping —
|
|
10
|
+
* derives the facts here and attaches them to the CANDIDATE_CAPTURED event as
|
|
11
|
+
* {@link TurnCommandProvenance}.
|
|
12
|
+
*
|
|
13
|
+
* TRUST BOUNDARY. This module asserts turn FACTS (which tools ran this turn),
|
|
14
|
+
* the same trust level as the captured bytes themselves. It never asserts
|
|
15
|
+
* CONSENT: `consentToolCallIds` merely POINT at transcript rows whose
|
|
16
|
+
* `approval_action` was authored by the server's SubmitApproval (and is
|
|
17
|
+
* preserved against runner writes) — the backend re-verifies every claimed row
|
|
18
|
+
* against that server-owned record before authoring the policy decision, so a
|
|
19
|
+
* runner cannot mint authorization it was never given.
|
|
20
|
+
*
|
|
21
|
+
* FAIL-CLOSED. Every uncertainty disqualifies (returns undefined → the set
|
|
22
|
+
* reviews manually, exactly as before DD-28): a file-tool call, an MCP tool, a
|
|
23
|
+
* sub-agent delegation, an unrecognized tool name, or an executed shell command
|
|
24
|
+
* with no provable consent source. Being conservative here costs only an extra
|
|
25
|
+
* review; being permissive would silently waive one.
|
|
26
|
+
*/
|
|
27
|
+
import { create } from "@bufbuild/protobuf";
|
|
28
|
+
import { TurnCommandProvenanceSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
|
|
29
|
+
import { ApprovalAction, ToolKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
30
|
+
import { classifyTool, toolApprovalCategory } from "../../shared/tool-kind.js";
|
|
31
|
+
import { isToolCallRowHidden } from "../../shared/tool-row.js";
|
|
32
|
+
import { toolCallIdentityToken } from "./message-translator.js";
|
|
33
|
+
/**
|
|
34
|
+
* Tool kinds that can never mutate the workspace: their presence in a turn is
|
|
35
|
+
* irrelevant to change-set provenance. Everything OUTSIDE this set is either a
|
|
36
|
+
* consent-mapped shell command or a disqualifier — never silently ignored.
|
|
37
|
+
*/
|
|
38
|
+
const NON_MUTATING_KINDS = new Set([
|
|
39
|
+
ToolKind.FILE_READ,
|
|
40
|
+
ToolKind.SEARCH,
|
|
41
|
+
ToolKind.LIST,
|
|
42
|
+
ToolKind.FETCH,
|
|
43
|
+
ToolKind.WEB_SEARCH,
|
|
44
|
+
ToolKind.THINK,
|
|
45
|
+
ToolKind.TODO,
|
|
46
|
+
]);
|
|
47
|
+
/**
|
|
48
|
+
* Derive the {@link TurnCommandProvenance} for the turn, or undefined when the
|
|
49
|
+
* turn does not qualify (any non-shell mutation-capable call, any unknown tool,
|
|
50
|
+
* any executed command without a provable consent source, or no executed
|
|
51
|
+
* consented command at all).
|
|
52
|
+
*/
|
|
53
|
+
export function deriveTurnCommandProvenance(inputs) {
|
|
54
|
+
const { messages, turnStartIndex, deniedTokens, grantTokenToConsentId, globalBypass } = inputs;
|
|
55
|
+
const consentIds = new Set();
|
|
56
|
+
let authorizedByAutoApproveAll = false;
|
|
57
|
+
let executedCommandCount = 0;
|
|
58
|
+
for (let i = turnStartIndex; i < messages.length; i++) {
|
|
59
|
+
for (const tc of messages[i].toolCalls) {
|
|
60
|
+
// A hidden row is a collapsed twin/reaction — it never executed.
|
|
61
|
+
if (isToolCallRowHidden(tc))
|
|
62
|
+
continue;
|
|
63
|
+
const kind = classifyTool(tc.name, tc.mcpServerSlug);
|
|
64
|
+
if (NON_MUTATING_KINDS.has(kind))
|
|
65
|
+
continue;
|
|
66
|
+
if (kind !== ToolKind.SHELL) {
|
|
67
|
+
// A file tool (mixed turn), a sub-agent (unattributable mutations), an
|
|
68
|
+
// MCP tool (opaque side effects), or an unrecognized name — the turn's
|
|
69
|
+
// mutations cannot be attributed to consented commands. Fail closed.
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
const token = toolCallIdentityToken(tc);
|
|
73
|
+
if (deniedTokens.has(token)) {
|
|
74
|
+
// This turn's pending gate — denied by the hook, never executed.
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
executedCommandCount++;
|
|
78
|
+
const consentId = grantTokenToConsentId.get(token);
|
|
79
|
+
if (consentId) {
|
|
80
|
+
consentIds.add(consentId);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const leaseConsentId = findLeaseConsentId(messages, tc.name);
|
|
84
|
+
if (leaseConsentId) {
|
|
85
|
+
consentIds.add(leaseConsentId);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (globalBypass) {
|
|
89
|
+
authorizedByAutoApproveAll = true;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
// An executed shell command with no grant, no lease, and no bypass should
|
|
93
|
+
// be impossible (the hook gates every un-consented shell); if it ever
|
|
94
|
+
// happens, the honest answer is a manual review, not a waived one.
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// A turn that executed no consented command has nothing to attribute the
|
|
99
|
+
// change set to — whatever changed came from somewhere else. Manual review.
|
|
100
|
+
if (executedCommandCount === 0)
|
|
101
|
+
return undefined;
|
|
102
|
+
return create(TurnCommandProvenanceSchema, {
|
|
103
|
+
consentToolCallIds: [...consentIds],
|
|
104
|
+
authorizedByAutoApproveAll,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Find the transcript row whose APPROVE_ALL authored the run-lifetime lease
|
|
109
|
+
* covering `toolName`'s category — the consent row a lease-executed command
|
|
110
|
+
* cites. Searched across the whole transcript (a lease usually originates in a
|
|
111
|
+
* prior turn); the first (earliest) APPROVE_ALL of the category is the lease's
|
|
112
|
+
* origin. Returns undefined when no lease row exists for the category.
|
|
113
|
+
*/
|
|
114
|
+
function findLeaseConsentId(messages, toolName) {
|
|
115
|
+
const category = toolApprovalCategory(toolName);
|
|
116
|
+
if (!category)
|
|
117
|
+
return undefined;
|
|
118
|
+
for (const msg of messages) {
|
|
119
|
+
for (const tc of msg.toolCalls) {
|
|
120
|
+
if (tc.approvalAction !== ApprovalAction.APPROVE_ALL)
|
|
121
|
+
continue;
|
|
122
|
+
if (toolApprovalCategory(tc.name) === category)
|
|
123
|
+
return tc.id;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=command-provenance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-provenance.js","sourceRoot":"","sources":["../../../src/activities/execute-cursor/command-provenance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resume-time exact-apply for approved whole-file writes (Cursor deny-only HITL).
|
|
3
|
+
*
|
|
4
|
+
* THE GUARANTEE THIS RESTORES
|
|
5
|
+
* ---------------------------
|
|
6
|
+
* A user must get "what you approve is what gets applied" — the contract the
|
|
7
|
+
* native LangGraph harness and Cursor's own IDE honor by pausing at the exact
|
|
8
|
+
* tool call and applying its exact args on approval. The Cursor deny-only
|
|
9
|
+
* harness cannot pause: it denies the tool, grants the RESOURCE (`write\n<path>`),
|
|
10
|
+
* and reinvokes the model, which REGENERATES content from scratch. So the bytes
|
|
11
|
+
* that land can differ from the bytes the gate showed (observed in production: a
|
|
12
|
+
* gate previewing one edit, an applied file carrying more).
|
|
13
|
+
*
|
|
14
|
+
* The fix turns an approved WHOLE-FILE write into a runner-executed action: the
|
|
15
|
+
* runner writes the EXACT approved bytes itself, marks the tool COMPLETED, and —
|
|
16
|
+
* crucially — issues NO resource grant for it. Any FURTHER write the model makes
|
|
17
|
+
* to that file on reinvocation is therefore re-gated, so the user sees and
|
|
18
|
+
* approves every change. This module owns the apply AND the grant-exclusion
|
|
19
|
+
* derivation ({@link excludeAppliedFromGrants}, the "issues NO grant" half); the
|
|
20
|
+
* caller wires the resulting grants and the prompt split (so the model is told
|
|
21
|
+
* the write is already applied and does not redo it).
|
|
22
|
+
*
|
|
23
|
+
* SCOPE — whole-file writes only. A hunk edit (`old_string`/`new_string`) is left
|
|
24
|
+
* on the grant + reinvocation path: applying a hunk would require locating the
|
|
25
|
+
* fragment in the file, which would make the runner a second source of truth for
|
|
26
|
+
* the edit result. Shell/MCP grants are already command-/name-specific, so their
|
|
27
|
+
* approved==applied property is already tight.
|
|
28
|
+
*
|
|
29
|
+
* SOURCE OF TRUTH — the approved whole-file bytes and target path are read from
|
|
30
|
+
* the gated tool call's `args` (the authoritative proposed content the deny-gate
|
|
31
|
+
* stamped from the hook input; see execute-cursor/message-translator.ts
|
|
32
|
+
* `applyGateInput`). This is the single copy — there is no separate captured
|
|
33
|
+
* `file_changes` mirror — so "what was shown == what is applied" holds by
|
|
34
|
+
* construction. (Phase 5 Slice 4 removed the redundant `ToolCall.file_changes`
|
|
35
|
+
* copy; `args` was always its source.)
|
|
36
|
+
*
|
|
37
|
+
* SAFETY — exact-apply writes ONLY a fully-resolved body. It never writes a
|
|
38
|
+
* truncated preview or the elision marker (which would silently corrupt the
|
|
39
|
+
* file); an unresolvable body or any write failure degrades to the existing
|
|
40
|
+
* grant + reinvocation path. Corruption is never an option — degradation to the
|
|
41
|
+
* prior behavior is.
|
|
42
|
+
*
|
|
43
|
+
* TESTING — the apply + grant-exclusion + re-gating composition is proven
|
|
44
|
+
* end-to-end (real workspace backend + the real deny-oracle hook) in
|
|
45
|
+
* `__tests__/deny-gate-exact-apply.test.ts`. That deterministic runner test is
|
|
46
|
+
* the achievable substitute for a pure-Go offline e2e, which is structurally
|
|
47
|
+
* infeasible (there is no offline Cursor agent driver — see DD-23).
|
|
48
|
+
*/
|
|
49
|
+
import type { AgentMessage, ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
50
|
+
import type { PendingApproval } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb";
|
|
51
|
+
import type { WorkspaceBackend } from "../../shared/workspace/types.js";
|
|
52
|
+
/** Options for {@link applyApprovedWholeFileWrites}. */
|
|
53
|
+
export interface ExactApplyOptions {
|
|
54
|
+
/** The (seeded) transcript whose approved tool calls are applied in place. */
|
|
55
|
+
readonly messages: AgentMessage[];
|
|
56
|
+
/** Workspace the files live in (local FS for OSS, sandbox in cloud). */
|
|
57
|
+
readonly workspaceBackend: WorkspaceBackend;
|
|
58
|
+
/** Configured workspace roots; a write is refused outside all of them. */
|
|
59
|
+
readonly workspaceDirs: readonly string[];
|
|
60
|
+
/** For structured logs. */
|
|
61
|
+
readonly executionId: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Apply every APPROVED whole-file write in `messages` to disk with its EXACT
|
|
65
|
+
* approved bytes, marking each applied tool call COMPLETED in place.
|
|
66
|
+
*
|
|
67
|
+
* Returns the set of tool-call ids that were exact-applied, so the caller can
|
|
68
|
+
* (a) exclude their resources from the approval grants — the key to re-gating a
|
|
69
|
+
* later write — and (b) describe them as "already applied" in the reinvocation
|
|
70
|
+
* prompt rather than asking the model to redo them.
|
|
71
|
+
*
|
|
72
|
+
* Idempotent: a call already COMPLETED (a Temporal activity retry re-runs this
|
|
73
|
+
* phase) is skipped, never written twice. Every uncertain case (no resolvable
|
|
74
|
+
* body, out-of-workspace target, write failure) is SKIPPED, leaving the call
|
|
75
|
+
* WAITING_APPROVAL so the existing grant + reinvocation path handles it — the
|
|
76
|
+
* conservative fallback that can never corrupt a file.
|
|
77
|
+
*/
|
|
78
|
+
export declare function applyApprovedWholeFileWrites(opts: ExactApplyOptions): Promise<Set<string>>;
|
|
79
|
+
/**
|
|
80
|
+
* The adjudicated approvals MINUS the ones the runner already exact-applied — the
|
|
81
|
+
* set the caller turns into resource grants for the resumed agent.
|
|
82
|
+
*
|
|
83
|
+
* This is the "issues NO grant for an applied write" half of the guarantee: an
|
|
84
|
+
* exact-applied whole-file write is intentionally NOT granted, so if the
|
|
85
|
+
* reinvoked model writes to that same file again it is re-gated and the user
|
|
86
|
+
* reviews the new change (the deny-gate's "what you approve is what gets applied"
|
|
87
|
+
* property). Non-applied approvals (hunk edits, shell, MCP, and every reject /
|
|
88
|
+
* skip) pass through unchanged — they still need their grant to flow on resume.
|
|
89
|
+
*
|
|
90
|
+
* Pure and order-independent. Load-bearing because the caller computes the
|
|
91
|
+
* adjudicated set ONCE (before {@link applyApprovedWholeFileWrites} flips the
|
|
92
|
+
* applied calls to COMPLETED) and never re-derives it, so this exclusion is the
|
|
93
|
+
* only thing that keeps an applied write out of the grants.
|
|
94
|
+
*/
|
|
95
|
+
export declare function excludeAppliedFromGrants(adjudicatedApprovals: readonly PendingApproval[], appliedToolCallIds: ReadonlySet<string>): PendingApproval[];
|
|
96
|
+
/**
|
|
97
|
+
* The exact approved bytes for a whole-file write, or `null` when they cannot be
|
|
98
|
+
* recovered (in which case the caller must fall back, never write).
|
|
99
|
+
*
|
|
100
|
+
* The bytes come from the gated call's `args` whole-file content — the single
|
|
101
|
+
* authoritative copy of the proposed body (the deny-gate stamped it from the
|
|
102
|
+
* hook input; see {@link isApprovedWholeFileWrite}). `args` is never offloaded to
|
|
103
|
+
* a ref (only dropped wholesale by the aggregate size backstop), so when present
|
|
104
|
+
* it is intact.
|
|
105
|
+
*
|
|
106
|
+
* It NEVER returns the elision marker: that is a lossy stand-in and writing it
|
|
107
|
+
* would corrupt the file. An `args` body equal to the marker is treated as
|
|
108
|
+
* unresolved, and the caller degrades to grant + reinvocation.
|
|
109
|
+
*/
|
|
110
|
+
export declare function resolveApprovedWholeFileContent(tc: ToolCall): string | null;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resume-time exact-apply for approved whole-file writes (Cursor deny-only HITL).
|
|
3
|
+
*
|
|
4
|
+
* THE GUARANTEE THIS RESTORES
|
|
5
|
+
* ---------------------------
|
|
6
|
+
* A user must get "what you approve is what gets applied" — the contract the
|
|
7
|
+
* native LangGraph harness and Cursor's own IDE honor by pausing at the exact
|
|
8
|
+
* tool call and applying its exact args on approval. The Cursor deny-only
|
|
9
|
+
* harness cannot pause: it denies the tool, grants the RESOURCE (`write\n<path>`),
|
|
10
|
+
* and reinvokes the model, which REGENERATES content from scratch. So the bytes
|
|
11
|
+
* that land can differ from the bytes the gate showed (observed in production: a
|
|
12
|
+
* gate previewing one edit, an applied file carrying more).
|
|
13
|
+
*
|
|
14
|
+
* The fix turns an approved WHOLE-FILE write into a runner-executed action: the
|
|
15
|
+
* runner writes the EXACT approved bytes itself, marks the tool COMPLETED, and —
|
|
16
|
+
* crucially — issues NO resource grant for it. Any FURTHER write the model makes
|
|
17
|
+
* to that file on reinvocation is therefore re-gated, so the user sees and
|
|
18
|
+
* approves every change. This module owns the apply AND the grant-exclusion
|
|
19
|
+
* derivation ({@link excludeAppliedFromGrants}, the "issues NO grant" half); the
|
|
20
|
+
* caller wires the resulting grants and the prompt split (so the model is told
|
|
21
|
+
* the write is already applied and does not redo it).
|
|
22
|
+
*
|
|
23
|
+
* SCOPE — whole-file writes only. A hunk edit (`old_string`/`new_string`) is left
|
|
24
|
+
* on the grant + reinvocation path: applying a hunk would require locating the
|
|
25
|
+
* fragment in the file, which would make the runner a second source of truth for
|
|
26
|
+
* the edit result. Shell/MCP grants are already command-/name-specific, so their
|
|
27
|
+
* approved==applied property is already tight.
|
|
28
|
+
*
|
|
29
|
+
* SOURCE OF TRUTH — the approved whole-file bytes and target path are read from
|
|
30
|
+
* the gated tool call's `args` (the authoritative proposed content the deny-gate
|
|
31
|
+
* stamped from the hook input; see execute-cursor/message-translator.ts
|
|
32
|
+
* `applyGateInput`). This is the single copy — there is no separate captured
|
|
33
|
+
* `file_changes` mirror — so "what was shown == what is applied" holds by
|
|
34
|
+
* construction. (Phase 5 Slice 4 removed the redundant `ToolCall.file_changes`
|
|
35
|
+
* copy; `args` was always its source.)
|
|
36
|
+
*
|
|
37
|
+
* SAFETY — exact-apply writes ONLY a fully-resolved body. It never writes a
|
|
38
|
+
* truncated preview or the elision marker (which would silently corrupt the
|
|
39
|
+
* file); an unresolvable body or any write failure degrades to the existing
|
|
40
|
+
* grant + reinvocation path. Corruption is never an option — degradation to the
|
|
41
|
+
* prior behavior is.
|
|
42
|
+
*
|
|
43
|
+
* TESTING — the apply + grant-exclusion + re-gating composition is proven
|
|
44
|
+
* end-to-end (real workspace backend + the real deny-oracle hook) in
|
|
45
|
+
* `__tests__/deny-gate-exact-apply.test.ts`. That deterministic runner test is
|
|
46
|
+
* the achievable substitute for a pure-Go offline e2e, which is structurally
|
|
47
|
+
* infeasible (there is no offline Cursor agent driver — see DD-23).
|
|
48
|
+
*/
|
|
49
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
50
|
+
import { ApprovalAction, ToolCallStatus, } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
51
|
+
import { ELISION_MARKER } from "../../shared/status-offload.js";
|
|
52
|
+
import { extractFilePath, extractWriteContent } from "../../shared/file-tools.js";
|
|
53
|
+
import { resolveWorkspacePath } from "../../shared/file-change.js";
|
|
54
|
+
import { utcTimestamp } from "../../shared/status.js";
|
|
55
|
+
/**
|
|
56
|
+
* Apply every APPROVED whole-file write in `messages` to disk with its EXACT
|
|
57
|
+
* approved bytes, marking each applied tool call COMPLETED in place.
|
|
58
|
+
*
|
|
59
|
+
* Returns the set of tool-call ids that were exact-applied, so the caller can
|
|
60
|
+
* (a) exclude their resources from the approval grants — the key to re-gating a
|
|
61
|
+
* later write — and (b) describe them as "already applied" in the reinvocation
|
|
62
|
+
* prompt rather than asking the model to redo them.
|
|
63
|
+
*
|
|
64
|
+
* Idempotent: a call already COMPLETED (a Temporal activity retry re-runs this
|
|
65
|
+
* phase) is skipped, never written twice. Every uncertain case (no resolvable
|
|
66
|
+
* body, out-of-workspace target, write failure) is SKIPPED, leaving the call
|
|
67
|
+
* WAITING_APPROVAL so the existing grant + reinvocation path handles it — the
|
|
68
|
+
* conservative fallback that can never corrupt a file.
|
|
69
|
+
*/
|
|
70
|
+
export async function applyApprovedWholeFileWrites(opts) {
|
|
71
|
+
const applied = new Set();
|
|
72
|
+
for (const msg of opts.messages) {
|
|
73
|
+
for (const tc of msg.toolCalls) {
|
|
74
|
+
if (!isApprovedWholeFileWrite(tc))
|
|
75
|
+
continue;
|
|
76
|
+
const args = argsRecord(tc);
|
|
77
|
+
const rawPath = extractFilePath(args);
|
|
78
|
+
if (!rawPath) {
|
|
79
|
+
// A whole-file write always carries a path arg; a missing one is
|
|
80
|
+
// unexpected — do not guess a path, fall back.
|
|
81
|
+
logSkip(opts.executionId, tc, "no file path in tool args");
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
// Resolve against the workspace root with the same convention the gate used
|
|
85
|
+
// (Cursor: real paths, not virtual), so the applied path matches the one the
|
|
86
|
+
// user approved.
|
|
87
|
+
const { absolutePath: target } = resolveWorkspacePath(rawPath, opts.workspaceBackend.rootDir,
|
|
88
|
+
/* virtualRoot */ false);
|
|
89
|
+
if (!isWithinWorkspace(target, opts.workspaceDirs)) {
|
|
90
|
+
logSkip(opts.executionId, tc, `target outside workspace: ${target}`);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const content = resolveApprovedWholeFileContent(tc);
|
|
94
|
+
if (content === null) {
|
|
95
|
+
logSkip(opts.executionId, tc, "exact approved bytes unresolvable");
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
await opts.workspaceBackend.writeFile(target, content);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
logSkip(opts.executionId, tc, `write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
// The tool is now genuinely done: the runner applied exactly what the user
|
|
106
|
+
// approved (the same `args` bytes the gate showed). Mark COMPLETED in place
|
|
107
|
+
// (same id — the backend's append-only-at-identity guard accepts a status
|
|
108
|
+
// change). The approval_action is preserved as the audit trail; the status
|
|
109
|
+
// flip removes it from the server's pending_approvals projection (which
|
|
110
|
+
// keys on WAITING_APPROVAL).
|
|
111
|
+
tc.status = ToolCallStatus.TOOL_CALL_COMPLETED;
|
|
112
|
+
if (!tc.completedAt)
|
|
113
|
+
tc.completedAt = utcTimestamp();
|
|
114
|
+
tc.error = "";
|
|
115
|
+
applied.add(tc.id);
|
|
116
|
+
console.log(`ExecuteCursor exact-apply: wrote approved bytes to ${target} ` +
|
|
117
|
+
`(tool=${tc.id}); no resource grant issued, so a further change ` +
|
|
118
|
+
`re-gates. execution=${opts.executionId}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return applied;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The adjudicated approvals MINUS the ones the runner already exact-applied — the
|
|
125
|
+
* set the caller turns into resource grants for the resumed agent.
|
|
126
|
+
*
|
|
127
|
+
* This is the "issues NO grant for an applied write" half of the guarantee: an
|
|
128
|
+
* exact-applied whole-file write is intentionally NOT granted, so if the
|
|
129
|
+
* reinvoked model writes to that same file again it is re-gated and the user
|
|
130
|
+
* reviews the new change (the deny-gate's "what you approve is what gets applied"
|
|
131
|
+
* property). Non-applied approvals (hunk edits, shell, MCP, and every reject /
|
|
132
|
+
* skip) pass through unchanged — they still need their grant to flow on resume.
|
|
133
|
+
*
|
|
134
|
+
* Pure and order-independent. Load-bearing because the caller computes the
|
|
135
|
+
* adjudicated set ONCE (before {@link applyApprovedWholeFileWrites} flips the
|
|
136
|
+
* applied calls to COMPLETED) and never re-derives it, so this exclusion is the
|
|
137
|
+
* only thing that keeps an applied write out of the grants.
|
|
138
|
+
*/
|
|
139
|
+
export function excludeAppliedFromGrants(adjudicatedApprovals, appliedToolCallIds) {
|
|
140
|
+
return adjudicatedApprovals.filter((pa) => !appliedToolCallIds.has(pa.toolCallId));
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The exact approved bytes for a whole-file write, or `null` when they cannot be
|
|
144
|
+
* recovered (in which case the caller must fall back, never write).
|
|
145
|
+
*
|
|
146
|
+
* The bytes come from the gated call's `args` whole-file content — the single
|
|
147
|
+
* authoritative copy of the proposed body (the deny-gate stamped it from the
|
|
148
|
+
* hook input; see {@link isApprovedWholeFileWrite}). `args` is never offloaded to
|
|
149
|
+
* a ref (only dropped wholesale by the aggregate size backstop), so when present
|
|
150
|
+
* it is intact.
|
|
151
|
+
*
|
|
152
|
+
* It NEVER returns the elision marker: that is a lossy stand-in and writing it
|
|
153
|
+
* would corrupt the file. An `args` body equal to the marker is treated as
|
|
154
|
+
* unresolved, and the caller degrades to grant + reinvocation.
|
|
155
|
+
*/
|
|
156
|
+
export function resolveApprovedWholeFileContent(tc) {
|
|
157
|
+
const content = extractWriteContent(argsRecord(tc));
|
|
158
|
+
if (content !== null && content !== ELISION_MARKER)
|
|
159
|
+
return content;
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Whether `tc` is an approved, still-pending WHOLE-FILE write eligible for
|
|
164
|
+
* exact-apply. APPROVE and APPROVE_ALL both approve the clicked tool (the latter
|
|
165
|
+
* also leases the class, handled separately by the caller). A SKIP/REJECT, an
|
|
166
|
+
* edit-family call (`old_string`/`new_string`, which carries no whole-file body),
|
|
167
|
+
* and an already-applied (COMPLETED) call are all excluded.
|
|
168
|
+
*
|
|
169
|
+
* The whole-file predicate is `extractWriteContent(args) !== null` — the exact
|
|
170
|
+
* condition the removed field-22 capture used to classify a change WHOLE_FILE
|
|
171
|
+
* (a whole-file body present) vs HUNK_ONLY (an edit fragment). Reading it from
|
|
172
|
+
* `args` keeps eligibility on the single source of truth.
|
|
173
|
+
*/
|
|
174
|
+
function isApprovedWholeFileWrite(tc) {
|
|
175
|
+
if (tc.status !== ToolCallStatus.TOOL_CALL_WAITING_APPROVAL)
|
|
176
|
+
return false;
|
|
177
|
+
if (tc.approvalAction !== ApprovalAction.APPROVE &&
|
|
178
|
+
tc.approvalAction !== ApprovalAction.APPROVE_ALL) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
return extractWriteContent(argsRecord(tc)) !== null;
|
|
182
|
+
}
|
|
183
|
+
/** The gated tool call's structured args as a plain record (empty when absent). */
|
|
184
|
+
function argsRecord(tc) {
|
|
185
|
+
return tc.args && typeof tc.args === "object"
|
|
186
|
+
? tc.args
|
|
187
|
+
: {};
|
|
188
|
+
}
|
|
189
|
+
/** Whether `absTarget` resolves inside one of the configured workspace roots. */
|
|
190
|
+
function isWithinWorkspace(absTarget, workspaceDirs) {
|
|
191
|
+
const target = resolve(absTarget);
|
|
192
|
+
return workspaceDirs.some((dir) => {
|
|
193
|
+
const root = resolve(dir);
|
|
194
|
+
if (target === root)
|
|
195
|
+
return true;
|
|
196
|
+
const rel = relative(root, target);
|
|
197
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
function logSkip(executionId, tc, reason) {
|
|
201
|
+
console.log(`ExecuteCursor exact-apply skipped (falling back to grant+reinvocation): ` +
|
|
202
|
+
`tool=${tc.id} reason="${reason}" execution=${executionId}`);
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=exact-apply.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exact-apply.js","sourceRoot":"","sources":["../../../src/activities/execute-cursor/exact-apply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EACL,cAAc,EACd,cAAc,GACf,MAAM,8DAA8D,CAAC;AAMtE,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAClF,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAEnE,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AActD;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,IAAuB;IAEvB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAElC,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChC,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;YAC/B,IAAI,CAAC,wBAAwB,CAAC,EAAE,CAAC;gBAAE,SAAS;YAE5C,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,CAAC,CAAC;YAC5B,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;YACtC,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,iEAAiE;gBACjE,+CAA+C;gBAC/C,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,EAAE,2BAA2B,CAAC,CAAC;gBAC3D,SAAS;YACX,CAAC;YACD,4EAA4E;YAC5E,6EAA6E;YAC7E,iBAAiB;YACjB,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,oBAAoB,CACnD,OAAO,EACP,IAAI,CAAC,gBAAgB,CAAC,OAAO;YAC7B,iBAAiB,CAAC,KAAK,CACxB,CAAC;YACF,IAAI,CAAC,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;gBACnD,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,EAAE,6BAA6B,MAAM,EAAE,CAAC,CAAC;gBACrE,SAAS;YACX,CAAC;YAED,MAAM,OAAO,GAAG,+BAA+B,CAAC,EAAE,CAAC,CAAC;YACpD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACrB,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,EAAE,mCAAmC,CAAC,CAAC;gBACnE,SAAS;YACX,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YACzD,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CACL,IAAI,CAAC,WAAW,EAChB,EAAE,EACF,iBAAiB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACpE,CAAC;gBACF,SAAS;YACX,CAAC;YAED,2EAA2E;YAC3E,4EAA4E;YAC5E,0EAA0E;YAC1E,2EAA2E;YAC3E,wEAAwE;YACxE,6BAA6B;YAC7B,EAAE,CAAC,MAAM,GAAG,cAAc,CAAC,mBAAmB,CAAC;YAC/C,IAAI,CAAC,EAAE,CAAC,WAAW;gBAAE,EAAE,CAAC,WAAW,GAAG,YAAY,EAAE,CAAC;YACrD,EAAE,CAAC,KAAK,GAAG,EAAE,CAAC;YACd,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YACnB,OAAO,CAAC,GAAG,CACT,sDAAsD,MAAM,GAAG;gBAC7D,SAAS,EAAE,CAAC,EAAE,mDAAmD;gBACjE,uBAAuB,IAAI,CAAC,WAAW,EAAE,CAC5C,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,wBAAwB,CACtC,oBAAgD,EAChD,kBAAuC;IAEvC,OAAO,oBAAoB,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;AACrF,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,+BAA+B,CAAC,EAAY;IAC1D,MAAM,OAAO,GAAG,mBAAmB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;IACpD,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,cAAc;QAAE,OAAO,OAAO,CAAC;IACnE,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,wBAAwB,CAAC,EAAY;IAC5C,IAAI,EAAE,CAAC,MAAM,KAAK,cAAc,CAAC,0BAA0B;QAAE,OAAO,KAAK,CAAC;IAC1E,IACE,EAAE,CAAC,cAAc,KAAK,cAAc,CAAC,OAAO;QAC5C,EAAE,CAAC,cAAc,KAAK,cAAc,CAAC,WAAW,EAChD,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,mBAAmB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;AACtD,CAAC;AAED,mFAAmF;AACnF,SAAS,UAAU,CAAC,EAAY;IAC9B,OAAO,EAAE,CAAC,IAAI,IAAI,OAAO,EAAE,CAAC,IAAI,KAAK,QAAQ;QAC3C,CAAC,CAAE,EAAE,CAAC,IAAgC;QACtC,CAAC,CAAC,EAAE,CAAC;AACT,CAAC;AAED,iFAAiF;AACjF,SAAS,iBAAiB,CACxB,SAAiB,EACjB,aAAgC;IAEhC,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAClC,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;QAChC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QACjC,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACnC,OAAO,GAAG,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,OAAO,CAAC,WAAmB,EAAE,EAAY,EAAE,MAAc;IAChE,OAAO,CAAC,GAAG,CACT,0EAA0E;QACxE,QAAQ,EAAE,CAAC,EAAE,YAAY,MAAM,eAAe,WAAW,EAAE,CAC9D,CAAC;AACJ,CAAC"}
|
|
@@ -54,47 +54,76 @@
|
|
|
54
54
|
* recorded here correlates to the streamed tool call, and an approval grant
|
|
55
55
|
* matches the agent's re-attempt on reinvocation.
|
|
56
56
|
*
|
|
57
|
+
* Content-exact identity (the sibling-hole fix): for a file edit the coarse
|
|
58
|
+
* (category, salient) is not enough — approving one edit to a file must not let
|
|
59
|
+
* a DIFFERENT edit to the SAME file ride through. So the hook ALSO computes a
|
|
60
|
+
* CONTENT token `base64(category \n salient \n contentDigest)`, where
|
|
61
|
+
* contentDigest is a sha256 over the edit content (mirror of file-tools.ts
|
|
62
|
+
* contentDigest; see buildContentDigestScript). It allows a built-in when EITHER
|
|
63
|
+
* the content token (a file edit approved with this exact content) OR the coarse
|
|
64
|
+
* token (shell/delete, or a content-less degrade) is granted, and records the
|
|
65
|
+
* content token as the denial identity. The runner grants the content token when
|
|
66
|
+
* it has the approved content (the persisted approval_content_digest), so a
|
|
67
|
+
* sibling edit re-gates; it degrades to the coarse grant only when the content is
|
|
68
|
+
* unrecoverable.
|
|
69
|
+
*
|
|
57
70
|
* Policy evaluation order (first match wins). The model is "gate the dangerous
|
|
58
71
|
* set, allow the rest" — matching the native harness and avoiding denial of
|
|
59
72
|
* auto-approved MCP tools (which are absent from mcpToolPolicies):
|
|
60
73
|
* 0. Scope guard: not the runner's own agent → allow (never touch the ledger)
|
|
61
|
-
* 1. Missing state file → deny (fail-closed); autoApproveAll
|
|
74
|
+
* 1. Missing state file → deny (fail-closed); autoApproveAll (the pre-armed
|
|
75
|
+
* spec.auto_approve_all global bypass) → allow
|
|
62
76
|
* 2. beforeMCPExecution event → MCP tool present in mcpToolPolicies
|
|
63
77
|
* (require-approval):
|
|
64
78
|
* a. name token in approvedGrantTokens → allow (reinvocation grant)
|
|
65
79
|
* b. otherwise → record denial, deny
|
|
66
|
-
* (auto-approved / unlisted MCP tools fall through → allow
|
|
80
|
+
* (auto-approved / unlisted MCP tools fall through → allow; a server-scoped
|
|
81
|
+
* lease drops the server's tools from mcpToolPolicies, so they fall through)
|
|
67
82
|
* 3. preToolUse event → gated built-in (category non-empty):
|
|
68
83
|
* a. identity token in approvedGrantTokens → allow (reinvocation grant)
|
|
69
|
-
* b.
|
|
84
|
+
* b. category in leasedCategories → allow (run-lifetime scoped lease)
|
|
85
|
+
* c. otherwise → record denial, deny
|
|
70
86
|
* (read-only / ungated built-ins fall through → allow)
|
|
71
87
|
*/
|
|
72
88
|
/**
|
|
73
|
-
* Generates the bash hook script content.
|
|
89
|
+
* Generates the STABLE bash hook script content.
|
|
90
|
+
*
|
|
91
|
+
* The script is STABLE across executions in a runner process — its only inputs
|
|
92
|
+
* are the absolute path of the active-turn pointer (and the runner's Node
|
|
93
|
+
* binary), both constant for a given workspace. This is deliberate and
|
|
94
|
+
* load-bearing: the Cursor SDK loads `<workspace>/.cursor/hooks.json` (the hook
|
|
95
|
+
* script PATH) ONCE per runner process and caches it, ignoring later
|
|
96
|
+
* per-execution rewrites. A per-session script with per-session baked paths
|
|
97
|
+
* therefore gets cached at the FIRST execution and reused for every later one,
|
|
98
|
+
* recording their denials into the FIRST session's ledger while each later runner
|
|
99
|
+
* reads its own (empty) ledger and silently completes — the no-approval-button /
|
|
100
|
+
* "execution completed" regression. Keeping the script stable and resolving the
|
|
101
|
+
* CURRENT turn's artifacts from the pointer (which bash re-reads every
|
|
102
|
+
* invocation; see {@link ActiveTurnPointer}/writeActiveTurnPointer) makes a
|
|
103
|
+
* long-lived multi-session runner correct.
|
|
74
104
|
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* generation time from approval-policy.ts.
|
|
105
|
+
* From the pointer the script reads the current turn's approval-state file (the
|
|
106
|
+
* single source of truth for the dynamic inputs: autoApproveAll, leasedCategories,
|
|
107
|
+
* mcpToolPolicies, approvedGrantTokens), denial ledger, and runner PID. The
|
|
108
|
+
* static policy (which built-ins are gated, their categories, the salient arg
|
|
109
|
+
* fields) is baked at generation time from approval-policy.ts.
|
|
81
110
|
*
|
|
82
111
|
* The identity token encoding (`base64(key \n salient)`) must stay byte-identical
|
|
83
112
|
* to grantToken() in approval-state.ts.
|
|
84
113
|
*
|
|
85
114
|
* Scope guard (the crux of issue #173): the Cursor SDK loads project hooks from
|
|
86
|
-
* `<workspace>/.cursor/hooks.json`,
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* invocation
|
|
115
|
+
* `<workspace>/.cursor/hooks.json`, the SAME per-repo surface every Cursor client
|
|
116
|
+
* reads. When a session runs against the user's real repo, the user's own
|
|
117
|
+
* interactive Cursor IDE would otherwise load and run this hook too — gating the
|
|
118
|
+
* IDE, polluting the denial ledger, and (in multi-root windows) failing closed.
|
|
119
|
+
* We make the gate apply ONLY to the runner's own agent by checking, on every
|
|
120
|
+
* invocation, whether the runner PID (FROM THE POINTER) is an ancestor of the
|
|
121
|
+
* hook process. The SDK runs hooks in-process via child_process, so the runner's
|
|
122
|
+
* own agent (and its delegated sub-agents) spawn the hook as a descendant of the
|
|
123
|
+
* runner; any other Cursor client spawns it under a different process tree. A
|
|
124
|
+
* non-descendant invocation is allowed immediately and never touches the ledger.
|
|
125
|
+
* Combined with pointer teardown, a leftover hooks.json is self-neutralizing:
|
|
126
|
+
* once the turn ends (pointer removed) or the runner exits (PID dead), no
|
|
127
|
+
* invocation gates, so the gate is inert.
|
|
99
128
|
*/
|
|
100
|
-
export declare function generateHookScript(
|
|
129
|
+
export declare function generateHookScript(activePointerPath: string, workspaceRoot?: string): string;
|