@stigmer/runner 3.14.0 → 3.15.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__/execution-record-fixture.d.ts +47 -0
- package/dist/__test-utils__/execution-record-fixture.js +68 -0
- package/dist/__test-utils__/execution-record-fixture.js.map +1 -0
- package/dist/__test-utils__/harness-contract/types.d.ts +166 -0
- package/dist/__test-utils__/harness-contract/types.js +49 -0
- package/dist/__test-utils__/harness-contract/types.js.map +1 -0
- package/dist/__test-utils__/hermetic-activity.d.ts +268 -0
- package/dist/__test-utils__/hermetic-activity.js +385 -0
- package/dist/__test-utils__/hermetic-activity.js.map +1 -0
- package/dist/__test-utils__/mock-client.d.ts +13 -0
- package/dist/__test-utils__/mock-client.js +45 -0
- package/dist/__test-utils__/mock-client.js.map +1 -0
- package/dist/__test-utils__/mock-workspace.d.ts +15 -0
- package/dist/__test-utils__/mock-workspace.js +21 -0
- package/dist/__test-utils__/mock-workspace.js.map +1 -0
- package/dist/__test-utils__/model-registry-fixture.d.ts +56 -0
- package/dist/__test-utils__/model-registry-fixture.js +66 -0
- package/dist/__test-utils__/model-registry-fixture.js.map +1 -0
- package/dist/__test-utils__/turn-input-fixture.d.ts +60 -0
- package/dist/__test-utils__/turn-input-fixture.js +118 -0
- package/dist/__test-utils__/turn-input-fixture.js.map +1 -0
- package/dist/activities/execute-cursor/__test-utils__/contract-subject.d.ts +107 -0
- package/dist/activities/execute-cursor/__test-utils__/contract-subject.js +344 -0
- package/dist/activities/execute-cursor/__test-utils__/contract-subject.js.map +1 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +20 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +58 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +2 -32
- package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.d.ts +148 -0
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +257 -0
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -0
- package/dist/activities/execute-cursor/__test-utils__/scripted-agent.d.ts +183 -0
- package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js +262 -0
- package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js.map +1 -0
- package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.d.ts +138 -0
- package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js +191 -0
- package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js.map +1 -0
- package/dist/activities/execute-cursor/adapter.d.ts +55 -0
- package/dist/activities/execute-cursor/adapter.js +119 -0
- package/dist/activities/execute-cursor/adapter.js.map +1 -0
- package/dist/activities/execute-cursor/approval-state.d.ts +2 -2
- package/dist/activities/execute-cursor/approval-state.js.map +1 -1
- package/dist/activities/execute-cursor/capture-flow.d.ts +15 -30
- package/dist/activities/execute-cursor/capture-flow.js +17 -26
- package/dist/activities/execute-cursor/capture-flow.js.map +1 -1
- package/dist/activities/execute-cursor/cursor-capabilities.d.ts +23 -0
- package/dist/activities/execute-cursor/cursor-capabilities.js +33 -0
- package/dist/activities/execute-cursor/cursor-capabilities.js.map +1 -0
- package/dist/activities/execute-cursor/cursor-mcp-config.d.ts +2 -2
- package/dist/activities/execute-cursor/cursor-mcp-config.js.map +1 -1
- package/dist/activities/execute-cursor/fetch-interceptor.d.ts +15 -25
- package/dist/activities/execute-cursor/fetch-interceptor.js +39 -33
- package/dist/activities/execute-cursor/fetch-interceptor.js.map +1 -1
- package/dist/activities/execute-cursor/http2-interceptor.d.ts +21 -17
- package/dist/activities/execute-cursor/http2-interceptor.js +38 -31
- package/dist/activities/execute-cursor/http2-interceptor.js.map +1 -1
- package/dist/activities/execute-cursor/message-translator.d.ts +28 -19
- package/dist/activities/execute-cursor/message-translator.js +76 -27
- package/dist/activities/execute-cursor/message-translator.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +180 -11
- package/dist/activities/execute-cursor/prompt-builder.js +171 -0
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/rejection-capture.d.ts +3 -6
- package/dist/activities/execute-cursor/rejection-capture.js +5 -7
- package/dist/activities/execute-cursor/rejection-capture.js.map +1 -1
- package/dist/activities/execute-cursor/session-lifecycle.d.ts +1 -4
- package/dist/activities/execute-cursor/session-lifecycle.js.map +1 -1
- package/dist/activities/execute-cursor/turn-boundary.d.ts +1 -1
- package/dist/activities/execute-cursor/turn-settle.d.ts +49 -0
- package/dist/activities/execute-cursor/turn-settle.js +507 -0
- package/dist/activities/execute-cursor/turn-settle.js.map +1 -0
- package/dist/activities/execute-cursor/turn-setup.d.ts +173 -0
- package/dist/activities/execute-cursor/turn-setup.js +496 -0
- package/dist/activities/execute-cursor/turn-setup.js.map +1 -0
- package/dist/activities/execute-cursor/turn-stream.d.ts +41 -71
- package/dist/activities/execute-cursor/turn-stream.js +70 -135
- package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
- package/dist/activities/execute-cursor/turn.d.ts +43 -0
- package/dist/activities/execute-cursor/turn.js +222 -0
- package/dist/activities/execute-cursor/turn.js.map +1 -0
- package/dist/activities/execute-cursor/usage-pricing.d.ts +38 -0
- package/dist/activities/execute-cursor/usage-pricing.js +50 -0
- package/dist/activities/execute-cursor/usage-pricing.js.map +1 -0
- package/dist/activities/execute-deep-agent/approval-file-change.d.ts +1 -1
- package/dist/activities/execute-deep-agent/approval-file-change.js +1 -1
- package/dist/activities/execute-deep-agent/environment.js +1 -1
- package/dist/activities/execute-deep-agent/environment.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.js +3 -2
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/activities/hydrate-workflow-execution.js +1 -1
- package/dist/activities/hydrate-workflow-execution.js.map +1 -1
- package/dist/config.d.ts +15 -0
- package/dist/config.js.map +1 -1
- package/dist/harness/capabilities.d.ts +87 -0
- package/dist/harness/capabilities.js +37 -0
- package/dist/harness/capabilities.js.map +1 -0
- package/dist/harness/persist-chokepoint.d.ts +74 -0
- package/dist/harness/persist-chokepoint.js +88 -0
- package/dist/harness/persist-chokepoint.js.map +1 -0
- package/dist/harness/registry.d.ts +106 -0
- package/dist/harness/registry.js +168 -0
- package/dist/harness/registry.js.map +1 -0
- package/dist/harness/run-turn.d.ts +53 -0
- package/dist/harness/run-turn.js +528 -0
- package/dist/harness/run-turn.js.map +1 -0
- package/dist/harness/stop-controller.d.ts +50 -0
- package/dist/harness/stop-controller.js +62 -0
- package/dist/harness/stop-controller.js.map +1 -0
- package/dist/harness/terminal-table.d.ts +141 -0
- package/dist/harness/terminal-table.js +239 -0
- package/dist/harness/terminal-table.js.map +1 -0
- package/dist/harness/turn-context.d.ts +523 -0
- package/dist/harness/turn-context.js +739 -0
- package/dist/harness/turn-context.js.map +1 -0
- package/dist/harness/types.d.ts +448 -0
- package/dist/harness/types.js +64 -0
- package/dist/harness/types.js.map +1 -0
- package/dist/harness/usage-accumulator.d.ts +68 -0
- package/dist/harness/usage-accumulator.js +101 -0
- package/dist/harness/usage-accumulator.js.map +1 -0
- package/dist/harness-adapters.d.ts +27 -0
- package/dist/harness-adapters.js +28 -0
- package/dist/harness-adapters.js.map +1 -0
- package/dist/main.js.map +1 -1
- package/dist/runner-manager.js +64 -48
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.d.ts +5 -4
- package/dist/runner.js +65 -64
- package/dist/runner.js.map +1 -1
- package/dist/shared/approval-policy.d.ts +1 -1
- package/dist/shared/approval-policy.js.map +1 -1
- package/dist/{activities/execute-cursor → shared}/attachment-resolver.d.ts +2 -2
- package/dist/{activities/execute-cursor → shared}/attachment-resolver.js +5 -5
- package/dist/shared/attachment-resolver.js.map +1 -0
- package/dist/{activities/execute-cursor → shared}/blueprint-resolver.d.ts +7 -5
- package/dist/{activities/execute-cursor → shared}/blueprint-resolver.js +3 -63
- package/dist/shared/blueprint-resolver.js.map +1 -0
- package/dist/shared/channel-attachment.d.ts +1 -1
- package/dist/shared/channel-attachment.js.map +1 -1
- package/dist/{activities/execute-cursor → shared}/cost-guard.d.ts +5 -1
- package/dist/{activities/execute-cursor → shared}/cost-guard.js +5 -1
- package/dist/shared/cost-guard.js.map +1 -0
- package/dist/{activities/execute-cursor → shared}/env-resolver.d.ts +1 -1
- package/dist/shared/env-resolver.js.map +1 -0
- package/dist/{activities/execute-cursor → shared}/exact-apply.d.ts +1 -1
- package/dist/{activities/execute-cursor → shared}/exact-apply.js +6 -6
- package/dist/shared/exact-apply.js.map +1 -0
- package/dist/shared/execution-context.d.ts +35 -0
- package/dist/shared/execution-context.js +38 -0
- package/dist/shared/execution-context.js.map +1 -0
- package/dist/{activities/execute-cursor → shared}/extract-structured-output.d.ts +5 -2
- package/dist/{activities/execute-cursor → shared}/extract-structured-output.js +9 -6
- package/dist/shared/extract-structured-output.js.map +1 -0
- package/dist/shared/filereview/capture.d.ts +1 -1
- package/dist/shared/filereview/capture.js +1 -1
- package/dist/shared/heartbeat.d.ts +13 -11
- package/dist/shared/heartbeat.js +14 -29
- package/dist/shared/heartbeat.js.map +1 -1
- package/dist/{activities/execute-cursor → shared}/persist-decision.d.ts +5 -2
- package/dist/{activities/execute-cursor → shared}/persist-decision.js +4 -1
- package/dist/shared/persist-decision.js.map +1 -0
- package/dist/{activities/execute-cursor → shared}/skill-resolver.d.ts +16 -3
- package/dist/{activities/execute-cursor → shared}/skill-resolver.js +12 -4
- package/dist/shared/skill-resolver.js.map +1 -0
- package/dist/shared/subagent-rows.d.ts +21 -0
- package/dist/shared/subagent-rows.js +34 -0
- package/dist/shared/subagent-rows.js.map +1 -0
- package/dist/shared/worker-shutdown.d.ts +43 -16
- package/dist/shared/worker-shutdown.js +28 -13
- package/dist/shared/worker-shutdown.js.map +1 -1
- package/dist/shared/workspace/platform-dir.d.ts +1 -1
- package/dist/shared/workspace/platform-dir.js +1 -1
- package/dist/shared/workspace/session-provision.d.ts +54 -0
- package/dist/{activities/execute-cursor/workspace-provision.js → shared/workspace/session-provision.js} +15 -12
- package/dist/shared/workspace/session-provision.js.map +1 -0
- package/package.json +4 -4
- package/src/__test-utils__/__tests__/harness-contract-self-check.test.ts +325 -0
- package/src/__test-utils__/config-fixture.ts +63 -0
- package/src/__test-utils__/execution-record-fixture.ts +104 -0
- package/src/__test-utils__/harness-boot-order-child.ts +58 -0
- package/src/__test-utils__/harness-contract/contract.ts +581 -0
- package/src/__test-utils__/harness-contract/recording-sink.ts +107 -0
- package/src/__test-utils__/harness-contract/runtime-contract.ts +666 -0
- package/src/__test-utils__/harness-contract/scripted-adapter.ts +349 -0
- package/src/__test-utils__/harness-contract/types.ts +176 -0
- package/src/__test-utils__/hermetic-activity.ts +502 -0
- package/src/__test-utils__/model-registry-fixture.ts +72 -0
- package/src/__test-utils__/module-specifiers.ts +82 -0
- package/src/__test-utils__/proto-helpers.ts +25 -0
- package/src/__test-utils__/turn-input-fixture.ts +155 -0
- package/src/__tests__/harness-boot-order.test.ts +77 -0
- package/src/__tests__/harness-contract.test.ts +27 -0
- package/src/__tests__/runner-manager.test.ts +18 -0
- package/src/activities/execute-cursor/__test-utils__/__tests__/scripted-double.test.ts +102 -0
- package/src/activities/execute-cursor/__test-utils__/contract-subject.ts +396 -0
- package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +66 -0
- package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +2 -43
- package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +342 -0
- package/src/activities/execute-cursor/__test-utils__/scripted-agent.ts +368 -0
- package/src/activities/execute-cursor/__test-utils__/scripted-sdk.ts +254 -0
- package/src/activities/execute-cursor/__tests__/adapter-is-temporal-free.test.ts +45 -0
- package/src/activities/execute-cursor/__tests__/approval-decisions-agree.test.ts +90 -0
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +7 -8
- package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +28 -1
- package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/fetch-interceptor.test.ts +64 -6
- package/src/activities/execute-cursor/__tests__/hermetic/deny-and-retry.test.ts +228 -0
- package/src/activities/execute-cursor/__tests__/hermetic/file-review-capture.test.ts +180 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/cost-cap.status.json +53 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/deny-and-retry.turn1.status.json +55 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/deny-and-retry.turn2.status.json +77 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/file-review-capture.status.json +126 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/pause.status.json +40 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/plain-turn.status.json +48 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/platform-stop.status.json +56 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/recovery-fresh-agent.status.json +53 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/recovery-transport-timeout.status.json +48 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/resolution-error.status.json +18 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/run-cancelled.status.json +36 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/run-error-non-retryable.status.json +37 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/sdk-error-at-create.status.json +38 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/stall.status.json +59 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/tool-call.status.json +68 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/unattributed-hook-block.status.json +79 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/worker-shutdown.status.json +42 -0
- package/src/activities/execute-cursor/__tests__/hermetic/harness-contract.test.ts +170 -0
- package/src/activities/execute-cursor/__tests__/hermetic/pause-vs-shutdown.test.ts +205 -0
- package/src/activities/execute-cursor/__tests__/hermetic/plain-turn.test.ts +171 -0
- package/src/activities/execute-cursor/__tests__/hermetic/recovery-fresh-agent.test.ts +156 -0
- package/src/activities/execute-cursor/__tests__/hermetic/run-wait-arms.test.ts +286 -0
- package/src/activities/execute-cursor/__tests__/hermetic/stream-self-stop-arms.test.ts +332 -0
- package/src/activities/execute-cursor/__tests__/hermetic/thrown-error-arms.test.ts +193 -0
- package/src/activities/execute-cursor/__tests__/hermetic/tool-call.test.ts +137 -0
- package/src/activities/execute-cursor/__tests__/hermetic/unattributed-hook-block.test.ts +185 -0
- package/src/activities/execute-cursor/__tests__/hermetic/workspace-lock-timeout.test.ts +151 -0
- package/src/activities/execute-cursor/__tests__/http2-interceptor.test.ts +65 -35
- package/src/activities/execute-cursor/__tests__/message-translator.test.ts +0 -40
- package/src/activities/execute-cursor/__tests__/same-identity-reproposal.test.ts +171 -0
- package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +118 -243
- package/src/activities/execute-cursor/__tests__/usage-pricing.test.ts +84 -0
- package/src/activities/execute-cursor/adapter.ts +138 -0
- package/src/activities/execute-cursor/approval-state.ts +2 -2
- package/src/activities/execute-cursor/capture-flow.ts +18 -41
- package/src/activities/execute-cursor/cursor-capabilities.ts +35 -0
- package/src/activities/execute-cursor/cursor-mcp-config.ts +2 -2
- package/src/activities/execute-cursor/fetch-interceptor.ts +48 -45
- package/src/activities/execute-cursor/http2-interceptor.ts +47 -34
- package/src/activities/execute-cursor/message-translator.ts +90 -45
- package/src/activities/execute-cursor/prompt-builder.ts +327 -12
- package/src/activities/execute-cursor/rejection-capture.ts +5 -12
- package/src/activities/execute-cursor/session-lifecycle.ts +1 -5
- package/src/activities/execute-cursor/turn-boundary.ts +1 -1
- package/src/activities/execute-cursor/turn-settle.ts +601 -0
- package/src/activities/execute-cursor/turn-setup.ts +645 -0
- package/src/activities/execute-cursor/turn-stream.ts +84 -210
- package/src/activities/execute-cursor/turn.ts +240 -0
- package/src/activities/execute-cursor/usage-pricing.ts +67 -0
- package/src/activities/execute-deep-agent/approval-file-change.ts +1 -1
- package/src/activities/execute-deep-agent/environment.ts +1 -1
- package/src/activities/execute-deep-agent/setup.ts +3 -2
- package/src/activities/hydrate-workflow-execution.ts +1 -1
- package/src/config.ts +13 -0
- package/src/harness/__tests__/import-direction.test.ts +168 -0
- package/src/harness/__tests__/persist-chokepoint.test.ts +146 -0
- package/src/harness/__tests__/registry.test.ts +168 -0
- package/src/harness/__tests__/run-turn.test.ts +165 -0
- package/src/harness/__tests__/stop-controller.test.ts +49 -0
- package/src/harness/__tests__/turn-context.test.ts +178 -0
- package/src/harness/__tests__/usage-accumulator.test.ts +83 -0
- package/src/harness/capabilities.ts +92 -0
- package/src/harness/persist-chokepoint.ts +112 -0
- package/src/harness/registry.ts +197 -0
- package/src/harness/run-turn.ts +618 -0
- package/src/harness/stop-controller.ts +83 -0
- package/src/harness/terminal-table.ts +276 -0
- package/src/harness/turn-context.ts +1028 -0
- package/src/harness/types.ts +473 -0
- package/src/harness/usage-accumulator.ts +122 -0
- package/src/harness-adapters.ts +30 -0
- package/src/main.ts +1 -1
- package/src/runner-manager.ts +68 -61
- package/src/runner.ts +67 -78
- package/src/shared/__tests__/attachment-naming.test.ts +1 -1
- package/src/{activities/execute-cursor → shared}/__tests__/attachment-resolver.test.ts +6 -4
- package/src/{activities/execute-cursor → shared}/__tests__/cost-guard.test.ts +2 -1
- package/src/{activities/execute-cursor → shared}/__tests__/exact-apply.test.ts +3 -3
- package/src/{activities/execute-cursor → shared}/__tests__/extract-structured-output.test.ts +8 -8
- package/src/{activities/execute-cursor → shared}/__tests__/persist-decision.test.ts +4 -2
- package/src/{activities/execute-cursor → shared}/__tests__/skill-resolver.test.ts +1 -1
- package/src/shared/__tests__/subagent-rows.test.ts +34 -0
- package/src/shared/__tests__/worker-shutdown.test.ts +44 -39
- package/src/shared/approval-policy.ts +1 -1
- package/src/{activities/execute-cursor → shared}/attachment-resolver.ts +6 -6
- package/src/{activities/execute-cursor → shared}/blueprint-resolver.ts +7 -78
- package/src/shared/channel-attachment.ts +1 -1
- package/src/{activities/execute-cursor → shared}/cost-guard.ts +5 -1
- package/src/{activities/execute-cursor → shared}/env-resolver.ts +1 -1
- package/src/{activities/execute-cursor → shared}/exact-apply.ts +7 -7
- package/src/shared/execution-context.ts +45 -0
- package/src/{activities/execute-cursor → shared}/extract-structured-output.ts +10 -7
- package/src/shared/filereview/capture.ts +1 -1
- package/src/shared/heartbeat.ts +13 -33
- package/src/{activities/execute-cursor → shared}/persist-decision.ts +5 -2
- package/src/{activities/execute-cursor → shared}/skill-resolver.ts +20 -6
- package/src/shared/subagent-rows.ts +39 -0
- package/src/shared/worker-shutdown.ts +55 -25
- package/src/{activities/execute-cursor/__tests__/workspace-provision.test.ts → shared/workspace/__tests__/session-provision.test.ts} +25 -28
- package/src/shared/workspace/platform-dir.ts +1 -1
- package/src/{activities/execute-cursor/workspace-provision.ts → shared/workspace/session-provision.ts} +29 -16
- package/dist/activities/execute-cursor/attachment-resolver.js.map +0 -1
- package/dist/activities/execute-cursor/blueprint-resolver.js.map +0 -1
- package/dist/activities/execute-cursor/cost-guard.js.map +0 -1
- package/dist/activities/execute-cursor/env-resolver.js.map +0 -1
- package/dist/activities/execute-cursor/exact-apply.js.map +0 -1
- package/dist/activities/execute-cursor/extract-structured-output.js.map +0 -1
- package/dist/activities/execute-cursor/index.d.ts +0 -209
- package/dist/activities/execute-cursor/index.js +0 -2345
- package/dist/activities/execute-cursor/index.js.map +0 -1
- package/dist/activities/execute-cursor/persist-decision.js.map +0 -1
- package/dist/activities/execute-cursor/skill-resolver.js.map +0 -1
- package/dist/activities/execute-cursor/usage-accumulator.d.ts +0 -89
- package/dist/activities/execute-cursor/usage-accumulator.js +0 -121
- package/dist/activities/execute-cursor/usage-accumulator.js.map +0 -1
- package/dist/activities/execute-cursor/workspace-provision.d.ts +0 -42
- package/dist/activities/execute-cursor/workspace-provision.js.map +0 -1
- package/src/activities/execute-cursor/__tests__/usage-accumulator.test.ts +0 -164
- package/src/activities/execute-cursor/index.ts +0 -2876
- package/src/activities/execute-cursor/usage-accumulator.ts +0 -169
- /package/dist/{activities/execute-cursor → shared}/env-resolver.js +0 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two readers of one fact must agree: the turn runtime reads the adjudicated
|
|
3
|
+
* approvals as the VERDICT (`harness/turn-context.ts` `approvalDecisionsOf`,
|
|
4
|
+
* the `TurnInput.approvalDecisions` the contract carries) and this adapter
|
|
5
|
+
* reads the same rows for the FACTS beside the verdict (the pending-approval
|
|
6
|
+
* protos the grant builder and the reinvocation prompt render, the content
|
|
7
|
+
* digest that authorizes an approved edit by its exact bytes —
|
|
8
|
+
* `reconstructAdjudicatedApprovals`). The contract's rule is that an adapter
|
|
9
|
+
* never re-derives the verdict itself, so the two functions must select the
|
|
10
|
+
* same rows in the same order or the grants would be minted for a different
|
|
11
|
+
* set than the runtime decided on.
|
|
12
|
+
*
|
|
13
|
+
* Lives on the adapter's side because `src/harness/` never imports
|
|
14
|
+
* `src/activities/`, tests included (`harness/__tests__/import-direction.test.ts`).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { describe, expect, it } from "vitest";
|
|
18
|
+
import { create } from "@bufbuild/protobuf";
|
|
19
|
+
import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
20
|
+
import { AgentMessageSchema, ToolCallSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
21
|
+
import { ApprovalAction, MessageType, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
22
|
+
|
|
23
|
+
import { approvalDecisionsOf } from "../../../harness/turn-context.js";
|
|
24
|
+
import { reconstructAdjudicatedApprovals } from "../approval-state.js";
|
|
25
|
+
|
|
26
|
+
describe("the runtime's approvalDecisionsOf and the adapter's reconstructAdjudicatedApprovals", () => {
|
|
27
|
+
it("select the same rows in the same order over every row shape a transcript carries", () => {
|
|
28
|
+
const status = create(AgentExecutionStatusSchema, {
|
|
29
|
+
messages: [
|
|
30
|
+
create(AgentMessageSchema, {
|
|
31
|
+
type: MessageType.MESSAGE_AI,
|
|
32
|
+
toolCalls: [
|
|
33
|
+
create(ToolCallSchema, {
|
|
34
|
+
id: "tc-approved",
|
|
35
|
+
name: "Shell",
|
|
36
|
+
status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
|
|
37
|
+
approvalAction: ApprovalAction.APPROVE,
|
|
38
|
+
approvalContentDigest: "sha256:approved",
|
|
39
|
+
}),
|
|
40
|
+
create(ToolCallSchema, {
|
|
41
|
+
id: "tc-undecided",
|
|
42
|
+
name: "Shell",
|
|
43
|
+
status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
|
|
44
|
+
approvalAction: ApprovalAction.UNSPECIFIED,
|
|
45
|
+
}),
|
|
46
|
+
],
|
|
47
|
+
}),
|
|
48
|
+
create(AgentMessageSchema, {
|
|
49
|
+
type: MessageType.MESSAGE_AI,
|
|
50
|
+
toolCalls: [
|
|
51
|
+
create(ToolCallSchema, {
|
|
52
|
+
id: "tc-completed",
|
|
53
|
+
name: "Read",
|
|
54
|
+
status: ToolCallStatus.TOOL_CALL_COMPLETED,
|
|
55
|
+
approvalAction: ApprovalAction.APPROVE,
|
|
56
|
+
}),
|
|
57
|
+
create(ToolCallSchema, {
|
|
58
|
+
id: "tc-skipped",
|
|
59
|
+
name: "Write",
|
|
60
|
+
status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
|
|
61
|
+
approvalAction: ApprovalAction.SKIP,
|
|
62
|
+
}),
|
|
63
|
+
create(ToolCallSchema, {
|
|
64
|
+
id: "tc-rejected",
|
|
65
|
+
name: "Write",
|
|
66
|
+
status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
|
|
67
|
+
approvalAction: ApprovalAction.REJECT,
|
|
68
|
+
}),
|
|
69
|
+
],
|
|
70
|
+
}),
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const runtime = approvalDecisionsOf(status);
|
|
75
|
+
const adapter = reconstructAdjudicatedApprovals(status.messages);
|
|
76
|
+
|
|
77
|
+
expect([...runtime.entries()]).toEqual([...adapter.decisions.entries()]);
|
|
78
|
+
expect(
|
|
79
|
+
adapter.pendingApprovals.map((pa) => pa.toolCallId),
|
|
80
|
+
"the adapter's row facts cover exactly the rows the runtime decided on",
|
|
81
|
+
).toEqual([...runtime.keys()]);
|
|
82
|
+
expect([...runtime.keys()]).toEqual(["tc-approved", "tc-skipped", "tc-rejected"]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("both read nothing from a fresh transcript", () => {
|
|
86
|
+
const status = create(AgentExecutionStatusSchema, {});
|
|
87
|
+
expect(approvalDecisionsOf(status).size).toBe(0);
|
|
88
|
+
expect(reconstructAdjudicatedApprovals(status.messages).decisions.size).toBe(0);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -12,8 +12,8 @@ import { create } from "@bufbuild/protobuf";
|
|
|
12
12
|
import { ApprovalAction, InteractionMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
13
13
|
import { PendingApprovalSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb";
|
|
14
14
|
|
|
15
|
-
import { appendStructuredOutputDirective, buildPrompt, isHitlReinvocation, primarySendCarriesImages, promptCarriesStandingContext } from "../
|
|
16
|
-
import type { BuildPromptInput } from "../
|
|
15
|
+
import { appendStructuredOutputDirective, buildPrompt, isHitlReinvocation, primarySendCarriesImages, promptCarriesStandingContext } from "../prompt-builder.js";
|
|
16
|
+
import type { BuildPromptInput } from "../prompt-builder.js";
|
|
17
17
|
import { buildReinvocationPrompt, formatInteractionModePrefix, formatImplementPlanSection, formatToolApprovalProtocol, buildToolApprovalRuleFile } from "../prompt-builder.js";
|
|
18
18
|
import { PLAN_MODE_DIRECTIVE } from "../../../shared/plan-mode-prompt.js";
|
|
19
19
|
import type { AgentResolution, AgentResolutionReason } from "../session-lifecycle.js";
|
|
@@ -38,7 +38,7 @@ function resolution(
|
|
|
38
38
|
function input(overrides: Partial<BuildPromptInput>): BuildPromptInput {
|
|
39
39
|
return {
|
|
40
40
|
resolution: resolution("local", "resumed_successfully"),
|
|
41
|
-
approvalDecisions:
|
|
41
|
+
approvalDecisions: new Map(),
|
|
42
42
|
instructions: "You are a test agent.",
|
|
43
43
|
userMessage: USER_MESSAGE,
|
|
44
44
|
skills: [],
|
|
@@ -357,7 +357,7 @@ describe("buildPrompt", () => {
|
|
|
357
357
|
const prompt = buildPrompt(
|
|
358
358
|
input({
|
|
359
359
|
resolution: resolution("local", reason),
|
|
360
|
-
approvalDecisions: hitl ? hitlDecisions :
|
|
360
|
+
approvalDecisions: hitl ? hitlDecisions : new Map(),
|
|
361
361
|
pendingApprovals: hitl ? hitlApprovals : [],
|
|
362
362
|
recalledMemories: { facts: ["Prefers OpenTofu."] },
|
|
363
363
|
}),
|
|
@@ -395,8 +395,7 @@ describe("buildPrompt", () => {
|
|
|
395
395
|
});
|
|
396
396
|
|
|
397
397
|
describe("isHitlReinvocation (paired with resolution.reason at every consumer — issue #366)", () => {
|
|
398
|
-
it("is false with
|
|
399
|
-
expect(isHitlReinvocation(undefined)).toBe(false);
|
|
398
|
+
it("is false with an empty map (the shape a turn with no adjudicated approvals carries)", () => {
|
|
400
399
|
expect(isHitlReinvocation(new Map())).toBe(false);
|
|
401
400
|
});
|
|
402
401
|
|
|
@@ -504,8 +503,8 @@ describe("HITL recovery — fresh agent mid-HITL (issue #366)", () => {
|
|
|
504
503
|
// resolution-time resume failure.
|
|
505
504
|
expect(primarySendCarriesImages(decisions(), "created_after_resume_failure")).toBe(true);
|
|
506
505
|
// Non-HITL turns always carry the message's images, resumed or not.
|
|
507
|
-
expect(primarySendCarriesImages(
|
|
508
|
-
expect(primarySendCarriesImages(
|
|
506
|
+
expect(primarySendCarriesImages(new Map(), "resumed_successfully")).toBe(true);
|
|
507
|
+
expect(primarySendCarriesImages(new Map(), "created_first_execution")).toBe(true);
|
|
509
508
|
});
|
|
510
509
|
|
|
511
510
|
it("the structured-output directive is a pure append shared by the primary and recovery sends — absent schema, absent suffix", () => {
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
ToolCallStatus,
|
|
45
45
|
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
46
46
|
import {
|
|
47
|
-
|
|
47
|
+
CURSOR_FILE_REVIEW_IDENTITY,
|
|
48
48
|
captureBaselineToLedger,
|
|
49
49
|
captureTurnToLedger,
|
|
50
50
|
} from "../capture-flow.js";
|
|
@@ -52,6 +52,33 @@ import { toolIdentity, primaryToken } from "../approval-state.js";
|
|
|
52
52
|
import { contentDigest } from "../../../shared/file-tools.js";
|
|
53
53
|
import { buildObservationStagingScript, casObservationsDir } from "../cas-observations.js";
|
|
54
54
|
import { makeInMemoryArtifactStorage } from "../../../__test-utils__/fake-artifact-storage.js";
|
|
55
|
+
import { applyCaptureDecisions as sharedApplyCaptureDecisions } from "../../../shared/filereview/capture.js";
|
|
56
|
+
import { casBlobReader } from "../../../shared/filereview/cas-substrate.js";
|
|
57
|
+
import type { ArtifactStorage } from "../../../shared/artifact-storage.js";
|
|
58
|
+
import type { FileChangeSet } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The resume reconcile as the turn runtime runs it for this harness
|
|
62
|
+
* (`harness/turn-context.ts` `reconcileReinvocation`): the shared reconcile
|
|
63
|
+
* under Cursor's file-review identity, with the CAS blob reader derived from
|
|
64
|
+
* the same storage. Bound here so every resume case below proves the Cursor
|
|
65
|
+
* pair (harness id, excluded gate paths) over the shared code, exactly as
|
|
66
|
+
* production composes it.
|
|
67
|
+
*/
|
|
68
|
+
function applyCaptureDecisions(opts: {
|
|
69
|
+
readonly status: AgentExecutionStatus;
|
|
70
|
+
readonly gitRoot: string;
|
|
71
|
+
readonly executionId: string;
|
|
72
|
+
readonly changeSet: FileChangeSet;
|
|
73
|
+
readonly storage?: ArtifactStorage;
|
|
74
|
+
readonly gitWorkspace?: boolean;
|
|
75
|
+
}) {
|
|
76
|
+
return sharedApplyCaptureDecisions({
|
|
77
|
+
...opts,
|
|
78
|
+
...CURSOR_FILE_REVIEW_IDENTITY,
|
|
79
|
+
readBlob: opts.storage ? casBlobReader(opts.storage) : undefined,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
55
82
|
|
|
56
83
|
const execFileAsync = promisify(execFile);
|
|
57
84
|
const EXEC_ID = "exec-capflow-1";
|
|
@@ -50,7 +50,7 @@ import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
|
50
50
|
import { tmpdir } from "node:os";
|
|
51
51
|
import { join } from "node:path";
|
|
52
52
|
|
|
53
|
-
import { applyApprovedWholeFileWrites, excludeAppliedFromGrants } from "
|
|
53
|
+
import { applyApprovedWholeFileWrites, excludeAppliedFromGrants } from "../../../shared/exact-apply.js";
|
|
54
54
|
import { reconstructAdjudicatedApprovals, buildApprovalGrants } from "../approval-state.js";
|
|
55
55
|
import type { ApprovalGrant } from "../approval-state.js";
|
|
56
56
|
import { LocalWorkspaceBackend } from "../../../shared/workspace/local-backend.js";
|
|
@@ -19,7 +19,7 @@ describe("fetch-interceptor", () => {
|
|
|
19
19
|
let calls: CapturedCall[];
|
|
20
20
|
let installFetchInterceptor: typeof import("../fetch-interceptor.js").installFetchInterceptor;
|
|
21
21
|
let uninstallFetchInterceptor: typeof import("../fetch-interceptor.js").uninstallFetchInterceptor;
|
|
22
|
-
let getExecutionContext: typeof import("
|
|
22
|
+
let getExecutionContext: typeof import("../../../shared/execution-context.js").getExecutionContext;
|
|
23
23
|
|
|
24
24
|
beforeEach(async () => {
|
|
25
25
|
calls = [];
|
|
@@ -40,7 +40,9 @@ describe("fetch-interceptor", () => {
|
|
|
40
40
|
const mod = await import("../fetch-interceptor.js");
|
|
41
41
|
installFetchInterceptor = mod.installFetchInterceptor;
|
|
42
42
|
uninstallFetchInterceptor = mod.uninstallFetchInterceptor;
|
|
43
|
-
|
|
43
|
+
// The store lives in shared/ and must be the SAME fresh instance the
|
|
44
|
+
// interceptor just imported, so it is resolved after the reset too.
|
|
45
|
+
getExecutionContext = (await import("../../../shared/execution-context.js")).getExecutionContext;
|
|
44
46
|
});
|
|
45
47
|
|
|
46
48
|
afterEach(() => {
|
|
@@ -50,7 +52,7 @@ describe("fetch-interceptor", () => {
|
|
|
50
52
|
|
|
51
53
|
describe("Connect-RPC paths via fetch (BiDi proxy auth injection)", () => {
|
|
52
54
|
beforeEach(() => {
|
|
53
|
-
installFetchInterceptor({ proxyEndpoint: PROXY_ENDPOINT,
|
|
55
|
+
installFetchInterceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: { current: STIGMER_TOKEN } });
|
|
54
56
|
});
|
|
55
57
|
|
|
56
58
|
it("injects x-stigmer-auth on /aiserver.v1 path targeting proxy endpoint", async () => {
|
|
@@ -139,7 +141,7 @@ describe("fetch-interceptor", () => {
|
|
|
139
141
|
|
|
140
142
|
describe("REST path URL rewriting (existing behavior)", () => {
|
|
141
143
|
beforeEach(() => {
|
|
142
|
-
installFetchInterceptor({ proxyEndpoint: PROXY_ENDPOINT,
|
|
144
|
+
installFetchInterceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: { current: STIGMER_TOKEN } });
|
|
143
145
|
});
|
|
144
146
|
|
|
145
147
|
it("rewrites Cursor-domain REST URLs and replaces auth", async () => {
|
|
@@ -171,9 +173,65 @@ describe("fetch-interceptor", () => {
|
|
|
171
173
|
});
|
|
172
174
|
});
|
|
173
175
|
|
|
176
|
+
describe("the token ref is read per request (the root rotates it in place; Q-S2-7)", () => {
|
|
177
|
+
let proxyTokenRef: { current: string | null };
|
|
178
|
+
|
|
179
|
+
beforeEach(() => {
|
|
180
|
+
proxyTokenRef = { current: STIGMER_TOKEN };
|
|
181
|
+
installFetchInterceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef });
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("a rotation reaches the next REST rewrite and the next BiDi-auth fetch without a call into the module", async () => {
|
|
185
|
+
proxyTokenRef.current = "refreshed-stigmer-jwt-token";
|
|
186
|
+
|
|
187
|
+
await globalThis.fetch("https://api.cursor.com/v1/models", { headers: { authorization: "Bearer original-key" } });
|
|
188
|
+
await globalThis.fetch(`${PROXY_ENDPOINT}/aiserver.v1.AnalyticsService/BootstrapStatsig`, { method: "POST" });
|
|
189
|
+
|
|
190
|
+
expect(calls).toHaveLength(2);
|
|
191
|
+
expect(new Headers(calls[0].init?.headers).get("authorization")).toBe("Bearer refreshed-stigmer-jwt-token");
|
|
192
|
+
expect(new Headers(calls[1].init?.headers).get("x-stigmer-auth")).toBe("Bearer refreshed-stigmer-jwt-token");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("an emptied ref sends no proxy credential and never leaks the SDK's own authorization to the proxy", async () => {
|
|
196
|
+
proxyTokenRef.current = null;
|
|
197
|
+
|
|
198
|
+
await globalThis.fetch("https://api.cursor.com/v1/models", { headers: { authorization: "Bearer cursor-secret" } });
|
|
199
|
+
await globalThis.fetch(`${PROXY_ENDPOINT}/aiserver.v1.AnalyticsService/BootstrapStatsig`, { method: "POST" });
|
|
200
|
+
|
|
201
|
+
expect(new Headers(calls[0].init?.headers).get("authorization")).toBeNull();
|
|
202
|
+
expect(new Headers(calls[1].init?.headers).get("x-stigmer-auth")).toBeNull();
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe("install guard", () => {
|
|
207
|
+
it("a proxy endpoint with no bound ref fails the install, as a missing token always has, and leaves fetch untouched", () => {
|
|
208
|
+
const before = globalThis.fetch;
|
|
209
|
+
expect(() => installFetchInterceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: undefined })).toThrow(
|
|
210
|
+
/STIGMER_TOKEN is required when STIGMER_PROXY_ENDPOINT is set/,
|
|
211
|
+
);
|
|
212
|
+
expect(() => installFetchInterceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: { current: null } })).toThrow(
|
|
213
|
+
/STIGMER_TOKEN is required when STIGMER_PROXY_ENDPOINT is set/,
|
|
214
|
+
);
|
|
215
|
+
expect(globalThis.fetch).toBe(before);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("is idempotent: a second install rebinds the ref and keeps delegating to the fetch captured at load", async () => {
|
|
219
|
+
const first = { current: "first-token" };
|
|
220
|
+
const second = { current: "second-token" };
|
|
221
|
+
installFetchInterceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: first });
|
|
222
|
+
installFetchInterceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: second });
|
|
223
|
+
|
|
224
|
+
await globalThis.fetch(`${PROXY_ENDPOINT}/aiserver.v1.AnalyticsService/BootstrapStatsig`, { method: "POST" });
|
|
225
|
+
|
|
226
|
+
// One hop to the captured fetch (a self-capture would recurse or double-wrap).
|
|
227
|
+
expect(calls).toHaveLength(1);
|
|
228
|
+
expect(new Headers(calls[0].init?.headers).get("x-stigmer-auth")).toBe("Bearer second-token");
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
174
232
|
describe("timed REST path timing", () => {
|
|
175
233
|
beforeEach(() => {
|
|
176
|
-
installFetchInterceptor({ proxyEndpoint: PROXY_ENDPOINT,
|
|
234
|
+
installFetchInterceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: { current: STIGMER_TOKEN } });
|
|
177
235
|
});
|
|
178
236
|
|
|
179
237
|
/**
|
|
@@ -284,7 +342,7 @@ describe("fetch-interceptor", () => {
|
|
|
284
342
|
|
|
285
343
|
describe("passthrough (no interception)", () => {
|
|
286
344
|
beforeEach(() => {
|
|
287
|
-
installFetchInterceptor({ proxyEndpoint: PROXY_ENDPOINT,
|
|
345
|
+
installFetchInterceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: { current: STIGMER_TOKEN } });
|
|
288
346
|
});
|
|
289
347
|
|
|
290
348
|
it("does NOT intercept Connect-RPC paths on non-proxy hosts", async () => {
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hermetic golden: the DENY-AND-RETRY approval round trip through two real
|
|
3
|
+
* `ExecuteCursor` invocations, with the REAL bash hook as the out-of-process
|
|
4
|
+
* half.
|
|
5
|
+
*
|
|
6
|
+
* Invariant pinned (the Cursor pause primitive, parent §4a `pausePrimitive:
|
|
7
|
+
* "deny-and-retry"`): the model proposes a gated built-in (`shell`); the
|
|
8
|
+
* workspace hook the activity installed DENIES it and appends to the denial
|
|
9
|
+
* ledger; the stream loop reads the ledger on the next `tool_call` event, cancels
|
|
10
|
+
* the run, and the turn boundary overlays the row as WAITING_APPROVAL; the
|
|
11
|
+
* activity persists WAITING_FOR_APPROVAL, parks the agent, and RETURNS a slim
|
|
12
|
+
* status. The user approves (the server writes `approval_action` on the row —
|
|
13
|
+
* here the record does, per field ownership). The reinvocation (`thread_id` set,
|
|
14
|
+
* `turn_seq` 1) seeds its transcript from the persisted execution, derives a
|
|
15
|
+
* grant from the approved row, reinstalls the gate, and the SAME hook now ALLOWS
|
|
16
|
+
* the re-issued call — which the model emits under a FRESH `call_id`, and which
|
|
17
|
+
* the transcript reconciles onto the committed row by canonical identity (a
|
|
18
|
+
* superset, never a duplicate, never a second gate). The turn ends COMPLETED.
|
|
19
|
+
*
|
|
20
|
+
* Two goldens, one per invocation: `goldens/deny-and-retry.turn1.status.json`
|
|
21
|
+
* (the paused transcript) and `goldens/deny-and-retry.turn2.status.json` (the
|
|
22
|
+
* resumed, completed transcript). S2 must reproduce both byte for byte.
|
|
23
|
+
*
|
|
24
|
+
* Parent phase rows exercised beyond `tool-call`: reinvocation detection and
|
|
25
|
+
* transcript seeding (Phase 3); reading approval decisions from the persisted
|
|
26
|
+
* transcript (`reconstructAdjudicatedApprovals`); grants → approval state →
|
|
27
|
+
* the gate (Phase 5c); the first-denial early stop in the stream loop; the
|
|
28
|
+
* turn boundary's denial overlay; `enterApprovalPause` (WAITING_FOR_APPROVAL,
|
|
29
|
+
* park, return); the parked-agent checkout on the resume (#215, no `Agent.resume`
|
|
30
|
+
* call); the resume prompt.
|
|
31
|
+
*
|
|
32
|
+
* The hook is the runner's own generated bash script, found the way the SDK
|
|
33
|
+
* finds it (`.cursor/hooks.json` in the workspace) and run with the SDK's real
|
|
34
|
+
* preToolUse input shape (`cursor-hook-harness.ts` builders). Skipped where
|
|
35
|
+
* `bash` is unavailable — reported as SKIPPED, never a silent pass.
|
|
36
|
+
*
|
|
37
|
+
* Regenerate ONLY after a deliberate behavior change:
|
|
38
|
+
* npx vitest run src/activities/execute-cursor/__tests__/hermetic -u
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
|
42
|
+
import { toJson } from "@bufbuild/protobuf";
|
|
43
|
+
import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
44
|
+
import {
|
|
45
|
+
ApprovalAction,
|
|
46
|
+
ExecutionPhase,
|
|
47
|
+
ToolCallStatus,
|
|
48
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
49
|
+
|
|
50
|
+
vi.mock("@cursor/sdk", async () =>
|
|
51
|
+
(await import("../../__test-utils__/scripted-sdk.js")).scriptedCursorSdkModule(),
|
|
52
|
+
);
|
|
53
|
+
vi.mock("../../../../client/stigmer-client.js", async () =>
|
|
54
|
+
(await import("../../../../__test-utils__/hermetic-activity.js")).hermeticStigmerClientModule(),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
import {
|
|
58
|
+
ScriptedClock,
|
|
59
|
+
createHermeticEnvironment,
|
|
60
|
+
type HermeticEnvironment,
|
|
61
|
+
} from "../../../../__test-utils__/hermetic-activity.js";
|
|
62
|
+
import { hasBash, hookShell } from "../../__test-utils__/cursor-hook-harness.js";
|
|
63
|
+
import { ScriptedCursorAgent, sdkEvents, step } from "../../__test-utils__/scripted-agent.js";
|
|
64
|
+
import {
|
|
65
|
+
FIXTURE,
|
|
66
|
+
SDK_CATALOG,
|
|
67
|
+
beginCursorScenario,
|
|
68
|
+
cursorExecutionRecord,
|
|
69
|
+
runCursorTurn,
|
|
70
|
+
runWorkspaceHook,
|
|
71
|
+
sessionWorkspaceDir,
|
|
72
|
+
} from "../../__test-utils__/hermetic-cursor.js";
|
|
73
|
+
import { stubRegistryFetch } from "../../../../__test-utils__/model-registry-fixture.js";
|
|
74
|
+
|
|
75
|
+
const AGENT_ID = "agent-hermetic-deny-0001";
|
|
76
|
+
const RUN_1 = "run-hermetic-deny-0001";
|
|
77
|
+
const RUN_2 = "run-hermetic-deny-0002";
|
|
78
|
+
const CALL_TURN_1 = "call-hermetic-shell-0001";
|
|
79
|
+
const CALL_TURN_2 = "call-hermetic-shell-0002";
|
|
80
|
+
const USER_MESSAGE = "Run the build and tell me if it passes.";
|
|
81
|
+
const COMMAND = "npm run build";
|
|
82
|
+
const SHELL_ARGS = { command: COMMAND };
|
|
83
|
+
const BUILD_OUTPUT = "build ok (hermetic)\n";
|
|
84
|
+
const FINAL_TEXT = "The build passes.";
|
|
85
|
+
|
|
86
|
+
describe.skipIf(!hasBash)("ExecuteCursor hermetic — deny-and-retry approval round trip", () => {
|
|
87
|
+
let env: HermeticEnvironment;
|
|
88
|
+
let registry: ReturnType<typeof stubRegistryFetch>;
|
|
89
|
+
const clock = new ScriptedClock();
|
|
90
|
+
|
|
91
|
+
beforeAll(() => {
|
|
92
|
+
env = createHermeticEnvironment();
|
|
93
|
+
registry = stubRegistryFetch();
|
|
94
|
+
clock.install();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
afterAll(() => {
|
|
98
|
+
clock.uninstall();
|
|
99
|
+
registry.restore();
|
|
100
|
+
env.dispose();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("pauses on the hook's deny, then completes on the approved re-issue", async () => {
|
|
104
|
+
// ── Arrange ──────────────────────────────────────────────────────────────
|
|
105
|
+
const workspaceRoot = sessionWorkspaceDir(env);
|
|
106
|
+
const hookDecisions: string[] = [];
|
|
107
|
+
const runHook = (label: string) =>
|
|
108
|
+
step.effect(label, () => {
|
|
109
|
+
hookDecisions.push(runWorkspaceHook(workspaceRoot, hookShell(COMMAND)).permission);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const ev1 = sdkEvents(AGENT_ID, RUN_1);
|
|
113
|
+
const ev2 = sdkEvents(AGENT_ID, RUN_2);
|
|
114
|
+
const agent = new ScriptedCursorAgent({
|
|
115
|
+
agentId: AGENT_ID,
|
|
116
|
+
runIds: [RUN_1, RUN_2],
|
|
117
|
+
observeStep: () => clock.tick(),
|
|
118
|
+
turns: [
|
|
119
|
+
// Turn 1: the SDK runs the hook before executing the gated tool; the
|
|
120
|
+
// hook denies and writes the ledger; the SDK reports the call blocked.
|
|
121
|
+
[
|
|
122
|
+
step.event(ev1.init()),
|
|
123
|
+
step.event(ev1.assistant("I'll run the build now.")),
|
|
124
|
+
step.event(ev1.toolCall(CALL_TURN_1, "shell", "running", SHELL_ARGS)),
|
|
125
|
+
runHook("hook: turn 1 (expect deny)"),
|
|
126
|
+
step.event(ev1.toolCall(CALL_TURN_1, "shell", "error", SHELL_ARGS, "Blocked by hook")),
|
|
127
|
+
// Never reached: the loop detects the denial on the event above and
|
|
128
|
+
// cancels the run. Present so a regression that does NOT cancel is
|
|
129
|
+
// caught by the golden, not by a missing-result throw.
|
|
130
|
+
step.event(ev1.assistant("The build was blocked.")),
|
|
131
|
+
step.finished({ result: "The build was blocked." }),
|
|
132
|
+
],
|
|
133
|
+
// Turn 2 (resumed): the hook runs again for the re-issued call and now
|
|
134
|
+
// allows it (the grant); the tool runs under a FRESH call id.
|
|
135
|
+
[
|
|
136
|
+
runHook("hook: turn 2 (expect allow)"),
|
|
137
|
+
step.event(ev2.toolCall(CALL_TURN_2, "shell", "running", SHELL_ARGS)),
|
|
138
|
+
step.event(ev2.toolCall(CALL_TURN_2, "shell", "completed", SHELL_ARGS, BUILD_OUTPUT)),
|
|
139
|
+
step.event(ev2.assistant(FINAL_TEXT)),
|
|
140
|
+
step.turnEnded({ inputTokens: 3_100, outputTokens: 60 }),
|
|
141
|
+
step.finished({ result: FINAL_TEXT, model: { id: FIXTURE.model, params: [] } }),
|
|
142
|
+
],
|
|
143
|
+
],
|
|
144
|
+
});
|
|
145
|
+
const record = cursorExecutionRecord({ message: USER_MESSAGE });
|
|
146
|
+
const scenario = beginCursorScenario({
|
|
147
|
+
env,
|
|
148
|
+
clock,
|
|
149
|
+
record,
|
|
150
|
+
sdk: { agents: [agent], catalog: SDK_CATALOG },
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// ── Act 1: the first invocation ──────────────────────────────────────────
|
|
154
|
+
const turn1 = await runCursorTurn(scenario, { threadId: "", turnSeq: 0 });
|
|
155
|
+
|
|
156
|
+
// ── Assert 1: paused for approval, returned, agent parked ────────────────
|
|
157
|
+
expect(hookDecisions, "the real hook denied the gated shell call").toEqual(["deny"]);
|
|
158
|
+
expect(turn1.outcome.kind, "an approval pause RETURNS to the workflow").toBe("returned");
|
|
159
|
+
expect((turn1.outcome as { value: Record<string, unknown> }).value.phase).toBe(
|
|
160
|
+
"EXECUTION_WAITING_FOR_APPROVAL",
|
|
161
|
+
);
|
|
162
|
+
expect(record.persistedPhases).toEqual([
|
|
163
|
+
ExecutionPhase.EXECUTION_IN_PROGRESS,
|
|
164
|
+
ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL,
|
|
165
|
+
]);
|
|
166
|
+
expect(agent.runs[0].cancelCalls, "the first denial cancels the run exactly once").toHaveLength(1);
|
|
167
|
+
expect(agent.closeCalls, "the agent is parked for the resume, not closed").toBe(0);
|
|
168
|
+
|
|
169
|
+
const waiting = record.waitingToolCalls();
|
|
170
|
+
expect(waiting, "exactly one gate row").toHaveLength(1);
|
|
171
|
+
expect(waiting[0].id).toBe(CALL_TURN_1);
|
|
172
|
+
expect(waiting[0].name).toBe("shell");
|
|
173
|
+
expect(waiting[0].requiresApproval).toBe(true);
|
|
174
|
+
// The gate row carries what the HOOK saw (the ledger's captured tool_input:
|
|
175
|
+
// command, cwd, timeout), overlaid on the stream's args — the golden pins
|
|
176
|
+
// the full shape; here only the shared salient field is asserted.
|
|
177
|
+
expect(waiting[0].args).toMatchObject(SHELL_ARGS);
|
|
178
|
+
// The model's post-denial narration never reaches the user as fact.
|
|
179
|
+
expect(record.status?.messages.some((m) => m.content === "The build was blocked.")).toBe(false);
|
|
180
|
+
|
|
181
|
+
const turn1Json = JSON.stringify(toJson(AgentExecutionStatusSchema, record.lastFullStatus!), null, 2) + "\n";
|
|
182
|
+
await expect(turn1Json).toMatchFileSnapshot("./goldens/deny-and-retry.turn1.status.json");
|
|
183
|
+
|
|
184
|
+
// ── The user approves (the server's SubmitApproval effect on the row) ────
|
|
185
|
+
clock.tick();
|
|
186
|
+
expect(record.decideWaitingToolCalls(ApprovalAction.APPROVE, new Date().toISOString())).toBe(1);
|
|
187
|
+
|
|
188
|
+
// ── Act 2: the reinvocation ──────────────────────────────────────────────
|
|
189
|
+
const turn2 = await runCursorTurn(scenario, { threadId: AGENT_ID, turnSeq: 1 });
|
|
190
|
+
|
|
191
|
+
// ── Assert 2: allowed, reconciled, completed ─────────────────────────────
|
|
192
|
+
expect(hookDecisions, "the SAME hook allows the approved re-issue").toEqual(["deny", "allow"]);
|
|
193
|
+
expect(turn2.outcome.kind).toBe("returned");
|
|
194
|
+
const slim2 = (turn2.outcome as { value: Record<string, unknown> }).value;
|
|
195
|
+
expect(slim2.phase).toBe("EXECUTION_COMPLETED");
|
|
196
|
+
expect(slim2.final_text).toBe(FINAL_TEXT);
|
|
197
|
+
expect(record.persistedPhases).toEqual([
|
|
198
|
+
ExecutionPhase.EXECUTION_IN_PROGRESS,
|
|
199
|
+
ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL,
|
|
200
|
+
ExecutionPhase.EXECUTION_IN_PROGRESS,
|
|
201
|
+
ExecutionPhase.EXECUTION_COMPLETED,
|
|
202
|
+
]);
|
|
203
|
+
|
|
204
|
+
// #215: the resume checks the parked agent out of the session cache; the
|
|
205
|
+
// SDK is asked to create once for the whole round trip and never to resume.
|
|
206
|
+
expect(scenario.sdk.resolutions.map((r) => r.kind)).toEqual(["create"]);
|
|
207
|
+
expect(agent.sends).toHaveLength(2);
|
|
208
|
+
|
|
209
|
+
// Each tool gates exactly once: the committed row survives under its
|
|
210
|
+
// ORIGINAL id, now COMPLETED with the tool's result; the fresh call id the
|
|
211
|
+
// model emitted is reconciled onto it, never a second row.
|
|
212
|
+
const rows = record.toolCalls();
|
|
213
|
+
expect(rows.filter((tc) => tc.name === "shell")).toHaveLength(1);
|
|
214
|
+
const shell = rows.find((tc) => tc.name === "shell")!;
|
|
215
|
+
expect(shell.id).toBe(CALL_TURN_1);
|
|
216
|
+
expect(shell.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
217
|
+
expect(shell.result).toBe(BUILD_OUTPUT);
|
|
218
|
+
expect(shell.approvalAction, "the decision survives on the row").toBe(ApprovalAction.APPROVE);
|
|
219
|
+
expect(rows.some((tc) => tc.id === CALL_TURN_2)).toBe(false);
|
|
220
|
+
expect(record.waitingToolCalls()).toHaveLength(0);
|
|
221
|
+
|
|
222
|
+
// ── Assert: hermeticity ──────────────────────────────────────────────────
|
|
223
|
+
expect(registry.urls.every((u) => u.includes("/model-registry"))).toBe(true);
|
|
224
|
+
|
|
225
|
+
const turn2Json = JSON.stringify(toJson(AgentExecutionStatusSchema, record.lastFullStatus!), null, 2) + "\n";
|
|
226
|
+
await expect(turn2Json).toMatchFileSnapshot("./goldens/deny-and-retry.turn2.status.json");
|
|
227
|
+
});
|
|
228
|
+
});
|