@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
|
@@ -26,16 +26,18 @@
|
|
|
26
26
|
* starts from the user message plus re-injected instructions.
|
|
27
27
|
*/
|
|
28
28
|
import { heartbeat, Context, CancelledFailure } from "@temporalio/activity";
|
|
29
|
-
import { create } from "@bufbuild/protobuf";
|
|
29
|
+
import { create, clone } from "@bufbuild/protobuf";
|
|
30
30
|
import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
31
31
|
import { AgentMessageSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
32
|
-
import {
|
|
32
|
+
import { SubAgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
|
|
33
|
+
import { ExecutionControlSignal, ExecutionPhase, FileChangeSetStatus, InteractionMode, MessageType, ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
33
34
|
import { StigmerClient } from "../../client/stigmer-client.js";
|
|
34
35
|
import { resolveAgent } from "./session-lifecycle.js";
|
|
35
36
|
import { CursorMode } from "@stigmer/protos/ai/stigmer/agentic/session/v1/enum_pb";
|
|
36
37
|
import { determineCursorMode, isCloudMode } from "./cursor-mode.js";
|
|
37
|
-
import { MessageAccumulator, reconcileDeniedToolCalls, cancelInProgressSubAgentProtos } from "./message-translator.js";
|
|
38
|
+
import { MessageAccumulator, reconcileDeniedToolCalls, clearProvisionalPostDenialNarration, cancelInProgressSubAgentProtos, collapseRedundantToolCallTwins } from "./message-translator.js";
|
|
38
39
|
import { utcTimestamp, persistStatus, reportSetupProgress, slimStatus } from "../../shared/status.js";
|
|
40
|
+
import { collectSubAgentToolCallIds } from "../../shared/tool-row.js";
|
|
39
41
|
import { startStallWatchdog, StallTimeoutError, formatStallFailure } from "../../shared/stall-watchdog.js";
|
|
40
42
|
import { createArtifactStorage, loadArtifactStorageConfig } from "../../shared/artifact-storage.js";
|
|
41
43
|
import { publishPlanArtifact } from "../../shared/plan-artifact.js";
|
|
@@ -46,7 +48,7 @@ import { StreamingUpdateScheduler, loadStreamingConfig } from "../../shared/stre
|
|
|
46
48
|
import { createCursorEventRecorder } from "./cursor-event-recorder.js";
|
|
47
49
|
import { resolveMcpServers, validateMcpServerEnv } from "./mcp-resolver.js";
|
|
48
50
|
import { mergeApprovalPolicies } from "./approval-policy.js";
|
|
49
|
-
import {
|
|
51
|
+
import { deriveActiveLeases } from "../../shared/approval-policy.js";
|
|
50
52
|
import { backfillMcpServersIfNeeded } from "./connect-backfill.js";
|
|
51
53
|
import { resolveExecutionEnv } from "./env-resolver.js";
|
|
52
54
|
import { resolveBlueprint } from "./blueprint-resolver.js";
|
|
@@ -56,7 +58,14 @@ import { resolveAttachments } from "./attachment-resolver.js";
|
|
|
56
58
|
import { buildEnhancedPrompt, buildReinvocationPrompt } from "./prompt-builder.js";
|
|
57
59
|
import { installHitlGate, removeHitlGate } from "./workspace-setup.js";
|
|
58
60
|
import { ensureHitlDir } from "../../shared/workspace/platform-dir.js";
|
|
59
|
-
import {
|
|
61
|
+
import { LocalWorkspaceBackend } from "../../shared/workspace/local-backend.js";
|
|
62
|
+
import { buildApprovalState, buildApprovalGrants, emitCursorGrantReceipts, primaryToken, readDenialLedger, reconstructAdjudicatedApprovals, watchDenialLedger } from "./approval-state.js";
|
|
63
|
+
import { deriveTurnCommandProvenance } from "./command-provenance.js";
|
|
64
|
+
import { applyApprovedWholeFileWrites, excludeAppliedFromGrants } from "./exact-apply.js";
|
|
65
|
+
import { isGitWorkTree } from "../../shared/filereview/git-substrate.js";
|
|
66
|
+
import { captureBaselineToLedger, captureTurnToLedger, applyCaptureDecisions, deriveCaptureMode, } from "./capture-flow.js";
|
|
67
|
+
import { deriveExecutionFingerprintKey } from "../../shared/approval-fingerprint.js";
|
|
68
|
+
import { getRunnerHitlMasterSecret } from "../../shared/fingerprint-secret.js";
|
|
60
69
|
import { provisionCursorWorkspace } from "./workspace-provision.js";
|
|
61
70
|
import { setInterceptorExecutionId, runWithExecutionContext } from "./fetch-interceptor.js";
|
|
62
71
|
import { closeProxySessions } from "./http2-interceptor.js";
|
|
@@ -64,12 +73,18 @@ import { resolveModelId, ensureLoaded as ensurePricingLoaded } from "./model-pri
|
|
|
64
73
|
import { UsageAccumulator } from "./usage-accumulator.js";
|
|
65
74
|
import { StreamingUsageSummarySchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/usage_pb";
|
|
66
75
|
import { activityStarted, activityFinished } from "../../idle-watchdog.js";
|
|
76
|
+
import { normalizeActivityInput } from "../../shared/activity-input.js";
|
|
67
77
|
import { getCapturedRejection, clearCapturedRejection } from "./rejection-capture.js";
|
|
68
78
|
import { synthesizeError, formatClassifiedError, shouldRetryWithFreshAgent } from "./error-classifier.js";
|
|
69
79
|
import { createAgent, createCloudAgent } from "./session-lifecycle.js";
|
|
70
80
|
import { setMaxListeners } from "node:events";
|
|
71
81
|
import { startHeartbeat } from "../../shared/heartbeat.js";
|
|
72
82
|
import { getShutdownSignalForQueue } from "../../runner-manager.js";
|
|
83
|
+
// How long Phase 12 waits for the first-denial-stop's run.cancel() to settle
|
|
84
|
+
// before reading the final denial ledger and capturing the turn's tree. Long
|
|
85
|
+
// enough for the SDK's normal teardown, short enough that a wedged cancel
|
|
86
|
+
// cannot noticeably delay the approval pause the user is already waiting on.
|
|
87
|
+
const FIRST_DENIAL_CANCEL_TIMEOUT_MS = 5_000;
|
|
73
88
|
/**
|
|
74
89
|
* Creates the activity functions bound to the runner config.
|
|
75
90
|
* Returned object is passed to Temporal Worker.create({ activities }).
|
|
@@ -81,10 +96,15 @@ export function createCursorActivities(config) {
|
|
|
81
96
|
tokenRef: config.stigmerTokenRef,
|
|
82
97
|
});
|
|
83
98
|
return {
|
|
84
|
-
|
|
99
|
+
// Accepts the new typed object OR the legacy positional args (transitional
|
|
100
|
+
// dual-shape so the runner can deploy before the control planes — see
|
|
101
|
+
// shared/activity-input.ts). Drop the positional arm once both control
|
|
102
|
+
// planes send the object.
|
|
103
|
+
ExecuteCursor: async (arg0, arg1) => {
|
|
104
|
+
const { executionId, threadId, turnSeq } = normalizeActivityInput(arg0, arg1);
|
|
85
105
|
activityStarted();
|
|
86
106
|
try {
|
|
87
|
-
return await executeCursor(config, client, executionId, threadId);
|
|
107
|
+
return await executeCursor(config, client, executionId, threadId, turnSeq);
|
|
88
108
|
}
|
|
89
109
|
finally {
|
|
90
110
|
activityFinished();
|
|
@@ -92,15 +112,19 @@ export function createCursorActivities(config) {
|
|
|
92
112
|
},
|
|
93
113
|
};
|
|
94
114
|
}
|
|
95
|
-
async function executeCursor(config, client, executionId, threadId) {
|
|
96
|
-
console.log(`ExecuteCursor started: execution=${executionId}, threadId=${threadId || "(new)"}`);
|
|
115
|
+
async function executeCursor(config, client, executionId, threadId, turnSeq) {
|
|
116
|
+
console.log(`ExecuteCursor started: execution=${executionId}, threadId=${threadId || "(new)"}, turnSeq=${turnSeq}`);
|
|
97
117
|
// Ensure fresh HTTP/2 transport — prevents a degraded session from a
|
|
98
118
|
// prior workflow task from poisoning this execution's agent stream.
|
|
99
119
|
closeProxySessions();
|
|
100
120
|
setInterceptorExecutionId(executionId);
|
|
101
|
-
return runWithExecutionContext(executionId, () => executeCursorInner(config, client, executionId, threadId));
|
|
121
|
+
return runWithExecutionContext(executionId, () => executeCursorInner(config, client, executionId, threadId, turnSeq));
|
|
102
122
|
}
|
|
103
|
-
async function executeCursorInner(config, client, executionId, threadId
|
|
123
|
+
async function executeCursorInner(config, client, executionId, threadId,
|
|
124
|
+
// turnSeq is the monotonic HITL-cycle index (0 on the first turn). The
|
|
125
|
+
// file-review producer consumes it to mint the deterministic change-set id
|
|
126
|
+
// (executionId:turnSeq) in the capture phase.
|
|
127
|
+
turnSeq) {
|
|
104
128
|
const status = create(AgentExecutionStatusSchema, {
|
|
105
129
|
phase: ExecutionPhase.EXECUTION_IN_PROGRESS,
|
|
106
130
|
startedAt: utcTimestamp(),
|
|
@@ -135,6 +159,29 @@ async function executeCursorInner(config, client, executionId, threadId) {
|
|
|
135
159
|
// message surfaced to the user; both feed the Phase 11a stall branch.
|
|
136
160
|
let stallDetected = false;
|
|
137
161
|
let stallError;
|
|
162
|
+
// Set the moment the preToolUse hook records its first denial in the ledger.
|
|
163
|
+
// We then stop consuming the stream and cancel the run so the model never
|
|
164
|
+
// reacts to Cursor's tool-failure surface (narrate defeat, attempt a second
|
|
165
|
+
// gated tool) — converging the Cursor harness toward the native harness, which
|
|
166
|
+
// pauses BEFORE the model sees a denial. Phase 12 reconciles the denied tool
|
|
167
|
+
// calls into WAITING_FOR_APPROVAL exactly as after a natural stream end.
|
|
168
|
+
let firstDenialDetected = false;
|
|
169
|
+
// Flipped by the denial-ledger fs watcher the instant the hook writes a
|
|
170
|
+
// denial, so the NEXT stream event of ANY type triggers the ledger read —
|
|
171
|
+
// instead of waiting for the next tool_call event, during which the model's
|
|
172
|
+
// full post-denial reaction (thinking, narration, a workaround tool) would
|
|
173
|
+
// stream and persist (observed in production: aex_01kwj07f7g23c3wp9sn8496z5g).
|
|
174
|
+
// The tool_call-event read below remains the backstop where fs.watch is
|
|
175
|
+
// unreliable.
|
|
176
|
+
let denialLedgerDirty = false;
|
|
177
|
+
let stopDenialWatcher;
|
|
178
|
+
// The in-flight run.cancel() started by the first-denial stop. Awaited
|
|
179
|
+
// (timeboxed) before Phase 12 so the agent process has actually stopped
|
|
180
|
+
// before the final ledger read and the turn-boundary tree capture — closing
|
|
181
|
+
// the race where a post-denial workaround's ledger entry lands after the
|
|
182
|
+
// read (it would then never be collapsed) or a late tool mutates the tree
|
|
183
|
+
// mid-capture.
|
|
184
|
+
let denialCancelSettled;
|
|
138
185
|
let periodicHeartbeat;
|
|
139
186
|
// Progress-based stall watchdog (see ../../shared/stall-watchdog.ts). Stopped
|
|
140
187
|
// in the finally on every exit path; complements the liveness heartbeat.
|
|
@@ -174,6 +221,32 @@ async function executeCursorInner(config, client, executionId, threadId) {
|
|
|
174
221
|
await reportSetupProgress(client, executionId, "Provisioning workspace");
|
|
175
222
|
blueprint.workspaceDirs = await provisionCursorWorkspace(config, session, envVars, sessionId ?? "");
|
|
176
223
|
heartbeat();
|
|
224
|
+
// Apply-then-review is the universal file-review model (Slice 2c). When the
|
|
225
|
+
// primary workspace is a real git work tree, file edits flow during the turn
|
|
226
|
+
// and are captured per-file from the git diff at the turn boundary
|
|
227
|
+
// (capture-flow.ts / shared/filereview/git-substrate.ts). A NON-git workspace
|
|
228
|
+
// has no git snapshot, so it captures every file write via the path-scoped CAS
|
|
229
|
+
// substrate instead — which requires artifact storage to persist blobs; when
|
|
230
|
+
// storage is unavailable a non-git workspace falls back to the classic
|
|
231
|
+
// deny-gate (no regression). `gitWorkspace` selects the substrate; both flow
|
|
232
|
+
// file edits and review post-hoc, and the deny-gate then survives only for
|
|
233
|
+
// shell/MCP/irreversible tools. Detected once from the provisioned primary root.
|
|
234
|
+
const primaryWorkspaceDir = blueprint.workspaceDirs[0];
|
|
235
|
+
const gitWorkspace = primaryWorkspaceDir
|
|
236
|
+
? await isGitWorkTree(primaryWorkspaceDir)
|
|
237
|
+
: false;
|
|
238
|
+
const captureMode = deriveCaptureMode(primaryWorkspaceDir, gitWorkspace, !!artifactStorage);
|
|
239
|
+
// Pre-turn baseline tree, pinned before the agent runs (capture mode only)
|
|
240
|
+
// so the turn-end capture diffs against it and the tree restores exactly.
|
|
241
|
+
let baselineTree;
|
|
242
|
+
// Deterministic id of the change set this turn may produce:
|
|
243
|
+
// `${executionId}:${turnSeq}`. Minted from the workflow-threaded turn index
|
|
244
|
+
// so it is stable across a Temporal retry (idempotent ledger authoring) and
|
|
245
|
+
// unique per turn. The resume reconcile reads the change set id back from the
|
|
246
|
+
// DECIDED projection, not from turnSeq — so a "wasted" id on a pure-reconcile
|
|
247
|
+
// resume (which never authors a baseline) is harmless.
|
|
248
|
+
const changeSetId = `${executionId}:${turnSeq}`;
|
|
249
|
+
heartbeat();
|
|
177
250
|
// Set OTel baggage so downstream calls carry execution context.
|
|
178
251
|
try {
|
|
179
252
|
const { setBaggage, BAGGAGE_EXECUTION_ID, BAGGAGE_SESSION_ID, BAGGAGE_ORG_ID } = await import("../../otel.js");
|
|
@@ -202,12 +275,72 @@ async function executeCursorInner(config, client, executionId, threadId) {
|
|
|
202
275
|
// by reinvocation time — the decision survives only on the tool call. This
|
|
203
276
|
// feeds both the grant builder and the reinvocation prompt below.
|
|
204
277
|
let adjudicatedApprovals = [];
|
|
278
|
+
// tool-call id -> content digest of the approved edit, threaded into the
|
|
279
|
+
// grant builder so an approved edit is authorized by its exact content (a
|
|
280
|
+
// sibling edit to the same file re-gates). Sourced from the persisted
|
|
281
|
+
// approval_content_digest field (see reconstructAdjudicatedApprovals).
|
|
282
|
+
let adjudicatedContentDigests = new Map();
|
|
283
|
+
// Sub-agent executions carried over from the persisted transcript on a
|
|
284
|
+
// resume, handed to the MessageAccumulator so a gated tool inside a
|
|
285
|
+
// delegated sub-agent survives the round-trip (see seeding below).
|
|
286
|
+
let seededSubAgents = [];
|
|
205
287
|
if (isReinvocation) {
|
|
206
288
|
const existingStatus = execution.status;
|
|
289
|
+
// Seed the in-progress status from the persisted execution BEFORE the
|
|
290
|
+
// MessageAccumulator wraps status.messages, so this resumed turn APPENDS
|
|
291
|
+
// onto prior history rather than rebuilding from empty. A Cursor resume
|
|
292
|
+
// re-issues approved tool calls with fresh ids; a from-empty rebuild would
|
|
293
|
+
// drop the previously-committed ids and the backend's append-only-at-
|
|
294
|
+
// identity guard would reject the whole update, stalling the run (the
|
|
295
|
+
// "approval propagation is broken" watchdog failure). The resumed re-runs
|
|
296
|
+
// are reconciled onto these seeded calls by canonical identity inside the
|
|
297
|
+
// accumulator. Mirrors the deep-agent seedStatusFromExecution.
|
|
298
|
+
seededSubAgents = seedCursorTranscriptFromExecution(status, execution);
|
|
299
|
+
// File-review reconcile (the dual-source half): reconcile every change set
|
|
300
|
+
// the server projected as DECIDED, sourced from the ledger decisions and
|
|
301
|
+
// the pinned git refs (approved kept at their "after" bytes, rejected
|
|
302
|
+
// snapped back to baseline — all uncommitted, hash-verified). This is
|
|
303
|
+
// independent of tool approvals: a single turn can carry BOTH a DECIDED
|
|
304
|
+
// file change set AND an approved shell/MCP action.
|
|
305
|
+
let reconciledFileReview = false;
|
|
306
|
+
let fileReviewFailed = false;
|
|
307
|
+
let fileReviewFailureDetail = "";
|
|
308
|
+
const discardedPaths = [];
|
|
309
|
+
if (captureMode && primaryWorkspaceDir) {
|
|
310
|
+
const decidedSets = (existingStatus?.fileChangeSets ?? []).filter((cs) => cs.status === FileChangeSetStatus.DECIDED);
|
|
311
|
+
for (const changeSet of decidedSets) {
|
|
312
|
+
const capResult = await applyCaptureDecisions({
|
|
313
|
+
status,
|
|
314
|
+
gitRoot: primaryWorkspaceDir,
|
|
315
|
+
executionId,
|
|
316
|
+
changeSet,
|
|
317
|
+
// Thread the CAS store so CAS-captured files in the change set
|
|
318
|
+
// reconcile from the durable manifest (approved after-blobs written,
|
|
319
|
+
// rejected snapped back). In a non-git workspace this is the ONLY
|
|
320
|
+
// reconcile; in a git tree it composes with the git-ref reconcile.
|
|
321
|
+
storage: artifactStorage,
|
|
322
|
+
gitWorkspace,
|
|
323
|
+
});
|
|
324
|
+
if (!capResult.isCaptureTurn)
|
|
325
|
+
continue;
|
|
326
|
+
reconciledFileReview = true;
|
|
327
|
+
if (capResult.failed) {
|
|
328
|
+
fileReviewFailed = true;
|
|
329
|
+
fileReviewFailureDetail = capResult.failureDetail ?? "file review reconcile failed";
|
|
330
|
+
}
|
|
331
|
+
if (capResult.hadReject)
|
|
332
|
+
discardedPaths.push(...capResult.rejectedPaths);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// Tool approvals (shell / MCP / gitignored writes) still resolve from the
|
|
336
|
+
// message transcript — the deny-gate path, unchanged by the file-review
|
|
337
|
+
// cutover.
|
|
207
338
|
const adjudicated = reconstructAdjudicatedApprovals(existingStatus?.messages ?? []);
|
|
208
339
|
if (adjudicated.decisions.size > 0) {
|
|
209
340
|
approvalDecisions = adjudicated.decisions;
|
|
210
341
|
adjudicatedApprovals = adjudicated.pendingApprovals;
|
|
342
|
+
adjudicatedContentDigests = adjudicated.contentDigests;
|
|
343
|
+
// A reject of an irreversible action (shell/MCP) fails the execution.
|
|
211
344
|
const hasReject = [...approvalDecisions.values()].some((a) => a === ApprovalAction.REJECT);
|
|
212
345
|
if (hasReject) {
|
|
213
346
|
status.phase = ExecutionPhase.EXECUTION_FAILED;
|
|
@@ -221,6 +354,45 @@ async function executeCursorInner(config, client, executionId, threadId) {
|
|
|
221
354
|
await persist(status);
|
|
222
355
|
return slimStatus(status);
|
|
223
356
|
}
|
|
357
|
+
// else: fall through to run the approved shell/MCP. The agent may produce
|
|
358
|
+
// further edits, captured as a new change set in the next cycle.
|
|
359
|
+
}
|
|
360
|
+
else if (reconciledFileReview) {
|
|
361
|
+
// Pure file review: the agent already finished its full turn during
|
|
362
|
+
// capture, so keeping/discarding a change does NOT re-prompt it
|
|
363
|
+
// (Cursor-like). The reconcile is done; the execution is complete.
|
|
364
|
+
status.phase = ExecutionPhase.EXECUTION_COMPLETED;
|
|
365
|
+
status.completedAt = utcTimestamp();
|
|
366
|
+
if (fileReviewFailed) {
|
|
367
|
+
// What-you-approve-is-what-applies could not be honored (on-disk bytes
|
|
368
|
+
// diverged from the approved digest). Surface it to the human; the
|
|
369
|
+
// FileReviewFailure(HASH_MISMATCH) event is the audit record.
|
|
370
|
+
status.messages.push(create(AgentMessageSchema, {
|
|
371
|
+
type: MessageType.MESSAGE_SYSTEM,
|
|
372
|
+
content: "Some approved file changes could not be applied because the file " +
|
|
373
|
+
"changed after review: " + fileReviewFailureDetail + ".",
|
|
374
|
+
timestamp: utcTimestamp(),
|
|
375
|
+
}));
|
|
376
|
+
}
|
|
377
|
+
else if (discardedPaths.length > 0) {
|
|
378
|
+
// A reject is a DISCARD that COMPLETES (not FAILED) — surface a SYSTEM
|
|
379
|
+
// note listing the reverted files. This note is for the human; it does
|
|
380
|
+
// NOT re-sync the Cursor SDK agent (its native context still believes
|
|
381
|
+
// those edits stuck). The agent self-corrects by re-reading, and any
|
|
382
|
+
// edit it makes from that stale belief is itself re-surfaced as a new
|
|
383
|
+
// change set next turn (the structural safety net). See
|
|
384
|
+
// design-decisions/capture-reject-next-turn-resync-not-built.md.
|
|
385
|
+
status.messages.push(create(AgentMessageSchema, {
|
|
386
|
+
type: MessageType.MESSAGE_SYSTEM,
|
|
387
|
+
content: "Some proposed file changes were discarded by the user and were not applied: " +
|
|
388
|
+
discardedPaths.join(", ") + ".",
|
|
389
|
+
timestamp: utcTimestamp(),
|
|
390
|
+
}));
|
|
391
|
+
}
|
|
392
|
+
await persist(status);
|
|
393
|
+
console.log(`ExecuteCursor file-review resume short-circuit: execution=${executionId}, ` +
|
|
394
|
+
`failed=${fileReviewFailed}, discarded=${discardedPaths.length}`);
|
|
395
|
+
return slimStatus(status);
|
|
224
396
|
}
|
|
225
397
|
}
|
|
226
398
|
// Phase 4: Resolve MCP servers with approval policies
|
|
@@ -232,16 +404,17 @@ async function executeCursorInner(config, client, executionId, threadId) {
|
|
|
232
404
|
const mcpConfig = mcpResolution.cursorConfig;
|
|
233
405
|
// Phase 4b: Merge approval policies from all layers.
|
|
234
406
|
//
|
|
235
|
-
//
|
|
236
|
-
// spec.auto_approve_all
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
const
|
|
407
|
+
// Two bypasses (see ActiveLeases, shared with the native harness): the
|
|
408
|
+
// pre-armed spec.auto_approve_all is the one whole-run global bypass; an
|
|
409
|
+
// interactive APPROVE_ALL grants a run-lifetime lease scoped to that action's
|
|
410
|
+
// class. deriveActiveLeases keeps this contract defined once. Server-scoped
|
|
411
|
+
// leases drop that server's tools from the merged map (so the hook treats
|
|
412
|
+
// them as auto-approved); the global bypass empties the map entirely.
|
|
413
|
+
const leases = deriveActiveLeases(execution);
|
|
414
|
+
const globalBypass = leases.global;
|
|
242
415
|
const agentOverrides = blueprint.mergedMcpServerUsages
|
|
243
416
|
.flatMap((u) => u.toolApprovalOverrides ?? []);
|
|
244
|
-
const mergedPolicies = mergeApprovalPolicies(mcpResolution.resolvedServers, agentOverrides,
|
|
417
|
+
const mergedPolicies = mergeApprovalPolicies(mcpResolution.resolvedServers, agentOverrides, leases);
|
|
245
418
|
heartbeat();
|
|
246
419
|
// Phase 4c: Validate MCP server env health (diagnostic, non-blocking)
|
|
247
420
|
const mcpWarnings = validateMcpServerEnv(mcpResolution.resolvedServers, blueprint.mergedMcpServerUsages, envVars);
|
|
@@ -251,7 +424,7 @@ async function executeCursorInner(config, client, executionId, threadId) {
|
|
|
251
424
|
}
|
|
252
425
|
// Phase 5: Resolve skills (merged from agent + session)
|
|
253
426
|
await reportSetupProgress(client, executionId, "Resolving skills");
|
|
254
|
-
|
|
427
|
+
// (primaryWorkspaceDir / captureMode were resolved right after provisioning.)
|
|
255
428
|
const skillMetadata = await resolveSkills(client, blueprint.mergedSkillRefs, {
|
|
256
429
|
sessionId,
|
|
257
430
|
primaryWorkspaceDir,
|
|
@@ -260,6 +433,34 @@ async function executeCursorInner(config, client, executionId, threadId) {
|
|
|
260
433
|
// Phase 5b: Resolve attachments
|
|
261
434
|
const attachmentResults = await resolveAttachments(spec.attachments, sessionId, primaryWorkspaceDir, config.mode);
|
|
262
435
|
const attachmentPaths = attachmentResults.map((a) => a.relativePath);
|
|
436
|
+
// Phase 5b3: Exact-apply approved whole-file writes (HITL "what you approve
|
|
437
|
+
// is what gets applied"). The Cursor deny-only harness reinvokes the model,
|
|
438
|
+
// which regenerates content, so a resource grant alone cannot guarantee the
|
|
439
|
+
// bytes that land match the bytes the user approved. The runner therefore
|
|
440
|
+
// writes the EXACT approved whole-file content itself, marks those tool calls
|
|
441
|
+
// COMPLETED, and (below) issues NO grant for them — so any FURTHER change the
|
|
442
|
+
// model makes to those files is re-gated. Hunk edits / shell / MCP stay on
|
|
443
|
+
// the grant + reinvocation path. Every uncertain case degrades to that path,
|
|
444
|
+
// so this can never corrupt a file (see exact-apply.ts).
|
|
445
|
+
let appliedToolCallIds = new Set();
|
|
446
|
+
// Exact-apply is the deny-gate path's "what you approve is what gets applied"
|
|
447
|
+
// mechanism (the model regenerates content on reinvocation). Capture mode
|
|
448
|
+
// does not reinvoke the model for file edits — it applies the exact captured
|
|
449
|
+
// bytes itself in applyCaptureDecisions — so exact-apply is scoped OUT of it.
|
|
450
|
+
if (!captureMode && isReinvocation && approvalDecisions) {
|
|
451
|
+
appliedToolCallIds = await applyApprovedWholeFileWrites({
|
|
452
|
+
messages: status.messages,
|
|
453
|
+
workspaceBackend: new LocalWorkspaceBackend(primaryWorkspaceDir),
|
|
454
|
+
workspaceDirs: blueprint.workspaceDirs,
|
|
455
|
+
executionId,
|
|
456
|
+
});
|
|
457
|
+
if (appliedToolCallIds.size > 0) {
|
|
458
|
+
// Persist the applied writes (tool calls now COMPLETED with the approved
|
|
459
|
+
// diff) before reinvocation, so the applied state is durable even if the
|
|
460
|
+
// continuation fails, and the UI reflects it immediately.
|
|
461
|
+
await persist(status);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
263
464
|
// Phase 5c: Install the HITL approval gate BEFORE resolving the agent.
|
|
264
465
|
//
|
|
265
466
|
// The gate's runtime artifacts (hook script, approval-state file, denial
|
|
@@ -273,12 +474,44 @@ async function executeCursorInner(config, client, executionId, threadId) {
|
|
|
273
474
|
//
|
|
274
475
|
// On reinvocation, turn the user's approvals into tool-identity grants so
|
|
275
476
|
// the resumed agent's re-attempt (which carries a fresh tool-call id) is
|
|
276
|
-
// allowed through.
|
|
477
|
+
// allowed through. Exact-applied writes are EXCLUDED from the grants: with no
|
|
478
|
+
// grant, a further write to that file is re-gated (the user sees every change).
|
|
479
|
+
// Capture mode: pin the pre-turn baseline tree before the agent runs (and
|
|
480
|
+
// before the gate is installed, though the gate files are excluded from the
|
|
481
|
+
// capture anyway). The turn-end capture diffs the post-turn tree against this
|
|
482
|
+
// to build the per-file cards; the baseline ref is also what a reject reverts
|
|
483
|
+
// to on resume. Covers a fresh turn and the approved-irreversible resume
|
|
484
|
+
// fall-through (the agent will run and may make further edits).
|
|
485
|
+
if (captureMode && primaryWorkspaceDir) {
|
|
486
|
+
// Pin the pre-turn tree AND author BASELINE_CAPTURED so the projection can
|
|
487
|
+
// materialize the change set (status CAPTURING) before any candidate exists.
|
|
488
|
+
// The event rides the next persist; CAPTURING does not arm the unified gate.
|
|
489
|
+
baselineTree = await captureBaselineToLedger({
|
|
490
|
+
status,
|
|
491
|
+
gitRoot: primaryWorkspaceDir,
|
|
492
|
+
executionId,
|
|
493
|
+
changeSetId,
|
|
494
|
+
gitWorkspace,
|
|
495
|
+
});
|
|
496
|
+
}
|
|
277
497
|
hitlDir = await ensureHitlDir(sessionId);
|
|
498
|
+
const grantApprovals = excludeAppliedFromGrants(adjudicatedApprovals, appliedToolCallIds);
|
|
278
499
|
const approvalGrants = approvalDecisions
|
|
279
|
-
? buildApprovalGrants(
|
|
500
|
+
? buildApprovalGrants(grantApprovals, approvalDecisions, adjudicatedContentDigests)
|
|
280
501
|
: undefined;
|
|
281
|
-
|
|
502
|
+
if (approvalGrants && approvalGrants.length > 0 && !globalBypass) {
|
|
503
|
+
emitCursorGrantReceipts(approvalGrants, deriveExecutionFingerprintKey(getRunnerHitlMasterSecret(), executionId), executionId);
|
|
504
|
+
}
|
|
505
|
+
// CAS capture requires artifact storage to persist blobs
|
|
506
|
+
// (captureCandidateToLedger throws without it). In a git tree, captureMode
|
|
507
|
+
// alone governs tracked-file capture (no storage needed) and captureIgnored is
|
|
508
|
+
// the narrower switch (git tree + storage) that also captures gitignored
|
|
509
|
+
// writes. In a non-git workspace ALL capture is CAS, so captureMode already
|
|
510
|
+
// required storage — captureIgnored then equals captureMode. When storage is
|
|
511
|
+
// absent a git tree keeps gating gitignored writes and a non-git workspace
|
|
512
|
+
// falls back to the deny-gate entirely (no regression).
|
|
513
|
+
const captureIgnored = captureMode && !!artifactStorage;
|
|
514
|
+
const approvalState = buildApprovalState(mergedPolicies, globalBypass, leases.categories, approvalGrants, captureMode, captureIgnored, gitWorkspace);
|
|
282
515
|
const hitlGate = await installHitlGate({
|
|
283
516
|
workspaceRoot: primaryWorkspaceDir,
|
|
284
517
|
hitlDir,
|
|
@@ -289,6 +522,12 @@ async function executeCursorInner(config, client, executionId, threadId) {
|
|
|
289
522
|
await removeHitlGate(hitlGate);
|
|
290
523
|
await removeStigmerSymlink(primaryWorkspaceDir);
|
|
291
524
|
};
|
|
525
|
+
// Arm the denial watcher as soon as the gate exists. The per-turn ledger
|
|
526
|
+
// reset may flip the flag once before the run starts; the loop's read then
|
|
527
|
+
// sees an empty ledger and clears it — harmless by construction.
|
|
528
|
+
stopDenialWatcher = watchDenialLedger(hitlDir, () => {
|
|
529
|
+
denialLedgerDirty = true;
|
|
530
|
+
});
|
|
292
531
|
// Phase 5d: Ensure model pricing registry is populated before validation
|
|
293
532
|
await ensurePricingLoaded();
|
|
294
533
|
// Phase 6: Validate model selection
|
|
@@ -383,6 +622,7 @@ async function executeCursorInner(config, client, executionId, threadId) {
|
|
|
383
622
|
workspaceFileRefs: spec.workspaceFileRefs ?? [],
|
|
384
623
|
attachmentPaths,
|
|
385
624
|
pendingApprovals: adjudicatedApprovals,
|
|
625
|
+
appliedToolCallIds,
|
|
386
626
|
interactionMode,
|
|
387
627
|
});
|
|
388
628
|
// Phase 10a: Inject structured output instruction for Cursor harness
|
|
@@ -493,7 +733,16 @@ async function executeCursorInner(config, client, executionId, threadId) {
|
|
|
493
733
|
});
|
|
494
734
|
}
|
|
495
735
|
});
|
|
496
|
-
|
|
736
|
+
// Everything at an index >= this was produced by THIS turn's stream — the
|
|
737
|
+
// positional turn boundary the approved-command provenance (DD-28) scopes
|
|
738
|
+
// its qualification to. Snapshotted before the accumulator can append.
|
|
739
|
+
const turnStartMessageIndex = status.messages.length;
|
|
740
|
+
const accumulator = new MessageAccumulator(status.messages, {
|
|
741
|
+
mergedPolicies,
|
|
742
|
+
provenance: { globalBypass, leasedCategories: leases.categories },
|
|
743
|
+
workspaceRoot: primaryWorkspaceDir,
|
|
744
|
+
seededSubAgents,
|
|
745
|
+
});
|
|
497
746
|
// Shared cadence with the native harness: discrete state changes force a
|
|
498
747
|
// flush; high-frequency token deltas ride this scheduler's time cadence
|
|
499
748
|
// (env-tunable via STREAMING_* — see loadStreamingConfig).
|
|
@@ -518,6 +767,45 @@ async function executeCursorInner(config, client, executionId, threadId) {
|
|
|
518
767
|
if (event.type === "tool_call" && event.name === "task") {
|
|
519
768
|
accumulator.trackSubAgentExecution(event);
|
|
520
769
|
}
|
|
770
|
+
// First-denial stop (HITL clean pause). In CAPTURE mode this fires only for
|
|
771
|
+
// an IRREVERSIBLE tool the hook still gates (shell, MCP, or a gitignored
|
|
772
|
+
// write/delete) — file edits flow freely and are captured at the turn
|
|
773
|
+
// boundary, so they never enter the ledger. In the deny-gate FALLBACK
|
|
774
|
+
// (non-git workspace) it fires for every gated file edit too. Either way:
|
|
775
|
+
// the preToolUse hook appends to the denial ledger the instant it gates a
|
|
776
|
+
// tool — before Cursor surfaces the failure to the model — and the fs
|
|
777
|
+
// watcher flips denialLedgerDirty the moment that write lands. Confirming
|
|
778
|
+
// the flag with a read on the very next event (of ANY type — thinking
|
|
779
|
+
// deltas arrive within milliseconds) ends the turn before the model's
|
|
780
|
+
// reaction can persist: waiting for the next tool_call event let the full
|
|
781
|
+
// post-denial reaction (thinking, narration, a workaround shell) stream
|
|
782
|
+
// and persist live (production case aex_01kwj07f7g23c3wp9sn8496z5g). The
|
|
783
|
+
// tool_call-event read stays as the backstop for platforms where fs.watch
|
|
784
|
+
// is unreliable; the current event was already accumulated above, so the
|
|
785
|
+
// anchor's own row is always present for the Phase 12 gate overlay. This
|
|
786
|
+
// mirrors the native harness's pause-before-react semantics; Phase 12
|
|
787
|
+
// reconciles the denied calls and its trim remains the last-resort
|
|
788
|
+
// backstop for anything that persisted before the stop.
|
|
789
|
+
if (!firstDenialDetected && hitlDir && (denialLedgerDirty || event.type === "tool_call")) {
|
|
790
|
+
denialLedgerDirty = false;
|
|
791
|
+
const denials = await readDenialLedger(hitlDir);
|
|
792
|
+
if (denials.length > 0) {
|
|
793
|
+
firstDenialDetected = true;
|
|
794
|
+
console.log(`ExecuteCursor first denial detected (${denials.length} ledger ` +
|
|
795
|
+
`entr${denials.length === 1 ? "y" : "ies"}); stopping turn to pause ` +
|
|
796
|
+
`cleanly for approval: execution=${executionId}`);
|
|
797
|
+
if (run.supports?.("cancel")) {
|
|
798
|
+
// Kept (not fire-and-forget): awaited timeboxed before Phase 12 so
|
|
799
|
+
// the ledger read and tree capture see a stopped agent.
|
|
800
|
+
denialCancelSettled = run.cancel().then(() => { }, (cancelErr) => {
|
|
801
|
+
console.warn(`ExecuteCursor run.cancel() after first denial failed (non-fatal): ` +
|
|
802
|
+
`execution=${executionId}, ` +
|
|
803
|
+
`error=${cancelErr instanceof Error ? cancelErr.message : cancelErr}`);
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
break;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
521
809
|
deltaEnricher.applyEnrichments(status.messages);
|
|
522
810
|
eventCount++;
|
|
523
811
|
if (event.type === "status") {
|
|
@@ -561,13 +849,15 @@ async function executeCursorInner(config, client, executionId, threadId) {
|
|
|
561
849
|
}
|
|
562
850
|
}
|
|
563
851
|
catch (streamErr) {
|
|
564
|
-
// run.cancel() from the stall watchdog
|
|
565
|
-
//
|
|
566
|
-
//
|
|
852
|
+
// run.cancel() — from the stall watchdog or the first-denial stop — can
|
|
853
|
+
// make the stream iterator reject as it tears down; that is the expected
|
|
854
|
+
// teardown for both, so swallow it and fall through (stallDetected ->
|
|
855
|
+
// Phase 11a; firstDenialDetected -> Phase 12). Anything else is a genuine
|
|
567
856
|
// stream failure — rethrow it to the outer error handler.
|
|
568
|
-
if (!stallDetected)
|
|
857
|
+
if (!stallDetected && !firstDenialDetected)
|
|
569
858
|
throw streamErr;
|
|
570
|
-
console.warn(`ExecuteCursor stream ended via
|
|
859
|
+
console.warn(`ExecuteCursor stream ended via cancel: execution=${executionId}, ` +
|
|
860
|
+
`stall=${stallDetected}, firstDenial=${firstDenialDetected}`);
|
|
571
861
|
}
|
|
572
862
|
periodicHeartbeat.stop();
|
|
573
863
|
stallWatchdog.stop();
|
|
@@ -716,12 +1006,125 @@ async function executeCursorInner(config, client, executionId, threadId) {
|
|
|
716
1006
|
// harness — the approval surface is driven entirely by tool-call status. We
|
|
717
1007
|
// deliberately do NOT set status.pendingApprovals here: any value would be
|
|
718
1008
|
// discarded by the backend's recompute on the next updateStatus.
|
|
1009
|
+
//
|
|
1010
|
+
// Before reading the ledger, wait (timeboxed) for the first-denial-stop's
|
|
1011
|
+
// run.cancel() to settle. run.cancel() races the SDK's auto-execution: until
|
|
1012
|
+
// it lands, the agent process may still attempt a post-denial workaround
|
|
1013
|
+
// whose hook denial would land AFTER a premature ledger read — the row then
|
|
1014
|
+
// never collapses and renders as RUNNING forever (production case
|
|
1015
|
+
// aex_01kwj07f7g23c3wp9sn8496z5g) — or a late tool could mutate the tree
|
|
1016
|
+
// mid-capture. The timebox keeps a wedged cancel from hanging the pause;
|
|
1017
|
+
// the Phase 12 trims below remain the backstop for that degraded case.
|
|
1018
|
+
if (firstDenialDetected && denialCancelSettled) {
|
|
1019
|
+
await Promise.race([
|
|
1020
|
+
denialCancelSettled,
|
|
1021
|
+
new Promise((resolve) => {
|
|
1022
|
+
const timer = setTimeout(resolve, FIRST_DENIAL_CANCEL_TIMEOUT_MS);
|
|
1023
|
+
timer.unref();
|
|
1024
|
+
}),
|
|
1025
|
+
]);
|
|
1026
|
+
}
|
|
719
1027
|
const deniedLedger = await readDenialLedger(hitlDir ?? "");
|
|
720
|
-
|
|
721
|
-
|
|
1028
|
+
// Capture mode: author the net change set to the file_review ledger as the
|
|
1029
|
+
// CANDIDATE_CAPTURED event (projected server-side to a file_change_set
|
|
1030
|
+
// AWAITING_REVIEW — the single review surface). The runner-owned gate files
|
|
1031
|
+
// are excluded from the capture. The agent's edits are LEFT applied on the
|
|
1032
|
+
// working tree (Cursor parity — the user reviews the real change; nothing is
|
|
1033
|
+
// committed and the next turn is blocked until approval, and a reject snaps
|
|
1034
|
+
// each file back on resume). Runs BEFORE the denial reconcile so a denied
|
|
1035
|
+
// (gitignored) write stays on the deny-gate path while every flowed edit is
|
|
1036
|
+
// captured to the ledger.
|
|
1037
|
+
let capturedChangeCount = 0;
|
|
1038
|
+
// `baselineTree !== undefined` means a baseline was authored this turn — the
|
|
1039
|
+
// git tree sha for a git workspace, or "" (empty, but authored) for a non-git
|
|
1040
|
+
// one. A plain truthiness check would wrongly skip the non-git capture.
|
|
1041
|
+
if (captureMode && baselineTree !== undefined && primaryWorkspaceDir) {
|
|
1042
|
+
const deniedTokens = new Set(deniedLedger.map((e) => e.token));
|
|
1043
|
+
// Approved-command turn facts (DD-28): when every mutation-capable call
|
|
1044
|
+
// this turn was a consented shell command, attach the provenance so the
|
|
1045
|
+
// backend can verify the cited consent rows and auto-keep the set instead
|
|
1046
|
+
// of arming a second gate. Fail-closed: any non-qualifying turn attaches
|
|
1047
|
+
// nothing and reviews manually exactly as before.
|
|
1048
|
+
const commandProvenance = deriveTurnCommandProvenance({
|
|
1049
|
+
messages: status.messages,
|
|
1050
|
+
turnStartIndex: turnStartMessageIndex,
|
|
1051
|
+
deniedTokens,
|
|
1052
|
+
grantTokenToConsentId: new Map((approvalGrants ?? []).map((g) => [
|
|
1053
|
+
primaryToken(g.key, g.salient, g.contentDigest),
|
|
1054
|
+
g.sourceToolCallId,
|
|
1055
|
+
])),
|
|
1056
|
+
globalBypass,
|
|
1057
|
+
});
|
|
1058
|
+
if (commandProvenance) {
|
|
1059
|
+
console.log(`ExecuteCursor capture: turn qualifies for approved-command auto-keep ` +
|
|
1060
|
+
`(consent rows: ${commandProvenance.consentToolCallIds.join(",") || "(auto_approve_all)"}); ` +
|
|
1061
|
+
`attaching provenance to candidate (execution=${executionId})`);
|
|
1062
|
+
}
|
|
1063
|
+
const captured = await captureTurnToLedger({
|
|
1064
|
+
status,
|
|
1065
|
+
gitRoot: primaryWorkspaceDir,
|
|
1066
|
+
executionId,
|
|
1067
|
+
changeSetId,
|
|
1068
|
+
baselineTree,
|
|
1069
|
+
messages: status.messages,
|
|
1070
|
+
deniedTokens,
|
|
1071
|
+
commandProvenance,
|
|
1072
|
+
// Scope sub-agent row stamping to this turn: the seeded prior sub-agents
|
|
1073
|
+
// (cloned in on resume) are the "before this turn" rows to skip.
|
|
1074
|
+
priorSubAgentToolCallIds: collectSubAgentToolCallIds(seededSubAgents),
|
|
1075
|
+
// The CAS half: read the sidecar the hook staged this turn and compose it
|
|
1076
|
+
// into the change set. hitlDir + storage are present when captureIgnored
|
|
1077
|
+
// was on (a git tree's gitignored writes, or ALL writes in a non-git
|
|
1078
|
+
// workspace). In a git tree this composes with the git diff (HYBRID); in a
|
|
1079
|
+
// non-git workspace it IS the whole change set (CAS-only).
|
|
1080
|
+
hitlDir,
|
|
1081
|
+
storage: artifactStorage,
|
|
1082
|
+
gitWorkspace,
|
|
1083
|
+
});
|
|
1084
|
+
capturedChangeCount = captured.length;
|
|
1085
|
+
if (capturedChangeCount > 0) {
|
|
1086
|
+
console.log(`ExecuteCursor capture: ${capturedChangeCount} file change(s) authored to the ` +
|
|
1087
|
+
`file_review ledger (change_set=${changeSetId}), working tree left applied ` +
|
|
1088
|
+
`for review (execution=${executionId})`);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
// The gate reads each denied file's pre-edit `before` from the workspace the
|
|
1092
|
+
// runner is co-located with (local FS for OSS; the sandbox in cloud), so a
|
|
1093
|
+
// whole-file rewrite gate renders a true before/after diff. The tool was
|
|
1094
|
+
// DENIED, so disk still holds the old content. User files are never platform
|
|
1095
|
+
// paths, so no platformDir routing is needed here.
|
|
1096
|
+
const gateWorkspaceBackend = new LocalWorkspaceBackend(primaryWorkspaceDir);
|
|
1097
|
+
const deniedToolCalls = await reconcileDeniedToolCalls(status.messages, deniedLedger, mergedPolicies, gateWorkspaceBackend);
|
|
1098
|
+
// Observability: a synthesized placeholder (id `approval:*`) means a denial
|
|
1099
|
+
// correlated to NO streamed tool call in either the exact or the normalized
|
|
1100
|
+
// pass. After the normalized-path fallback this should be ~0; a non-zero rate
|
|
1101
|
+
// is the early-warning signal of a NEW identity drift (the gate would then
|
|
1102
|
+
// show "No preview available" with no diff). Logged, not thrown — the
|
|
1103
|
+
// synthesized gate still safely surfaces the approval.
|
|
1104
|
+
const synthesizedGateCount = deniedToolCalls.filter((tc) => tc.id.startsWith("approval:")).length;
|
|
1105
|
+
if (synthesizedGateCount > 0) {
|
|
1106
|
+
console.warn(`ExecuteCursor reconcile synthesized ${synthesizedGateCount} placeholder gate(s) ` +
|
|
1107
|
+
`with no correlated stream call (execution=${executionId}); ` +
|
|
1108
|
+
`possible hook/stream identity drift — gate(s) will lack a diff`);
|
|
1109
|
+
}
|
|
1110
|
+
if (deniedToolCalls.length > 0 || capturedChangeCount > 0) {
|
|
1111
|
+
if (deniedToolCalls.length > 0) {
|
|
1112
|
+
// Deterministic clean-pause: a turn that pauses for approval must read as
|
|
1113
|
+
// the same shape the native harness produces — pre-tool text + the gated
|
|
1114
|
+
// tool calls — never the model's provisional reaction to Cursor's deny
|
|
1115
|
+
// (e.g. "blocked by a hook; enable it in your Cursor settings"). We blank
|
|
1116
|
+
// that reaction in place (keeping the message count, so the finalize stays
|
|
1117
|
+
// append-only) rather than removing it. See
|
|
1118
|
+
// clearProvisionalPostDenialNarration for the full rationale.
|
|
1119
|
+
const redactedNarration = clearProvisionalPostDenialNarration(status.messages, deniedToolCalls);
|
|
1120
|
+
if (redactedNarration.length > 0) {
|
|
1121
|
+
console.log(`ExecuteCursor redacted ${redactedNarration.length} provisional post-denial narration message(s) before pausing for approval`);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
722
1124
|
status.phase = ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL;
|
|
723
1125
|
await persist(status);
|
|
724
|
-
console.log(`ExecuteCursor returning WAITING_FOR_APPROVAL: ${deniedToolCalls.length}
|
|
1126
|
+
console.log(`ExecuteCursor returning WAITING_FOR_APPROVAL: ${deniedToolCalls.length} gated tool(s), ` +
|
|
1127
|
+
`${capturedChangeCount} file card(s) pending`);
|
|
725
1128
|
return slimStatus(status);
|
|
726
1129
|
}
|
|
727
1130
|
// Phase 13: Map final result
|
|
@@ -1020,6 +1423,19 @@ async function executeCursorInner(config, client, executionId, threadId) {
|
|
|
1020
1423
|
}
|
|
1021
1424
|
}
|
|
1022
1425
|
}
|
|
1426
|
+
// Collapse any redundant same-identity tool-call twin born this turn before
|
|
1427
|
+
// the terminal persist. On a resume turn the gated tool is already granted, so
|
|
1428
|
+
// there is no denial ledger and reconcileDeniedToolCalls never runs — the
|
|
1429
|
+
// extra attempt the model emits beside the approved action (a stuck RUNNING
|
|
1430
|
+
// zombie, a denied-reported-as-success COMPLETED, or an all-no-change double)
|
|
1431
|
+
// would otherwise persist as a second "No preview available" card. The shared
|
|
1432
|
+
// routine keeps the diff/output carrier and blanks the rest to hidden SKIPPED
|
|
1433
|
+
// rows in place, preserving each committed id so the finalize stays append-only.
|
|
1434
|
+
const collapsedTwins = collapseRedundantToolCallTwins(status.messages);
|
|
1435
|
+
if (collapsedTwins > 0) {
|
|
1436
|
+
console.log(`ExecuteCursor collapsed ${collapsedTwins} redundant tool-call twin(s) at ` +
|
|
1437
|
+
`terminal finalize (kept in place as hidden SKIPPED rows): execution=${executionId}`);
|
|
1438
|
+
}
|
|
1023
1439
|
// NOW persist — subscriber sees COMPLETED + structured_output atomically
|
|
1024
1440
|
await persist(status);
|
|
1025
1441
|
console.log(`ExecuteCursor completed: execution=${executionId}, phase=${ExecutionPhase[status.phase]}, ` +
|
|
@@ -1180,6 +1596,9 @@ async function executeCursorInner(config, client, executionId, threadId) {
|
|
|
1180
1596
|
// path stops it after the stream loop; this covers throws before that
|
|
1181
1597
|
// point so no orphaned timer survives the activity.
|
|
1182
1598
|
stallWatchdog?.stop();
|
|
1599
|
+
// Close the denial-ledger watcher on EVERY exit path (idempotent) so no
|
|
1600
|
+
// orphaned fs.watch handle survives the activity.
|
|
1601
|
+
stopDenialWatcher?.();
|
|
1183
1602
|
// Tear down the HITL gate on EVERY exit path (success, error, approval
|
|
1184
1603
|
// pause, cancellation) so attaching a real repo leaves the user's
|
|
1185
1604
|
// .cursor/hooks.json and workspace untouched between turns (issue #173).
|
|
@@ -1196,6 +1615,42 @@ async function executeCursorInner(config, client, executionId, threadId) {
|
|
|
1196
1615
|
}
|
|
1197
1616
|
}
|
|
1198
1617
|
}
|
|
1618
|
+
/**
|
|
1619
|
+
* Seed an in-progress status from the persisted execution on a durable resume
|
|
1620
|
+
* (HITL approval, pause/resume, or transient recovery) so the upcoming turn
|
|
1621
|
+
* APPENDS onto prior history instead of replacing it. This is the Cursor analog
|
|
1622
|
+
* of the deep-agent's seedStatusFromExecution (execute-deep-agent/index.ts).
|
|
1623
|
+
*
|
|
1624
|
+
* Why it is required: a resumed Cursor agent re-issues the previously gated tool
|
|
1625
|
+
* calls with brand-new call ids. Without seeding, the MessageAccumulator would
|
|
1626
|
+
* rebuild the transcript from empty and emit a status that drops the already-
|
|
1627
|
+
* committed tool-call ids. The backend's append-only-at-identity guard
|
|
1628
|
+
* (AgentExecutionUpdateStatusHandler / update_status.go) rejects any non-
|
|
1629
|
+
* terminal update that drops a committed tool-call id, so the resumed progress
|
|
1630
|
+
* would never persist — the run stalls in WAITING_FOR_APPROVAL with no pending
|
|
1631
|
+
* approvals and the workflow watchdog fails it. Seeding makes the resume status
|
|
1632
|
+
* a strict superset; the re-runs are then reconciled in place onto these seeded
|
|
1633
|
+
* calls by canonical identity inside the accumulator.
|
|
1634
|
+
*
|
|
1635
|
+
* The persisted protos are cloned so the input execution stays immutable, and
|
|
1636
|
+
* the seeded messages are pushed into status.messages (which the accumulator
|
|
1637
|
+
* wraps by reference) BEFORE the accumulator is constructed. Sub-agent
|
|
1638
|
+
* executions are returned rather than written to status.subAgentExecutions
|
|
1639
|
+
* directly, because the accumulator owns that array (it overwrites
|
|
1640
|
+
* status.subAgentExecutions with its own on every flush) — handing them to the
|
|
1641
|
+
* accumulator keeps the seeded sub-agent rows from being clobbered.
|
|
1642
|
+
*
|
|
1643
|
+
* @returns the cloned sub-agent executions to seed into the MessageAccumulator.
|
|
1644
|
+
*/
|
|
1645
|
+
function seedCursorTranscriptFromExecution(status, execution) {
|
|
1646
|
+
const persisted = execution.status;
|
|
1647
|
+
if (!persisted || persisted.messages.length === 0)
|
|
1648
|
+
return [];
|
|
1649
|
+
for (const message of persisted.messages) {
|
|
1650
|
+
status.messages.push(clone(AgentMessageSchema, message));
|
|
1651
|
+
}
|
|
1652
|
+
return persisted.subAgentExecutions.map((sub) => clone(SubAgentExecutionSchema, sub));
|
|
1653
|
+
}
|
|
1199
1654
|
// ---------------------------------------------------------------------------
|
|
1200
1655
|
// Structured Output Extraction (Cursor Harness Tier 2)
|
|
1201
1656
|
// ---------------------------------------------------------------------------
|
|
@@ -1204,41 +1659,21 @@ async function executeCursorInner(config, client, executionId, threadId) {
|
|
|
1204
1659
|
* economy-tier LLM with withStructuredOutput (function-calling).
|
|
1205
1660
|
* Guarantees schema-conformant JSON output via the API's tool-use mechanism.
|
|
1206
1661
|
*
|
|
1207
|
-
*
|
|
1208
|
-
*
|
|
1209
|
-
*
|
|
1210
|
-
* call-llm.ts constructModel().
|
|
1662
|
+
* Construction (registry-id resolution, provider inference, proxy wiring) is
|
|
1663
|
+
* delegated to the shared buildChatModel so the economy model's registry id is
|
|
1664
|
+
* always resolved to a provider API id before the call.
|
|
1211
1665
|
*/
|
|
1212
1666
|
async function extractStructuredOutput(agentResponse, schema, config, primaryModel) {
|
|
1213
|
-
const { ChatOpenAI } = await import("@langchain/openai");
|
|
1214
|
-
const { ChatAnthropic } = await import("@langchain/anthropic");
|
|
1215
|
-
const { inferProvider, resolveProxyBaseUrl, buildProxyHeaders } = await import("../../shared/llm-proxy.js");
|
|
1216
1667
|
const { getEconomyModel } = await import("../../shared/model-registry.js");
|
|
1668
|
+
const { buildChatModel } = await import("../../shared/model-client.js");
|
|
1217
1669
|
const extractionModel = await getEconomyModel(primaryModel);
|
|
1218
|
-
const provider = inferProvider(extractionModel);
|
|
1219
1670
|
const proxyEndpoint = config.proxyEndpoint ?? config.stigmerBackendEndpoint;
|
|
1220
|
-
const
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
:
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
: (config.stigmerToken ?? process.env.ANTHROPIC_API_KEY ?? "proxy-managed");
|
|
1227
|
-
const llm = provider === "openai"
|
|
1228
|
-
? new ChatOpenAI({
|
|
1229
|
-
model: extractionModel,
|
|
1230
|
-
apiKey,
|
|
1231
|
-
temperature: 0,
|
|
1232
|
-
maxTokens: 4096,
|
|
1233
|
-
configuration: { baseURL: baseUrl, defaultHeaders: headers },
|
|
1234
|
-
})
|
|
1235
|
-
: new ChatAnthropic({
|
|
1236
|
-
model: extractionModel,
|
|
1237
|
-
apiKey,
|
|
1238
|
-
temperature: 0,
|
|
1239
|
-
maxTokens: 4096,
|
|
1240
|
-
clientOptions: { baseURL: baseUrl, defaultHeaders: headers },
|
|
1241
|
-
});
|
|
1671
|
+
const { model: llm } = await buildChatModel({
|
|
1672
|
+
modelName: extractionModel,
|
|
1673
|
+
proxyEndpoint,
|
|
1674
|
+
stigmerToken: config.stigmerToken ?? undefined,
|
|
1675
|
+
maxTokens: 4096,
|
|
1676
|
+
});
|
|
1242
1677
|
const zodSchema = jsonSchemaToZod(schema);
|
|
1243
1678
|
const structured = llm.withStructuredOutput(zodSchema);
|
|
1244
1679
|
const result = await structured.invoke([
|
|
@@ -1270,9 +1705,10 @@ export function buildPrompt(input) {
|
|
|
1270
1705
|
const { resolution, approvalDecisions, instructions, userMessage, skills, subAgents, workspaceDirs, workspaceFileRefs, attachmentPaths, interactionMode, } = input;
|
|
1271
1706
|
const isHitlReinvocation = approvalDecisions !== undefined && approvalDecisions.size > 0;
|
|
1272
1707
|
// HITL reinvocation: the agent is resumed, so its native context carries the
|
|
1273
|
-
// prior conversation; the reinvocation prompt conveys the approval decisions
|
|
1708
|
+
// prior conversation; the reinvocation prompt conveys the approval decisions
|
|
1709
|
+
// (and which approved writes the runner already exact-applied).
|
|
1274
1710
|
if (isHitlReinvocation) {
|
|
1275
|
-
return buildReinvocationPrompt(input.pendingApprovals, approvalDecisions);
|
|
1711
|
+
return buildReinvocationPrompt(input.pendingApprovals, approvalDecisions, input.appliedToolCallIds);
|
|
1276
1712
|
}
|
|
1277
1713
|
// A successfully resumed agent carries its own conversation context via the
|
|
1278
1714
|
// SDK's native store — send the raw user message with no preamble.
|