@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,12 +27,14 @@
|
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
import { heartbeat, Context, CancelledFailure } from "@temporalio/activity";
|
|
30
|
-
import { create, type JsonObject } from "@bufbuild/protobuf";
|
|
30
|
+
import { create, clone, type JsonObject } from "@bufbuild/protobuf";
|
|
31
31
|
import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
32
32
|
import { AgentMessageSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
33
|
+
import { SubAgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
|
|
34
|
+
import type { SubAgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
|
|
33
35
|
import type { PendingApproval } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb";
|
|
34
|
-
import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
35
|
-
import { ExecutionControlSignal, ExecutionPhase, InteractionMode, MessageType, ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
36
|
+
import type { AgentExecution, AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
37
|
+
import { ExecutionControlSignal, ExecutionPhase, FileChangeSetStatus, InteractionMode, MessageType, ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
36
38
|
import type { SDKMessage, Run, ConversationTurn } from "@cursor/sdk";
|
|
37
39
|
|
|
38
40
|
import type { Config } from "../../config.js";
|
|
@@ -41,8 +43,9 @@ import { resolveAgent } from "./session-lifecycle.js";
|
|
|
41
43
|
import type { AgentResolution, CreateAgentOptions, CreateCloudAgentOptions } from "./session-lifecycle.js";
|
|
42
44
|
import { CursorMode } from "@stigmer/protos/ai/stigmer/agentic/session/v1/enum_pb";
|
|
43
45
|
import { determineCursorMode, isCloudMode } from "./cursor-mode.js";
|
|
44
|
-
import { MessageAccumulator, reconcileDeniedToolCalls, cancelInProgressSubAgentProtos } from "./message-translator.js";
|
|
46
|
+
import { MessageAccumulator, reconcileDeniedToolCalls, clearProvisionalPostDenialNarration, cancelInProgressSubAgentProtos, collapseRedundantToolCallTwins } from "./message-translator.js";
|
|
45
47
|
import { utcTimestamp, persistStatus, reportSetupProgress, slimStatus } from "../../shared/status.js";
|
|
48
|
+
import { collectSubAgentToolCallIds } from "../../shared/tool-row.js";
|
|
46
49
|
import { startStallWatchdog, StallTimeoutError, formatStallFailure, type StallWatchdog } from "../../shared/stall-watchdog.js";
|
|
47
50
|
import { createArtifactStorage, loadArtifactStorageConfig, type ArtifactStorage } from "../../shared/artifact-storage.js";
|
|
48
51
|
import { publishPlanArtifact } from "../../shared/plan-artifact.js";
|
|
@@ -53,7 +56,7 @@ import { StreamingUpdateScheduler, loadStreamingConfig } from "../../shared/stre
|
|
|
53
56
|
import { createCursorEventRecorder } from "./cursor-event-recorder.js";
|
|
54
57
|
import { resolveMcpServers, validateMcpServerEnv } from "./mcp-resolver.js";
|
|
55
58
|
import { mergeApprovalPolicies } from "./approval-policy.js";
|
|
56
|
-
import {
|
|
59
|
+
import { deriveActiveLeases } from "../../shared/approval-policy.js";
|
|
57
60
|
import { backfillMcpServersIfNeeded } from "./connect-backfill.js";
|
|
58
61
|
import { resolveExecutionEnv } from "./env-resolver.js";
|
|
59
62
|
import { resolveBlueprint } from "./blueprint-resolver.js";
|
|
@@ -63,7 +66,19 @@ import { resolveAttachments } from "./attachment-resolver.js";
|
|
|
63
66
|
import { buildEnhancedPrompt, buildReinvocationPrompt } from "./prompt-builder.js";
|
|
64
67
|
import { installHitlGate, removeHitlGate } from "./workspace-setup.js";
|
|
65
68
|
import { ensureHitlDir } from "../../shared/workspace/platform-dir.js";
|
|
66
|
-
import {
|
|
69
|
+
import { LocalWorkspaceBackend } from "../../shared/workspace/local-backend.js";
|
|
70
|
+
import { buildApprovalState, buildApprovalGrants, emitCursorGrantReceipts, primaryToken, readDenialLedger, reconstructAdjudicatedApprovals, watchDenialLedger } from "./approval-state.js";
|
|
71
|
+
import { deriveTurnCommandProvenance } from "./command-provenance.js";
|
|
72
|
+
import { applyApprovedWholeFileWrites, excludeAppliedFromGrants } from "./exact-apply.js";
|
|
73
|
+
import { isGitWorkTree } from "../../shared/filereview/git-substrate.js";
|
|
74
|
+
import {
|
|
75
|
+
captureBaselineToLedger,
|
|
76
|
+
captureTurnToLedger,
|
|
77
|
+
applyCaptureDecisions,
|
|
78
|
+
deriveCaptureMode,
|
|
79
|
+
} from "./capture-flow.js";
|
|
80
|
+
import { deriveExecutionFingerprintKey } from "../../shared/approval-fingerprint.js";
|
|
81
|
+
import { getRunnerHitlMasterSecret } from "../../shared/fingerprint-secret.js";
|
|
67
82
|
import { provisionCursorWorkspace } from "./workspace-provision.js";
|
|
68
83
|
import { setInterceptorExecutionId, runWithExecutionContext } from "./fetch-interceptor.js";
|
|
69
84
|
import { closeProxySessions } from "./http2-interceptor.js";
|
|
@@ -71,6 +86,7 @@ import { resolveModelId, ensureLoaded as ensurePricingLoaded } from "./model-pri
|
|
|
71
86
|
import { UsageAccumulator } from "./usage-accumulator.js";
|
|
72
87
|
import { StreamingUsageSummarySchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/usage_pb";
|
|
73
88
|
import { activityStarted, activityFinished } from "../../idle-watchdog.js";
|
|
89
|
+
import { normalizeActivityInput, type ExecuteActivityInput } from "../../shared/activity-input.js";
|
|
74
90
|
import { getCapturedRejection, clearCapturedRejection } from "./rejection-capture.js";
|
|
75
91
|
import { synthesizeError, formatClassifiedError, shouldRetryWithFreshAgent } from "./error-classifier.js";
|
|
76
92
|
import type { ClassifiedError } from "./error-classifier.js";
|
|
@@ -79,6 +95,12 @@ import { setMaxListeners } from "node:events";
|
|
|
79
95
|
import { startHeartbeat } from "../../shared/heartbeat.js";
|
|
80
96
|
import { getShutdownSignalForQueue } from "../../runner-manager.js";
|
|
81
97
|
|
|
98
|
+
// How long Phase 12 waits for the first-denial-stop's run.cancel() to settle
|
|
99
|
+
// before reading the final denial ledger and capturing the turn's tree. Long
|
|
100
|
+
// enough for the SDK's normal teardown, short enough that a wedged cancel
|
|
101
|
+
// cannot noticeably delay the approval pause the user is already waiting on.
|
|
102
|
+
const FIRST_DENIAL_CANCEL_TIMEOUT_MS = 5_000;
|
|
103
|
+
|
|
82
104
|
/**
|
|
83
105
|
* Creates the activity functions bound to the runner config.
|
|
84
106
|
* Returned object is passed to Temporal Worker.create({ activities }).
|
|
@@ -91,10 +113,18 @@ export function createCursorActivities(config: Config) {
|
|
|
91
113
|
});
|
|
92
114
|
|
|
93
115
|
return {
|
|
94
|
-
|
|
116
|
+
// Accepts the new typed object OR the legacy positional args (transitional
|
|
117
|
+
// dual-shape so the runner can deploy before the control planes — see
|
|
118
|
+
// shared/activity-input.ts). Drop the positional arm once both control
|
|
119
|
+
// planes send the object.
|
|
120
|
+
ExecuteCursor: async (
|
|
121
|
+
arg0: ExecuteActivityInput | string,
|
|
122
|
+
arg1?: string,
|
|
123
|
+
): Promise<unknown> => {
|
|
124
|
+
const { executionId, threadId, turnSeq } = normalizeActivityInput(arg0, arg1);
|
|
95
125
|
activityStarted();
|
|
96
126
|
try {
|
|
97
|
-
return await executeCursor(config, client, executionId, threadId);
|
|
127
|
+
return await executeCursor(config, client, executionId, threadId, turnSeq);
|
|
98
128
|
} finally {
|
|
99
129
|
activityFinished();
|
|
100
130
|
}
|
|
@@ -107,15 +137,16 @@ async function executeCursor(
|
|
|
107
137
|
client: StigmerClient,
|
|
108
138
|
executionId: string,
|
|
109
139
|
threadId: string,
|
|
140
|
+
turnSeq: number,
|
|
110
141
|
): Promise<unknown> {
|
|
111
|
-
console.log(`ExecuteCursor started: execution=${executionId}, threadId=${threadId || "(new)"}`);
|
|
142
|
+
console.log(`ExecuteCursor started: execution=${executionId}, threadId=${threadId || "(new)"}, turnSeq=${turnSeq}`);
|
|
112
143
|
|
|
113
144
|
// Ensure fresh HTTP/2 transport — prevents a degraded session from a
|
|
114
145
|
// prior workflow task from poisoning this execution's agent stream.
|
|
115
146
|
closeProxySessions();
|
|
116
147
|
|
|
117
148
|
setInterceptorExecutionId(executionId);
|
|
118
|
-
return runWithExecutionContext(executionId, () => executeCursorInner(config, client, executionId, threadId));
|
|
149
|
+
return runWithExecutionContext(executionId, () => executeCursorInner(config, client, executionId, threadId, turnSeq));
|
|
119
150
|
}
|
|
120
151
|
|
|
121
152
|
async function executeCursorInner(
|
|
@@ -123,6 +154,10 @@ async function executeCursorInner(
|
|
|
123
154
|
client: StigmerClient,
|
|
124
155
|
executionId: string,
|
|
125
156
|
threadId: string,
|
|
157
|
+
// turnSeq is the monotonic HITL-cycle index (0 on the first turn). The
|
|
158
|
+
// file-review producer consumes it to mint the deterministic change-set id
|
|
159
|
+
// (executionId:turnSeq) in the capture phase.
|
|
160
|
+
turnSeq: number,
|
|
126
161
|
): Promise<unknown> {
|
|
127
162
|
|
|
128
163
|
const status = create(AgentExecutionStatusSchema, {
|
|
@@ -163,6 +198,29 @@ async function executeCursorInner(
|
|
|
163
198
|
// message surfaced to the user; both feed the Phase 11a stall branch.
|
|
164
199
|
let stallDetected = false;
|
|
165
200
|
let stallError: StallTimeoutError | undefined;
|
|
201
|
+
// Set the moment the preToolUse hook records its first denial in the ledger.
|
|
202
|
+
// We then stop consuming the stream and cancel the run so the model never
|
|
203
|
+
// reacts to Cursor's tool-failure surface (narrate defeat, attempt a second
|
|
204
|
+
// gated tool) — converging the Cursor harness toward the native harness, which
|
|
205
|
+
// pauses BEFORE the model sees a denial. Phase 12 reconciles the denied tool
|
|
206
|
+
// calls into WAITING_FOR_APPROVAL exactly as after a natural stream end.
|
|
207
|
+
let firstDenialDetected = false;
|
|
208
|
+
// Flipped by the denial-ledger fs watcher the instant the hook writes a
|
|
209
|
+
// denial, so the NEXT stream event of ANY type triggers the ledger read —
|
|
210
|
+
// instead of waiting for the next tool_call event, during which the model's
|
|
211
|
+
// full post-denial reaction (thinking, narration, a workaround tool) would
|
|
212
|
+
// stream and persist (observed in production: aex_01kwj07f7g23c3wp9sn8496z5g).
|
|
213
|
+
// The tool_call-event read below remains the backstop where fs.watch is
|
|
214
|
+
// unreliable.
|
|
215
|
+
let denialLedgerDirty = false;
|
|
216
|
+
let stopDenialWatcher: (() => void) | undefined;
|
|
217
|
+
// The in-flight run.cancel() started by the first-denial stop. Awaited
|
|
218
|
+
// (timeboxed) before Phase 12 so the agent process has actually stopped
|
|
219
|
+
// before the final ledger read and the turn-boundary tree capture — closing
|
|
220
|
+
// the race where a post-denial workaround's ledger entry lands after the
|
|
221
|
+
// read (it would then never be collapsed) or a late tool mutates the tree
|
|
222
|
+
// mid-capture.
|
|
223
|
+
let denialCancelSettled: Promise<void> | undefined;
|
|
166
224
|
let periodicHeartbeat: ReturnType<typeof startHeartbeat> | undefined;
|
|
167
225
|
// Progress-based stall watchdog (see ../../shared/stall-watchdog.ts). Stopped
|
|
168
226
|
// in the finally on every exit path; complements the liveness heartbeat.
|
|
@@ -209,6 +267,33 @@ async function executeCursorInner(
|
|
|
209
267
|
);
|
|
210
268
|
heartbeat();
|
|
211
269
|
|
|
270
|
+
// Apply-then-review is the universal file-review model (Slice 2c). When the
|
|
271
|
+
// primary workspace is a real git work tree, file edits flow during the turn
|
|
272
|
+
// and are captured per-file from the git diff at the turn boundary
|
|
273
|
+
// (capture-flow.ts / shared/filereview/git-substrate.ts). A NON-git workspace
|
|
274
|
+
// has no git snapshot, so it captures every file write via the path-scoped CAS
|
|
275
|
+
// substrate instead — which requires artifact storage to persist blobs; when
|
|
276
|
+
// storage is unavailable a non-git workspace falls back to the classic
|
|
277
|
+
// deny-gate (no regression). `gitWorkspace` selects the substrate; both flow
|
|
278
|
+
// file edits and review post-hoc, and the deny-gate then survives only for
|
|
279
|
+
// shell/MCP/irreversible tools. Detected once from the provisioned primary root.
|
|
280
|
+
const primaryWorkspaceDir = blueprint.workspaceDirs[0];
|
|
281
|
+
const gitWorkspace = primaryWorkspaceDir
|
|
282
|
+
? await isGitWorkTree(primaryWorkspaceDir)
|
|
283
|
+
: false;
|
|
284
|
+
const captureMode = deriveCaptureMode(primaryWorkspaceDir, gitWorkspace, !!artifactStorage);
|
|
285
|
+
// Pre-turn baseline tree, pinned before the agent runs (capture mode only)
|
|
286
|
+
// so the turn-end capture diffs against it and the tree restores exactly.
|
|
287
|
+
let baselineTree: string | undefined;
|
|
288
|
+
// Deterministic id of the change set this turn may produce:
|
|
289
|
+
// `${executionId}:${turnSeq}`. Minted from the workflow-threaded turn index
|
|
290
|
+
// so it is stable across a Temporal retry (idempotent ledger authoring) and
|
|
291
|
+
// unique per turn. The resume reconcile reads the change set id back from the
|
|
292
|
+
// DECIDED projection, not from turnSeq — so a "wasted" id on a pure-reconcile
|
|
293
|
+
// resume (which never authors a baseline) is harmless.
|
|
294
|
+
const changeSetId = `${executionId}:${turnSeq}`;
|
|
295
|
+
heartbeat();
|
|
296
|
+
|
|
212
297
|
// Set OTel baggage so downstream calls carry execution context.
|
|
213
298
|
try {
|
|
214
299
|
const { setBaggage, BAGGAGE_EXECUTION_ID, BAGGAGE_SESSION_ID, BAGGAGE_ORG_ID } = await import("../../otel.js");
|
|
@@ -242,14 +327,76 @@ async function executeCursorInner(
|
|
|
242
327
|
// by reinvocation time — the decision survives only on the tool call. This
|
|
243
328
|
// feeds both the grant builder and the reinvocation prompt below.
|
|
244
329
|
let adjudicatedApprovals: PendingApproval[] = [];
|
|
330
|
+
// tool-call id -> content digest of the approved edit, threaded into the
|
|
331
|
+
// grant builder so an approved edit is authorized by its exact content (a
|
|
332
|
+
// sibling edit to the same file re-gates). Sourced from the persisted
|
|
333
|
+
// approval_content_digest field (see reconstructAdjudicatedApprovals).
|
|
334
|
+
let adjudicatedContentDigests: Map<string, string> = new Map();
|
|
335
|
+
// Sub-agent executions carried over from the persisted transcript on a
|
|
336
|
+
// resume, handed to the MessageAccumulator so a gated tool inside a
|
|
337
|
+
// delegated sub-agent survives the round-trip (see seeding below).
|
|
338
|
+
let seededSubAgents: SubAgentExecution[] = [];
|
|
245
339
|
|
|
246
340
|
if (isReinvocation) {
|
|
247
341
|
const existingStatus = execution.status;
|
|
342
|
+
// Seed the in-progress status from the persisted execution BEFORE the
|
|
343
|
+
// MessageAccumulator wraps status.messages, so this resumed turn APPENDS
|
|
344
|
+
// onto prior history rather than rebuilding from empty. A Cursor resume
|
|
345
|
+
// re-issues approved tool calls with fresh ids; a from-empty rebuild would
|
|
346
|
+
// drop the previously-committed ids and the backend's append-only-at-
|
|
347
|
+
// identity guard would reject the whole update, stalling the run (the
|
|
348
|
+
// "approval propagation is broken" watchdog failure). The resumed re-runs
|
|
349
|
+
// are reconciled onto these seeded calls by canonical identity inside the
|
|
350
|
+
// accumulator. Mirrors the deep-agent seedStatusFromExecution.
|
|
351
|
+
seededSubAgents = seedCursorTranscriptFromExecution(status, execution);
|
|
352
|
+
|
|
353
|
+
// File-review reconcile (the dual-source half): reconcile every change set
|
|
354
|
+
// the server projected as DECIDED, sourced from the ledger decisions and
|
|
355
|
+
// the pinned git refs (approved kept at their "after" bytes, rejected
|
|
356
|
+
// snapped back to baseline — all uncommitted, hash-verified). This is
|
|
357
|
+
// independent of tool approvals: a single turn can carry BOTH a DECIDED
|
|
358
|
+
// file change set AND an approved shell/MCP action.
|
|
359
|
+
let reconciledFileReview = false;
|
|
360
|
+
let fileReviewFailed = false;
|
|
361
|
+
let fileReviewFailureDetail = "";
|
|
362
|
+
const discardedPaths: string[] = [];
|
|
363
|
+
if (captureMode && primaryWorkspaceDir) {
|
|
364
|
+
const decidedSets = (existingStatus?.fileChangeSets ?? []).filter(
|
|
365
|
+
(cs) => cs.status === FileChangeSetStatus.DECIDED,
|
|
366
|
+
);
|
|
367
|
+
for (const changeSet of decidedSets) {
|
|
368
|
+
const capResult = await applyCaptureDecisions({
|
|
369
|
+
status,
|
|
370
|
+
gitRoot: primaryWorkspaceDir,
|
|
371
|
+
executionId,
|
|
372
|
+
changeSet,
|
|
373
|
+
// Thread the CAS store so CAS-captured files in the change set
|
|
374
|
+
// reconcile from the durable manifest (approved after-blobs written,
|
|
375
|
+
// rejected snapped back). In a non-git workspace this is the ONLY
|
|
376
|
+
// reconcile; in a git tree it composes with the git-ref reconcile.
|
|
377
|
+
storage: artifactStorage,
|
|
378
|
+
gitWorkspace,
|
|
379
|
+
});
|
|
380
|
+
if (!capResult.isCaptureTurn) continue;
|
|
381
|
+
reconciledFileReview = true;
|
|
382
|
+
if (capResult.failed) {
|
|
383
|
+
fileReviewFailed = true;
|
|
384
|
+
fileReviewFailureDetail = capResult.failureDetail ?? "file review reconcile failed";
|
|
385
|
+
}
|
|
386
|
+
if (capResult.hadReject) discardedPaths.push(...capResult.rejectedPaths);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Tool approvals (shell / MCP / gitignored writes) still resolve from the
|
|
391
|
+
// message transcript — the deny-gate path, unchanged by the file-review
|
|
392
|
+
// cutover.
|
|
248
393
|
const adjudicated = reconstructAdjudicatedApprovals(existingStatus?.messages ?? []);
|
|
249
394
|
if (adjudicated.decisions.size > 0) {
|
|
250
395
|
approvalDecisions = adjudicated.decisions;
|
|
251
396
|
adjudicatedApprovals = adjudicated.pendingApprovals;
|
|
397
|
+
adjudicatedContentDigests = adjudicated.contentDigests;
|
|
252
398
|
|
|
399
|
+
// A reject of an irreversible action (shell/MCP) fails the execution.
|
|
253
400
|
const hasReject = [...approvalDecisions.values()].some(
|
|
254
401
|
(a) => a === ApprovalAction.REJECT,
|
|
255
402
|
);
|
|
@@ -265,6 +412,47 @@ async function executeCursorInner(
|
|
|
265
412
|
await persist(status);
|
|
266
413
|
return slimStatus(status);
|
|
267
414
|
}
|
|
415
|
+
// else: fall through to run the approved shell/MCP. The agent may produce
|
|
416
|
+
// further edits, captured as a new change set in the next cycle.
|
|
417
|
+
} else if (reconciledFileReview) {
|
|
418
|
+
// Pure file review: the agent already finished its full turn during
|
|
419
|
+
// capture, so keeping/discarding a change does NOT re-prompt it
|
|
420
|
+
// (Cursor-like). The reconcile is done; the execution is complete.
|
|
421
|
+
status.phase = ExecutionPhase.EXECUTION_COMPLETED;
|
|
422
|
+
status.completedAt = utcTimestamp();
|
|
423
|
+
if (fileReviewFailed) {
|
|
424
|
+
// What-you-approve-is-what-applies could not be honored (on-disk bytes
|
|
425
|
+
// diverged from the approved digest). Surface it to the human; the
|
|
426
|
+
// FileReviewFailure(HASH_MISMATCH) event is the audit record.
|
|
427
|
+
status.messages.push(create(AgentMessageSchema, {
|
|
428
|
+
type: MessageType.MESSAGE_SYSTEM,
|
|
429
|
+
content:
|
|
430
|
+
"Some approved file changes could not be applied because the file " +
|
|
431
|
+
"changed after review: " + fileReviewFailureDetail + ".",
|
|
432
|
+
timestamp: utcTimestamp(),
|
|
433
|
+
}));
|
|
434
|
+
} else if (discardedPaths.length > 0) {
|
|
435
|
+
// A reject is a DISCARD that COMPLETES (not FAILED) — surface a SYSTEM
|
|
436
|
+
// note listing the reverted files. This note is for the human; it does
|
|
437
|
+
// NOT re-sync the Cursor SDK agent (its native context still believes
|
|
438
|
+
// those edits stuck). The agent self-corrects by re-reading, and any
|
|
439
|
+
// edit it makes from that stale belief is itself re-surfaced as a new
|
|
440
|
+
// change set next turn (the structural safety net). See
|
|
441
|
+
// design-decisions/capture-reject-next-turn-resync-not-built.md.
|
|
442
|
+
status.messages.push(create(AgentMessageSchema, {
|
|
443
|
+
type: MessageType.MESSAGE_SYSTEM,
|
|
444
|
+
content:
|
|
445
|
+
"Some proposed file changes were discarded by the user and were not applied: " +
|
|
446
|
+
discardedPaths.join(", ") + ".",
|
|
447
|
+
timestamp: utcTimestamp(),
|
|
448
|
+
}));
|
|
449
|
+
}
|
|
450
|
+
await persist(status);
|
|
451
|
+
console.log(
|
|
452
|
+
`ExecuteCursor file-review resume short-circuit: execution=${executionId}, ` +
|
|
453
|
+
`failed=${fileReviewFailed}, discarded=${discardedPaths.length}`,
|
|
454
|
+
);
|
|
455
|
+
return slimStatus(status);
|
|
268
456
|
}
|
|
269
457
|
}
|
|
270
458
|
|
|
@@ -284,20 +472,20 @@ async function executeCursorInner(
|
|
|
284
472
|
|
|
285
473
|
// Phase 4b: Merge approval policies from all layers.
|
|
286
474
|
//
|
|
287
|
-
//
|
|
288
|
-
// spec.auto_approve_all
|
|
289
|
-
//
|
|
290
|
-
//
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
const
|
|
294
|
-
|
|
475
|
+
// Two bypasses (see ActiveLeases, shared with the native harness): the
|
|
476
|
+
// pre-armed spec.auto_approve_all is the one whole-run global bypass; an
|
|
477
|
+
// interactive APPROVE_ALL grants a run-lifetime lease scoped to that action's
|
|
478
|
+
// class. deriveActiveLeases keeps this contract defined once. Server-scoped
|
|
479
|
+
// leases drop that server's tools from the merged map (so the hook treats
|
|
480
|
+
// them as auto-approved); the global bypass empties the map entirely.
|
|
481
|
+
const leases = deriveActiveLeases(execution);
|
|
482
|
+
const globalBypass = leases.global;
|
|
295
483
|
const agentOverrides = blueprint.mergedMcpServerUsages
|
|
296
484
|
.flatMap((u) => u.toolApprovalOverrides ?? []);
|
|
297
485
|
const mergedPolicies = mergeApprovalPolicies(
|
|
298
486
|
mcpResolution.resolvedServers,
|
|
299
487
|
agentOverrides,
|
|
300
|
-
|
|
488
|
+
leases,
|
|
301
489
|
);
|
|
302
490
|
heartbeat();
|
|
303
491
|
|
|
@@ -316,7 +504,7 @@ async function executeCursorInner(
|
|
|
316
504
|
|
|
317
505
|
// Phase 5: Resolve skills (merged from agent + session)
|
|
318
506
|
await reportSetupProgress(client, executionId, "Resolving skills");
|
|
319
|
-
|
|
507
|
+
// (primaryWorkspaceDir / captureMode were resolved right after provisioning.)
|
|
320
508
|
const skillMetadata = await resolveSkills(client, blueprint.mergedSkillRefs, {
|
|
321
509
|
sessionId,
|
|
322
510
|
primaryWorkspaceDir,
|
|
@@ -332,6 +520,35 @@ async function executeCursorInner(
|
|
|
332
520
|
);
|
|
333
521
|
const attachmentPaths = attachmentResults.map((a) => a.relativePath);
|
|
334
522
|
|
|
523
|
+
// Phase 5b3: Exact-apply approved whole-file writes (HITL "what you approve
|
|
524
|
+
// is what gets applied"). The Cursor deny-only harness reinvokes the model,
|
|
525
|
+
// which regenerates content, so a resource grant alone cannot guarantee the
|
|
526
|
+
// bytes that land match the bytes the user approved. The runner therefore
|
|
527
|
+
// writes the EXACT approved whole-file content itself, marks those tool calls
|
|
528
|
+
// COMPLETED, and (below) issues NO grant for them — so any FURTHER change the
|
|
529
|
+
// model makes to those files is re-gated. Hunk edits / shell / MCP stay on
|
|
530
|
+
// the grant + reinvocation path. Every uncertain case degrades to that path,
|
|
531
|
+
// so this can never corrupt a file (see exact-apply.ts).
|
|
532
|
+
let appliedToolCallIds: ReadonlySet<string> = new Set();
|
|
533
|
+
// Exact-apply is the deny-gate path's "what you approve is what gets applied"
|
|
534
|
+
// mechanism (the model regenerates content on reinvocation). Capture mode
|
|
535
|
+
// does not reinvoke the model for file edits — it applies the exact captured
|
|
536
|
+
// bytes itself in applyCaptureDecisions — so exact-apply is scoped OUT of it.
|
|
537
|
+
if (!captureMode && isReinvocation && approvalDecisions) {
|
|
538
|
+
appliedToolCallIds = await applyApprovedWholeFileWrites({
|
|
539
|
+
messages: status.messages,
|
|
540
|
+
workspaceBackend: new LocalWorkspaceBackend(primaryWorkspaceDir),
|
|
541
|
+
workspaceDirs: blueprint.workspaceDirs,
|
|
542
|
+
executionId,
|
|
543
|
+
});
|
|
544
|
+
if (appliedToolCallIds.size > 0) {
|
|
545
|
+
// Persist the applied writes (tool calls now COMPLETED with the approved
|
|
546
|
+
// diff) before reinvocation, so the applied state is durable even if the
|
|
547
|
+
// continuation fails, and the UI reflects it immediately.
|
|
548
|
+
await persist(status);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
335
552
|
// Phase 5c: Install the HITL approval gate BEFORE resolving the agent.
|
|
336
553
|
//
|
|
337
554
|
// The gate's runtime artifacts (hook script, approval-state file, denial
|
|
@@ -345,15 +562,56 @@ async function executeCursorInner(
|
|
|
345
562
|
//
|
|
346
563
|
// On reinvocation, turn the user's approvals into tool-identity grants so
|
|
347
564
|
// the resumed agent's re-attempt (which carries a fresh tool-call id) is
|
|
348
|
-
// allowed through.
|
|
565
|
+
// allowed through. Exact-applied writes are EXCLUDED from the grants: with no
|
|
566
|
+
// grant, a further write to that file is re-gated (the user sees every change).
|
|
567
|
+
// Capture mode: pin the pre-turn baseline tree before the agent runs (and
|
|
568
|
+
// before the gate is installed, though the gate files are excluded from the
|
|
569
|
+
// capture anyway). The turn-end capture diffs the post-turn tree against this
|
|
570
|
+
// to build the per-file cards; the baseline ref is also what a reject reverts
|
|
571
|
+
// to on resume. Covers a fresh turn and the approved-irreversible resume
|
|
572
|
+
// fall-through (the agent will run and may make further edits).
|
|
573
|
+
if (captureMode && primaryWorkspaceDir) {
|
|
574
|
+
// Pin the pre-turn tree AND author BASELINE_CAPTURED so the projection can
|
|
575
|
+
// materialize the change set (status CAPTURING) before any candidate exists.
|
|
576
|
+
// The event rides the next persist; CAPTURING does not arm the unified gate.
|
|
577
|
+
baselineTree = await captureBaselineToLedger({
|
|
578
|
+
status,
|
|
579
|
+
gitRoot: primaryWorkspaceDir,
|
|
580
|
+
executionId,
|
|
581
|
+
changeSetId,
|
|
582
|
+
gitWorkspace,
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
|
|
349
586
|
hitlDir = await ensureHitlDir(sessionId);
|
|
587
|
+
const grantApprovals = excludeAppliedFromGrants(adjudicatedApprovals, appliedToolCallIds);
|
|
350
588
|
const approvalGrants = approvalDecisions
|
|
351
|
-
? buildApprovalGrants(
|
|
589
|
+
? buildApprovalGrants(grantApprovals, approvalDecisions, adjudicatedContentDigests)
|
|
352
590
|
: undefined;
|
|
591
|
+
if (approvalGrants && approvalGrants.length > 0 && !globalBypass) {
|
|
592
|
+
emitCursorGrantReceipts(
|
|
593
|
+
approvalGrants,
|
|
594
|
+
deriveExecutionFingerprintKey(getRunnerHitlMasterSecret(), executionId),
|
|
595
|
+
executionId,
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
// CAS capture requires artifact storage to persist blobs
|
|
599
|
+
// (captureCandidateToLedger throws without it). In a git tree, captureMode
|
|
600
|
+
// alone governs tracked-file capture (no storage needed) and captureIgnored is
|
|
601
|
+
// the narrower switch (git tree + storage) that also captures gitignored
|
|
602
|
+
// writes. In a non-git workspace ALL capture is CAS, so captureMode already
|
|
603
|
+
// required storage — captureIgnored then equals captureMode. When storage is
|
|
604
|
+
// absent a git tree keeps gating gitignored writes and a non-git workspace
|
|
605
|
+
// falls back to the deny-gate entirely (no regression).
|
|
606
|
+
const captureIgnored = captureMode && !!artifactStorage;
|
|
353
607
|
const approvalState = buildApprovalState(
|
|
354
608
|
mergedPolicies,
|
|
355
|
-
|
|
609
|
+
globalBypass,
|
|
610
|
+
leases.categories,
|
|
356
611
|
approvalGrants,
|
|
612
|
+
captureMode,
|
|
613
|
+
captureIgnored,
|
|
614
|
+
gitWorkspace,
|
|
357
615
|
);
|
|
358
616
|
const hitlGate = await installHitlGate({
|
|
359
617
|
workspaceRoot: primaryWorkspaceDir,
|
|
@@ -365,6 +623,12 @@ async function executeCursorInner(
|
|
|
365
623
|
await removeHitlGate(hitlGate);
|
|
366
624
|
await removeStigmerSymlink(primaryWorkspaceDir);
|
|
367
625
|
};
|
|
626
|
+
// Arm the denial watcher as soon as the gate exists. The per-turn ledger
|
|
627
|
+
// reset may flip the flag once before the run starts; the loop's read then
|
|
628
|
+
// sees an empty ledger and clears it — harmless by construction.
|
|
629
|
+
stopDenialWatcher = watchDenialLedger(hitlDir, () => {
|
|
630
|
+
denialLedgerDirty = true;
|
|
631
|
+
});
|
|
368
632
|
|
|
369
633
|
// Phase 5d: Ensure model pricing registry is populated before validation
|
|
370
634
|
await ensurePricingLoaded();
|
|
@@ -489,6 +753,7 @@ async function executeCursorInner(
|
|
|
489
753
|
workspaceFileRefs: spec.workspaceFileRefs ?? [],
|
|
490
754
|
attachmentPaths,
|
|
491
755
|
pendingApprovals: adjudicatedApprovals,
|
|
756
|
+
appliedToolCallIds,
|
|
492
757
|
interactionMode,
|
|
493
758
|
});
|
|
494
759
|
|
|
@@ -618,7 +883,17 @@ async function executeCursorInner(
|
|
|
618
883
|
}
|
|
619
884
|
});
|
|
620
885
|
|
|
621
|
-
|
|
886
|
+
// Everything at an index >= this was produced by THIS turn's stream — the
|
|
887
|
+
// positional turn boundary the approved-command provenance (DD-28) scopes
|
|
888
|
+
// its qualification to. Snapshotted before the accumulator can append.
|
|
889
|
+
const turnStartMessageIndex = status.messages.length;
|
|
890
|
+
|
|
891
|
+
const accumulator = new MessageAccumulator(status.messages, {
|
|
892
|
+
mergedPolicies,
|
|
893
|
+
provenance: { globalBypass, leasedCategories: leases.categories },
|
|
894
|
+
workspaceRoot: primaryWorkspaceDir,
|
|
895
|
+
seededSubAgents,
|
|
896
|
+
});
|
|
622
897
|
// Shared cadence with the native harness: discrete state changes force a
|
|
623
898
|
// flush; high-frequency token deltas ride this scheduler's time cadence
|
|
624
899
|
// (env-tunable via STREAMING_* — see loadStreamingConfig).
|
|
@@ -650,6 +925,53 @@ async function executeCursorInner(
|
|
|
650
925
|
);
|
|
651
926
|
}
|
|
652
927
|
|
|
928
|
+
// First-denial stop (HITL clean pause). In CAPTURE mode this fires only for
|
|
929
|
+
// an IRREVERSIBLE tool the hook still gates (shell, MCP, or a gitignored
|
|
930
|
+
// write/delete) — file edits flow freely and are captured at the turn
|
|
931
|
+
// boundary, so they never enter the ledger. In the deny-gate FALLBACK
|
|
932
|
+
// (non-git workspace) it fires for every gated file edit too. Either way:
|
|
933
|
+
// the preToolUse hook appends to the denial ledger the instant it gates a
|
|
934
|
+
// tool — before Cursor surfaces the failure to the model — and the fs
|
|
935
|
+
// watcher flips denialLedgerDirty the moment that write lands. Confirming
|
|
936
|
+
// the flag with a read on the very next event (of ANY type — thinking
|
|
937
|
+
// deltas arrive within milliseconds) ends the turn before the model's
|
|
938
|
+
// reaction can persist: waiting for the next tool_call event let the full
|
|
939
|
+
// post-denial reaction (thinking, narration, a workaround shell) stream
|
|
940
|
+
// and persist live (production case aex_01kwj07f7g23c3wp9sn8496z5g). The
|
|
941
|
+
// tool_call-event read stays as the backstop for platforms where fs.watch
|
|
942
|
+
// is unreliable; the current event was already accumulated above, so the
|
|
943
|
+
// anchor's own row is always present for the Phase 12 gate overlay. This
|
|
944
|
+
// mirrors the native harness's pause-before-react semantics; Phase 12
|
|
945
|
+
// reconciles the denied calls and its trim remains the last-resort
|
|
946
|
+
// backstop for anything that persisted before the stop.
|
|
947
|
+
if (!firstDenialDetected && hitlDir && (denialLedgerDirty || event.type === "tool_call")) {
|
|
948
|
+
denialLedgerDirty = false;
|
|
949
|
+
const denials = await readDenialLedger(hitlDir);
|
|
950
|
+
if (denials.length > 0) {
|
|
951
|
+
firstDenialDetected = true;
|
|
952
|
+
console.log(
|
|
953
|
+
`ExecuteCursor first denial detected (${denials.length} ledger ` +
|
|
954
|
+
`entr${denials.length === 1 ? "y" : "ies"}); stopping turn to pause ` +
|
|
955
|
+
`cleanly for approval: execution=${executionId}`,
|
|
956
|
+
);
|
|
957
|
+
if (run.supports?.("cancel")) {
|
|
958
|
+
// Kept (not fire-and-forget): awaited timeboxed before Phase 12 so
|
|
959
|
+
// the ledger read and tree capture see a stopped agent.
|
|
960
|
+
denialCancelSettled = run.cancel().then(
|
|
961
|
+
() => {},
|
|
962
|
+
(cancelErr: unknown) => {
|
|
963
|
+
console.warn(
|
|
964
|
+
`ExecuteCursor run.cancel() after first denial failed (non-fatal): ` +
|
|
965
|
+
`execution=${executionId}, ` +
|
|
966
|
+
`error=${cancelErr instanceof Error ? cancelErr.message : cancelErr}`,
|
|
967
|
+
);
|
|
968
|
+
},
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
break;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
653
975
|
deltaEnricher.applyEnrichments(status.messages);
|
|
654
976
|
eventCount++;
|
|
655
977
|
|
|
@@ -703,12 +1025,16 @@ async function executeCursorInner(
|
|
|
703
1025
|
}
|
|
704
1026
|
}
|
|
705
1027
|
} catch (streamErr) {
|
|
706
|
-
// run.cancel() from the stall watchdog
|
|
707
|
-
//
|
|
708
|
-
//
|
|
1028
|
+
// run.cancel() — from the stall watchdog or the first-denial stop — can
|
|
1029
|
+
// make the stream iterator reject as it tears down; that is the expected
|
|
1030
|
+
// teardown for both, so swallow it and fall through (stallDetected ->
|
|
1031
|
+
// Phase 11a; firstDenialDetected -> Phase 12). Anything else is a genuine
|
|
709
1032
|
// stream failure — rethrow it to the outer error handler.
|
|
710
|
-
if (!stallDetected) throw streamErr;
|
|
711
|
-
console.warn(
|
|
1033
|
+
if (!stallDetected && !firstDenialDetected) throw streamErr;
|
|
1034
|
+
console.warn(
|
|
1035
|
+
`ExecuteCursor stream ended via cancel: execution=${executionId}, ` +
|
|
1036
|
+
`stall=${stallDetected}, firstDenial=${firstDenialDetected}`,
|
|
1037
|
+
);
|
|
712
1038
|
}
|
|
713
1039
|
|
|
714
1040
|
periodicHeartbeat.stop();
|
|
@@ -864,12 +1190,146 @@ async function executeCursorInner(
|
|
|
864
1190
|
// harness — the approval surface is driven entirely by tool-call status. We
|
|
865
1191
|
// deliberately do NOT set status.pendingApprovals here: any value would be
|
|
866
1192
|
// discarded by the backend's recompute on the next updateStatus.
|
|
1193
|
+
//
|
|
1194
|
+
// Before reading the ledger, wait (timeboxed) for the first-denial-stop's
|
|
1195
|
+
// run.cancel() to settle. run.cancel() races the SDK's auto-execution: until
|
|
1196
|
+
// it lands, the agent process may still attempt a post-denial workaround
|
|
1197
|
+
// whose hook denial would land AFTER a premature ledger read — the row then
|
|
1198
|
+
// never collapses and renders as RUNNING forever (production case
|
|
1199
|
+
// aex_01kwj07f7g23c3wp9sn8496z5g) — or a late tool could mutate the tree
|
|
1200
|
+
// mid-capture. The timebox keeps a wedged cancel from hanging the pause;
|
|
1201
|
+
// the Phase 12 trims below remain the backstop for that degraded case.
|
|
1202
|
+
if (firstDenialDetected && denialCancelSettled) {
|
|
1203
|
+
await Promise.race([
|
|
1204
|
+
denialCancelSettled,
|
|
1205
|
+
new Promise<void>((resolve) => {
|
|
1206
|
+
const timer = setTimeout(resolve, FIRST_DENIAL_CANCEL_TIMEOUT_MS);
|
|
1207
|
+
timer.unref();
|
|
1208
|
+
}),
|
|
1209
|
+
]);
|
|
1210
|
+
}
|
|
867
1211
|
const deniedLedger = await readDenialLedger(hitlDir ?? "");
|
|
868
|
-
|
|
869
|
-
|
|
1212
|
+
|
|
1213
|
+
// Capture mode: author the net change set to the file_review ledger as the
|
|
1214
|
+
// CANDIDATE_CAPTURED event (projected server-side to a file_change_set
|
|
1215
|
+
// AWAITING_REVIEW — the single review surface). The runner-owned gate files
|
|
1216
|
+
// are excluded from the capture. The agent's edits are LEFT applied on the
|
|
1217
|
+
// working tree (Cursor parity — the user reviews the real change; nothing is
|
|
1218
|
+
// committed and the next turn is blocked until approval, and a reject snaps
|
|
1219
|
+
// each file back on resume). Runs BEFORE the denial reconcile so a denied
|
|
1220
|
+
// (gitignored) write stays on the deny-gate path while every flowed edit is
|
|
1221
|
+
// captured to the ledger.
|
|
1222
|
+
let capturedChangeCount = 0;
|
|
1223
|
+
// `baselineTree !== undefined` means a baseline was authored this turn — the
|
|
1224
|
+
// git tree sha for a git workspace, or "" (empty, but authored) for a non-git
|
|
1225
|
+
// one. A plain truthiness check would wrongly skip the non-git capture.
|
|
1226
|
+
if (captureMode && baselineTree !== undefined && primaryWorkspaceDir) {
|
|
1227
|
+
const deniedTokens = new Set(deniedLedger.map((e) => e.token));
|
|
1228
|
+
// Approved-command turn facts (DD-28): when every mutation-capable call
|
|
1229
|
+
// this turn was a consented shell command, attach the provenance so the
|
|
1230
|
+
// backend can verify the cited consent rows and auto-keep the set instead
|
|
1231
|
+
// of arming a second gate. Fail-closed: any non-qualifying turn attaches
|
|
1232
|
+
// nothing and reviews manually exactly as before.
|
|
1233
|
+
const commandProvenance = deriveTurnCommandProvenance({
|
|
1234
|
+
messages: status.messages,
|
|
1235
|
+
turnStartIndex: turnStartMessageIndex,
|
|
1236
|
+
deniedTokens,
|
|
1237
|
+
grantTokenToConsentId: new Map(
|
|
1238
|
+
(approvalGrants ?? []).map((g) => [
|
|
1239
|
+
primaryToken(g.key, g.salient, g.contentDigest),
|
|
1240
|
+
g.sourceToolCallId,
|
|
1241
|
+
]),
|
|
1242
|
+
),
|
|
1243
|
+
globalBypass,
|
|
1244
|
+
});
|
|
1245
|
+
if (commandProvenance) {
|
|
1246
|
+
console.log(
|
|
1247
|
+
`ExecuteCursor capture: turn qualifies for approved-command auto-keep ` +
|
|
1248
|
+
`(consent rows: ${commandProvenance.consentToolCallIds.join(",") || "(auto_approve_all)"}); ` +
|
|
1249
|
+
`attaching provenance to candidate (execution=${executionId})`,
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
1252
|
+
const captured = await captureTurnToLedger({
|
|
1253
|
+
status,
|
|
1254
|
+
gitRoot: primaryWorkspaceDir,
|
|
1255
|
+
executionId,
|
|
1256
|
+
changeSetId,
|
|
1257
|
+
baselineTree,
|
|
1258
|
+
messages: status.messages,
|
|
1259
|
+
deniedTokens,
|
|
1260
|
+
commandProvenance,
|
|
1261
|
+
// Scope sub-agent row stamping to this turn: the seeded prior sub-agents
|
|
1262
|
+
// (cloned in on resume) are the "before this turn" rows to skip.
|
|
1263
|
+
priorSubAgentToolCallIds: collectSubAgentToolCallIds(seededSubAgents),
|
|
1264
|
+
// The CAS half: read the sidecar the hook staged this turn and compose it
|
|
1265
|
+
// into the change set. hitlDir + storage are present when captureIgnored
|
|
1266
|
+
// was on (a git tree's gitignored writes, or ALL writes in a non-git
|
|
1267
|
+
// workspace). In a git tree this composes with the git diff (HYBRID); in a
|
|
1268
|
+
// non-git workspace it IS the whole change set (CAS-only).
|
|
1269
|
+
hitlDir,
|
|
1270
|
+
storage: artifactStorage,
|
|
1271
|
+
gitWorkspace,
|
|
1272
|
+
});
|
|
1273
|
+
capturedChangeCount = captured.length;
|
|
1274
|
+
if (capturedChangeCount > 0) {
|
|
1275
|
+
console.log(
|
|
1276
|
+
`ExecuteCursor capture: ${capturedChangeCount} file change(s) authored to the ` +
|
|
1277
|
+
`file_review ledger (change_set=${changeSetId}), working tree left applied ` +
|
|
1278
|
+
`for review (execution=${executionId})`,
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// The gate reads each denied file's pre-edit `before` from the workspace the
|
|
1284
|
+
// runner is co-located with (local FS for OSS; the sandbox in cloud), so a
|
|
1285
|
+
// whole-file rewrite gate renders a true before/after diff. The tool was
|
|
1286
|
+
// DENIED, so disk still holds the old content. User files are never platform
|
|
1287
|
+
// paths, so no platformDir routing is needed here.
|
|
1288
|
+
const gateWorkspaceBackend = new LocalWorkspaceBackend(primaryWorkspaceDir);
|
|
1289
|
+
const deniedToolCalls = await reconcileDeniedToolCalls(
|
|
1290
|
+
status.messages,
|
|
1291
|
+
deniedLedger,
|
|
1292
|
+
mergedPolicies,
|
|
1293
|
+
gateWorkspaceBackend,
|
|
1294
|
+
);
|
|
1295
|
+
// Observability: a synthesized placeholder (id `approval:*`) means a denial
|
|
1296
|
+
// correlated to NO streamed tool call in either the exact or the normalized
|
|
1297
|
+
// pass. After the normalized-path fallback this should be ~0; a non-zero rate
|
|
1298
|
+
// is the early-warning signal of a NEW identity drift (the gate would then
|
|
1299
|
+
// show "No preview available" with no diff). Logged, not thrown — the
|
|
1300
|
+
// synthesized gate still safely surfaces the approval.
|
|
1301
|
+
const synthesizedGateCount = deniedToolCalls.filter((tc) =>
|
|
1302
|
+
tc.id.startsWith("approval:"),
|
|
1303
|
+
).length;
|
|
1304
|
+
if (synthesizedGateCount > 0) {
|
|
1305
|
+
console.warn(
|
|
1306
|
+
`ExecuteCursor reconcile synthesized ${synthesizedGateCount} placeholder gate(s) ` +
|
|
1307
|
+
`with no correlated stream call (execution=${executionId}); ` +
|
|
1308
|
+
`possible hook/stream identity drift — gate(s) will lack a diff`,
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1311
|
+
if (deniedToolCalls.length > 0 || capturedChangeCount > 0) {
|
|
1312
|
+
if (deniedToolCalls.length > 0) {
|
|
1313
|
+
// Deterministic clean-pause: a turn that pauses for approval must read as
|
|
1314
|
+
// the same shape the native harness produces — pre-tool text + the gated
|
|
1315
|
+
// tool calls — never the model's provisional reaction to Cursor's deny
|
|
1316
|
+
// (e.g. "blocked by a hook; enable it in your Cursor settings"). We blank
|
|
1317
|
+
// that reaction in place (keeping the message count, so the finalize stays
|
|
1318
|
+
// append-only) rather than removing it. See
|
|
1319
|
+
// clearProvisionalPostDenialNarration for the full rationale.
|
|
1320
|
+
const redactedNarration = clearProvisionalPostDenialNarration(status.messages, deniedToolCalls);
|
|
1321
|
+
if (redactedNarration.length > 0) {
|
|
1322
|
+
console.log(
|
|
1323
|
+
`ExecuteCursor redacted ${redactedNarration.length} provisional post-denial narration message(s) before pausing for approval`,
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
870
1327
|
status.phase = ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL;
|
|
871
1328
|
await persist(status);
|
|
872
|
-
console.log(
|
|
1329
|
+
console.log(
|
|
1330
|
+
`ExecuteCursor returning WAITING_FOR_APPROVAL: ${deniedToolCalls.length} gated tool(s), ` +
|
|
1331
|
+
`${capturedChangeCount} file card(s) pending`,
|
|
1332
|
+
);
|
|
873
1333
|
return slimStatus(status);
|
|
874
1334
|
}
|
|
875
1335
|
|
|
@@ -1216,6 +1676,22 @@ async function executeCursorInner(
|
|
|
1216
1676
|
}
|
|
1217
1677
|
}
|
|
1218
1678
|
|
|
1679
|
+
// Collapse any redundant same-identity tool-call twin born this turn before
|
|
1680
|
+
// the terminal persist. On a resume turn the gated tool is already granted, so
|
|
1681
|
+
// there is no denial ledger and reconcileDeniedToolCalls never runs — the
|
|
1682
|
+
// extra attempt the model emits beside the approved action (a stuck RUNNING
|
|
1683
|
+
// zombie, a denied-reported-as-success COMPLETED, or an all-no-change double)
|
|
1684
|
+
// would otherwise persist as a second "No preview available" card. The shared
|
|
1685
|
+
// routine keeps the diff/output carrier and blanks the rest to hidden SKIPPED
|
|
1686
|
+
// rows in place, preserving each committed id so the finalize stays append-only.
|
|
1687
|
+
const collapsedTwins = collapseRedundantToolCallTwins(status.messages);
|
|
1688
|
+
if (collapsedTwins > 0) {
|
|
1689
|
+
console.log(
|
|
1690
|
+
`ExecuteCursor collapsed ${collapsedTwins} redundant tool-call twin(s) at ` +
|
|
1691
|
+
`terminal finalize (kept in place as hidden SKIPPED rows): execution=${executionId}`,
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1219
1695
|
// NOW persist — subscriber sees COMPLETED + structured_output atomically
|
|
1220
1696
|
await persist(status);
|
|
1221
1697
|
|
|
@@ -1390,6 +1866,10 @@ async function executeCursorInner(
|
|
|
1390
1866
|
// point so no orphaned timer survives the activity.
|
|
1391
1867
|
stallWatchdog?.stop();
|
|
1392
1868
|
|
|
1869
|
+
// Close the denial-ledger watcher on EVERY exit path (idempotent) so no
|
|
1870
|
+
// orphaned fs.watch handle survives the activity.
|
|
1871
|
+
stopDenialWatcher?.();
|
|
1872
|
+
|
|
1393
1873
|
// Tear down the HITL gate on EVERY exit path (success, error, approval
|
|
1394
1874
|
// pause, cancellation) so attaching a real repo leaves the user's
|
|
1395
1875
|
// .cursor/hooks.json and workspace untouched between turns (issue #173).
|
|
@@ -1408,6 +1888,45 @@ async function executeCursorInner(
|
|
|
1408
1888
|
}
|
|
1409
1889
|
}
|
|
1410
1890
|
|
|
1891
|
+
/**
|
|
1892
|
+
* Seed an in-progress status from the persisted execution on a durable resume
|
|
1893
|
+
* (HITL approval, pause/resume, or transient recovery) so the upcoming turn
|
|
1894
|
+
* APPENDS onto prior history instead of replacing it. This is the Cursor analog
|
|
1895
|
+
* of the deep-agent's seedStatusFromExecution (execute-deep-agent/index.ts).
|
|
1896
|
+
*
|
|
1897
|
+
* Why it is required: a resumed Cursor agent re-issues the previously gated tool
|
|
1898
|
+
* calls with brand-new call ids. Without seeding, the MessageAccumulator would
|
|
1899
|
+
* rebuild the transcript from empty and emit a status that drops the already-
|
|
1900
|
+
* committed tool-call ids. The backend's append-only-at-identity guard
|
|
1901
|
+
* (AgentExecutionUpdateStatusHandler / update_status.go) rejects any non-
|
|
1902
|
+
* terminal update that drops a committed tool-call id, so the resumed progress
|
|
1903
|
+
* would never persist — the run stalls in WAITING_FOR_APPROVAL with no pending
|
|
1904
|
+
* approvals and the workflow watchdog fails it. Seeding makes the resume status
|
|
1905
|
+
* a strict superset; the re-runs are then reconciled in place onto these seeded
|
|
1906
|
+
* calls by canonical identity inside the accumulator.
|
|
1907
|
+
*
|
|
1908
|
+
* The persisted protos are cloned so the input execution stays immutable, and
|
|
1909
|
+
* the seeded messages are pushed into status.messages (which the accumulator
|
|
1910
|
+
* wraps by reference) BEFORE the accumulator is constructed. Sub-agent
|
|
1911
|
+
* executions are returned rather than written to status.subAgentExecutions
|
|
1912
|
+
* directly, because the accumulator owns that array (it overwrites
|
|
1913
|
+
* status.subAgentExecutions with its own on every flush) — handing them to the
|
|
1914
|
+
* accumulator keeps the seeded sub-agent rows from being clobbered.
|
|
1915
|
+
*
|
|
1916
|
+
* @returns the cloned sub-agent executions to seed into the MessageAccumulator.
|
|
1917
|
+
*/
|
|
1918
|
+
function seedCursorTranscriptFromExecution(
|
|
1919
|
+
status: AgentExecutionStatus,
|
|
1920
|
+
execution: AgentExecution,
|
|
1921
|
+
): SubAgentExecution[] {
|
|
1922
|
+
const persisted = execution.status;
|
|
1923
|
+
if (!persisted || persisted.messages.length === 0) return [];
|
|
1924
|
+
for (const message of persisted.messages) {
|
|
1925
|
+
status.messages.push(clone(AgentMessageSchema, message));
|
|
1926
|
+
}
|
|
1927
|
+
return persisted.subAgentExecutions.map((sub) => clone(SubAgentExecutionSchema, sub));
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1411
1930
|
// ---------------------------------------------------------------------------
|
|
1412
1931
|
// Structured Output Extraction (Cursor Harness Tier 2)
|
|
1413
1932
|
// ---------------------------------------------------------------------------
|
|
@@ -1417,10 +1936,9 @@ async function executeCursorInner(
|
|
|
1417
1936
|
* economy-tier LLM with withStructuredOutput (function-calling).
|
|
1418
1937
|
* Guarantees schema-conformant JSON output via the API's tool-use mechanism.
|
|
1419
1938
|
*
|
|
1420
|
-
*
|
|
1421
|
-
*
|
|
1422
|
-
*
|
|
1423
|
-
* call-llm.ts constructModel().
|
|
1939
|
+
* Construction (registry-id resolution, provider inference, proxy wiring) is
|
|
1940
|
+
* delegated to the shared buildChatModel so the economy model's registry id is
|
|
1941
|
+
* always resolved to a provider API id before the call.
|
|
1424
1942
|
*/
|
|
1425
1943
|
async function extractStructuredOutput(
|
|
1426
1944
|
agentResponse: string,
|
|
@@ -1428,39 +1946,18 @@ async function extractStructuredOutput(
|
|
|
1428
1946
|
config: Config,
|
|
1429
1947
|
primaryModel: string,
|
|
1430
1948
|
): Promise<unknown | null> {
|
|
1431
|
-
const { ChatOpenAI } = await import("@langchain/openai");
|
|
1432
|
-
const { ChatAnthropic } = await import("@langchain/anthropic");
|
|
1433
|
-
const { inferProvider, resolveProxyBaseUrl, buildProxyHeaders } = await import("../../shared/llm-proxy.js");
|
|
1434
1949
|
const { getEconomyModel } = await import("../../shared/model-registry.js");
|
|
1950
|
+
const { buildChatModel } = await import("../../shared/model-client.js");
|
|
1435
1951
|
|
|
1436
1952
|
const extractionModel = await getEconomyModel(primaryModel);
|
|
1437
|
-
const provider = inferProvider(extractionModel);
|
|
1438
|
-
|
|
1439
1953
|
const proxyEndpoint = config.proxyEndpoint ?? config.stigmerBackendEndpoint;
|
|
1440
|
-
|
|
1441
|
-
const
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
: (config.stigmerToken ?? process.env.ANTHROPIC_API_KEY ?? "proxy-managed");
|
|
1448
|
-
|
|
1449
|
-
const llm = provider === "openai"
|
|
1450
|
-
? new ChatOpenAI({
|
|
1451
|
-
model: extractionModel,
|
|
1452
|
-
apiKey,
|
|
1453
|
-
temperature: 0,
|
|
1454
|
-
maxTokens: 4096,
|
|
1455
|
-
configuration: { baseURL: baseUrl, defaultHeaders: headers },
|
|
1456
|
-
})
|
|
1457
|
-
: new ChatAnthropic({
|
|
1458
|
-
model: extractionModel,
|
|
1459
|
-
apiKey,
|
|
1460
|
-
temperature: 0,
|
|
1461
|
-
maxTokens: 4096,
|
|
1462
|
-
clientOptions: { baseURL: baseUrl, defaultHeaders: headers },
|
|
1463
|
-
});
|
|
1954
|
+
|
|
1955
|
+
const { model: llm } = await buildChatModel({
|
|
1956
|
+
modelName: extractionModel,
|
|
1957
|
+
proxyEndpoint,
|
|
1958
|
+
stigmerToken: config.stigmerToken ?? undefined,
|
|
1959
|
+
maxTokens: 4096,
|
|
1960
|
+
});
|
|
1464
1961
|
|
|
1465
1962
|
const zodSchema = jsonSchemaToZod(schema);
|
|
1466
1963
|
const structured = llm.withStructuredOutput(zodSchema);
|
|
@@ -1492,6 +1989,12 @@ export interface BuildPromptInput {
|
|
|
1492
1989
|
workspaceFileRefs: string[];
|
|
1493
1990
|
attachmentPaths: string[];
|
|
1494
1991
|
pendingApprovals: import("@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb").PendingApproval[];
|
|
1992
|
+
/**
|
|
1993
|
+
* Approved whole-file writes the runner already applied itself (exact-apply).
|
|
1994
|
+
* The reinvocation prompt marks these as done so the model does not redo them;
|
|
1995
|
+
* the remaining approved actions are the ones it must still carry out.
|
|
1996
|
+
*/
|
|
1997
|
+
appliedToolCallIds?: ReadonlySet<string>;
|
|
1495
1998
|
interactionMode?: InteractionMode;
|
|
1496
1999
|
}
|
|
1497
2000
|
|
|
@@ -1528,9 +2031,14 @@ export function buildPrompt(input: BuildPromptInput): string {
|
|
|
1528
2031
|
const isHitlReinvocation = approvalDecisions !== undefined && approvalDecisions.size > 0;
|
|
1529
2032
|
|
|
1530
2033
|
// HITL reinvocation: the agent is resumed, so its native context carries the
|
|
1531
|
-
// prior conversation; the reinvocation prompt conveys the approval decisions
|
|
2034
|
+
// prior conversation; the reinvocation prompt conveys the approval decisions
|
|
2035
|
+
// (and which approved writes the runner already exact-applied).
|
|
1532
2036
|
if (isHitlReinvocation) {
|
|
1533
|
-
return buildReinvocationPrompt(
|
|
2037
|
+
return buildReinvocationPrompt(
|
|
2038
|
+
input.pendingApprovals,
|
|
2039
|
+
approvalDecisions,
|
|
2040
|
+
input.appliedToolCallIds,
|
|
2041
|
+
);
|
|
1534
2042
|
}
|
|
1535
2043
|
|
|
1536
2044
|
// A successfully resumed agent carries its own conversation context via the
|