@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
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
buildMergedConfig,
|
|
28
28
|
} from "../workspace-setup.js";
|
|
29
29
|
import { buildApprovalState } from "../approval-state.js";
|
|
30
|
+
import { getHitlGateDir } from "../../../shared/workspace/platform-dir.js";
|
|
30
31
|
|
|
31
32
|
const tempDirs: string[] = [];
|
|
32
33
|
afterEach(() => {
|
|
@@ -159,42 +160,117 @@ describe("buildMergedConfig", () => {
|
|
|
159
160
|
// ...and never "fix" the user's file: restore their exact original bytes.
|
|
160
161
|
expect(restoreTo).toBe(garbage);
|
|
161
162
|
});
|
|
163
|
+
|
|
164
|
+
// ── H-G regression: the pre-#173 in-workspace hook ──────────────────────────
|
|
165
|
+
// The root cause of "approved tool still blocked": an older runner build wrote
|
|
166
|
+
// the gate INTO the repo as `.cursor/hooks/stigmer-approval.sh` (a relative
|
|
167
|
+
// command). The recognizer used to match only the current `/.stigmer/sessions/`
|
|
168
|
+
// path, so the merge treated this stale entry as a USER hook and preserved it —
|
|
169
|
+
// it then ran alongside the current gate and vetoed every gated tool, even
|
|
170
|
+
// approved ones. These pin that it is now recognized, stripped, and self-healed.
|
|
171
|
+
|
|
172
|
+
it("recognizes and strips the legacy in-workspace hook; deletes the entirely-leftover file", () => {
|
|
173
|
+
// The exact shape observed in a polluted repo.
|
|
174
|
+
const original = JSON.stringify({
|
|
175
|
+
version: 1,
|
|
176
|
+
hooks: {
|
|
177
|
+
preToolUse: [
|
|
178
|
+
{ command: ".cursor/hooks/stigmer-approval.sh", timeout: 10, failClosed: true },
|
|
179
|
+
],
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
const fresh = "/home/u/.stigmer/sessions/ses-9/hitl/stigmer-approval.sh";
|
|
183
|
+
const { merged, restoreTo } = buildMergedConfig(original, pre(fresh));
|
|
184
|
+
|
|
185
|
+
// The active config carries ONLY the current runner hook — the stale legacy
|
|
186
|
+
// entry is gone, so it can never run alongside (and veto) the live gate.
|
|
187
|
+
const m = JSON.parse(merged);
|
|
188
|
+
expect(m.hooks.preToolUse.map((e: any) => e.command)).toEqual([fresh]);
|
|
189
|
+
|
|
190
|
+
// The whole hooks.json was our own leftover (only version+hooks, no user
|
|
191
|
+
// hooks remain after stripping) → teardown deletes it, leaving the repo
|
|
192
|
+
// pristine rather than a `{hooks:{preToolUse:[]}}` husk.
|
|
193
|
+
expect(restoreTo).toBeNull();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("strips the legacy hook but preserves a genuine user hook in the same array", () => {
|
|
197
|
+
const original = JSON.stringify({
|
|
198
|
+
version: 1,
|
|
199
|
+
hooks: {
|
|
200
|
+
preToolUse: [
|
|
201
|
+
{ command: "./user.sh" },
|
|
202
|
+
{ command: ".cursor/hooks/stigmer-approval.sh", timeout: 10, failClosed: true },
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
const fresh = "/home/u/.stigmer/sessions/ses-9/hitl/stigmer-approval.sh";
|
|
207
|
+
const { merged, restoreTo } = buildMergedConfig(original, pre(fresh));
|
|
208
|
+
|
|
209
|
+
// No duplicate, stale legacy entry dropped, user hook kept ahead of ours.
|
|
210
|
+
expect(JSON.parse(merged).hooks.preToolUse.map((e: any) => e.command)).toEqual([
|
|
211
|
+
"./user.sh",
|
|
212
|
+
fresh,
|
|
213
|
+
]);
|
|
214
|
+
// A real user hook remains → restore the cleaned form, not delete.
|
|
215
|
+
expect(JSON.parse(restoreTo!).hooks.preToolUse).toEqual([{ command: "./user.sh" }]);
|
|
216
|
+
});
|
|
162
217
|
});
|
|
163
218
|
|
|
164
219
|
describe("installHitlGate / removeHitlGate", () => {
|
|
165
|
-
const approvalState = buildApprovalState(new Map(), false);
|
|
220
|
+
const approvalState = buildApprovalState(new Map(), false, new Set());
|
|
221
|
+
|
|
222
|
+
// Sandbox HOME so the workspace-scoped gate dir (`~/.stigmer/hitl-gate/<hash>`)
|
|
223
|
+
// lands under the per-test temp root, asserted and cleaned with everything else.
|
|
224
|
+
const realHome = process.env.HOME;
|
|
225
|
+
afterEach(() => {
|
|
226
|
+
if (realHome === undefined) delete process.env.HOME;
|
|
227
|
+
else process.env.HOME = realHome;
|
|
228
|
+
});
|
|
166
229
|
|
|
167
230
|
function dirs() {
|
|
168
231
|
const root = freshRoot();
|
|
232
|
+
process.env.HOME = root;
|
|
169
233
|
const workspaceRoot = join(root, "repo");
|
|
170
234
|
const hitlDir = join(root, ".stigmer", "sessions", "ses-1", "hitl");
|
|
171
235
|
mkdirSync(workspaceRoot, { recursive: true });
|
|
172
|
-
|
|
236
|
+
// The stable hook script + active-turn pointer live in the workspace-scoped
|
|
237
|
+
// gate dir (outside the repo), not the per-session HITL dir.
|
|
238
|
+
const gateDir = getHitlGateDir(workspaceRoot);
|
|
239
|
+
return { workspaceRoot, hitlDir, gateDir };
|
|
173
240
|
}
|
|
174
241
|
|
|
175
242
|
it("writes artifacts OUTSIDE the workspace and a hooks.json with an absolute command", async () => {
|
|
176
|
-
const { workspaceRoot, hitlDir } = dirs();
|
|
243
|
+
const { workspaceRoot, hitlDir, gateDir } = dirs();
|
|
177
244
|
await installHitlGate({ workspaceRoot, hitlDir, approvalState, runnerPid: process.pid });
|
|
178
245
|
|
|
179
|
-
//
|
|
180
|
-
|
|
246
|
+
// Per-session hook input/output live in the HITL dir; the STABLE hook script
|
|
247
|
+
// and the active-turn pointer live in the workspace gate dir. Neither is in
|
|
248
|
+
// the repo.
|
|
181
249
|
expect(existsSync(join(hitlDir, "approval-state.json"))).toBe(true);
|
|
182
250
|
expect(existsSync(join(hitlDir, "denials.jsonl"))).toBe(true);
|
|
251
|
+
expect(existsSync(join(gateDir, "stigmer-approval.sh"))).toBe(true);
|
|
252
|
+
expect(existsSync(join(gateDir, "active.json"))).toBe(true);
|
|
183
253
|
// The script is executable.
|
|
184
|
-
expect(statSync(join(
|
|
254
|
+
expect(statSync(join(gateDir, "stigmer-approval.sh")).mode & 0o111).toBeTruthy();
|
|
255
|
+
|
|
256
|
+
// The pointer names THIS turn's per-session artifacts + runner PID.
|
|
257
|
+
const pointer = JSON.parse(readFileSync(join(gateDir, "active.json"), "utf-8"));
|
|
258
|
+
expect(pointer.stateFile).toBe(join(hitlDir, "approval-state.json"));
|
|
259
|
+
expect(pointer.ledgerFile).toBe(join(hitlDir, "denials.jsonl"));
|
|
260
|
+
expect(pointer.runnerPid).toBe(process.pid);
|
|
185
261
|
|
|
186
|
-
// The only in-repo file is hooks.json, pointing at the script by
|
|
187
|
-
// path (the relative path was the multi-root exit-127 bug).
|
|
262
|
+
// The only in-repo file is hooks.json, pointing at the STABLE script by
|
|
263
|
+
// ABSOLUTE path (the relative path was the multi-root exit-127 bug).
|
|
188
264
|
const hooksJson = JSON.parse(
|
|
189
265
|
readFileSync(join(workspaceRoot, ".cursor", "hooks.json"), "utf-8"),
|
|
190
266
|
);
|
|
191
267
|
const command = hooksJson.hooks.preToolUse[0].command;
|
|
192
|
-
expect(command).toBe(join(
|
|
268
|
+
expect(command).toBe(join(gateDir, "stigmer-approval.sh"));
|
|
193
269
|
expect(command.startsWith("/")).toBe(true);
|
|
194
270
|
// The SAME script gates MCP via beforeMCPExecution (preToolUse does not
|
|
195
271
|
// enforce MCP); the script branches internally on hook_event_name.
|
|
196
272
|
expect(hooksJson.hooks.beforeMCPExecution[0].command).toBe(
|
|
197
|
-
join(
|
|
273
|
+
join(gateDir, "stigmer-approval.sh"),
|
|
198
274
|
);
|
|
199
275
|
// The workspace holds no relocated artifacts.
|
|
200
276
|
expect(existsSync(join(workspaceRoot, ".cursor", "hooks"))).toBe(false);
|
|
@@ -250,4 +326,108 @@ describe("installHitlGate / removeHitlGate", () => {
|
|
|
250
326
|
expect(existsSync(join(workspaceRoot, ".cursor", "hooks.json"))).toBe(false);
|
|
251
327
|
}
|
|
252
328
|
});
|
|
329
|
+
|
|
330
|
+
it("installs the always-applied tool-approval rule and removes it on teardown", async () => {
|
|
331
|
+
const { workspaceRoot, hitlDir } = dirs();
|
|
332
|
+
const rulePath = join(workspaceRoot, ".cursor", "rules", "stigmer-tool-approval.mdc");
|
|
333
|
+
|
|
334
|
+
const handle = await installHitlGate({
|
|
335
|
+
workspaceRoot, hitlDir, approvalState, runnerPid: process.pid,
|
|
336
|
+
});
|
|
337
|
+
// During the turn the rule is present and carries the protocol.
|
|
338
|
+
expect(existsSync(rulePath)).toBe(true);
|
|
339
|
+
const ruleBody = readFileSync(rulePath, "utf-8");
|
|
340
|
+
expect(ruleBody).toContain("alwaysApply: true");
|
|
341
|
+
expect(ruleBody.toLowerCase()).toContain("blocked by a hook");
|
|
342
|
+
|
|
343
|
+
await removeHitlGate(handle);
|
|
344
|
+
// Teardown removes our rule and the now-empty rules dir (the repo had none).
|
|
345
|
+
expect(existsSync(rulePath)).toBe(false);
|
|
346
|
+
expect(existsSync(join(workspaceRoot, ".cursor", "rules"))).toBe(false);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("heals a workspace polluted by a pre-#173 in-workspace gate (H-G end-to-end)", async () => {
|
|
350
|
+
const { workspaceRoot, hitlDir, gateDir } = dirs();
|
|
351
|
+
// Reconstruct exactly what a polluted repo looks like: a hooks.json pointing
|
|
352
|
+
// at a relative in-workspace script, plus the orphaned script/state/ledger
|
|
353
|
+
// files an older runner left in `.cursor/hooks/`.
|
|
354
|
+
const cursorDir = join(workspaceRoot, ".cursor");
|
|
355
|
+
const legacyHooksDir = join(cursorDir, "hooks");
|
|
356
|
+
mkdirSync(legacyHooksDir, { recursive: true });
|
|
357
|
+
writeFileSync(
|
|
358
|
+
join(cursorDir, "hooks.json"),
|
|
359
|
+
JSON.stringify({
|
|
360
|
+
version: 1,
|
|
361
|
+
hooks: {
|
|
362
|
+
preToolUse: [
|
|
363
|
+
{ command: ".cursor/hooks/stigmer-approval.sh", timeout: 10, failClosed: true },
|
|
364
|
+
],
|
|
365
|
+
},
|
|
366
|
+
}),
|
|
367
|
+
"utf-8",
|
|
368
|
+
);
|
|
369
|
+
writeFileSync(join(legacyHooksDir, "stigmer-approval.sh"), "#!/bin/bash\nexit 0\n", "utf-8");
|
|
370
|
+
writeFileSync(join(legacyHooksDir, "stigmer-approval-state.json"), "{}", "utf-8");
|
|
371
|
+
writeFileSync(join(legacyHooksDir, "stigmer-denials.jsonl"), "", "utf-8");
|
|
372
|
+
|
|
373
|
+
const handle = await installHitlGate({
|
|
374
|
+
workspaceRoot, hitlDir, approvalState, runnerPid: process.pid,
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// The active gate is ONLY the current runner-owned hook (absolute, in the
|
|
378
|
+
// session dir). The stale relative entry that vetoed approved tools is gone.
|
|
379
|
+
const during = JSON.parse(readFileSync(join(cursorDir, "hooks.json"), "utf-8"));
|
|
380
|
+
expect(during.hooks.preToolUse.map((e: any) => e.command)).toEqual([
|
|
381
|
+
join(gateDir, "stigmer-approval.sh"),
|
|
382
|
+
]);
|
|
383
|
+
// The orphaned legacy files are removed; the now-empty hooks dir is gone too.
|
|
384
|
+
expect(existsSync(join(legacyHooksDir, "stigmer-approval.sh"))).toBe(false);
|
|
385
|
+
expect(existsSync(join(legacyHooksDir, "stigmer-approval-state.json"))).toBe(false);
|
|
386
|
+
expect(existsSync(join(legacyHooksDir, "stigmer-denials.jsonl"))).toBe(false);
|
|
387
|
+
expect(existsSync(legacyHooksDir)).toBe(false);
|
|
388
|
+
|
|
389
|
+
await removeHitlGate(handle);
|
|
390
|
+
// The hooks.json was entirely our own leftover → teardown deletes it, leaving
|
|
391
|
+
// the repo pristine (no husk, no stale entry waiting to re-pollute).
|
|
392
|
+
expect(existsSync(join(cursorDir, "hooks.json"))).toBe(false);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it("removes only stigmer-* files from .cursor/hooks, preserving a user's own hook script", async () => {
|
|
396
|
+
const { workspaceRoot, hitlDir } = dirs();
|
|
397
|
+
const legacyHooksDir = join(workspaceRoot, ".cursor", "hooks");
|
|
398
|
+
mkdirSync(legacyHooksDir, { recursive: true });
|
|
399
|
+
writeFileSync(join(legacyHooksDir, "stigmer-approval.sh"), "#!/bin/bash\nexit 0\n", "utf-8");
|
|
400
|
+
writeFileSync(join(legacyHooksDir, "user-custom.sh"), "#!/bin/bash\necho hi\n", "utf-8");
|
|
401
|
+
|
|
402
|
+
const handle = await installHitlGate({
|
|
403
|
+
workspaceRoot, hitlDir, approvalState, runnerPid: process.pid,
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Our leftover is gone; the user's script and the dir they own remain.
|
|
407
|
+
expect(existsSync(join(legacyHooksDir, "stigmer-approval.sh"))).toBe(false);
|
|
408
|
+
expect(readFileSync(join(legacyHooksDir, "user-custom.sh"), "utf-8")).toBe(
|
|
409
|
+
"#!/bin/bash\necho hi\n",
|
|
410
|
+
);
|
|
411
|
+
expect(existsSync(legacyHooksDir)).toBe(true);
|
|
412
|
+
|
|
413
|
+
await removeHitlGate(handle);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it("preserves a user's own .cursor/rules and rules dir after teardown", async () => {
|
|
417
|
+
const { workspaceRoot, hitlDir } = dirs();
|
|
418
|
+
const rulesDir = join(workspaceRoot, ".cursor", "rules");
|
|
419
|
+
mkdirSync(rulesDir, { recursive: true });
|
|
420
|
+
const userRule = join(rulesDir, "user-rule.mdc");
|
|
421
|
+
writeFileSync(userRule, "---\nalwaysApply: false\n---\nmine\n", "utf-8");
|
|
422
|
+
|
|
423
|
+
const handle = await installHitlGate({
|
|
424
|
+
workspaceRoot, hitlDir, approvalState, runnerPid: process.pid,
|
|
425
|
+
});
|
|
426
|
+
await removeHitlGate(handle);
|
|
427
|
+
|
|
428
|
+
// Our rule is gone; the user's rule and the dir they owned remain untouched.
|
|
429
|
+
expect(existsSync(join(rulesDir, "stigmer-tool-approval.mdc"))).toBe(false);
|
|
430
|
+
expect(readFileSync(userRule, "utf-8")).toBe("---\nalwaysApply: false\n---\nmine\n");
|
|
431
|
+
expect(existsSync(rulesDir)).toBe(true);
|
|
432
|
+
});
|
|
253
433
|
});
|
|
@@ -16,21 +16,20 @@
|
|
|
16
16
|
* a separate local policy applies.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import type
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
19
|
+
import { toolApprovalCategory, type ToolApprovalCategory } from "../../shared/tool-kind.js";
|
|
20
|
+
|
|
21
|
+
// The four-level MCP policy merge is harness-agnostic and lives in exactly one
|
|
22
|
+
// place (shared/approval-policy.ts) so the Cursor and native harnesses can never
|
|
23
|
+
// diverge. Re-exported here so existing Cursor-harness imports
|
|
24
|
+
// (`from "./approval-policy.js"`) keep working unchanged. This module now owns
|
|
25
|
+
// only the Cursor-specific built-in-tool gating helpers below.
|
|
26
|
+
export {
|
|
27
|
+
mergeApprovalPolicies,
|
|
28
|
+
lookupMcpToolPolicy,
|
|
29
|
+
resolveApprovalMessage,
|
|
30
|
+
POLICY_ENGINE_VERSION,
|
|
31
|
+
} from "../../shared/approval-policy.js";
|
|
32
|
+
export type { MergedToolPolicy, PolicySource } from "../../shared/approval-policy.js";
|
|
34
33
|
|
|
35
34
|
/**
|
|
36
35
|
* Built-in Cursor tools the preToolUse hook gates, named as the hook receives
|
|
@@ -53,36 +52,24 @@ const BUILT_IN_GATED: ReadonlySet<string> = new Set([
|
|
|
53
52
|
]);
|
|
54
53
|
|
|
55
54
|
/**
|
|
56
|
-
* Canonical approval category for a gated tool
|
|
57
|
-
* name via the shared {@link classifyTool}.
|
|
55
|
+
* Canonical approval category for a gated tool.
|
|
58
56
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* reinvocation regardless of which taxonomy named it. `FILE_WRITE` and
|
|
65
|
-
* `FILE_EDIT` both map to `write` because the Cursor hook reports every file
|
|
66
|
-
* mutation — create or edit — as `Write`.
|
|
57
|
+
* This is the cross-substrate identity, and it lives in ONE place:
|
|
58
|
+
* {@link toolApprovalCategory} in shared/tool-kind.ts (shared with the deep-agent
|
|
59
|
+
* gateway). The names below are kept as the Cursor harness's public surface
|
|
60
|
+
* (`approvalCategory` / `ApprovalCategory`) so existing imports do not churn, but
|
|
61
|
+
* they are exact aliases — there is no second implementation to drift.
|
|
67
62
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
63
|
+
* Why this matters here: the hook (`Write`/`Shell`/`Delete`) and the stream
|
|
64
|
+
* (`edit`/`shell`/`delete`) name the same operation differently, so neither raw
|
|
65
|
+
* name is a stable cross-layer identity. The category collapses both onto one
|
|
66
|
+
* value so the denial ledger (recorded by the hook) correlates to the streamed
|
|
67
|
+
* tool call (read by the runner) and an approval grant matches the agent's
|
|
68
|
+
* re-attempt on reinvocation regardless of which taxonomy named it.
|
|
70
69
|
*/
|
|
71
|
-
export type ApprovalCategory =
|
|
70
|
+
export type ApprovalCategory = ToolApprovalCategory;
|
|
72
71
|
|
|
73
|
-
export
|
|
74
|
-
switch (classifyTool(toolName)) {
|
|
75
|
-
case ToolKind.FILE_WRITE:
|
|
76
|
-
case ToolKind.FILE_EDIT:
|
|
77
|
-
return "write";
|
|
78
|
-
case ToolKind.FILE_DELETE:
|
|
79
|
-
return "delete";
|
|
80
|
-
case ToolKind.SHELL:
|
|
81
|
-
return "shell";
|
|
82
|
-
default:
|
|
83
|
-
return undefined;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
72
|
+
export const approvalCategory = toolApprovalCategory;
|
|
86
73
|
|
|
87
74
|
/**
|
|
88
75
|
* Human-readable approval-message template per canonical category. Keyed by
|
|
@@ -186,121 +173,3 @@ export function extractArgKey(args: Record<string, unknown> | undefined): string
|
|
|
186
173
|
return "";
|
|
187
174
|
}
|
|
188
175
|
|
|
189
|
-
/**
|
|
190
|
-
* Merge approval policies from all four levels into a single lookup map.
|
|
191
|
-
*
|
|
192
|
-
* Keys are the raw tool name (e.g., "apply_cloud_resource"). Each MCP server
|
|
193
|
-
* contributes its own set of policies, so the map is keyed by
|
|
194
|
-
* "serverSlug/toolName" to avoid collisions between servers.
|
|
195
|
-
*
|
|
196
|
-
* Policy chain (each level overrides the previous):
|
|
197
|
-
* 1. status.toolApprovals — presence means "requires approval"
|
|
198
|
-
* 2. spec.pinnedToolApprovals — presence means "requires approval" (overrides)
|
|
199
|
-
* 3. agent tool_approval_overrides — explicit boolean per tool
|
|
200
|
-
* 4. auto_approve_all — bypasses everything
|
|
201
|
-
*
|
|
202
|
-
* When auto_approve_all is true, the returned map is empty (no tool
|
|
203
|
-
* requires approval).
|
|
204
|
-
*/
|
|
205
|
-
export function mergeApprovalPolicies(
|
|
206
|
-
resolvedServers: ResolvedMcpServer[],
|
|
207
|
-
agentOverrides: ToolApprovalOverride[],
|
|
208
|
-
autoApproveAll: boolean,
|
|
209
|
-
): Map<string, MergedToolPolicy> {
|
|
210
|
-
const merged = new Map<string, MergedToolPolicy>();
|
|
211
|
-
|
|
212
|
-
if (autoApproveAll) return merged;
|
|
213
|
-
|
|
214
|
-
for (const server of resolvedServers) {
|
|
215
|
-
const serverPolicies = new Map<string, { requiresApproval: boolean; message: string }>();
|
|
216
|
-
|
|
217
|
-
// Layer 1: system-generated defaults (presence = requires approval)
|
|
218
|
-
for (const policy of server.toolApprovals) {
|
|
219
|
-
if (!policy.toolName) continue;
|
|
220
|
-
serverPolicies.set(policy.toolName, {
|
|
221
|
-
requiresApproval: true,
|
|
222
|
-
message: policy.message || `Execute tool: ${policy.toolName}`,
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Layer 2: manual overrides (presence = requires approval, overrides layer 1)
|
|
227
|
-
for (const pinned of server.pinnedToolApprovals) {
|
|
228
|
-
if (!pinned.toolName) continue;
|
|
229
|
-
serverPolicies.set(pinned.toolName, {
|
|
230
|
-
requiresApproval: true,
|
|
231
|
-
message: pinned.message || serverPolicies.get(pinned.toolName)?.message || `Execute tool: ${pinned.toolName}`,
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Layer 3: per-agent overrides (explicit boolean, can enable or disable)
|
|
236
|
-
for (const override of agentOverrides) {
|
|
237
|
-
if (!override.toolName) continue;
|
|
238
|
-
const existing = serverPolicies.get(override.toolName);
|
|
239
|
-
if (existing) {
|
|
240
|
-
existing.requiresApproval = override.requiresApproval;
|
|
241
|
-
if (override.message) {
|
|
242
|
-
existing.message = override.message;
|
|
243
|
-
}
|
|
244
|
-
} else if (override.requiresApproval) {
|
|
245
|
-
serverPolicies.set(override.toolName, {
|
|
246
|
-
requiresApproval: true,
|
|
247
|
-
message: override.message || `Execute tool: ${override.toolName}`,
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Write merged policies into the result map
|
|
253
|
-
for (const [toolName, policy] of serverPolicies) {
|
|
254
|
-
if (!policy.requiresApproval) continue;
|
|
255
|
-
const key = `${server.slug}/${toolName}`;
|
|
256
|
-
merged.set(key, {
|
|
257
|
-
toolName,
|
|
258
|
-
mcpServerSlug: server.slug,
|
|
259
|
-
requiresApproval: true,
|
|
260
|
-
approvalMessage: policy.message,
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return merged;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Look up whether an MCP tool requires approval.
|
|
270
|
-
*
|
|
271
|
-
* @param toolName - The actual MCP tool name (e.g., "apply_cloud_resource")
|
|
272
|
-
* @param mcpServerSlug - The MCP server slug (e.g., "planton")
|
|
273
|
-
* @param policies - The merged policy map from mergeApprovalPolicies()
|
|
274
|
-
* @returns The policy if approval is required, undefined if auto-approved
|
|
275
|
-
*/
|
|
276
|
-
export function lookupMcpToolPolicy(
|
|
277
|
-
toolName: string,
|
|
278
|
-
mcpServerSlug: string,
|
|
279
|
-
policies: Map<string, MergedToolPolicy>,
|
|
280
|
-
): MergedToolPolicy | undefined {
|
|
281
|
-
return policies.get(`${mcpServerSlug}/${toolName}`);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Resolve {{args.field}} placeholders in an approval message using the
|
|
286
|
-
* tool's actual arguments.
|
|
287
|
-
*
|
|
288
|
-
* Placeholder syntax matches the proto-documented format:
|
|
289
|
-
* - {{args.field_name}} — replaced with the argument value
|
|
290
|
-
* - {{tool_name}} — replaced with the tool name
|
|
291
|
-
* - Missing fields are replaced with "<unknown>"
|
|
292
|
-
*/
|
|
293
|
-
export function resolveApprovalMessage(
|
|
294
|
-
template: string,
|
|
295
|
-
toolName: string,
|
|
296
|
-
args: Record<string, unknown>,
|
|
297
|
-
): string {
|
|
298
|
-
return template
|
|
299
|
-
.replace(/\{\{tool_name\}\}/g, toolName)
|
|
300
|
-
.replace(/\{\{args\.(\w+)\}\}/g, (_match, field: string) => {
|
|
301
|
-
const value = args[field];
|
|
302
|
-
if (value === undefined || value === null) return "<unknown>";
|
|
303
|
-
if (typeof value === "string") return value;
|
|
304
|
-
return JSON.stringify(value);
|
|
305
|
-
});
|
|
306
|
-
}
|