@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,168 @@
|
|
|
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
|
+
|
|
28
|
+
import { create } from "@bufbuild/protobuf";
|
|
29
|
+
import type { AgentMessage } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
30
|
+
import { TurnCommandProvenanceSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
|
|
31
|
+
import type { TurnCommandProvenance } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
|
|
32
|
+
import { ApprovalAction, ToolKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
33
|
+
import { classifyTool, toolApprovalCategory } from "../../shared/tool-kind.js";
|
|
34
|
+
import { isToolCallRowHidden } from "../../shared/tool-row.js";
|
|
35
|
+
import { toolCallIdentityToken } from "./message-translator.js";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Tool kinds that can never mutate the workspace: their presence in a turn is
|
|
39
|
+
* irrelevant to change-set provenance. Everything OUTSIDE this set is either a
|
|
40
|
+
* consent-mapped shell command or a disqualifier — never silently ignored.
|
|
41
|
+
*/
|
|
42
|
+
const NON_MUTATING_KINDS: ReadonlySet<ToolKind> = new Set([
|
|
43
|
+
ToolKind.FILE_READ,
|
|
44
|
+
ToolKind.SEARCH,
|
|
45
|
+
ToolKind.LIST,
|
|
46
|
+
ToolKind.FETCH,
|
|
47
|
+
ToolKind.WEB_SEARCH,
|
|
48
|
+
ToolKind.THINK,
|
|
49
|
+
ToolKind.TODO,
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
export interface CommandProvenanceInputs {
|
|
53
|
+
/**
|
|
54
|
+
* The full transcript at capture time. Turn scoping is positional: messages
|
|
55
|
+
* from index {@link turnStartIndex} onward were produced by THIS turn's
|
|
56
|
+
* stream; everything before was seeded from prior turns. Lease consent rows
|
|
57
|
+
* (APPROVE_ALL) are looked up across the WHOLE transcript — a lease is
|
|
58
|
+
* run-lifetime, its granting row usually lives in a prior turn.
|
|
59
|
+
*/
|
|
60
|
+
readonly messages: readonly AgentMessage[];
|
|
61
|
+
/** Index of the first message created by this turn's stream. */
|
|
62
|
+
readonly turnStartIndex: number;
|
|
63
|
+
/**
|
|
64
|
+
* Identity tokens the hook denied THIS turn (the denial ledger). A denied
|
|
65
|
+
* shell row never executed — it is this turn's pending gate, not a mutation
|
|
66
|
+
* source — so it neither needs consent nor disqualifies.
|
|
67
|
+
*/
|
|
68
|
+
readonly deniedTokens: ReadonlySet<string>;
|
|
69
|
+
/**
|
|
70
|
+
* Grant token → the tool-call id of the approval row that minted the grant
|
|
71
|
+
* (the row carrying the server-authored approval_action). An executed shell
|
|
72
|
+
* command whose identity token is a key here was authorized by that
|
|
73
|
+
* per-command approval.
|
|
74
|
+
*/
|
|
75
|
+
readonly grantTokenToConsentId: ReadonlyMap<string, string>;
|
|
76
|
+
/** True when the pre-armed spec.auto_approve_all bypassed the gate. */
|
|
77
|
+
readonly globalBypass: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Derive the {@link TurnCommandProvenance} for the turn, or undefined when the
|
|
82
|
+
* turn does not qualify (any non-shell mutation-capable call, any unknown tool,
|
|
83
|
+
* any executed command without a provable consent source, or no executed
|
|
84
|
+
* consented command at all).
|
|
85
|
+
*/
|
|
86
|
+
export function deriveTurnCommandProvenance(
|
|
87
|
+
inputs: CommandProvenanceInputs,
|
|
88
|
+
): TurnCommandProvenance | undefined {
|
|
89
|
+
const { messages, turnStartIndex, deniedTokens, grantTokenToConsentId, globalBypass } = inputs;
|
|
90
|
+
|
|
91
|
+
const consentIds = new Set<string>();
|
|
92
|
+
let authorizedByAutoApproveAll = false;
|
|
93
|
+
let executedCommandCount = 0;
|
|
94
|
+
|
|
95
|
+
for (let i = turnStartIndex; i < messages.length; i++) {
|
|
96
|
+
for (const tc of messages[i].toolCalls) {
|
|
97
|
+
// A hidden row is a collapsed twin/reaction — it never executed.
|
|
98
|
+
if (isToolCallRowHidden(tc)) continue;
|
|
99
|
+
|
|
100
|
+
const kind = classifyTool(tc.name, tc.mcpServerSlug);
|
|
101
|
+
if (NON_MUTATING_KINDS.has(kind)) continue;
|
|
102
|
+
|
|
103
|
+
if (kind !== ToolKind.SHELL) {
|
|
104
|
+
// A file tool (mixed turn), a sub-agent (unattributable mutations), an
|
|
105
|
+
// MCP tool (opaque side effects), or an unrecognized name — the turn's
|
|
106
|
+
// mutations cannot be attributed to consented commands. Fail closed.
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const token = toolCallIdentityToken(tc);
|
|
111
|
+
if (deniedTokens.has(token)) {
|
|
112
|
+
// This turn's pending gate — denied by the hook, never executed.
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
executedCommandCount++;
|
|
116
|
+
|
|
117
|
+
const consentId = grantTokenToConsentId.get(token);
|
|
118
|
+
if (consentId) {
|
|
119
|
+
consentIds.add(consentId);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const leaseConsentId = findLeaseConsentId(messages, tc.name);
|
|
123
|
+
if (leaseConsentId) {
|
|
124
|
+
consentIds.add(leaseConsentId);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (globalBypass) {
|
|
128
|
+
authorizedByAutoApproveAll = true;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
// An executed shell command with no grant, no lease, and no bypass should
|
|
132
|
+
// be impossible (the hook gates every un-consented shell); if it ever
|
|
133
|
+
// happens, the honest answer is a manual review, not a waived one.
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// A turn that executed no consented command has nothing to attribute the
|
|
139
|
+
// change set to — whatever changed came from somewhere else. Manual review.
|
|
140
|
+
if (executedCommandCount === 0) return undefined;
|
|
141
|
+
|
|
142
|
+
return create(TurnCommandProvenanceSchema, {
|
|
143
|
+
consentToolCallIds: [...consentIds],
|
|
144
|
+
authorizedByAutoApproveAll,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Find the transcript row whose APPROVE_ALL authored the run-lifetime lease
|
|
150
|
+
* covering `toolName`'s category — the consent row a lease-executed command
|
|
151
|
+
* cites. Searched across the whole transcript (a lease usually originates in a
|
|
152
|
+
* prior turn); the first (earliest) APPROVE_ALL of the category is the lease's
|
|
153
|
+
* origin. Returns undefined when no lease row exists for the category.
|
|
154
|
+
*/
|
|
155
|
+
function findLeaseConsentId(
|
|
156
|
+
messages: readonly AgentMessage[],
|
|
157
|
+
toolName: string,
|
|
158
|
+
): string | undefined {
|
|
159
|
+
const category = toolApprovalCategory(toolName);
|
|
160
|
+
if (!category) return undefined;
|
|
161
|
+
for (const msg of messages) {
|
|
162
|
+
for (const tc of msg.toolCalls) {
|
|
163
|
+
if (tc.approvalAction !== ApprovalAction.APPROVE_ALL) continue;
|
|
164
|
+
if (toolApprovalCategory(tc.name) === category) return tc.id;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
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
|
+
|
|
50
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
51
|
+
import {
|
|
52
|
+
ApprovalAction,
|
|
53
|
+
ToolCallStatus,
|
|
54
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
55
|
+
import type {
|
|
56
|
+
AgentMessage,
|
|
57
|
+
ToolCall,
|
|
58
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
59
|
+
import type { PendingApproval } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb";
|
|
60
|
+
import { ELISION_MARKER } from "../../shared/status-offload.js";
|
|
61
|
+
import { extractFilePath, extractWriteContent } from "../../shared/file-tools.js";
|
|
62
|
+
import { resolveWorkspacePath } from "../../shared/file-change.js";
|
|
63
|
+
import type { WorkspaceBackend } from "../../shared/workspace/types.js";
|
|
64
|
+
import { utcTimestamp } from "../../shared/status.js";
|
|
65
|
+
|
|
66
|
+
/** Options for {@link applyApprovedWholeFileWrites}. */
|
|
67
|
+
export interface ExactApplyOptions {
|
|
68
|
+
/** The (seeded) transcript whose approved tool calls are applied in place. */
|
|
69
|
+
readonly messages: AgentMessage[];
|
|
70
|
+
/** Workspace the files live in (local FS for OSS, sandbox in cloud). */
|
|
71
|
+
readonly workspaceBackend: WorkspaceBackend;
|
|
72
|
+
/** Configured workspace roots; a write is refused outside all of them. */
|
|
73
|
+
readonly workspaceDirs: readonly string[];
|
|
74
|
+
/** For structured logs. */
|
|
75
|
+
readonly executionId: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Apply every APPROVED whole-file write in `messages` to disk with its EXACT
|
|
80
|
+
* approved bytes, marking each applied tool call COMPLETED in place.
|
|
81
|
+
*
|
|
82
|
+
* Returns the set of tool-call ids that were exact-applied, so the caller can
|
|
83
|
+
* (a) exclude their resources from the approval grants — the key to re-gating a
|
|
84
|
+
* later write — and (b) describe them as "already applied" in the reinvocation
|
|
85
|
+
* prompt rather than asking the model to redo them.
|
|
86
|
+
*
|
|
87
|
+
* Idempotent: a call already COMPLETED (a Temporal activity retry re-runs this
|
|
88
|
+
* phase) is skipped, never written twice. Every uncertain case (no resolvable
|
|
89
|
+
* body, out-of-workspace target, write failure) is SKIPPED, leaving the call
|
|
90
|
+
* WAITING_APPROVAL so the existing grant + reinvocation path handles it — the
|
|
91
|
+
* conservative fallback that can never corrupt a file.
|
|
92
|
+
*/
|
|
93
|
+
export async function applyApprovedWholeFileWrites(
|
|
94
|
+
opts: ExactApplyOptions,
|
|
95
|
+
): Promise<Set<string>> {
|
|
96
|
+
const applied = new Set<string>();
|
|
97
|
+
|
|
98
|
+
for (const msg of opts.messages) {
|
|
99
|
+
for (const tc of msg.toolCalls) {
|
|
100
|
+
if (!isApprovedWholeFileWrite(tc)) continue;
|
|
101
|
+
|
|
102
|
+
const args = argsRecord(tc);
|
|
103
|
+
const rawPath = extractFilePath(args);
|
|
104
|
+
if (!rawPath) {
|
|
105
|
+
// A whole-file write always carries a path arg; a missing one is
|
|
106
|
+
// unexpected — do not guess a path, fall back.
|
|
107
|
+
logSkip(opts.executionId, tc, "no file path in tool args");
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
// Resolve against the workspace root with the same convention the gate used
|
|
111
|
+
// (Cursor: real paths, not virtual), so the applied path matches the one the
|
|
112
|
+
// user approved.
|
|
113
|
+
const { absolutePath: target } = resolveWorkspacePath(
|
|
114
|
+
rawPath,
|
|
115
|
+
opts.workspaceBackend.rootDir,
|
|
116
|
+
/* virtualRoot */ false,
|
|
117
|
+
);
|
|
118
|
+
if (!isWithinWorkspace(target, opts.workspaceDirs)) {
|
|
119
|
+
logSkip(opts.executionId, tc, `target outside workspace: ${target}`);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const content = resolveApprovedWholeFileContent(tc);
|
|
124
|
+
if (content === null) {
|
|
125
|
+
logSkip(opts.executionId, tc, "exact approved bytes unresolvable");
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
await opts.workspaceBackend.writeFile(target, content);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
logSkip(
|
|
133
|
+
opts.executionId,
|
|
134
|
+
tc,
|
|
135
|
+
`write failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
136
|
+
);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// The tool is now genuinely done: the runner applied exactly what the user
|
|
141
|
+
// approved (the same `args` bytes the gate showed). Mark COMPLETED in place
|
|
142
|
+
// (same id — the backend's append-only-at-identity guard accepts a status
|
|
143
|
+
// change). The approval_action is preserved as the audit trail; the status
|
|
144
|
+
// flip removes it from the server's pending_approvals projection (which
|
|
145
|
+
// keys on WAITING_APPROVAL).
|
|
146
|
+
tc.status = ToolCallStatus.TOOL_CALL_COMPLETED;
|
|
147
|
+
if (!tc.completedAt) tc.completedAt = utcTimestamp();
|
|
148
|
+
tc.error = "";
|
|
149
|
+
applied.add(tc.id);
|
|
150
|
+
console.log(
|
|
151
|
+
`ExecuteCursor exact-apply: wrote approved bytes to ${target} ` +
|
|
152
|
+
`(tool=${tc.id}); no resource grant issued, so a further change ` +
|
|
153
|
+
`re-gates. execution=${opts.executionId}`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return applied;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The adjudicated approvals MINUS the ones the runner already exact-applied — the
|
|
163
|
+
* set the caller turns into resource grants for the resumed agent.
|
|
164
|
+
*
|
|
165
|
+
* This is the "issues NO grant for an applied write" half of the guarantee: an
|
|
166
|
+
* exact-applied whole-file write is intentionally NOT granted, so if the
|
|
167
|
+
* reinvoked model writes to that same file again it is re-gated and the user
|
|
168
|
+
* reviews the new change (the deny-gate's "what you approve is what gets applied"
|
|
169
|
+
* property). Non-applied approvals (hunk edits, shell, MCP, and every reject /
|
|
170
|
+
* skip) pass through unchanged — they still need their grant to flow on resume.
|
|
171
|
+
*
|
|
172
|
+
* Pure and order-independent. Load-bearing because the caller computes the
|
|
173
|
+
* adjudicated set ONCE (before {@link applyApprovedWholeFileWrites} flips the
|
|
174
|
+
* applied calls to COMPLETED) and never re-derives it, so this exclusion is the
|
|
175
|
+
* only thing that keeps an applied write out of the grants.
|
|
176
|
+
*/
|
|
177
|
+
export function excludeAppliedFromGrants(
|
|
178
|
+
adjudicatedApprovals: readonly PendingApproval[],
|
|
179
|
+
appliedToolCallIds: ReadonlySet<string>,
|
|
180
|
+
): PendingApproval[] {
|
|
181
|
+
return adjudicatedApprovals.filter((pa) => !appliedToolCallIds.has(pa.toolCallId));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* The exact approved bytes for a whole-file write, or `null` when they cannot be
|
|
186
|
+
* recovered (in which case the caller must fall back, never write).
|
|
187
|
+
*
|
|
188
|
+
* The bytes come from the gated call's `args` whole-file content — the single
|
|
189
|
+
* authoritative copy of the proposed body (the deny-gate stamped it from the
|
|
190
|
+
* hook input; see {@link isApprovedWholeFileWrite}). `args` is never offloaded to
|
|
191
|
+
* a ref (only dropped wholesale by the aggregate size backstop), so when present
|
|
192
|
+
* it is intact.
|
|
193
|
+
*
|
|
194
|
+
* It NEVER returns the elision marker: that is a lossy stand-in and writing it
|
|
195
|
+
* would corrupt the file. An `args` body equal to the marker is treated as
|
|
196
|
+
* unresolved, and the caller degrades to grant + reinvocation.
|
|
197
|
+
*/
|
|
198
|
+
export function resolveApprovedWholeFileContent(tc: ToolCall): string | null {
|
|
199
|
+
const content = extractWriteContent(argsRecord(tc));
|
|
200
|
+
if (content !== null && content !== ELISION_MARKER) return content;
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Whether `tc` is an approved, still-pending WHOLE-FILE write eligible for
|
|
206
|
+
* exact-apply. APPROVE and APPROVE_ALL both approve the clicked tool (the latter
|
|
207
|
+
* also leases the class, handled separately by the caller). A SKIP/REJECT, an
|
|
208
|
+
* edit-family call (`old_string`/`new_string`, which carries no whole-file body),
|
|
209
|
+
* and an already-applied (COMPLETED) call are all excluded.
|
|
210
|
+
*
|
|
211
|
+
* The whole-file predicate is `extractWriteContent(args) !== null` — the exact
|
|
212
|
+
* condition the removed field-22 capture used to classify a change WHOLE_FILE
|
|
213
|
+
* (a whole-file body present) vs HUNK_ONLY (an edit fragment). Reading it from
|
|
214
|
+
* `args` keeps eligibility on the single source of truth.
|
|
215
|
+
*/
|
|
216
|
+
function isApprovedWholeFileWrite(tc: ToolCall): boolean {
|
|
217
|
+
if (tc.status !== ToolCallStatus.TOOL_CALL_WAITING_APPROVAL) return false;
|
|
218
|
+
if (
|
|
219
|
+
tc.approvalAction !== ApprovalAction.APPROVE &&
|
|
220
|
+
tc.approvalAction !== ApprovalAction.APPROVE_ALL
|
|
221
|
+
) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
return extractWriteContent(argsRecord(tc)) !== null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** The gated tool call's structured args as a plain record (empty when absent). */
|
|
228
|
+
function argsRecord(tc: ToolCall): Record<string, unknown> {
|
|
229
|
+
return tc.args && typeof tc.args === "object"
|
|
230
|
+
? (tc.args as Record<string, unknown>)
|
|
231
|
+
: {};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Whether `absTarget` resolves inside one of the configured workspace roots. */
|
|
235
|
+
function isWithinWorkspace(
|
|
236
|
+
absTarget: string,
|
|
237
|
+
workspaceDirs: readonly string[],
|
|
238
|
+
): boolean {
|
|
239
|
+
const target = resolve(absTarget);
|
|
240
|
+
return workspaceDirs.some((dir) => {
|
|
241
|
+
const root = resolve(dir);
|
|
242
|
+
if (target === root) return true;
|
|
243
|
+
const rel = relative(root, target);
|
|
244
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function logSkip(executionId: string, tc: ToolCall, reason: string): void {
|
|
249
|
+
console.log(
|
|
250
|
+
`ExecuteCursor exact-apply skipped (falling back to grant+reinvocation): ` +
|
|
251
|
+
`tool=${tc.id} reason="${reason}" execution=${executionId}`,
|
|
252
|
+
);
|
|
253
|
+
}
|