@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
|
@@ -16,9 +16,31 @@
|
|
|
16
16
|
* pausing again.
|
|
17
17
|
*
|
|
18
18
|
* Platform tool defaults: DeepAgents JS backend tools (read, write,
|
|
19
|
-
* edit, execute, etc.) are not covered by MCP policy chains.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* edit, execute, etc.) are not covered by MCP policy chains. Built-in tools
|
|
20
|
+
* are classified through the shared {@link toolApprovalCategory} — the single
|
|
21
|
+
* source of truth, shared with the Cursor deny-oracle hook — so read-only tools
|
|
22
|
+
* are auto-approved and every mutating tool (write/edit/delete/shell) is gated
|
|
23
|
+
* fail-CLOSED, by category rather than by a hand-maintained name list. An
|
|
24
|
+
* unrecognized mutating built-in (e.g. `bash`, `overwrite_file`) is therefore
|
|
25
|
+
* gated by what it does, not by whether someone remembered to list it.
|
|
26
|
+
*
|
|
27
|
+
* Gateway invariant (Phase 2): this middleware IS the in-process execution
|
|
28
|
+
* gateway for the deep-agent harness — `handler(request)` is the side effect. A
|
|
29
|
+
* side effect runs only with a backing authorization: either (a) the tool was
|
|
30
|
+
* auto-approved (policy/classifier cleared it, or auto-approve-all disabled the
|
|
31
|
+
* whole gate), or (b) the user explicitly approved THIS interrupted call. Every
|
|
32
|
+
* other outcome — skip, reject, or an unrecognized decision — returns a
|
|
33
|
+
* ToolMessage WITHOUT executing. There is no path from a model proposal to a
|
|
34
|
+
* side effect that skips an authorization.
|
|
35
|
+
*
|
|
36
|
+
* Shadow ExecutionReceipt: when the gateway lets a side effect through it emits a
|
|
37
|
+
* structured, non-persisted receipt (a `[hitl-gateway] receipt …` log carrying
|
|
38
|
+
* the action's HMAC fingerprint and the authorization source). This is an audit
|
|
39
|
+
* + uniformity signal only — no proto, no storage — mirroring the Phase-1 shadow
|
|
40
|
+
* discipline. On the deep-agent normal path the fingerprint match is guaranteed
|
|
41
|
+
* by LangGraph checkpoint replay (the resumed action equals the approved one), so
|
|
42
|
+
* the receipt is defense-in-depth here; the fingerprint earns real enforcement
|
|
43
|
+
* teeth in the out-of-process Cursor substrate.
|
|
22
44
|
*/
|
|
23
45
|
|
|
24
46
|
import { ToolMessage } from "@langchain/core/messages";
|
|
@@ -26,13 +48,79 @@ import { interrupt } from "@langchain/langgraph";
|
|
|
26
48
|
import type { StigmerMiddleware, ToolCallRequest } from "./types.js";
|
|
27
49
|
import {
|
|
28
50
|
type MergedToolPolicy,
|
|
51
|
+
type PolicySource,
|
|
52
|
+
POLICY_ENGINE_VERSION,
|
|
29
53
|
resolveApprovalMessage,
|
|
30
54
|
} from "../shared/approval-policy.js";
|
|
55
|
+
import { toolApprovalCategory, type ToolApprovalCategory } from "../shared/tool-kind.js";
|
|
56
|
+
import { extractFilePath } from "../shared/file-tools.js";
|
|
57
|
+
import { isSecretLikePath } from "../shared/filereview/secret-paths.js";
|
|
58
|
+
import {
|
|
59
|
+
computeApprovalFingerprint,
|
|
60
|
+
type FingerprintKey,
|
|
61
|
+
} from "../shared/approval-fingerprint.js";
|
|
31
62
|
|
|
32
63
|
export interface ApprovalGateConfig {
|
|
33
64
|
readonly policies: ReadonlyMap<string, MergedToolPolicy>;
|
|
34
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Built-in approval categories with a run-lifetime lease (the scoped successor
|
|
67
|
+
* to auto-approve-all; see ActiveLeases). A built-in whose category is leased
|
|
68
|
+
* is auto-approved for the rest of the run. MCP-server leases are NOT carried
|
|
69
|
+
* here — they are applied upstream by dropping the server's tools from
|
|
70
|
+
* `policies` (mergeApprovalPolicies), which clears them for this gate too.
|
|
71
|
+
* Absent/empty means no built-in lease is active. Inherited verbatim by
|
|
72
|
+
* sub-agents along with the rest of this config.
|
|
73
|
+
*/
|
|
74
|
+
readonly leasedCategories?: ReadonlySet<ToolApprovalCategory>;
|
|
35
75
|
readonly toolServerMap: ReadonlyMap<string, string>;
|
|
76
|
+
/**
|
|
77
|
+
* Per-execution HMAC key for the shadow receipt's action fingerprint. Derived
|
|
78
|
+
* from the runner master secret + execution_id (see fingerprint-secret.ts).
|
|
79
|
+
* Optional: when absent (unit tests, no-secret paths) the receipt is emitted
|
|
80
|
+
* without a fingerprint rather than failing.
|
|
81
|
+
*/
|
|
82
|
+
readonly fingerprintKey?: FingerprintKey;
|
|
83
|
+
/** Execution id, carried into the shadow receipt for audit correlation. */
|
|
84
|
+
readonly executionId?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Apply-then-review capture mode (git workspaces). When true, a built-in
|
|
87
|
+
* `write`/`delete` whose target path is capturable (git-tracked — see
|
|
88
|
+
* {@link isCapturablePath}) FLOWS instead of interrupting: the edit is reviewed
|
|
89
|
+
* post-hoc as a captured `FileChangeSet`, not gated before it runs. A gitignored
|
|
90
|
+
* path is NOT capturable, so it stays gated (the git substrate cannot capture or
|
|
91
|
+
* revert it) — the exact twin of the Cursor hook's `__stigmer_is_gitignored`
|
|
92
|
+
* allow-branch. `shell` and MCP tools are never bypassed by this flag. Off (the
|
|
93
|
+
* default) keeps the classic true-pause gate for every mutation.
|
|
94
|
+
*/
|
|
95
|
+
readonly fileCaptureMode?: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Capturability predicate for {@link fileCaptureMode}: given a tool's raw target
|
|
98
|
+
* path (as the model wrote it), resolves true when the path would be captured by
|
|
99
|
+
* the git snapshot (tracked / not gitignored). Injected by setup so the gate
|
|
100
|
+
* stays pure and testable, and so the harness owns path normalization (the
|
|
101
|
+
* deep-agent virtual-root path mapping). Absent ⇒ nothing is bypassed (safe).
|
|
102
|
+
*/
|
|
103
|
+
readonly isCapturablePath?: (rawPath: string) => Promise<boolean>;
|
|
104
|
+
/**
|
|
105
|
+
* Route gitignored `write`/`edit` edits into CAS capture (apply-then-review)
|
|
106
|
+
* instead of the interrupt gate, applying the DD-E secret gate. When off, a
|
|
107
|
+
* gitignored path stays on the interrupt gate exactly as before.
|
|
108
|
+
*
|
|
109
|
+
* TRUE ONLY FOR THE PARENT GATE. Sub-agents build their own plain filesystem
|
|
110
|
+
* backends, which the CAS observer does not wrap, so flowing their gitignored
|
|
111
|
+
* edits would apply unobserved, unreviewable bytes. `buildSubAgentMiddleware`
|
|
112
|
+
* therefore forces this to false for sub-agent gates — it must NOT be inherited
|
|
113
|
+
* as true. (Sub-agent git-tracked edits are still captured by the turn-boundary
|
|
114
|
+
* git diff, which is backend-agnostic.)
|
|
115
|
+
*/
|
|
116
|
+
readonly captureIgnored?: boolean;
|
|
117
|
+
/**
|
|
118
|
+
* Sink for a gitignored path hard-blocked as secret-like (DD-E): the write is
|
|
119
|
+
* never applied and never captured, and the turn boundary reads these paths to
|
|
120
|
+
* author a `DIFF_UNREVIEWABLE` change entry (path only — the name is not the
|
|
121
|
+
* secret; the CONTENT never leaves the workspace). Absent ⇒ nothing recorded.
|
|
122
|
+
*/
|
|
123
|
+
readonly recordBlockedSecret?: (rawPath: string) => void;
|
|
36
124
|
}
|
|
37
125
|
|
|
38
126
|
interface ApprovalDecision {
|
|
@@ -40,36 +128,25 @@ interface ApprovalDecision {
|
|
|
40
128
|
readonly comment?: string;
|
|
41
129
|
}
|
|
42
130
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
["write", "Write file: {{args.path}}"],
|
|
53
|
-
["write_file", "Write file: {{args.path}}"],
|
|
54
|
-
["edit", "Edit file: {{args.path}}"],
|
|
55
|
-
["edit_file", "Edit file: {{args.path}}"],
|
|
56
|
-
["create", "Create file: {{args.path}}"],
|
|
57
|
-
["create_file", "Create file: {{args.path}}"],
|
|
58
|
-
["delete", "Delete: {{args.path}}"],
|
|
59
|
-
["delete_file", "Delete file: {{args.path}}"],
|
|
60
|
-
["execute", "Execute command: {{args.command}}"],
|
|
61
|
-
["shell", "Execute shell command: {{args.command}}"],
|
|
62
|
-
["str_replace_editor", "Edit file: {{args.path}}"],
|
|
63
|
-
]);
|
|
131
|
+
// Approval-message template per mutating category. Keyed by category (not raw
|
|
132
|
+
// tool name) so every alias of a mutation renders one message; placeholders
|
|
133
|
+
// resolve against the deep-agent stream arg shape (`path`/`command`). Mirrors
|
|
134
|
+
// the Cursor side's CATEGORY_APPROVAL_MESSAGE.
|
|
135
|
+
const CATEGORY_APPROVAL_MESSAGE: Record<ToolApprovalCategory, string> = {
|
|
136
|
+
write: "Write file: {{args.path}}",
|
|
137
|
+
delete: "Delete: {{args.path}}",
|
|
138
|
+
shell: "Execute command: {{args.command}}",
|
|
139
|
+
};
|
|
64
140
|
|
|
65
141
|
export function createApprovalGateMiddleware(
|
|
66
142
|
config: ApprovalGateConfig,
|
|
67
143
|
): StigmerMiddleware {
|
|
68
|
-
const { policies,
|
|
144
|
+
const { policies, toolServerMap } = config;
|
|
145
|
+
const leasedCategories = config.leasedCategories ?? EMPTY_CATEGORY_SET;
|
|
69
146
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
147
|
+
// No global-bypass early return: a pre-armed spec.auto_approve_all means the
|
|
148
|
+
// gate is never even installed (setup.ts builds this config only when not
|
|
149
|
+
// global). Scoped leases keep the gate active so non-leased actions still gate.
|
|
73
150
|
|
|
74
151
|
return {
|
|
75
152
|
name: "ApprovalGateMiddleware",
|
|
@@ -78,15 +155,65 @@ export function createApprovalGateMiddleware(
|
|
|
78
155
|
const { toolCall } = request;
|
|
79
156
|
const toolName = toolCall.name;
|
|
80
157
|
const serverSlug = toolServerMap.get(toolName) ?? "";
|
|
158
|
+
const category = toolApprovalCategory(toolName);
|
|
159
|
+
|
|
160
|
+
// Capture mode (git workspaces): a git-tracked built-in file mutation flows
|
|
161
|
+
// during the turn and is reviewed post-hoc via the file_review ledger (the
|
|
162
|
+
// apply-then-review model). shell and MCP tools (serverSlug present) are
|
|
163
|
+
// never bypassed here.
|
|
164
|
+
if (
|
|
165
|
+
config.fileCaptureMode &&
|
|
166
|
+
config.isCapturablePath &&
|
|
167
|
+
!serverSlug &&
|
|
168
|
+
(category === "write" || category === "delete")
|
|
169
|
+
) {
|
|
170
|
+
const path = extractFilePath(toolCall.args);
|
|
171
|
+
if (path !== null) {
|
|
172
|
+
if (await config.isCapturablePath(path)) {
|
|
173
|
+
// Git-tracked: flows and is reviewed post-hoc by the git substrate.
|
|
174
|
+
emitExecutionReceipt(config, toolCall, serverSlug, category, "auto_approve", "file_capture");
|
|
175
|
+
return await handler(request);
|
|
176
|
+
}
|
|
177
|
+
// Gitignored path. Only the PARENT gate (captureIgnored) routes it into
|
|
178
|
+
// CAS capture; sub-agent gates fall through to the interrupt gate below,
|
|
179
|
+
// exactly as before (their backends are not CAS-observed).
|
|
180
|
+
if (config.captureIgnored) {
|
|
181
|
+
if (isSecretLikePath(path)) {
|
|
182
|
+
// DD-E fail-closed: a secret-like gitignored edit is NEVER applied
|
|
183
|
+
// and NEVER captured. Record it so the turn boundary authors a
|
|
184
|
+
// DIFF_UNREVIEWABLE entry (blocking approval); nothing is written.
|
|
185
|
+
config.recordBlockedSecret?.(path);
|
|
186
|
+
return new ToolMessage({
|
|
187
|
+
content:
|
|
188
|
+
`Tool '${toolName}' was blocked for security: '${path}' matches a ` +
|
|
189
|
+
`secret-like path Stigmer will not capture for review. Nothing was written.`,
|
|
190
|
+
tool_call_id: toolCall.id,
|
|
191
|
+
name: toolName,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
if (category === "write") {
|
|
195
|
+
// Non-secret gitignored write/edit: flows (apply-then-review). The
|
|
196
|
+
// CAS observer already holds its before-bytes and the turn boundary
|
|
197
|
+
// captures it into CAS. (A gitignored delete has no backend capture
|
|
198
|
+
// path, so it falls through to the interrupt gate.)
|
|
199
|
+
emitExecutionReceipt(config, toolCall, serverSlug, category, "auto_approve", "file_capture");
|
|
200
|
+
return await handler(request);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
81
205
|
|
|
82
206
|
const requirement = resolveToolApproval(
|
|
83
207
|
toolName,
|
|
84
208
|
serverSlug,
|
|
85
209
|
toolCall.args,
|
|
86
210
|
policies,
|
|
211
|
+
leasedCategories,
|
|
87
212
|
);
|
|
88
213
|
|
|
89
214
|
if (!requirement.requiresApproval) {
|
|
215
|
+
// Backing authorization: the classifier/policy auto-approved this tool.
|
|
216
|
+
emitExecutionReceipt(config, toolCall, serverSlug, category, "auto_approve", requirement.source);
|
|
90
217
|
return await handler(request);
|
|
91
218
|
}
|
|
92
219
|
|
|
@@ -95,6 +222,10 @@ export function createApprovalGateMiddleware(
|
|
|
95
222
|
tool_name: toolName,
|
|
96
223
|
mcp_server_slug: serverSlug,
|
|
97
224
|
message: requirement.message,
|
|
225
|
+
// Carry the gate's provenance verdict through the interrupt so the
|
|
226
|
+
// reinvocation that seeds the WAITING_APPROVAL tool call (index.ts) can
|
|
227
|
+
// persist ToolCall.approval_policy_source without re-deriving it.
|
|
228
|
+
policy_source: requirement.source,
|
|
98
229
|
};
|
|
99
230
|
|
|
100
231
|
const response = interrupt(approvalRequest) as ApprovalDecision;
|
|
@@ -106,6 +237,8 @@ export function createApprovalGateMiddleware(
|
|
|
106
237
|
).toString().toLowerCase();
|
|
107
238
|
|
|
108
239
|
if (action === "approve") {
|
|
240
|
+
// Backing authorization: the user approved THIS interrupted call.
|
|
241
|
+
emitExecutionReceipt(config, toolCall, serverSlug, category, "approval", requirement.source);
|
|
109
242
|
return await handler(request);
|
|
110
243
|
}
|
|
111
244
|
|
|
@@ -143,6 +276,8 @@ export function createApprovalGateMiddleware(
|
|
|
143
276
|
interface ApprovalRequirement {
|
|
144
277
|
readonly requiresApproval: boolean;
|
|
145
278
|
readonly message: string;
|
|
279
|
+
/** Which policy layer determined this verdict — stamped on the shadow receipt. */
|
|
280
|
+
readonly source: PolicySource;
|
|
146
281
|
}
|
|
147
282
|
|
|
148
283
|
function resolveToolApproval(
|
|
@@ -150,30 +285,104 @@ function resolveToolApproval(
|
|
|
150
285
|
serverSlug: string,
|
|
151
286
|
args: Record<string, unknown>,
|
|
152
287
|
policies: ReadonlyMap<string, MergedToolPolicy>,
|
|
288
|
+
leasedCategories: ReadonlySet<ToolApprovalCategory>,
|
|
153
289
|
): ApprovalRequirement {
|
|
154
290
|
if (serverSlug) {
|
|
291
|
+
// MCP tools stay governed by the connect-flow classifier + four-level policy
|
|
292
|
+
// chain. The policy map carries only the tools that REQUIRE approval, so an
|
|
293
|
+
// absent entry means the classifier already auto-approved it — fail-OPEN is
|
|
294
|
+
// correct here (a fail-closed default would re-gate everything the classifier
|
|
295
|
+
// cleared). This is deliberately NOT changed by the built-in fail-closed flip.
|
|
296
|
+
//
|
|
297
|
+
// A server-scoped lease surfaces here as an absent entry too: the leased
|
|
298
|
+
// server's tools are dropped from `policies` upstream (mergeApprovalPolicies),
|
|
299
|
+
// so a lease-cleared MCP tool takes this same auto-approve path. (Its shadow
|
|
300
|
+
// receipt therefore reads classifier_default rather than approval_lease; a
|
|
301
|
+
// faithful per-server lease provenance would need the lease set threaded here
|
|
302
|
+
// and is deferred with the rest of the persisted-receipt work.)
|
|
155
303
|
const key = `${serverSlug}/${toolName}`;
|
|
156
304
|
const policy = policies.get(key);
|
|
157
305
|
if (policy) {
|
|
158
306
|
return {
|
|
159
307
|
requiresApproval: policy.requiresApproval,
|
|
160
308
|
message: resolveApprovalMessage(policy.approvalMessage, toolName, args),
|
|
309
|
+
source: policy.source,
|
|
161
310
|
};
|
|
162
311
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (SAFE_PLATFORM_TOOLS.has(toolName)) {
|
|
167
|
-
return { requiresApproval: false, message: "" };
|
|
312
|
+
// Absent = cleared by the MCP four-level chain (classifier base) or by a
|
|
313
|
+
// server-scoped lease (dropped upstream).
|
|
314
|
+
return { requiresApproval: false, message: "", source: "classifier_default" };
|
|
168
315
|
}
|
|
169
316
|
|
|
170
|
-
|
|
171
|
-
|
|
317
|
+
// Built-in/platform tool: gate exactly the mutating categories via the shared
|
|
318
|
+
// classifier. Fail-CLOSED for the mutating set — any built-in classifyTool
|
|
319
|
+
// deems write/edit/delete/shell requires approval. Read-only and unclassified
|
|
320
|
+
// built-ins are not mutating, so they remain fail-open.
|
|
321
|
+
const category = toolApprovalCategory(toolName);
|
|
322
|
+
if (category) {
|
|
323
|
+
// Run-lifetime category lease: the user chose "approve all <category>"
|
|
324
|
+
// earlier in this run, so every built-in of that category is auto-approved
|
|
325
|
+
// for the rest of the run (the scoped successor to auto-approve-all).
|
|
326
|
+
if (leasedCategories.has(category)) {
|
|
327
|
+
return { requiresApproval: false, message: "", source: "approval_lease" };
|
|
328
|
+
}
|
|
172
329
|
return {
|
|
173
330
|
requiresApproval: true,
|
|
174
|
-
message: resolveApprovalMessage(
|
|
331
|
+
message: resolveApprovalMessage(CATEGORY_APPROVAL_MESSAGE[category], toolName, args),
|
|
332
|
+
source: "builtin_category",
|
|
175
333
|
};
|
|
176
334
|
}
|
|
177
335
|
|
|
178
|
-
return { requiresApproval: false, message: "" };
|
|
336
|
+
return { requiresApproval: false, message: "", source: "builtin_category" };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** Shared empty set so a config without leases allocates nothing per call. */
|
|
340
|
+
const EMPTY_CATEGORY_SET: ReadonlySet<ToolApprovalCategory> = new Set();
|
|
341
|
+
|
|
342
|
+
type AuthorizationSource = "auto_approve" | "approval";
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Emit the shadow ExecutionReceipt when the gateway authorizes a side effect.
|
|
346
|
+
*
|
|
347
|
+
* Scope: only side-effecting actions are recorded — a mutating built-in (a
|
|
348
|
+
* non-empty {@link ToolApprovalCategory}) or any MCP tool (`serverSlug` present).
|
|
349
|
+
* Read-only built-ins are not side effects, so recording them would only dilute
|
|
350
|
+
* the signal. The receipt is a structured log carrying the action's HMAC
|
|
351
|
+
* fingerprint and the authorization source; it is never persisted and crosses no
|
|
352
|
+
* wire (no proto). The fingerprint is omitted (empty) when no per-execution key
|
|
353
|
+
* was supplied (tests / no-secret paths).
|
|
354
|
+
*/
|
|
355
|
+
function emitExecutionReceipt(
|
|
356
|
+
config: ApprovalGateConfig,
|
|
357
|
+
toolCall: { id: string; name: string; args: Record<string, unknown> },
|
|
358
|
+
serverSlug: string,
|
|
359
|
+
category: ToolApprovalCategory | undefined,
|
|
360
|
+
source: AuthorizationSource,
|
|
361
|
+
policySource: PolicySource,
|
|
362
|
+
): void {
|
|
363
|
+
if (!category && !serverSlug) return;
|
|
364
|
+
|
|
365
|
+
const fingerprint = config.fingerprintKey
|
|
366
|
+
? computeApprovalFingerprint(config.fingerprintKey, {
|
|
367
|
+
toolName: toolCall.name,
|
|
368
|
+
mcpServerSlug: serverSlug,
|
|
369
|
+
args: toolCall.args,
|
|
370
|
+
})
|
|
371
|
+
: "";
|
|
372
|
+
|
|
373
|
+
console.log(
|
|
374
|
+
"[hitl-gateway] receipt " +
|
|
375
|
+
JSON.stringify({
|
|
376
|
+
executionId: config.executionId ?? "",
|
|
377
|
+
toolCallId: toolCall.id,
|
|
378
|
+
toolName: toolCall.name,
|
|
379
|
+
mcpServerSlug: serverSlug,
|
|
380
|
+
category: category ?? "",
|
|
381
|
+
authorization: source,
|
|
382
|
+
policySource,
|
|
383
|
+
policyEngineVersion: POLICY_ENGINE_VERSION,
|
|
384
|
+
fingerprint,
|
|
385
|
+
substrate: "deep-agent",
|
|
386
|
+
}),
|
|
387
|
+
);
|
|
179
388
|
}
|
package/src/middleware/types.ts
CHANGED
|
@@ -94,11 +94,11 @@ export interface OtelSpansConfig {
|
|
|
94
94
|
readonly toolServerMap: ReadonlyMap<string, string>;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
97
|
+
// Single source of truth lives in approval-gate.ts (the gate consumes it);
|
|
98
|
+
// imported as a type (no runtime cycle) and re-exported so existing importers of
|
|
99
|
+
// `ApprovalGateConfig` from this module keep working.
|
|
100
|
+
import type { ApprovalGateConfig } from "./approval-gate.js";
|
|
101
|
+
export type { ApprovalGateConfig };
|
|
102
102
|
|
|
103
103
|
/**
|
|
104
104
|
* Top-level configuration for buildMiddlewareStack().
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
normalizeActivityInput,
|
|
4
|
+
type ExecuteActivityInput,
|
|
5
|
+
} from "../activity-input.js";
|
|
6
|
+
|
|
7
|
+
describe("normalizeActivityInput — dual-shape activity boundary", () => {
|
|
8
|
+
it("reads the new typed object (snake_case wire keys from Go/Java)", () => {
|
|
9
|
+
const wire: ExecuteActivityInput = {
|
|
10
|
+
execution_id: "exec-1",
|
|
11
|
+
thread_id: "thread-1",
|
|
12
|
+
invoker_identity_account_id: "acct-1",
|
|
13
|
+
turn_seq: 2,
|
|
14
|
+
};
|
|
15
|
+
expect(normalizeActivityInput(wire)).toEqual({
|
|
16
|
+
executionId: "exec-1",
|
|
17
|
+
threadId: "thread-1",
|
|
18
|
+
turnSeq: 2,
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("reads the legacy positional args (old Go control plane: 2 args)", () => {
|
|
23
|
+
expect(normalizeActivityInput("exec-2", "thread-2")).toEqual({
|
|
24
|
+
executionId: "exec-2",
|
|
25
|
+
threadId: "thread-2",
|
|
26
|
+
turnSeq: 0,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("ignores a legacy 3rd positional arg (old Java sent invokerId as arg2)", () => {
|
|
31
|
+
// The runner only declares (arg0, arg1); a 3rd positional arg is dropped by
|
|
32
|
+
// JS, so the old Java (executionId, threadId, invokerId) call still works.
|
|
33
|
+
expect(normalizeActivityInput("exec-3", "thread-3")).toEqual({
|
|
34
|
+
executionId: "exec-3",
|
|
35
|
+
threadId: "thread-3",
|
|
36
|
+
turnSeq: 0,
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("treats an empty thread_id (first run / new harness state) as empty", () => {
|
|
41
|
+
expect(
|
|
42
|
+
normalizeActivityInput({
|
|
43
|
+
execution_id: "exec-4",
|
|
44
|
+
thread_id: "",
|
|
45
|
+
invoker_identity_account_id: "acct-4",
|
|
46
|
+
}),
|
|
47
|
+
).toEqual({ executionId: "exec-4", threadId: "", turnSeq: 0 });
|
|
48
|
+
|
|
49
|
+
expect(normalizeActivityInput("exec-5")).toEqual({
|
|
50
|
+
executionId: "exec-5",
|
|
51
|
+
threadId: "",
|
|
52
|
+
turnSeq: 0,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("defaults turn_seq to 0 when the object omits it (first turn / older control plane)", () => {
|
|
57
|
+
expect(
|
|
58
|
+
normalizeActivityInput({
|
|
59
|
+
execution_id: "exec-7",
|
|
60
|
+
thread_id: "thread-7",
|
|
61
|
+
invoker_identity_account_id: "acct-7",
|
|
62
|
+
}),
|
|
63
|
+
).toEqual({ executionId: "exec-7", threadId: "thread-7", turnSeq: 0 });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("deserializes the exact JSON shape the control planes emit", () => {
|
|
67
|
+
// Mirrors the bytes the Go struct / Java record serialize to (snake_case),
|
|
68
|
+
// including the additive turn_seq the cutover threads through.
|
|
69
|
+
const fromWire = JSON.parse(
|
|
70
|
+
'{"execution_id":"exec-6","thread_id":"thread-6","invoker_identity_account_id":"acct-6","turn_seq":3}',
|
|
71
|
+
) as ExecuteActivityInput;
|
|
72
|
+
expect(normalizeActivityInput(fromWire)).toEqual({
|
|
73
|
+
executionId: "exec-6",
|
|
74
|
+
threadId: "thread-6",
|
|
75
|
+
turnSeq: 3,
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import {
|
|
5
|
+
canonicalJson,
|
|
6
|
+
canonicalToolActionJson,
|
|
7
|
+
canonicalizeToolAction,
|
|
8
|
+
type ToolActionInput,
|
|
9
|
+
} from "../approval-canonicalize.js";
|
|
10
|
+
|
|
11
|
+
interface Vector {
|
|
12
|
+
name: string;
|
|
13
|
+
input: ToolActionInput;
|
|
14
|
+
expected: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Single source of truth, shared with the future Go/Java implementations.
|
|
18
|
+
const vectorsPath = fileURLToPath(
|
|
19
|
+
new URL("../../../../../../apis/testdata/hitl/canonicalization/vectors.json", import.meta.url),
|
|
20
|
+
);
|
|
21
|
+
const corpus = JSON.parse(readFileSync(vectorsPath, "utf-8")) as { vectors: Vector[] };
|
|
22
|
+
|
|
23
|
+
describe("approval canonicalization vector corpus", () => {
|
|
24
|
+
it("loads a non-trivial corpus", () => {
|
|
25
|
+
expect(corpus.vectors.length).toBeGreaterThanOrEqual(10);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
for (const v of corpus.vectors) {
|
|
29
|
+
it(`vector: ${v.name}`, () => {
|
|
30
|
+
expect(canonicalToolActionJson(v.input)).toBe(v.expected);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("canonicalJson", () => {
|
|
36
|
+
it("sorts object keys by UTF-16 code unit", () => {
|
|
37
|
+
expect(canonicalJson({ b: 1, a: 2, A: 3 })).toBe('{"A":3,"a":2,"b":1}');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("preserves array order", () => {
|
|
41
|
+
expect(canonicalJson([3, 1, 2])).toBe("[3,1,2]");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("drops undefined-valued keys", () => {
|
|
45
|
+
expect(canonicalJson({ a: 1, b: undefined })).toBe('{"a":1}');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("rejects non-integer numbers (outside the canonical domain)", () => {
|
|
49
|
+
expect(() => canonicalJson({ x: 1.5 })).toThrow(/non-integer/);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("rejects non-finite numbers", () => {
|
|
53
|
+
expect(() => canonicalJson({ x: Infinity })).toThrow(/non-finite/);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("canonicalizeToolAction normalization", () => {
|
|
58
|
+
it("is idempotent (canonicalizing the canonical form is a fixed point)", () => {
|
|
59
|
+
const input: ToolActionInput = {
|
|
60
|
+
toolName: "Write",
|
|
61
|
+
paths: ["/ws/src/b.ts", "/ws/src/a.ts"],
|
|
62
|
+
workspaceRoot: "/ws",
|
|
63
|
+
args: { flag: true },
|
|
64
|
+
};
|
|
65
|
+
const once = canonicalToolActionJson(input);
|
|
66
|
+
const twice = canonicalToolActionJson(input);
|
|
67
|
+
expect(twice).toBe(once);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("redacts secret values to a stable digest without cleartext", () => {
|
|
71
|
+
const input: ToolActionInput = {
|
|
72
|
+
toolName: "create_secret",
|
|
73
|
+
args: { name: "db", value: "super-secret-password" },
|
|
74
|
+
secretKeys: ["value"],
|
|
75
|
+
};
|
|
76
|
+
const out = canonicalizeToolAction(input);
|
|
77
|
+
const redacted = out.args.value as string;
|
|
78
|
+
|
|
79
|
+
expect(redacted).toMatch(/^sha256:[0-9a-f]{64}$/);
|
|
80
|
+
expect(redacted).not.toContain("super-secret-password");
|
|
81
|
+
expect(canonicalToolActionJson(input)).not.toContain("super-secret-password");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("redaction is stable for the same secret and distinct for different secrets", () => {
|
|
85
|
+
const base = (value: string): ToolActionInput => ({
|
|
86
|
+
toolName: "t",
|
|
87
|
+
args: { value },
|
|
88
|
+
secretKeys: ["value"],
|
|
89
|
+
});
|
|
90
|
+
const a1 = canonicalizeToolAction(base("secret-a")).args.value;
|
|
91
|
+
const a2 = canonicalizeToolAction(base("secret-a")).args.value;
|
|
92
|
+
const b = canonicalizeToolAction(base("secret-b")).args.value;
|
|
93
|
+
|
|
94
|
+
expect(a1).toBe(a2);
|
|
95
|
+
expect(a1).not.toBe(b);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("makes a workspace-absolute path relative and normalizes separators", () => {
|
|
99
|
+
const out = canonicalizeToolAction({
|
|
100
|
+
toolName: "Write",
|
|
101
|
+
paths: ["C:\\ws\\src\\main.ts"],
|
|
102
|
+
workspaceRoot: "C:\\ws",
|
|
103
|
+
});
|
|
104
|
+
expect(out.paths).toEqual(["src/main.ts"]);
|
|
105
|
+
});
|
|
106
|
+
});
|