@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,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hermetic goldens: USER PAUSE versus WORKER SHUTDOWN — the two interruptions
|
|
3
|
+
* that THROW, and the byte-pinned copy that tells them apart.
|
|
4
|
+
*
|
|
5
|
+
* Invariant pinned (the throw-vs-return table the runtime owns,
|
|
6
|
+
* `harness/terminal-table.ts`; parent §5c): both interruptions end the activity with a thrown Temporal
|
|
7
|
+
* `CancelledFailure` (never a return), but they persist DIFFERENT terminal
|
|
8
|
+
* states the control plane keys on:
|
|
9
|
+
*
|
|
10
|
+
* - a user pause (Temporal delivers cancellation, no shutdown signal) persists
|
|
11
|
+
* EXECUTION_PAUSED with "Execution paused by user. Use resume to continue."
|
|
12
|
+
* and throws "Activity paused by orchestrator";
|
|
13
|
+
* - a worker shutdown (the runner's per-queue shutdown signal is aborted AND
|
|
14
|
+
* cancellation is delivered) persists EXECUTION_FAILED with the error copy
|
|
15
|
+
* "Execution interrupted: runner worker was shut down. Retry or resume." and
|
|
16
|
+
* throws "Activity cancelled (worker shutdown, not user pause)".
|
|
17
|
+
*
|
|
18
|
+
* The cloud channel decision table matches on that copy, so the goldens
|
|
19
|
+
* (`goldens/pause.status.json`, `goldens/worker-shutdown.status.json`) are the
|
|
20
|
+
* wire contract, character for character. Both interruptions are triggered from
|
|
21
|
+
* a script `effect` step — never a timer — so the instant is deterministic: the
|
|
22
|
+
* stream loop sees `isCancelled()` on its next iteration and never processes
|
|
23
|
+
* the event that follows.
|
|
24
|
+
*
|
|
25
|
+
* What the goldens ALSO pin: each path appends its system message exactly
|
|
26
|
+
* ONCE. The orchestrator this net was recorded on appended it twice — once in
|
|
27
|
+
* `resolvePreBoundaryTerminal` before the throw, once again in the outer
|
|
28
|
+
* `catch (CancelledFailure)`, which re-stamped and re-persisted — and the
|
|
29
|
+
* goldens pinned that double on purpose so S2 could reproduce the wire byte
|
|
30
|
+
* for byte (stigmer#1054, Q-S2-4). The runtime's `settleWith` is now the one
|
|
31
|
+
* writer of a terminal arm, so a second row has no code path to come from.
|
|
32
|
+
*
|
|
33
|
+
* Runtime phases exercised: the adapter's stop-signal cancel; the runtime's
|
|
34
|
+
* `classifyTurnInterruption`; the terminal table's pause and worker-shutdown
|
|
35
|
+
* arms through `settleWith`; the `finally` teardown under a thrown exit.
|
|
36
|
+
*
|
|
37
|
+
* Regeneration history:
|
|
38
|
+
* - S2 M3b (entry 20260911.03): timestamp-only diff — every terminal stamp
|
|
39
|
+
* one scripted second earlier: the adapter cancels the SDK run the instant
|
|
40
|
+
* the runtime's stop signal aborts (Q-M3-4), before the SDK double pulls,
|
|
41
|
+
* and the clock ticks on, one more step. Transcript unchanged.
|
|
42
|
+
* - 2026-09-12 (stigmer#1054, the PR after S2): one removed system row per
|
|
43
|
+
* golden, nothing else. The transcript now carries each terminal row once.
|
|
44
|
+
*
|
|
45
|
+
* Regenerate ONLY after a deliberate behavior change:
|
|
46
|
+
* npx vitest run src/activities/execute-cursor/__tests__/hermetic -u
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
|
50
|
+
import { toJson } from "@bufbuild/protobuf";
|
|
51
|
+
import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
52
|
+
import { ExecutionPhase, MessageType } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
53
|
+
|
|
54
|
+
vi.mock("@cursor/sdk", async () =>
|
|
55
|
+
(await import("../../__test-utils__/scripted-sdk.js")).scriptedCursorSdkModule(),
|
|
56
|
+
);
|
|
57
|
+
vi.mock("../../../../client/stigmer-client.js", async () =>
|
|
58
|
+
(await import("../../../../__test-utils__/hermetic-activity.js")).hermeticStigmerClientModule(),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
import {
|
|
62
|
+
ScriptedClock,
|
|
63
|
+
createHermeticEnvironment,
|
|
64
|
+
threwCancelledFailure,
|
|
65
|
+
type HermeticEnvironment,
|
|
66
|
+
type InvocationControls,
|
|
67
|
+
} from "../../../../__test-utils__/hermetic-activity.js";
|
|
68
|
+
import { ScriptedCursorAgent, sdkEvents, step } from "../../__test-utils__/scripted-agent.js";
|
|
69
|
+
import {
|
|
70
|
+
SDK_CATALOG,
|
|
71
|
+
beginCursorScenario,
|
|
72
|
+
cursorExecutionRecord,
|
|
73
|
+
runCursorTurn,
|
|
74
|
+
} from "../../__test-utils__/hermetic-cursor.js";
|
|
75
|
+
import { stubRegistryFetch } from "../../../../__test-utils__/model-registry-fixture.js";
|
|
76
|
+
|
|
77
|
+
const USER_MESSAGE = "Refactor the parser module.";
|
|
78
|
+
const NEVER_SEEN = "This text must never reach the transcript.";
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A turn that is interrupted mid-stream by `interrupt(controls)`: one assistant
|
|
82
|
+
* message lands, then the interruption, then an event the loop must NOT process.
|
|
83
|
+
*/
|
|
84
|
+
function interruptedTurn(
|
|
85
|
+
agentId: string,
|
|
86
|
+
runId: string,
|
|
87
|
+
clock: ScriptedClock,
|
|
88
|
+
getControls: () => InvocationControls,
|
|
89
|
+
interrupt: (controls: InvocationControls) => void,
|
|
90
|
+
): ScriptedCursorAgent {
|
|
91
|
+
const ev = sdkEvents(agentId, runId);
|
|
92
|
+
return new ScriptedCursorAgent({
|
|
93
|
+
agentId,
|
|
94
|
+
runIds: [runId],
|
|
95
|
+
observeStep: () => clock.tick(),
|
|
96
|
+
turns: [
|
|
97
|
+
[
|
|
98
|
+
step.event(ev.init()),
|
|
99
|
+
step.event(ev.assistant("Starting on the parser.")),
|
|
100
|
+
step.effect("interrupt the activity from outside", () => interrupt(getControls())),
|
|
101
|
+
step.event(ev.assistant(NEVER_SEEN)),
|
|
102
|
+
step.finished({ result: NEVER_SEEN }),
|
|
103
|
+
],
|
|
104
|
+
],
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
describe("ExecuteCursor hermetic — user pause vs worker shutdown", () => {
|
|
109
|
+
let env: HermeticEnvironment;
|
|
110
|
+
let registry: ReturnType<typeof stubRegistryFetch>;
|
|
111
|
+
const clock = new ScriptedClock();
|
|
112
|
+
|
|
113
|
+
beforeAll(() => {
|
|
114
|
+
env = createHermeticEnvironment();
|
|
115
|
+
registry = stubRegistryFetch();
|
|
116
|
+
clock.install();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
afterAll(() => {
|
|
120
|
+
clock.uninstall();
|
|
121
|
+
registry.restore();
|
|
122
|
+
env.dispose();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("a user pause persists PAUSED and throws CancelledFailure", async () => {
|
|
126
|
+
// ── Arrange ──────────────────────────────────────────────────────────────
|
|
127
|
+
clock.reset(); // both goldens read from :00
|
|
128
|
+
let controls: InvocationControls | undefined;
|
|
129
|
+
const agent = interruptedTurn(
|
|
130
|
+
"agent-hermetic-pause-0001",
|
|
131
|
+
"run-hermetic-pause-0001",
|
|
132
|
+
clock,
|
|
133
|
+
() => controls!,
|
|
134
|
+
(c) => c.cancel(),
|
|
135
|
+
);
|
|
136
|
+
const record = cursorExecutionRecord({ message: USER_MESSAGE });
|
|
137
|
+
const scenario = beginCursorScenario({ env, clock, record, sdk: { agents: [agent], catalog: SDK_CATALOG } });
|
|
138
|
+
|
|
139
|
+
// ── Act ──────────────────────────────────────────────────────────────────
|
|
140
|
+
const invocation = await runCursorTurn(scenario, { onControls: (c) => (controls = c) });
|
|
141
|
+
|
|
142
|
+
// ── Assert: the throw and the persisted state ────────────────────────────
|
|
143
|
+
expect(threwCancelledFailure(invocation.outcome), "a pause THROWS CancelledFailure").toBe(true);
|
|
144
|
+
if (!threwCancelledFailure(invocation.outcome)) throw new Error("unreachable");
|
|
145
|
+
expect(invocation.outcome.error.message).toBe("Activity paused by orchestrator");
|
|
146
|
+
expect(record.persistedPhases).toEqual([
|
|
147
|
+
ExecutionPhase.EXECUTION_IN_PROGRESS,
|
|
148
|
+
ExecutionPhase.EXECUTION_PAUSED,
|
|
149
|
+
]);
|
|
150
|
+
const final = record.lastFullStatus!;
|
|
151
|
+
expect(final.error, "a pause is not an error").toBe("");
|
|
152
|
+
expect(final.completedAt, "a paused turn is not complete").toBe("");
|
|
153
|
+
expect(
|
|
154
|
+
final.messages.filter((m) => m.type === MessageType.MESSAGE_SYSTEM).map((m) => m.content),
|
|
155
|
+
).toEqual(["Execution paused by user. Use resume to continue."]);
|
|
156
|
+
expect(final.messages.some((m) => m.content === NEVER_SEEN), "nothing after the cancel is processed").toBe(false);
|
|
157
|
+
|
|
158
|
+
const json = JSON.stringify(toJson(AgentExecutionStatusSchema, final), null, 2) + "\n";
|
|
159
|
+
await expect(json).toMatchFileSnapshot("./goldens/pause.status.json");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("a worker shutdown persists FAILED with the shutdown copy and throws CancelledFailure", async () => {
|
|
163
|
+
// ── Arrange ──────────────────────────────────────────────────────────────
|
|
164
|
+
clock.reset();
|
|
165
|
+
let controls: InvocationControls | undefined;
|
|
166
|
+
const agent = interruptedTurn(
|
|
167
|
+
"agent-hermetic-shutdown-0001",
|
|
168
|
+
"run-hermetic-shutdown-0001",
|
|
169
|
+
clock,
|
|
170
|
+
() => controls!,
|
|
171
|
+
(c) => {
|
|
172
|
+
// The runner-manager drains: the queue's shutdown signal aborts, then
|
|
173
|
+
// Temporal delivers the cancellation.
|
|
174
|
+
c.signalWorkerShutdown();
|
|
175
|
+
c.cancel();
|
|
176
|
+
},
|
|
177
|
+
);
|
|
178
|
+
const record = cursorExecutionRecord({ message: USER_MESSAGE });
|
|
179
|
+
const scenario = beginCursorScenario({ env, clock, record, sdk: { agents: [agent], catalog: SDK_CATALOG } });
|
|
180
|
+
|
|
181
|
+
// ── Act ──────────────────────────────────────────────────────────────────
|
|
182
|
+
const invocation = await runCursorTurn(scenario, { onControls: (c) => (controls = c) });
|
|
183
|
+
|
|
184
|
+
// ── Assert: the throw and the persisted state ────────────────────────────
|
|
185
|
+
expect(threwCancelledFailure(invocation.outcome), "a shutdown THROWS CancelledFailure").toBe(true);
|
|
186
|
+
if (!threwCancelledFailure(invocation.outcome)) throw new Error("unreachable");
|
|
187
|
+
expect(invocation.outcome.error.message).toBe("Activity cancelled (worker shutdown, not user pause)");
|
|
188
|
+
expect(record.persistedPhases).toEqual([
|
|
189
|
+
ExecutionPhase.EXECUTION_IN_PROGRESS,
|
|
190
|
+
ExecutionPhase.EXECUTION_FAILED,
|
|
191
|
+
]);
|
|
192
|
+
const final = record.lastFullStatus!;
|
|
193
|
+
expect(final.error).toBe("Execution interrupted: runner worker was shut down. Retry or resume.");
|
|
194
|
+
expect(final.completedAt).not.toBe("");
|
|
195
|
+
expect(
|
|
196
|
+
final.messages.filter((m) => m.type === MessageType.MESSAGE_SYSTEM).map((m) => m.content),
|
|
197
|
+
).toEqual([
|
|
198
|
+
"Execution interrupted: the runner worker was shut down while the agent was still running. You can retry or resume.",
|
|
199
|
+
]);
|
|
200
|
+
expect(final.messages.some((m) => m.content === NEVER_SEEN)).toBe(false);
|
|
201
|
+
|
|
202
|
+
const json = JSON.stringify(toJson(AgentExecutionStatusSchema, final), null, 2) + "\n";
|
|
203
|
+
await expect(json).toMatchFileSnapshot("./goldens/worker-shutdown.status.json");
|
|
204
|
+
});
|
|
205
|
+
});
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hermetic golden: a PLAIN TURN through the whole `ExecuteCursor` activity.
|
|
3
|
+
*
|
|
4
|
+
* Invariant pinned: an execution with no tools, no workspace entries, no MCP
|
|
5
|
+
* servers and no skills — the user asks, the agent answers — runs the full
|
|
6
|
+
* setup pipeline and ends COMPLETED with exactly the transcript, usage and
|
|
7
|
+
* timestamps recorded in `goldens/plain-turn.status.json`, RETURNING a slim
|
|
8
|
+
* status to the workflow (never throwing). This is the baseline every other
|
|
9
|
+
* scenario is a delta from, and the first proof that the activity is byte-
|
|
10
|
+
* stable under the hermetic driver.
|
|
11
|
+
*
|
|
12
|
+
* Parent phase rows exercised (20260908.02 `T01_0_plan.md` §3a): normalize
|
|
13
|
+
* input / heartbeat; artifact storage + the `persist` chokepoint; fetch
|
|
14
|
+
* execution, session, agent, blueprint; resolve environment (NOT_FOUND ->
|
|
15
|
+
* empty); provision workspace (per-session dir); workspace turn lock; MCP
|
|
16
|
+
* resolve (none); skills (none); attachments (none); HITL gate install;
|
|
17
|
+
* pricing + model id + variant params; create the engine session; write
|
|
18
|
+
* `harness_state_id` back; prompt composition; usage accumulation; run the
|
|
19
|
+
* turn; terminal mapping COMPLETED -> return; cleanup.
|
|
20
|
+
*
|
|
21
|
+
* Nothing here is a live Cursor agent: `@cursor/sdk` is the scripted double
|
|
22
|
+
* (`__test-utils__/scripted-sdk.ts`) and the control plane is the in-memory
|
|
23
|
+
* execution record (`__test-utils__/hermetic-activity.ts`). No network, no
|
|
24
|
+
* `CURSOR_API_KEY`, no Temporal worker.
|
|
25
|
+
*
|
|
26
|
+
* Regenerate the golden ONLY after a deliberate behavior change:
|
|
27
|
+
* npx vitest run src/activities/execute-cursor/__tests__/hermetic -u
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
|
31
|
+
import { toJson } from "@bufbuild/protobuf";
|
|
32
|
+
import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
33
|
+
import { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
34
|
+
|
|
35
|
+
// Both mocks are hoisted by vitest; they must live in this file. Each factory
|
|
36
|
+
// imports the reusable module and returns its mock surface.
|
|
37
|
+
vi.mock("@cursor/sdk", async () =>
|
|
38
|
+
(await import("../../__test-utils__/scripted-sdk.js")).scriptedCursorSdkModule(),
|
|
39
|
+
);
|
|
40
|
+
vi.mock("../../../../client/stigmer-client.js", async () =>
|
|
41
|
+
(await import("../../../../__test-utils__/hermetic-activity.js")).hermeticStigmerClientModule(),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
import {
|
|
45
|
+
ScriptedClock,
|
|
46
|
+
createHermeticEnvironment,
|
|
47
|
+
type HermeticEnvironment,
|
|
48
|
+
} from "../../../../__test-utils__/hermetic-activity.js";
|
|
49
|
+
import { ScriptedCursorAgent, step } from "../../__test-utils__/scripted-agent.js";
|
|
50
|
+
import {
|
|
51
|
+
FIXTURE,
|
|
52
|
+
SDK_CATALOG,
|
|
53
|
+
beginCursorScenario,
|
|
54
|
+
cursorExecutionRecord,
|
|
55
|
+
runCursorTurn,
|
|
56
|
+
} from "../../__test-utils__/hermetic-cursor.js";
|
|
57
|
+
import { stubRegistryFetch } from "../../../../__test-utils__/model-registry-fixture.js";
|
|
58
|
+
|
|
59
|
+
const AGENT_ID = "agent-hermetic-plain-0001";
|
|
60
|
+
const RUN_ID = "run-hermetic-plain-0001";
|
|
61
|
+
const USER_MESSAGE = "Say hello in five words or fewer.";
|
|
62
|
+
const ASSISTANT_TEXT = "Hello there, hermetic world.";
|
|
63
|
+
|
|
64
|
+
describe("ExecuteCursor hermetic — plain turn", () => {
|
|
65
|
+
let env: HermeticEnvironment;
|
|
66
|
+
let registry: ReturnType<typeof stubRegistryFetch>;
|
|
67
|
+
const clock = new ScriptedClock();
|
|
68
|
+
|
|
69
|
+
beforeAll(() => {
|
|
70
|
+
env = createHermeticEnvironment();
|
|
71
|
+
registry = stubRegistryFetch();
|
|
72
|
+
clock.install();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
afterAll(() => {
|
|
76
|
+
clock.uninstall();
|
|
77
|
+
registry.restore();
|
|
78
|
+
env.dispose();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("completes with the golden transcript and returns a slim COMPLETED status", async () => {
|
|
82
|
+
// ── Arrange ──────────────────────────────────────────────────────────────
|
|
83
|
+
const agent = new ScriptedCursorAgent({
|
|
84
|
+
agentId: AGENT_ID,
|
|
85
|
+
runIds: [RUN_ID],
|
|
86
|
+
observeStep: () => clock.tick(),
|
|
87
|
+
turns: [
|
|
88
|
+
[
|
|
89
|
+
step.event({ type: "system", subtype: "init", agent_id: AGENT_ID, run_id: RUN_ID }),
|
|
90
|
+
step.event({
|
|
91
|
+
type: "assistant",
|
|
92
|
+
agent_id: AGENT_ID,
|
|
93
|
+
run_id: RUN_ID,
|
|
94
|
+
message: { role: "assistant", content: [{ type: "text", text: ASSISTANT_TEXT }] },
|
|
95
|
+
}),
|
|
96
|
+
step.turnEnded({ inputTokens: 1_200, outputTokens: 40 }),
|
|
97
|
+
step.finished({
|
|
98
|
+
result: ASSISTANT_TEXT,
|
|
99
|
+
model: {
|
|
100
|
+
id: FIXTURE.model,
|
|
101
|
+
params: [
|
|
102
|
+
{ id: "fast", value: "false" },
|
|
103
|
+
{ id: "thinking", value: "false" },
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
}),
|
|
107
|
+
],
|
|
108
|
+
],
|
|
109
|
+
});
|
|
110
|
+
const record = cursorExecutionRecord({ message: USER_MESSAGE });
|
|
111
|
+
const scenario = beginCursorScenario({
|
|
112
|
+
env,
|
|
113
|
+
clock,
|
|
114
|
+
record,
|
|
115
|
+
sdk: { agents: [agent], catalog: SDK_CATALOG },
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// ── Act ──────────────────────────────────────────────────────────────────
|
|
119
|
+
const invocation = await runCursorTurn(scenario);
|
|
120
|
+
|
|
121
|
+
// ── Assert: the outcome the workflow sees ────────────────────────────────
|
|
122
|
+
expect(invocation.outcome.kind, "a completed turn RETURNS, never throws").toBe("returned");
|
|
123
|
+
const slim = (invocation.outcome as { value: Record<string, unknown> }).value;
|
|
124
|
+
expect(slim.phase).toBe("EXECUTION_COMPLETED");
|
|
125
|
+
expect(slim.final_text).toBe(ASSISTANT_TEXT);
|
|
126
|
+
expect(slim, "the slim return carries no transcript").not.toHaveProperty("messages");
|
|
127
|
+
|
|
128
|
+
// ── Assert: what the control plane received, in order ────────────────────
|
|
129
|
+
expect(record.persistedPhases).toEqual([
|
|
130
|
+
ExecutionPhase.EXECUTION_IN_PROGRESS,
|
|
131
|
+
ExecutionPhase.EXECUTION_COMPLETED,
|
|
132
|
+
]);
|
|
133
|
+
expect(record.setupProgress, "the setup pipeline's phase labels, in order").toEqual([
|
|
134
|
+
"Fetching execution",
|
|
135
|
+
"Resolving agent blueprint",
|
|
136
|
+
"Resolving environment",
|
|
137
|
+
"Provisioning workspace",
|
|
138
|
+
"Resolving MCP servers",
|
|
139
|
+
"Resolving skills",
|
|
140
|
+
"Initializing Cursor agent",
|
|
141
|
+
]);
|
|
142
|
+
|
|
143
|
+
// ── Assert: what the SDK was asked ───────────────────────────────────────
|
|
144
|
+
expect(scenario.sdk.resolutions).toHaveLength(1);
|
|
145
|
+
expect(scenario.sdk.resolutions[0].kind, "a fresh session creates, never resumes").toBe("create");
|
|
146
|
+
expect(scenario.sdk.resolutions[0].options?.model).toEqual({
|
|
147
|
+
id: FIXTURE.model,
|
|
148
|
+
params: [
|
|
149
|
+
{ id: "fast", value: "false" },
|
|
150
|
+
{ id: "thinking", value: "false" },
|
|
151
|
+
],
|
|
152
|
+
});
|
|
153
|
+
expect(agent.sends).toHaveLength(1);
|
|
154
|
+
expect(String(agent.sends[0].message), "the prompt carries the user's message").toContain(USER_MESSAGE);
|
|
155
|
+
expect(agent.sends[0].hasOnDelta).toBe(true);
|
|
156
|
+
|
|
157
|
+
// ── Assert: the harness_state_id write-back ─────────────────────────────
|
|
158
|
+
expect(record.sessionUpdates).toHaveLength(1);
|
|
159
|
+
expect(record.sessionUpdates[0].spec?.harnessStateId).toBe(AGENT_ID);
|
|
160
|
+
|
|
161
|
+
// ── Assert: hermeticity ──────────────────────────────────────────────────
|
|
162
|
+
expect(invocation.heartbeats.length, "the whole-activity heartbeat ran").toBeGreaterThan(0);
|
|
163
|
+
expect(registry.urls.every((u) => u.includes("/model-registry")), "no network beyond the registry").toBe(true);
|
|
164
|
+
|
|
165
|
+
// ── Assert: the golden ───────────────────────────────────────────────────
|
|
166
|
+
const finalStatus = record.lastFullStatus;
|
|
167
|
+
expect(finalStatus).toBeDefined();
|
|
168
|
+
const json = JSON.stringify(toJson(AgentExecutionStatusSchema, finalStatus!), null, 2) + "\n";
|
|
169
|
+
await expect(json).toMatchFileSnapshot("./goldens/plain-turn.status.json");
|
|
170
|
+
});
|
|
171
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hermetic golden: POISONED-HANDLE RECOVERY — a resumed agent whose run fails
|
|
3
|
+
* with a transport error is replaced by a fresh agent, once, and the turn
|
|
4
|
+
* completes on the replacement.
|
|
5
|
+
*
|
|
6
|
+
* Invariant pinned: when `run.wait()` reports `error` on an agent that was
|
|
7
|
+
* RESUMED (`resolution.reason === "resumed_successfully"`) and the classifier
|
|
8
|
+
* files the error as `network` or `agent-stale`, the activity closes the stale
|
|
9
|
+
* handle, calls `Agent.create` for a fresh one, writes the fresh id to the
|
|
10
|
+
* session's `harness_state_id`, re-runs the turn on the fresh agent through the
|
|
11
|
+
* IDENTICAL stream loop and boundary, and ends COMPLETED. The retry happens at
|
|
12
|
+
* most once (`alreadyRetriedWithFreshAgent`). The golden
|
|
13
|
+
* (`goldens/recovery-fresh-agent.status.json`) pins what the user sees after a
|
|
14
|
+
* recovered turn.
|
|
15
|
+
*
|
|
16
|
+
* Parent phase rows exercised beyond the earlier scenarios: `Agent.resume`
|
|
17
|
+
* through `resolveAgentWithTransportRecovery` (a second execution in a session:
|
|
18
|
+
* `thread_id` set, no parked agent); the `status: ERROR` stream event feeding
|
|
19
|
+
* `streamErrorMessage`; `run.wait()` error mapping; the error classifier
|
|
20
|
+
* (`error-classifier.ts` NETWORK_PATTERNS, the same shapes its own tests pin);
|
|
21
|
+
* the fresh-agent recovery spine (`runRecoveryStream`); the second
|
|
22
|
+
* `harness_state_id` write-back.
|
|
23
|
+
*
|
|
24
|
+
* Regenerate ONLY after a deliberate behavior change:
|
|
25
|
+
* npx vitest run src/activities/execute-cursor/__tests__/hermetic -u
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
|
29
|
+
import { toJson } from "@bufbuild/protobuf";
|
|
30
|
+
import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
31
|
+
import { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
32
|
+
|
|
33
|
+
vi.mock("@cursor/sdk", async () =>
|
|
34
|
+
(await import("../../__test-utils__/scripted-sdk.js")).scriptedCursorSdkModule(),
|
|
35
|
+
);
|
|
36
|
+
vi.mock("../../../../client/stigmer-client.js", async () =>
|
|
37
|
+
(await import("../../../../__test-utils__/hermetic-activity.js")).hermeticStigmerClientModule(),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
import {
|
|
41
|
+
ScriptedClock,
|
|
42
|
+
createHermeticEnvironment,
|
|
43
|
+
type HermeticEnvironment,
|
|
44
|
+
} from "../../../../__test-utils__/hermetic-activity.js";
|
|
45
|
+
import { ScriptedCursorAgent, sdkEvents, step } from "../../__test-utils__/scripted-agent.js";
|
|
46
|
+
import {
|
|
47
|
+
FIXTURE,
|
|
48
|
+
SDK_CATALOG,
|
|
49
|
+
beginCursorScenario,
|
|
50
|
+
cursorExecutionRecord,
|
|
51
|
+
runCursorTurn,
|
|
52
|
+
} from "../../__test-utils__/hermetic-cursor.js";
|
|
53
|
+
import { stubRegistryFetch } from "../../../../__test-utils__/model-registry-fixture.js";
|
|
54
|
+
|
|
55
|
+
const STALE_AGENT_ID = "agent-hermetic-stale-0001";
|
|
56
|
+
const FRESH_AGENT_ID = "agent-hermetic-fresh-0002";
|
|
57
|
+
const RUN_STALE = "run-hermetic-stale-0001";
|
|
58
|
+
const RUN_FRESH = "run-hermetic-fresh-0001";
|
|
59
|
+
const USER_MESSAGE = "Summarize what changed since yesterday.";
|
|
60
|
+
// A real network-class failure shape (error-classifier NETWORK_PATTERNS:
|
|
61
|
+
// "unavailable", "econnreset", "fetch failed").
|
|
62
|
+
const TRANSPORT_ERROR = "UNAVAILABLE: fetch failed (ECONNRESET) while streaming the run";
|
|
63
|
+
const FINAL_TEXT = "Two files changed since yesterday: notes.md and README.md.";
|
|
64
|
+
|
|
65
|
+
describe("ExecuteCursor hermetic — poisoned-handle recovery on a fresh agent", () => {
|
|
66
|
+
let env: HermeticEnvironment;
|
|
67
|
+
let registry: ReturnType<typeof stubRegistryFetch>;
|
|
68
|
+
const clock = new ScriptedClock();
|
|
69
|
+
|
|
70
|
+
beforeAll(() => {
|
|
71
|
+
env = createHermeticEnvironment();
|
|
72
|
+
registry = stubRegistryFetch();
|
|
73
|
+
clock.install();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
afterAll(() => {
|
|
77
|
+
clock.uninstall();
|
|
78
|
+
registry.restore();
|
|
79
|
+
env.dispose();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("replaces the stale resumed agent once and completes on the fresh one", async () => {
|
|
83
|
+
// ── Arrange ──────────────────────────────────────────────────────────────
|
|
84
|
+
const evStale = sdkEvents(STALE_AGENT_ID, RUN_STALE);
|
|
85
|
+
const evFresh = sdkEvents(FRESH_AGENT_ID, RUN_FRESH);
|
|
86
|
+
const stale = new ScriptedCursorAgent({
|
|
87
|
+
agentId: STALE_AGENT_ID,
|
|
88
|
+
runIds: [RUN_STALE],
|
|
89
|
+
observeStep: () => clock.tick(),
|
|
90
|
+
turns: [
|
|
91
|
+
[
|
|
92
|
+
step.event(evStale.init()),
|
|
93
|
+
step.event(evStale.assistant("Let me look at the recent changes.")),
|
|
94
|
+
step.event(evStale.status("ERROR", TRANSPORT_ERROR)),
|
|
95
|
+
step.errored({ result: TRANSPORT_ERROR }),
|
|
96
|
+
],
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
const fresh = new ScriptedCursorAgent({
|
|
100
|
+
agentId: FRESH_AGENT_ID,
|
|
101
|
+
runIds: [RUN_FRESH],
|
|
102
|
+
observeStep: () => clock.tick(),
|
|
103
|
+
turns: [
|
|
104
|
+
[
|
|
105
|
+
step.event(evFresh.init()),
|
|
106
|
+
step.event(evFresh.assistant(FINAL_TEXT)),
|
|
107
|
+
step.turnEnded({ inputTokens: 2_800, outputTokens: 55 }),
|
|
108
|
+
step.finished({ result: FINAL_TEXT, model: { id: FIXTURE.model, params: [] } }),
|
|
109
|
+
],
|
|
110
|
+
],
|
|
111
|
+
});
|
|
112
|
+
const record = cursorExecutionRecord({ message: USER_MESSAGE });
|
|
113
|
+
const scenario = beginCursorScenario({
|
|
114
|
+
env,
|
|
115
|
+
clock,
|
|
116
|
+
record,
|
|
117
|
+
// `Agent.create` hands out `fresh`; `Agent.resume(STALE)` finds `stale`,
|
|
118
|
+
// the previous turn's agent the session knows only by harness_state_id.
|
|
119
|
+
sdk: { agents: [fresh], resumableAgents: [stale], catalog: SDK_CATALOG },
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// ── Act: the second execution in the session resumes the stale handle ────
|
|
123
|
+
const invocation = await runCursorTurn(scenario, { threadId: STALE_AGENT_ID, turnSeq: 0 });
|
|
124
|
+
|
|
125
|
+
// ── Assert: outcome ──────────────────────────────────────────────────────
|
|
126
|
+
expect(invocation.outcome.kind, "a recovered turn RETURNS like any completion").toBe("returned");
|
|
127
|
+
const slim = (invocation.outcome as { value: Record<string, unknown> }).value;
|
|
128
|
+
expect(slim.phase).toBe("EXECUTION_COMPLETED");
|
|
129
|
+
expect(slim.final_text).toBe(FINAL_TEXT);
|
|
130
|
+
expect(record.persistedPhases).toEqual([
|
|
131
|
+
ExecutionPhase.EXECUTION_IN_PROGRESS,
|
|
132
|
+
ExecutionPhase.EXECUTION_COMPLETED,
|
|
133
|
+
]);
|
|
134
|
+
|
|
135
|
+
// ── Assert: the recovery spine ───────────────────────────────────────────
|
|
136
|
+
expect(scenario.sdk.resolutions.map((r) => [r.kind, r.agentId])).toEqual([
|
|
137
|
+
["resume", STALE_AGENT_ID],
|
|
138
|
+
["create", FRESH_AGENT_ID],
|
|
139
|
+
]);
|
|
140
|
+
expect(stale.sends, "the stale handle ran exactly once").toHaveLength(1);
|
|
141
|
+
expect(stale.closeCalls, "the poisoned handle is disposed").toBe(1);
|
|
142
|
+
expect(fresh.sends, "the fresh agent ran the turn exactly once — no second retry").toHaveLength(1);
|
|
143
|
+
expect(String(fresh.sends[0].message), "the rebuilt prompt carries the user's message").toContain(USER_MESSAGE);
|
|
144
|
+
|
|
145
|
+
// The session now points at the fresh agent (the stale id is superseded).
|
|
146
|
+
const lastSessionWrite = record.sessionUpdates.at(-1);
|
|
147
|
+
expect(lastSessionWrite?.spec?.harnessStateId).toBe(FRESH_AGENT_ID);
|
|
148
|
+
|
|
149
|
+
// ── Assert: hermeticity ──────────────────────────────────────────────────
|
|
150
|
+
expect(registry.urls.every((u) => u.includes("/model-registry"))).toBe(true);
|
|
151
|
+
|
|
152
|
+
// ── Assert: the golden ───────────────────────────────────────────────────
|
|
153
|
+
const json = JSON.stringify(toJson(AgentExecutionStatusSchema, record.lastFullStatus!), null, 2) + "\n";
|
|
154
|
+
await expect(json).toMatchFileSnapshot("./goldens/recovery-fresh-agent.status.json");
|
|
155
|
+
});
|
|
156
|
+
});
|