@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,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The scripted harness adapter — the reference implementation of
|
|
3
|
+
* `HarnessAdapter`, driven by a `TurnScenario` per turn instead of a vendor
|
|
4
|
+
* SDK.
|
|
5
|
+
*
|
|
6
|
+
* Two jobs. It is the fake the contract kit runs against under every
|
|
7
|
+
* capability combination, so the kit is proven before any real adapter
|
|
8
|
+
* implements the contract. And it is the TEMPLATE a future harness author
|
|
9
|
+
* reads first: every rule of the contract appears here as the smallest code
|
|
10
|
+
* that honours it, with the reason beside it. Its measured line count is the
|
|
11
|
+
* program's "lines a new harness must write" data point.
|
|
12
|
+
*
|
|
13
|
+
* The insight this fake makes visible: above the contract line, the two real
|
|
14
|
+
* pause primitives are indistinguishable. Whether the engine checkpoints at
|
|
15
|
+
* the gate (`interrupt`) or a hook denies the tool and the run is cancelled
|
|
16
|
+
* (`deny-and-retry`), the turn ends `awaiting_approval` with a WAITING row on
|
|
17
|
+
* the status, and the next invocation carries the decision. How the engine
|
|
18
|
+
* RESUMES is the adapter's business, below the line, and the runtime never
|
|
19
|
+
* sees it. So this fake takes `pausePrimitive` as an option and does not
|
|
20
|
+
* branch on it — and the kit runs it under both to prove the kit does not
|
|
21
|
+
* branch on it either.
|
|
22
|
+
*
|
|
23
|
+
* What is deliberately simple here and would be real work in an adapter:
|
|
24
|
+
* "executing" a side effect is incrementing a counter; the engine state id is
|
|
25
|
+
* a counter too; the transcript rows are built with the shared proto
|
|
26
|
+
* factories. What is NOT simplified is the contract behaviour itself —
|
|
27
|
+
* settling with an outcome and never rejecting, stopping at every step
|
|
28
|
+
* boundary and inside a hang, binding before the first persist, executing an
|
|
29
|
+
* approval exactly once, refusing to resume a state it never minted — because
|
|
30
|
+
* those are what the kit measures.
|
|
31
|
+
*
|
|
32
|
+
* What this fake deliberately does NOT do, because it is not an adapter's
|
|
33
|
+
* job: carry a REJECTed or SKIPped row to its terminal status. The decision
|
|
34
|
+
* is on the row (the server's field) and the transition follows from it with
|
|
35
|
+
* no engine knowledge, so it belongs to the runtime — one writer per field,
|
|
36
|
+
* as `approvalDecisionsOf` is the runtime's one reader (S2 M4, Q-M4-1; the
|
|
37
|
+
* runtime's arm lands in S3). An adapter's whole duty for a non-executing
|
|
38
|
+
* decision is to not execute.
|
|
39
|
+
*
|
|
40
|
+
* Scenarios are arranged PER SESSION (the engine is per session in every real
|
|
41
|
+
* harness; a subject arranges that session's engine), and arranging replaces
|
|
42
|
+
* what that session had not yet played. A `runTurn` with no scenario arranged
|
|
43
|
+
* for its session is a test bug and throws (the same rule as the scripted
|
|
44
|
+
* `@cursor/sdk` agent's `send()` with no script left); every other exit is a
|
|
45
|
+
* `TurnOutcome`.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
import { ApprovalAction, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
49
|
+
import type { ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
50
|
+
|
|
51
|
+
import type { Config } from "../../config.js";
|
|
52
|
+
import type { HarnessCapabilities, PausePrimitive, StateIdSource } from "../../harness/capabilities.js";
|
|
53
|
+
import type { HarnessAdapter, TurnInput, TurnOutcome, TurnSink } from "../../harness/types.js";
|
|
54
|
+
import { DEEP_AGENT_VISION_PROFILE } from "../../shared/attachment-vision.js";
|
|
55
|
+
import type { ProposedAction } from "../approval-contract/types.js";
|
|
56
|
+
import { testConfig } from "../config-fixture.js";
|
|
57
|
+
import { aiMessage, findToolCallRow, toolCall, waitingToolCall } from "../proto-helpers.js";
|
|
58
|
+
import type { EngineView, HarnessContractSubject, ScenarioStep, TurnScenario } from "./types.js";
|
|
59
|
+
|
|
60
|
+
export interface ScriptedHarnessOptions {
|
|
61
|
+
/** Diagnostic name; defaults to a name that says which primitives this instance runs under. */
|
|
62
|
+
readonly name?: string;
|
|
63
|
+
readonly pausePrimitive: PausePrimitive;
|
|
64
|
+
readonly stateIdSource: StateIdSource;
|
|
65
|
+
/**
|
|
66
|
+
* The subject's `Config` over `testConfig()`'s inert defaults. The fake reads
|
|
67
|
+
* none of it; the RUNTIME does when the subject runs through the real
|
|
68
|
+
* activity (the workspace root it provisions under, the task queue the
|
|
69
|
+
* drain signal is keyed by).
|
|
70
|
+
*/
|
|
71
|
+
readonly config?: Partial<Config>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Resolves when the signal aborts; resolves at once if it already has. Parks
|
|
76
|
+
* on the abort EVENT, never on a timer, so a hanging turn holds nothing that
|
|
77
|
+
* could keep a process alive — the property the runtime's heartbeat relies
|
|
78
|
+
* on when it declares a stalled turn dead.
|
|
79
|
+
*/
|
|
80
|
+
function whenAborted(signal: AbortSignal): Promise<void> {
|
|
81
|
+
if (signal.aborted) return Promise.resolve();
|
|
82
|
+
return new Promise((resolve) => signal.addEventListener("abort", () => resolve(), { once: true }));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** A proposal already carried to a terminal row has been settled by an earlier invocation. */
|
|
86
|
+
function isSettled(row: ToolCall | undefined): boolean {
|
|
87
|
+
return row !== undefined
|
|
88
|
+
&& (row.status === ToolCallStatus.TOOL_CALL_COMPLETED || row.status === ToolCallStatus.TOOL_CALL_SKIPPED);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export class ScriptedHarnessAdapter implements HarnessAdapter {
|
|
92
|
+
readonly name: string;
|
|
93
|
+
readonly capabilities: HarnessCapabilities;
|
|
94
|
+
|
|
95
|
+
/** The next turn each session plays; arranging a session replaces its entry. */
|
|
96
|
+
private readonly arranged = new Map<string, TurnScenario>();
|
|
97
|
+
private readonly mintedStateIds = new Set<string>();
|
|
98
|
+
private readonly executions = new Map<string, number>();
|
|
99
|
+
private readonly hangWaiters: Array<() => void> = [];
|
|
100
|
+
private mintCounter = 0;
|
|
101
|
+
|
|
102
|
+
constructor(options: ScriptedHarnessOptions) {
|
|
103
|
+
this.name = options.name ?? `scripted(${options.pausePrimitive}, ${options.stateIdSource})`;
|
|
104
|
+
this.capabilities = {
|
|
105
|
+
pausePrimitive: options.pausePrimitive,
|
|
106
|
+
stateIdSource: options.stateIdSource,
|
|
107
|
+
systemPrompt: true,
|
|
108
|
+
subAgents: false,
|
|
109
|
+
toolRestriction: true,
|
|
110
|
+
visionProfile: DEEP_AGENT_VISION_PROFILE,
|
|
111
|
+
fileReview: { harnessId: "scripted", excludePaths: [] },
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Resolves the next time a turn parks on a `hang` step — for a test that
|
|
117
|
+
* stands where the runtime's watchdog stands and needs to know the engine
|
|
118
|
+
* is silent before it advances the clock. Never on a timer.
|
|
119
|
+
*/
|
|
120
|
+
whenHanging(): Promise<void> {
|
|
121
|
+
return new Promise((resolve) => this.hangWaiters.push(resolve));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ── Engine controls (the subject's side of the kit seam) ──────────────────
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* What the session's next `runTurn` plays. The rest of the view is not
|
|
128
|
+
* needed here: this fake reads its decisions from `input.approvalDecisions`
|
|
129
|
+
* and settles a repeated proposal from the row itself, so it has nothing to
|
|
130
|
+
* decide before the turn.
|
|
131
|
+
*/
|
|
132
|
+
arrange(turn: TurnScenario, view: Pick<EngineView, "sessionId">): void {
|
|
133
|
+
this.arranged.set(view.sessionId, turn);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
executionCount(toolCallId: string): number {
|
|
137
|
+
return this.executions.get(toolCallId) ?? 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ── HarnessAdapter ────────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
/** Nothing to install: a real adapter imports its SDK lazily and installs its transport here. */
|
|
143
|
+
async boot(_config: Config): Promise<void> {}
|
|
144
|
+
|
|
145
|
+
/** Nothing held: a real adapter closes every engine it still has parked. */
|
|
146
|
+
async shutdown(): Promise<void> {}
|
|
147
|
+
|
|
148
|
+
/** Nothing is parked per session here; a real adapter releases the engine it parked for `sessionId`. */
|
|
149
|
+
async releaseSession(_sessionId: string): Promise<void> {}
|
|
150
|
+
|
|
151
|
+
async runTurn(input: TurnInput, sink: TurnSink): Promise<TurnOutcome> {
|
|
152
|
+
const turn = this.arranged.get(input.sessionId);
|
|
153
|
+
if (!turn) throw new Error(`${this.name}: runTurn called with no scenario arranged for session '${input.sessionId}' (test bug)`);
|
|
154
|
+
this.arranged.delete(input.sessionId);
|
|
155
|
+
|
|
156
|
+
// The signal may be aborted before the turn is entered; do no work then.
|
|
157
|
+
if (sink.stopSignal.aborted) return { kind: "interrupted" };
|
|
158
|
+
|
|
159
|
+
const resumed = await this.resolveEngineState(input, sink);
|
|
160
|
+
if (resumed.kind !== "ok") return resumed.outcome;
|
|
161
|
+
|
|
162
|
+
for (const step of turn) {
|
|
163
|
+
// Stop at every step boundary — the Cursor loop's per-event isCancelled().
|
|
164
|
+
if (sink.stopSignal.aborted) return { kind: "interrupted" };
|
|
165
|
+
const ended = await this.play(step, input, sink);
|
|
166
|
+
if (ended) return ended;
|
|
167
|
+
}
|
|
168
|
+
return { kind: "completed" };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Create or resume the engine, per `stateIdSource`. An engine-minted id is
|
|
173
|
+
* bound BEFORE the turn proceeds so a crash mid-turn still resumes; a
|
|
174
|
+
* resume of an id this engine never minted fails the way `Agent.resume` of
|
|
175
|
+
* an unknown id fails, so a runtime that threads the wrong id is caught. A
|
|
176
|
+
* deterministic engine's id is the runtime's; nothing to bind.
|
|
177
|
+
*/
|
|
178
|
+
private async resolveEngineState(
|
|
179
|
+
input: TurnInput,
|
|
180
|
+
sink: TurnSink,
|
|
181
|
+
): Promise<{ kind: "ok" } | { kind: "ended"; outcome: TurnOutcome }> {
|
|
182
|
+
if (this.capabilities.stateIdSource === "deterministic") return { kind: "ok" };
|
|
183
|
+
|
|
184
|
+
if (input.threadId === "") {
|
|
185
|
+
const minted = `${this.name}#state-${++this.mintCounter}`;
|
|
186
|
+
this.mintedStateIds.add(minted);
|
|
187
|
+
try {
|
|
188
|
+
await sink.bindHarnessState(minted);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
return {
|
|
191
|
+
kind: "ended",
|
|
192
|
+
outcome: { kind: "failed", surface: "internal", message: `${this.name}: could not bind engine state`, cause: err },
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return { kind: "ok" };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (!this.mintedStateIds.has(input.threadId)) {
|
|
199
|
+
return {
|
|
200
|
+
kind: "ended",
|
|
201
|
+
outcome: { kind: "failed", surface: "internal", message: `${this.name}: no engine state '${input.threadId}' to resume` },
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
return { kind: "ok" };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Play one step; returns the outcome that ends the turn, or undefined to continue. */
|
|
208
|
+
private async play(step: ScenarioStep, input: TurnInput, sink: TurnSink): Promise<TurnOutcome | undefined> {
|
|
209
|
+
switch (step.kind) {
|
|
210
|
+
case "say": {
|
|
211
|
+
sink.status.messages.push(aiMessage(step.text));
|
|
212
|
+
sink.recordActivity();
|
|
213
|
+
// Awaited, as the Cursor loop awaits its own: a platform STOP the
|
|
214
|
+
// runtime reads from this write aborts the signal before the next
|
|
215
|
+
// step boundary sees it (the contract's "MAY await for ordering").
|
|
216
|
+
await sink.requestPersist();
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
case "propose":
|
|
220
|
+
return this.propose(step.toolCallId, step.action, input, sink);
|
|
221
|
+
case "read": {
|
|
222
|
+
// Ungated: the row lands COMPLETED at once, the effect counts as run,
|
|
223
|
+
// and the persist is awaited like `say`'s — a real engine's tool call
|
|
224
|
+
// is the discrete event its loop flushes on.
|
|
225
|
+
const message = aiMessage("");
|
|
226
|
+
const row = toolCall(step.toolCallId, "read", ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
227
|
+
row.result = `contents of ${step.path}`;
|
|
228
|
+
message.toolCalls.push(row);
|
|
229
|
+
sink.status.messages.push(message);
|
|
230
|
+
this.executions.set(step.toolCallId, this.executionCount(step.toolCallId) + 1);
|
|
231
|
+
sink.recordActivity("read");
|
|
232
|
+
await sink.requestPersist();
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
case "usage": {
|
|
236
|
+
sink.reportUsage(step.delta);
|
|
237
|
+
sink.recordActivity();
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
case "hang": {
|
|
241
|
+
for (const waiter of this.hangWaiters.splice(0)) waiter();
|
|
242
|
+
await whenAborted(sink.stopSignal);
|
|
243
|
+
return { kind: "interrupted" };
|
|
244
|
+
}
|
|
245
|
+
case "fail":
|
|
246
|
+
return { kind: "failed", surface: step.surface, message: step.message };
|
|
247
|
+
case "cancelled":
|
|
248
|
+
return { kind: "cancelled" };
|
|
249
|
+
default: {
|
|
250
|
+
const exhaustive: never = step;
|
|
251
|
+
throw new Error(`${this.name}: unknown scenario step ${JSON.stringify(exhaustive)}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* The gate, above the contract line. The status is the single source of
|
|
258
|
+
* truth for whether the effect ran: a row already carried to COMPLETED or
|
|
259
|
+
* SKIPPED by an earlier invocation is settled and the step is a no-op, which
|
|
260
|
+
* is what makes "reinvoked twice with the same decision, still once" hold
|
|
261
|
+
* without a second ledger.
|
|
262
|
+
*/
|
|
263
|
+
private propose(
|
|
264
|
+
toolCallId: string,
|
|
265
|
+
action: ProposedAction,
|
|
266
|
+
input: TurnInput,
|
|
267
|
+
sink: TurnSink,
|
|
268
|
+
): TurnOutcome | undefined {
|
|
269
|
+
const existing = findToolCallRow(sink.status, toolCallId);
|
|
270
|
+
if (isSettled(existing)) return undefined;
|
|
271
|
+
|
|
272
|
+
const decision = input.approvalDecisions.get(toolCallId) ?? ApprovalAction.UNSPECIFIED;
|
|
273
|
+
switch (decision) {
|
|
274
|
+
case ApprovalAction.UNSPECIFIED: {
|
|
275
|
+
// Propose: surface the gated call as a WAITING row and stop the turn.
|
|
276
|
+
// Never write a second row for the same id on a resumed turn.
|
|
277
|
+
if (!existing) {
|
|
278
|
+
const message = aiMessage("");
|
|
279
|
+
message.toolCalls.push(waitingToolCall(toolCallId, action.kind, `Approve ${action.kind} ${action.resource}?`));
|
|
280
|
+
sink.status.messages.push(message);
|
|
281
|
+
}
|
|
282
|
+
sink.recordActivity();
|
|
283
|
+
sink.requestPersist();
|
|
284
|
+
return { kind: "awaiting_approval" };
|
|
285
|
+
}
|
|
286
|
+
case ApprovalAction.APPROVE:
|
|
287
|
+
case ApprovalAction.APPROVE_ALL: {
|
|
288
|
+
this.executions.set(toolCallId, this.executionCount(toolCallId) + 1);
|
|
289
|
+
this.completeRow(existing, toolCallId, action, sink);
|
|
290
|
+
return undefined;
|
|
291
|
+
}
|
|
292
|
+
case ApprovalAction.SKIP:
|
|
293
|
+
case ApprovalAction.REJECT: {
|
|
294
|
+
// Not executed, and the row is left as the runtime handed it (see
|
|
295
|
+
// the header): the engine simply moves on.
|
|
296
|
+
sink.recordActivity();
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
299
|
+
default: {
|
|
300
|
+
const exhaustive: never = decision;
|
|
301
|
+
throw new Error(`${this.name}: unknown approval action ${String(exhaustive)}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Report the executed action on its row, as a real engine's completion
|
|
308
|
+
* event does. The row normally exists (the runtime seeded the status with
|
|
309
|
+
* last turn's WAITING row); a decision with no row is a runtime that decided
|
|
310
|
+
* out of band, and the adapter still records what happened rather than
|
|
311
|
+
* losing the fact.
|
|
312
|
+
*/
|
|
313
|
+
private completeRow(existing: ToolCall | undefined, toolCallId: string, action: ProposedAction, sink: TurnSink): void {
|
|
314
|
+
if (existing) {
|
|
315
|
+
existing.status = ToolCallStatus.TOOL_CALL_COMPLETED;
|
|
316
|
+
} else {
|
|
317
|
+
const message = aiMessage("");
|
|
318
|
+
const row = waitingToolCall(toolCallId, action.kind, "");
|
|
319
|
+
row.status = ToolCallStatus.TOOL_CALL_COMPLETED;
|
|
320
|
+
message.toolCalls.push(row);
|
|
321
|
+
sink.status.messages.push(message);
|
|
322
|
+
}
|
|
323
|
+
sink.recordActivity();
|
|
324
|
+
sink.requestPersist();
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** The scripted adapter as a kit subject: the adapter IS its own engine, so the seam is a thin view over it. */
|
|
329
|
+
export interface ScriptedSubject extends HarnessContractSubject {
|
|
330
|
+
readonly adapter: ScriptedHarnessAdapter;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* The fake fills the `deep-agent` row in S2 — the one row the runtime does
|
|
335
|
+
* not yet serve for real (the native adapter lands in S3, and takes the row
|
|
336
|
+
* over from the fake in the runtime half then).
|
|
337
|
+
*/
|
|
338
|
+
export function scriptedSubject(options: ScriptedHarnessOptions): ScriptedSubject {
|
|
339
|
+
const adapter = new ScriptedHarnessAdapter(options);
|
|
340
|
+
return {
|
|
341
|
+
name: adapter.name,
|
|
342
|
+
harness: "deep-agent",
|
|
343
|
+
adapter,
|
|
344
|
+
config: testConfig(options.config),
|
|
345
|
+
arrange: (turn, view) => adapter.arrange(turn, view),
|
|
346
|
+
whenHanging: () => adapter.whenHanging(),
|
|
347
|
+
executionCount: (toolCallId) => adapter.executionCount(toolCallId),
|
|
348
|
+
};
|
|
349
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The test-facing seam of the harness contract kit.
|
|
3
|
+
*
|
|
4
|
+
* `HarnessAdapter.runTurn(input, sink)` gives a test no way to make the
|
|
5
|
+
* engine DO anything: propose a gated action, emit usage, hang, fail. The
|
|
6
|
+
* approval-contract kit met the same problem with `GatewaySubstrate` — a
|
|
7
|
+
* small interface each real substrate adapts so one invariant catalog can
|
|
8
|
+
* drive all of them. This is that seam for harnesses: a
|
|
9
|
+
* {@link HarnessContractSubject} owns the ENGINE (what the next turn will
|
|
10
|
+
* do), and the kit owns everything the runtime would own (the sink, the
|
|
11
|
+
* `TurnInput`, the state id threading, the reinvocation).
|
|
12
|
+
*
|
|
13
|
+
* The scenario vocabulary is deliberately small and engine-neutral: it says
|
|
14
|
+
* what the kit needs to PROVOKE, never how an engine behaves. The scripted
|
|
15
|
+
* fake consumes it directly; a real harness's subject translates it onto that
|
|
16
|
+
* harness's own double (the Cursor subject onto the scripted `@cursor/sdk`
|
|
17
|
+
* agent of `execute-cursor/__test-utils__/`). One vocabulary, not one per
|
|
18
|
+
* engine, so the kit cannot drift from what the fake can do.
|
|
19
|
+
*
|
|
20
|
+
* Gated actions reuse `ProposedAction` from `approval-contract/types.ts`, the
|
|
21
|
+
* taxonomy-free action every enforcement substrate already translates, so the
|
|
22
|
+
* two kits speak of the same logical side effect and neither restates the
|
|
23
|
+
* HITL taxonomy.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type { ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
27
|
+
|
|
28
|
+
import type { Config } from "../../config.js";
|
|
29
|
+
import type { HarnessName } from "../../harness/registry.js";
|
|
30
|
+
import type { FailureSurface, HarnessAdapter, UsageDelta } from "../../harness/types.js";
|
|
31
|
+
import type { ProposedAction } from "../approval-contract/types.js";
|
|
32
|
+
import type { TurnInputFixtureOverrides } from "../turn-input-fixture.js";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* One thing the engine does during a turn. A turn plays its steps in order
|
|
36
|
+
* and ends `completed` when none is left, unless a step ends it first.
|
|
37
|
+
*
|
|
38
|
+
* - `say`: the engine emits assistant text (a transcript row).
|
|
39
|
+
* - `propose`: the engine reaches a gated side effect identified by
|
|
40
|
+
* `toolCallId`. Undecided → the turn ends `awaiting_approval` and later
|
|
41
|
+
* steps do not run; APPROVE → the effect runs once and the turn continues;
|
|
42
|
+
* REJECT / SKIP → the effect never runs and the turn continues. WHICH
|
|
43
|
+
* kinds a harness gates is the harness's policy and the workspace's
|
|
44
|
+
* posture: a `shell` is gated by every harness in every posture; a `write`
|
|
45
|
+
* is gated only outside apply-then-review capture (a git tree, or artifact
|
|
46
|
+
* storage), so an arm that must gate on every subject proposes a `shell`.
|
|
47
|
+
* - `read`: the engine performs an UNGATED read of `path` — a tool-call row
|
|
48
|
+
* that runs at once, never pauses, and is the discrete event every harness
|
|
49
|
+
* flushes a persist on (the lever an arm pulls when it needs the control
|
|
50
|
+
* plane to answer a mid-turn persist).
|
|
51
|
+
* - `usage`: the engine reports one turn's token counts.
|
|
52
|
+
* - `hang`: the engine makes no further progress until told to stop. The
|
|
53
|
+
* step that the stop-signal invariants and the runtime's stall watchdog
|
|
54
|
+
* are built around; it must be parked on the signal, never on a timer.
|
|
55
|
+
* - `fail`: the engine fails with a message the adapter can name; the turn
|
|
56
|
+
* ends `failed` on the given surface (`engine` unless the scenario says
|
|
57
|
+
* otherwise — the runtime's three failure copies are its own invariant).
|
|
58
|
+
* - `cancelled`: the engine ends its own run cancelled with nothing to
|
|
59
|
+
* wait for (an SDK-side cancel); the turn ends `cancelled`.
|
|
60
|
+
*/
|
|
61
|
+
export type ScenarioStep =
|
|
62
|
+
| { readonly kind: "say"; readonly text: string }
|
|
63
|
+
| { readonly kind: "propose"; readonly toolCallId: string; readonly action: ProposedAction }
|
|
64
|
+
| { readonly kind: "read"; readonly toolCallId: string; readonly path: string }
|
|
65
|
+
| { readonly kind: "usage"; readonly delta: UsageDelta }
|
|
66
|
+
| { readonly kind: "hang" }
|
|
67
|
+
| { readonly kind: "fail"; readonly message: string; readonly surface: FailureSurface }
|
|
68
|
+
| { readonly kind: "cancelled" };
|
|
69
|
+
|
|
70
|
+
/** One turn's worth of engine behaviour. */
|
|
71
|
+
export type TurnScenario = readonly ScenarioStep[];
|
|
72
|
+
|
|
73
|
+
/** Step builders — the vocabulary a kit invariant reads as. */
|
|
74
|
+
export const scenario = {
|
|
75
|
+
say(text: string): ScenarioStep {
|
|
76
|
+
return { kind: "say", text };
|
|
77
|
+
},
|
|
78
|
+
propose(toolCallId: string, action: ProposedAction): ScenarioStep {
|
|
79
|
+
return { kind: "propose", toolCallId, action };
|
|
80
|
+
},
|
|
81
|
+
read(toolCallId: string, path: string): ScenarioStep {
|
|
82
|
+
return { kind: "read", toolCallId, path };
|
|
83
|
+
},
|
|
84
|
+
usage(delta: UsageDelta): ScenarioStep {
|
|
85
|
+
return { kind: "usage", delta };
|
|
86
|
+
},
|
|
87
|
+
hang(): ScenarioStep {
|
|
88
|
+
return { kind: "hang" };
|
|
89
|
+
},
|
|
90
|
+
fail(message: string, surface: FailureSurface = "engine"): ScenarioStep {
|
|
91
|
+
return { kind: "fail", message, surface };
|
|
92
|
+
},
|
|
93
|
+
cancelled(): ScenarioStep {
|
|
94
|
+
return { kind: "cancelled" };
|
|
95
|
+
},
|
|
96
|
+
} as const;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* What the engine can know about the turn it is about to play, handed to the
|
|
100
|
+
* subject with every {@link HarnessContractSubject.arrange}: which execution
|
|
101
|
+
* and session it serves (a real engine is per session — its handle is parked
|
|
102
|
+
* under the session, its workspace and hook state live under the session —
|
|
103
|
+
* so a subject arranges THAT session's engine, never "the next turn"), and
|
|
104
|
+
* the approval decisions the prompt will carry (the runtime's
|
|
105
|
+
* `approvalDecisionsOf` over the persisted status — the one reader both
|
|
106
|
+
* drivers call). A subject whose engine cannot re-run a settled action, or
|
|
107
|
+
* whose model is told not to perform a skipped one, reads its decision here
|
|
108
|
+
* instead of parsing the prompt for it.
|
|
109
|
+
*/
|
|
110
|
+
export interface EngineView {
|
|
111
|
+
readonly executionId: string;
|
|
112
|
+
readonly sessionId: string;
|
|
113
|
+
readonly approvalDecisions: ReadonlyMap<string, ApprovalAction>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* What a subject may say about the record its engine is handed, laid UNDER
|
|
118
|
+
* the driver's own facts: the five it threads (`executionId`, `threadId`,
|
|
119
|
+
* `turnSeq`, `sessionId`, `approvalDecisions`) and the persisted status it
|
|
120
|
+
* seeds are the kit's bookkeeping and never a subject's to state.
|
|
121
|
+
*/
|
|
122
|
+
export type SubjectInputOverrides = Omit<
|
|
123
|
+
TurnInputFixtureOverrides,
|
|
124
|
+
"executionId" | "threadId" | "turnSeq" | "sessionId" | "approvalDecisions" | "persistedStatus"
|
|
125
|
+
>;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* One adapter under test, with the engine controls the kit needs. Adapters
|
|
129
|
+
* translate scenarios into their own double's drives and report what the
|
|
130
|
+
* double observed; they never reimplement contract behaviour.
|
|
131
|
+
*
|
|
132
|
+
* The kit owns the adapter's lifetime: it boots the adapter before the first
|
|
133
|
+
* turn and shuts it down after the last (the registry boots every adapter
|
|
134
|
+
* before the worker polls, and a real adapter refuses a turn before boot).
|
|
135
|
+
* The exported assertion functions therefore REQUIRE a booted adapter.
|
|
136
|
+
*/
|
|
137
|
+
export interface HarnessContractSubject {
|
|
138
|
+
/** Stable name, used in suite titles and every assertion message. */
|
|
139
|
+
readonly name: string;
|
|
140
|
+
/** The registry row this adapter fills; the runtime half selects the activity by it. */
|
|
141
|
+
readonly harness: HarnessName;
|
|
142
|
+
/** The adapter under test — the real implementation of the contract. */
|
|
143
|
+
readonly adapter: HarnessAdapter;
|
|
144
|
+
/** What `adapter.boot` needs. The subject knows its adapter's fields; the kit does not. */
|
|
145
|
+
readonly config: Config;
|
|
146
|
+
/**
|
|
147
|
+
* Arrange the engine so that the NEXT `runTurn` for `view.sessionId` plays
|
|
148
|
+
* `turn` — REPLACING whatever was arranged for that session and not yet
|
|
149
|
+
* played (a turn interrupted before its engine ran leaves no stale script
|
|
150
|
+
* behind). Called by the kit before every `runTurn`, including a
|
|
151
|
+
* reinvocation (the engine re-reaches the same gated call on resume, so the
|
|
152
|
+
* same proposal is arranged again).
|
|
153
|
+
*/
|
|
154
|
+
arrange(turn: TurnScenario, view: EngineView): void;
|
|
155
|
+
/**
|
|
156
|
+
* What this engine needs the record to say beyond the driver's facts (the
|
|
157
|
+
* model the adapter validates, the workspace its gate installs into). A
|
|
158
|
+
* subject whose engine reads nothing of the record omits it.
|
|
159
|
+
*/
|
|
160
|
+
inputOverrides?(view: EngineView): SubjectInputOverrides;
|
|
161
|
+
/**
|
|
162
|
+
* Resolves the next time a turn parks on a `hang` step — the moment the
|
|
163
|
+
* kit stops a turn from the outside, and the moment the runtime half
|
|
164
|
+
* delivers a cancellation, a drain or the clock tick that trips the stall
|
|
165
|
+
* watchdog. A real engine reaches its hang only after real setup (file
|
|
166
|
+
* writes, catalog fetches); a stop delivered any earlier would land in that
|
|
167
|
+
* setup and prove nothing about the hang. Never a timer.
|
|
168
|
+
*/
|
|
169
|
+
whenHanging(): Promise<void>;
|
|
170
|
+
/**
|
|
171
|
+
* How many times the side effect behind `toolCallId` actually ran, as the
|
|
172
|
+
* subject's double observed it. The safety-critical observable; zero for an
|
|
173
|
+
* id the engine never reached.
|
|
174
|
+
*/
|
|
175
|
+
executionCount(toolCallId: string): number;
|
|
176
|
+
}
|