@stigmer/runner 3.0.9-dev.20260616060535 → 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 +10 -0
- package/dist/activities/execute-deep-agent/hitl.js +5 -1
- 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 +370 -56
- 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 +11 -5
- package/dist/activities/execute-deep-agent/status-builder.js +6 -2
- 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 +6 -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 +2 -5
- 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 +12 -11
- 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 +15 -1
- package/src/activities/execute-deep-agent/index.ts +434 -64
- 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 +19 -7
- 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 +8 -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,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @regression file-hitl-phase0 — pins file-edit HITL fix #6 (see _projects/2026-06/20260630.01.file-change-hitl-redesign/tasks/T01_3_regression-manifest.md)
|
|
3
|
+
*
|
|
4
|
+
* Resume-grant identity round-trip — the H1 lock.
|
|
5
|
+
*
|
|
6
|
+
* The Cursor HITL model has no pause primitive: a resumed agent re-issues an
|
|
7
|
+
* approved tool with a BRAND-NEW call id, and the preToolUse hook allows it only
|
|
8
|
+
* if its canonical `(category, salient)` identity matches an approved grant. So
|
|
9
|
+
* the single safety-critical equality the whole resume round-trip rests on is:
|
|
10
|
+
*
|
|
11
|
+
* grant minted on resume == the identity the hook denied on the gated turn
|
|
12
|
+
*
|
|
13
|
+
* If those two ever drift, the approved tool is re-denied on every resume, the
|
|
14
|
+
* run loops in WAITING_FOR_APPROVAL, and the edit is never applied — the exact
|
|
15
|
+
* production failure class this suite exists to make impossible to reintroduce.
|
|
16
|
+
*
|
|
17
|
+
* Why this is a real risk worth a dedicated suite: the two identities are
|
|
18
|
+
* computed by DIFFERENT code paths over the SAME tool call —
|
|
19
|
+
* - the denial/overlay token via {@link toolCallIdentityToken} -> toolCallArgs
|
|
20
|
+
* (prefers the structured `tc.args`), and
|
|
21
|
+
* - the resume grant via {@link buildApprovalGrants} -> parseArgs(argsPreview)
|
|
22
|
+
* (the JSON-string preview only).
|
|
23
|
+
* They agree today only because both derive from the same stream `event.args`.
|
|
24
|
+
* This suite drives the REAL stream path ({@link buildToolCallProto}) so any
|
|
25
|
+
* future change that lets the dual path diverge fails here, loudly, per category.
|
|
26
|
+
*
|
|
27
|
+
* Deterministic; no Cursor API key, no bash. The hook-side half of the equality
|
|
28
|
+
* (the bash hook recomputes the same token and ALLOWS it) is locked separately
|
|
29
|
+
* in hook-script.test.ts; together they chain grant == overlay == hook token.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { describe, it, expect } from "vitest";
|
|
33
|
+
import { create } from "@bufbuild/protobuf";
|
|
34
|
+
import {
|
|
35
|
+
AgentMessageSchema,
|
|
36
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
37
|
+
import type { AgentMessage } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
38
|
+
import {
|
|
39
|
+
ApprovalAction,
|
|
40
|
+
MessageType,
|
|
41
|
+
ToolCallStatus,
|
|
42
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
43
|
+
import type { SDKMessage } from "@cursor/sdk";
|
|
44
|
+
|
|
45
|
+
import {
|
|
46
|
+
buildToolCallProto,
|
|
47
|
+
reconcileDeniedToolCalls,
|
|
48
|
+
toolCallIdentityToken,
|
|
49
|
+
} from "../message-translator.js";
|
|
50
|
+
import {
|
|
51
|
+
buildApprovalGrants,
|
|
52
|
+
buildApprovalState,
|
|
53
|
+
primaryToken,
|
|
54
|
+
reconstructAdjudicatedApprovals,
|
|
55
|
+
type ApprovalGrant,
|
|
56
|
+
type DeniedLedgerEntry,
|
|
57
|
+
} from "../approval-state.js";
|
|
58
|
+
|
|
59
|
+
/** A built-in (non-MCP) stream tool_call event, as `@cursor/sdk` emits it. */
|
|
60
|
+
function builtInEvent(
|
|
61
|
+
callId: string,
|
|
62
|
+
name: string,
|
|
63
|
+
args: Record<string, unknown>,
|
|
64
|
+
): Extract<SDKMessage, { type: "tool_call" }> {
|
|
65
|
+
return {
|
|
66
|
+
type: "tool_call",
|
|
67
|
+
agent_id: "agent-1",
|
|
68
|
+
run_id: "r1",
|
|
69
|
+
call_id: callId,
|
|
70
|
+
name,
|
|
71
|
+
status: "running",
|
|
72
|
+
args,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** An MCP stream tool_call event (name="mcp", real identity nested in args). */
|
|
77
|
+
function mcpEvent(
|
|
78
|
+
callId: string,
|
|
79
|
+
slug: string,
|
|
80
|
+
toolName: string,
|
|
81
|
+
innerArgs: Record<string, unknown>,
|
|
82
|
+
): Extract<SDKMessage, { type: "tool_call" }> {
|
|
83
|
+
return {
|
|
84
|
+
type: "tool_call",
|
|
85
|
+
agent_id: "agent-1",
|
|
86
|
+
run_id: "r1",
|
|
87
|
+
call_id: callId,
|
|
88
|
+
name: "mcp",
|
|
89
|
+
status: "running",
|
|
90
|
+
args: { providerIdentifier: slug, toolName, args: innerArgs },
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Drive the full gated-turn -> approve -> resume round-trip for one stream event
|
|
96
|
+
* and return the two identities that must be equal: the token the hook recorded
|
|
97
|
+
* when it denied the gated call, and the grant token a resume mints after the
|
|
98
|
+
* user approves it. Mirrors the activity's real sequence:
|
|
99
|
+
* buildToolCallProto (stream) -> deny overlay (reconcileDeniedToolCalls) ->
|
|
100
|
+
* persist APPROVE -> reconstructAdjudicatedApprovals -> buildApprovalGrants.
|
|
101
|
+
*/
|
|
102
|
+
async function roundTrip(event: Extract<SDKMessage, { type: "tool_call" }>): Promise<{
|
|
103
|
+
denialToken: string;
|
|
104
|
+
grantPrimaryToken: string;
|
|
105
|
+
resumeStateToken: string | undefined;
|
|
106
|
+
grant: ApprovalGrant;
|
|
107
|
+
}> {
|
|
108
|
+
const tc = buildToolCallProto(event);
|
|
109
|
+
|
|
110
|
+
// The gated turn: the hook denies the call, recording its identity token. The
|
|
111
|
+
// runner's overlay (reconcileDeniedToolCalls) flips the same call to
|
|
112
|
+
// WAITING_APPROVAL by matching that token — so the denial token IS the
|
|
113
|
+
// canonical identity the resume grant must reproduce. For a file edit this is
|
|
114
|
+
// the CONTENT-exact token (category, salient, digest); for shell/delete/MCP it
|
|
115
|
+
// is the coarse token.
|
|
116
|
+
const denialToken = toolCallIdentityToken(tc);
|
|
117
|
+
const msg = create(AgentMessageSchema, {
|
|
118
|
+
type: MessageType.MESSAGE_AI,
|
|
119
|
+
content: "working",
|
|
120
|
+
toolCalls: [tc],
|
|
121
|
+
});
|
|
122
|
+
const ledger: DeniedLedgerEntry[] = [{ toolName: tc.name, token: denialToken }];
|
|
123
|
+
const denied = await reconcileDeniedToolCalls([msg], ledger);
|
|
124
|
+
expect(denied, "the overlay must match the denial token and surface the gate").toHaveLength(1);
|
|
125
|
+
|
|
126
|
+
// The user approves; the backend persists the decision on the tool call.
|
|
127
|
+
tc.approvalAction = ApprovalAction.APPROVE;
|
|
128
|
+
|
|
129
|
+
// The resume: reconstruct the adjudicated approvals from the persisted
|
|
130
|
+
// transcript (pending_approvals is already cleared by this point) and mint the
|
|
131
|
+
// grants the next turn's hook reads. The content digest threads through so the
|
|
132
|
+
// grant reproduces the content-exact denial identity.
|
|
133
|
+
const messages: AgentMessage[] = [msg];
|
|
134
|
+
const { pendingApprovals, decisions, contentDigests } = reconstructAdjudicatedApprovals(messages);
|
|
135
|
+
const grants = buildApprovalGrants(pendingApprovals, decisions, contentDigests);
|
|
136
|
+
expect(grants, "an approved tool must yield exactly one grant").toHaveLength(1);
|
|
137
|
+
|
|
138
|
+
const state = buildApprovalState(new Map(), false, new Set(), grants);
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
denialToken,
|
|
142
|
+
grantPrimaryToken: primaryToken(grants[0].key, grants[0].salient, grants[0].contentDigest),
|
|
143
|
+
resumeStateToken: state.approvedGrantTokens[0],
|
|
144
|
+
grant: grants[0],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
describe("Cursor resume-grant identity round-trip (H1 lock)", () => {
|
|
149
|
+
// Each case is a real gated category with a representative salient. The shell
|
|
150
|
+
// heredoc case is the deliberate stress: a multi-line command with quotes,
|
|
151
|
+
// unicode, and embedded newlines — the bytes most likely to drift if any layer
|
|
152
|
+
// re-serializes args differently.
|
|
153
|
+
const cases: Array<{
|
|
154
|
+
label: string;
|
|
155
|
+
event: Extract<SDKMessage, { type: "tool_call" }>;
|
|
156
|
+
expectedKey: string;
|
|
157
|
+
expectedSalient: string;
|
|
158
|
+
}> = [
|
|
159
|
+
{
|
|
160
|
+
label: "write (stream `edit`, path salient)",
|
|
161
|
+
event: builtInEvent("c_write", "edit", { path: "/work/notes.md", content: "hello" }),
|
|
162
|
+
expectedKey: "write",
|
|
163
|
+
expectedSalient: "/work/notes.md",
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
label: "write (stream `StrReplace`, path salient)",
|
|
167
|
+
event: builtInEvent("c_replace", "StrReplace", {
|
|
168
|
+
path: "/work/app.ts",
|
|
169
|
+
old_string: "a",
|
|
170
|
+
new_string: "b",
|
|
171
|
+
}),
|
|
172
|
+
expectedKey: "write",
|
|
173
|
+
expectedSalient: "/work/app.ts",
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
label: "delete (path salient)",
|
|
177
|
+
event: builtInEvent("c_del", "delete", { path: "/work/old.tmp" }),
|
|
178
|
+
expectedKey: "delete",
|
|
179
|
+
expectedSalient: "/work/old.tmp",
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
label: "shell (short command salient)",
|
|
183
|
+
event: builtInEvent("c_sh", "shell", { command: "rm -rf build" }),
|
|
184
|
+
expectedKey: "shell",
|
|
185
|
+
expectedSalient: "rm -rf build",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
label: "shell (long heredoc with quotes/newlines/unicode)",
|
|
189
|
+
event: builtInEvent("c_heredoc", "shell", {
|
|
190
|
+
command: "cat > notes.md << 'EOF'\n# Notes — \"quoted\" café\nline 2\nEOF",
|
|
191
|
+
}),
|
|
192
|
+
expectedKey: "shell",
|
|
193
|
+
expectedSalient: "cat > notes.md << 'EOF'\n# Notes — \"quoted\" café\nline 2\nEOF",
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
label: "notebook (target_notebook salient)",
|
|
197
|
+
event: builtInEvent("c_nb", "EditNotebook", {
|
|
198
|
+
target_notebook: "/work/analysis.ipynb",
|
|
199
|
+
cell_idx: 0,
|
|
200
|
+
new_string: "x",
|
|
201
|
+
}),
|
|
202
|
+
expectedKey: "write",
|
|
203
|
+
expectedSalient: "/work/analysis.ipynb",
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
label: "MCP (name-scoped identity, salient empty)",
|
|
207
|
+
event: mcpEvent("c_mcp", "open-computer-use", "click", { x: 10, y: 20 }),
|
|
208
|
+
expectedKey: "click",
|
|
209
|
+
expectedSalient: "",
|
|
210
|
+
},
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
for (const { label, event, expectedKey, expectedSalient } of cases) {
|
|
214
|
+
it(`${label}: resume grant token == denial/overlay token`, async () => {
|
|
215
|
+
const { denialToken, grantPrimaryToken, resumeStateToken, grant } = await roundTrip(event);
|
|
216
|
+
|
|
217
|
+
// The decisive equality: what the hook denied is exactly what the resume
|
|
218
|
+
// re-grants, so the re-issued call is ALLOWED, not re-gated.
|
|
219
|
+
expect(grantPrimaryToken, `${label}: grant must reproduce the denial identity`).toBe(denialToken);
|
|
220
|
+
|
|
221
|
+
// The grant carries the expected canonical identity (guards against a
|
|
222
|
+
// silently-empty salient sneaking past the equality on a shared bug).
|
|
223
|
+
expect(grant.key).toBe(expectedKey);
|
|
224
|
+
expect(grant.salient).toBe(expectedSalient);
|
|
225
|
+
|
|
226
|
+
// The state file the hook actually reads carries that same token verbatim.
|
|
227
|
+
expect(resumeStateToken, `${label}: state file must carry the matching grant token`).toBe(
|
|
228
|
+
denialToken,
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
it("sibling isolation: two DIFFERENT edits to the SAME file get distinct identities", () => {
|
|
234
|
+
// The exact bug this change fixes: approving the rename must NOT authorize
|
|
235
|
+
// the TODO edit. They share a path (same coarse token) but differ in content,
|
|
236
|
+
// so the content-exact identity distinguishes them.
|
|
237
|
+
const rename = buildToolCallProto(
|
|
238
|
+
builtInEvent("c1", "edit", { path: "/work/notes.md", old_string: "Planton Cloud", new_string: "Planton" }),
|
|
239
|
+
);
|
|
240
|
+
const todo = buildToolCallProto(
|
|
241
|
+
builtInEvent("c2", "edit", { path: "/work/notes.md", old_string: "", new_string: "## TODO\n" }),
|
|
242
|
+
);
|
|
243
|
+
const renameToken = toolCallIdentityToken(rename);
|
|
244
|
+
const todoToken = toolCallIdentityToken(todo);
|
|
245
|
+
|
|
246
|
+
// Distinct identities -> a grant for the rename never matches the TODO.
|
|
247
|
+
expect(renameToken).not.toBe(todoToken);
|
|
248
|
+
// Both are content tokens (3-part), not the bare coarse token they'd collapse
|
|
249
|
+
// to under the old (category, salient) scheme.
|
|
250
|
+
expect(renameToken).not.toBe(
|
|
251
|
+
Buffer.from("write\n/work/notes.md", "utf-8").toString("base64"),
|
|
252
|
+
);
|
|
253
|
+
// Re-issuing the SAME rename content reproduces its identity (identical
|
|
254
|
+
// re-attempt is allowed; a drifted one re-gates).
|
|
255
|
+
const renameAgain = buildToolCallProto(
|
|
256
|
+
builtInEvent("c3", "edit", { path: "/work/notes.md", old_string: "Planton Cloud", new_string: "Planton" }),
|
|
257
|
+
);
|
|
258
|
+
expect(toolCallIdentityToken(renameAgain)).toBe(renameToken);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
/** A decided-but-not-yet-resumed gated call, exactly as the backend persists it. */
|
|
262
|
+
function decidedToolCall(
|
|
263
|
+
event: Extract<SDKMessage, { type: "tool_call" }>,
|
|
264
|
+
action: ApprovalAction,
|
|
265
|
+
): AgentMessage {
|
|
266
|
+
const tc = buildToolCallProto(event);
|
|
267
|
+
tc.status = ToolCallStatus.TOOL_CALL_WAITING_APPROVAL;
|
|
268
|
+
tc.requiresApproval = true;
|
|
269
|
+
tc.approvalAction = action;
|
|
270
|
+
return create(AgentMessageSchema, {
|
|
271
|
+
type: MessageType.MESSAGE_AI,
|
|
272
|
+
content: "",
|
|
273
|
+
toolCalls: [tc],
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
it("REJECT and SKIP mint no grant (a denied-again tool must never be auto-allowed)", () => {
|
|
278
|
+
for (const decision of [ApprovalAction.REJECT, ApprovalAction.SKIP]) {
|
|
279
|
+
const msg = decidedToolCall(builtInEvent("c1", "edit", { path: "/work/x.txt" }), decision);
|
|
280
|
+
const { pendingApprovals, decisions } = reconstructAdjudicatedApprovals([msg]);
|
|
281
|
+
expect(pendingApprovals, "the decided call must be reconstructed").toHaveLength(1);
|
|
282
|
+
expect(buildApprovalGrants(pendingApprovals, decisions)).toEqual([]);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("APPROVE_ALL also mints the clicked tool's grant (the lease covers the class separately)", () => {
|
|
287
|
+
const msg = decidedToolCall(builtInEvent("c1", "shell", { command: "make build" }), ApprovalAction.APPROVE_ALL);
|
|
288
|
+
const { pendingApprovals, decisions } = reconstructAdjudicatedApprovals([msg]);
|
|
289
|
+
const grants = buildApprovalGrants(pendingApprovals, decisions);
|
|
290
|
+
expect(grants).toEqual([{ toolName: "shell", mcpServerSlug: "", key: "shell", salient: "make build", contentDigest: "", sourceToolCallId: "c1" }]);
|
|
291
|
+
});
|
|
292
|
+
});
|
|
@@ -14,7 +14,7 @@ import { PendingApprovalSchema } from "@stigmer/protos/ai/stigmer/agentic/agente
|
|
|
14
14
|
|
|
15
15
|
import { buildPrompt } from "../index.js";
|
|
16
16
|
import type { BuildPromptInput } from "../index.js";
|
|
17
|
-
import { buildReinvocationPrompt, formatToolApprovalProtocol } from "../prompt-builder.js";
|
|
17
|
+
import { buildReinvocationPrompt, formatToolApprovalProtocol, buildToolApprovalRuleFile } from "../prompt-builder.js";
|
|
18
18
|
import type { AgentResolution, AgentResolutionReason } from "../session-lifecycle.js";
|
|
19
19
|
|
|
20
20
|
const USER_MESSAGE = "What was the secret token I told you?";
|
|
@@ -151,12 +151,36 @@ describe("formatToolApprovalProtocol", () => {
|
|
|
151
151
|
expect(section).toContain("Invoke the tool and let the platform");
|
|
152
152
|
});
|
|
153
153
|
|
|
154
|
+
it("reframes a denied/blocked tool as the gate working, not a broken environment", () => {
|
|
155
|
+
const section = formatToolApprovalProtocol().toLowerCase();
|
|
156
|
+
// The load-bearing fix for the leaky Cursor deny path: the model must not
|
|
157
|
+
// read "blocked by a hook" as an error or tell the user to fix settings.
|
|
158
|
+
expect(section).toContain("blocked by a hook");
|
|
159
|
+
expect(section).toContain("not an error");
|
|
160
|
+
expect(section).toContain("cursor settings");
|
|
161
|
+
});
|
|
162
|
+
|
|
154
163
|
it("contains no characters that would break the prompt assembly", () => {
|
|
155
164
|
// Sanity: the protocol is plain prose joined into the user-message prompt.
|
|
156
165
|
expect(formatToolApprovalProtocol()).not.toContain("undefined");
|
|
157
166
|
});
|
|
158
167
|
});
|
|
159
168
|
|
|
169
|
+
describe("buildToolApprovalRuleFile", () => {
|
|
170
|
+
it("emits an always-applied .cursor/rules .mdc carrying the same protocol", () => {
|
|
171
|
+
const rule = buildToolApprovalRuleFile();
|
|
172
|
+
// Valid .mdc frontmatter that Cursor injects on every turn.
|
|
173
|
+
expect(rule.startsWith("---\n")).toBe(true);
|
|
174
|
+
expect(rule).toContain("alwaysApply: true");
|
|
175
|
+
expect(rule).toContain("# Tool approval protocol");
|
|
176
|
+
// Same single-sourced guidance as the system-prompt copy.
|
|
177
|
+
expect(rule.toLowerCase()).toContain("blocked by a hook");
|
|
178
|
+
expect(rule.toLowerCase()).toContain("not an error");
|
|
179
|
+
expect(rule.toLowerCase()).toContain("never tell the user to change cursor settings");
|
|
180
|
+
expect(rule).not.toContain("undefined");
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
160
184
|
describe("buildReinvocationPrompt", () => {
|
|
161
185
|
function pending(toolCallId: string, message: string) {
|
|
162
186
|
return create(PendingApprovalSchema, { toolCallId, toolName: "Write", message });
|
|
@@ -182,4 +206,47 @@ describe("buildReinvocationPrompt", () => {
|
|
|
182
206
|
expect(prompt).toContain("SKIPPED");
|
|
183
207
|
expect(prompt).toContain("Continue the rest of the task");
|
|
184
208
|
});
|
|
209
|
+
|
|
210
|
+
it("describes a runner-applied approval as ALREADY applied, not as one to carry out", () => {
|
|
211
|
+
// tc-1 was exact-applied by the runner (a whole-file write); tc-2 (a shell
|
|
212
|
+
// command) was approved but stays on the model's carry-out path.
|
|
213
|
+
const decisions = new Map<string, ApprovalAction>([
|
|
214
|
+
["tc-1", ApprovalAction.APPROVE],
|
|
215
|
+
["tc-2", ApprovalAction.APPROVE],
|
|
216
|
+
]);
|
|
217
|
+
const prompt = buildReinvocationPrompt(
|
|
218
|
+
[pending("tc-1", "Write file: a.txt"), pending("tc-2", "Run command: ls")],
|
|
219
|
+
decisions,
|
|
220
|
+
new Set(["tc-1"]),
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
// The applied write is announced as done — and explicitly NOT redo-able.
|
|
224
|
+
expect(prompt).toContain("ALREADY applied");
|
|
225
|
+
expect(prompt).toMatch(/do NOT redo/i);
|
|
226
|
+
// Both actions still appear by their human description.
|
|
227
|
+
expect(prompt).toContain("Write file: a.txt");
|
|
228
|
+
expect(prompt).toContain("Run command: ls");
|
|
229
|
+
// The applied write must NOT be in the "Carry them out now" block; only the
|
|
230
|
+
// shell command is. Assert by position: the already-applied block precedes
|
|
231
|
+
// the carry-out block, and the write is in the former.
|
|
232
|
+
const carryIdx = prompt.indexOf("Carry them out now");
|
|
233
|
+
const appliedIdx = prompt.indexOf("ALREADY applied");
|
|
234
|
+
expect(appliedIdx).toBeGreaterThanOrEqual(0);
|
|
235
|
+
expect(carryIdx).toBeGreaterThan(appliedIdx);
|
|
236
|
+
expect(prompt.slice(carryIdx)).toContain("Run command: ls");
|
|
237
|
+
expect(prompt.slice(carryIdx)).not.toContain("Write file: a.txt");
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("treats APPROVE_ALL like APPROVE for an already-applied write", () => {
|
|
241
|
+
const decisions = new Map<string, ApprovalAction>([
|
|
242
|
+
["tc-1", ApprovalAction.APPROVE_ALL],
|
|
243
|
+
]);
|
|
244
|
+
const prompt = buildReinvocationPrompt(
|
|
245
|
+
[pending("tc-1", "Write file: a.txt")],
|
|
246
|
+
decisions,
|
|
247
|
+
new Set(["tc-1"]),
|
|
248
|
+
);
|
|
249
|
+
expect(prompt).toContain("ALREADY applied");
|
|
250
|
+
expect(prompt).not.toContain("Carry them out now");
|
|
251
|
+
});
|
|
185
252
|
});
|