@stigmer/runner 3.14.1 → 3.15.1
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 +28 -5
- package/dist/__test-utils__/hermetic-activity.js +23 -7
- package/dist/__test-utils__/hermetic-activity.js.map +1 -1
- 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 +38 -62
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +47 -121
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/scripted-agent.d.ts +17 -1
- package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js +27 -4
- package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.d.ts +52 -11
- package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js +66 -7
- package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js.map +1 -1
- 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 +16 -0
- package/dist/harness/capabilities.js +1 -0
- package/dist/harness/capabilities.js.map +1 -1
- 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 +44 -5
- package/dist/harness/registry.js +61 -5
- package/dist/harness/registry.js.map +1 -1
- 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 +232 -52
- package/dist/harness/types.js +17 -8
- package/dist/harness/types.js.map +1 -1
- 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 +110 -14
- 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 +128 -83
- package/src/__test-utils__/harness-contract/recording-sink.ts +15 -4
- package/src/__test-utils__/harness-contract/runtime-contract.ts +666 -0
- package/src/__test-utils__/harness-contract/scripted-adapter.ts +92 -32
- package/src/__test-utils__/harness-contract/types.ts +85 -9
- package/src/__test-utils__/hermetic-activity.ts +33 -8
- package/src/__test-utils__/model-registry-fixture.ts +72 -0
- package/src/__test-utils__/module-specifiers.ts +82 -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 +7 -5
- 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 +65 -145
- package/src/activities/execute-cursor/__test-utils__/scripted-agent.ts +30 -4
- package/src/activities/execute-cursor/__test-utils__/scripted-sdk.ts +96 -8
- 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 +1 -1
- package/src/activities/execute-cursor/__tests__/hermetic/file-review-capture.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/cost-cap.status.json +53 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/pause.status.json +1 -6
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/platform-stop.status.json +56 -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/unattributed-hook-block.status.json +79 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/worker-shutdown.status.json +2 -7
- 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 +22 -18
- package/src/activities/execute-cursor/__tests__/hermetic/plain-turn.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/hermetic/recovery-fresh-agent.test.ts +1 -1
- 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 +1 -1
- 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 +1 -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 +17 -0
- package/src/harness/persist-chokepoint.ts +112 -0
- package/src/harness/registry.ts +79 -5
- 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 +248 -53
- 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
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* half: what an adapter owes the runtime through `runTurn`, `boot`,
|
|
8
8
|
* `shutdown` and `releaseSession`. The runtime-side half (the throw-vs-return
|
|
9
9
|
* table end to end, the whole-activity heartbeat, the single persist
|
|
10
|
-
* chokepoint, the byte-pinned copy) is proven against
|
|
11
|
-
*
|
|
10
|
+
* chokepoint, the byte-pinned copy) is `runtime-contract.ts`, proven against
|
|
11
|
+
* the real runtime under `MockActivityEnvironment` with the same subjects.
|
|
12
12
|
*
|
|
13
13
|
* The kit IS the runtime stand-in. {@link ExecutionDriver} does what the
|
|
14
14
|
* runtime and the server do around a turn: builds the `TurnInput`, threads
|
|
@@ -16,10 +16,17 @@
|
|
|
16
16
|
* the id the adapter bound; one fixed id for a deterministic harness),
|
|
17
17
|
* advances `turnSeq`, records a user's decision the way `SubmitApproval` does
|
|
18
18
|
* (on the persisted row's `approvalAction`, the one copy) and derives the
|
|
19
|
-
* decisions map from those rows on the next invocation
|
|
20
|
-
* reinvocation a CLONE of the previous status
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* decisions map from those rows on the next invocation with the runtime's
|
|
20
|
+
* own reader, hands each reinvocation a CLONE of the previous status on BOTH
|
|
21
|
+
* the sink and the record (`execution.status` — the runtime seeds the one
|
|
22
|
+
* from the other, and an adapter may read the record's copy for its facts;
|
|
23
|
+
* nothing survives by object identity), and owns the sink. The subject owns
|
|
24
|
+
* the engine, and tells the kit when that engine is parked on a hang, which
|
|
25
|
+
* is the only moment the kit stops a turn from the outside: a real engine
|
|
26
|
+
* reaches its hang after real setup, and a stop delivered earlier lands in
|
|
27
|
+
* the setup and proves nothing. The kit also owns the adapter's lifetime —
|
|
28
|
+
* booted before the first turn, shut down after the last — because the
|
|
29
|
+
* registry does, and a real adapter refuses a turn before boot.
|
|
23
30
|
*
|
|
24
31
|
* ── Invariant catalog ───────────────────────────────────────────────────────
|
|
25
32
|
* 1. Every exit is a `TurnOutcome`. `runTurn` resolves, never rejects, under
|
|
@@ -27,9 +34,12 @@
|
|
|
27
34
|
* never escapes (the runtime, not the adapter, throws it).
|
|
28
35
|
* 2. A proposal is a WAITING_APPROVAL row on `sink.status` when
|
|
29
36
|
* `awaiting_approval` resolves, and never executes in that turn.
|
|
30
|
-
* 3. Reinvoked with APPROVE for that id → executes exactly once
|
|
31
|
-
*
|
|
32
|
-
*
|
|
37
|
+
* 3. Reinvoked with APPROVE for that id → executes exactly once and the row
|
|
38
|
+
* is carried to COMPLETED; with REJECT or SKIP → never executes and the
|
|
39
|
+
* turn continues (the row's terminal status is the runtime's to write,
|
|
40
|
+
* not the adapter's — Q-M4-1); reinvoked again after the approval → still
|
|
41
|
+
* once, never re-gated.
|
|
42
|
+
* 4. Aborting `stopSignal` mid-hang settles `runTurn` as `interrupted` within
|
|
33
43
|
* {@link INTERRUPT_SETTLE_BOUND_MS} and nothing after the stop executes; a
|
|
34
44
|
* signal aborted BEFORE `runTurn` yields `interrupted` with nothing done.
|
|
35
45
|
* 5. Usage reaches the sink as non-negative deltas summing to what the engine
|
|
@@ -38,9 +48,9 @@
|
|
|
38
48
|
* adapter binds before its first persist and resumes by the bound id, and
|
|
39
49
|
* surfaces a rejected bind as `failed` with nothing done after it; a
|
|
40
50
|
* `deterministic` adapter never binds.
|
|
41
|
-
* 7. Lifetimes: `
|
|
42
|
-
*
|
|
43
|
-
* resolves.
|
|
51
|
+
* 7. Lifetimes: `releaseSession` for a served session and for an unknown one
|
|
52
|
+
* both resolve; `shutdown` after a release resolves; `boot` after
|
|
53
|
+
* `shutdown` resolves (a worker restart re-boots the same module state).
|
|
44
54
|
* 8. One adapter object serves concurrent turns independently: a hanging turn
|
|
45
55
|
* and a completing turn on the same adapter never cross outcomes or
|
|
46
56
|
* execution counts.
|
|
@@ -48,7 +58,8 @@
|
|
|
48
58
|
* Every invariant is an exported plain async function first and an `it`
|
|
49
59
|
* block second, so `__tests__/harness-contract-self-check.test.ts` can run
|
|
50
60
|
* each one against a deliberately broken adapter and prove it fires — a kit
|
|
51
|
-
* that cannot fail proves nothing. Every message names the subject.
|
|
61
|
+
* that cannot fail proves nothing. Every message names the subject. Each
|
|
62
|
+
* function requires a booted adapter; {@link describeHarnessContract} boots.
|
|
52
63
|
*
|
|
53
64
|
* Above the contract line the pause primitives are indistinguishable: both
|
|
54
65
|
* end a turn `awaiting_approval`, both take the decisions on reinvocation.
|
|
@@ -56,19 +67,21 @@
|
|
|
56
67
|
* runner test runs the fake under both to prove it.
|
|
57
68
|
*/
|
|
58
69
|
|
|
59
|
-
import { describe, it, expect } from "vitest";
|
|
70
|
+
import { afterAll, beforeAll, describe, it, expect } from "vitest";
|
|
60
71
|
import { CancelledFailure } from "@temporalio/activity";
|
|
61
72
|
import { clone } from "@bufbuild/protobuf";
|
|
62
73
|
import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
63
74
|
import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
64
75
|
import { ApprovalAction, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
65
76
|
|
|
77
|
+
import { approvalDecisionsOf } from "../../harness/turn-context.js";
|
|
66
78
|
import type { TurnInput, TurnOutcome, UsageDelta } from "../../harness/types.js";
|
|
67
79
|
import type { ProposedAction } from "../approval-contract/types.js";
|
|
68
80
|
import { emptyStatus, findToolCallRow } from "../proto-helpers.js";
|
|
81
|
+
import { turnInputFixture } from "../turn-input-fixture.js";
|
|
69
82
|
import { RecordingTurnSink } from "./recording-sink.js";
|
|
70
83
|
import { scenario } from "./types.js";
|
|
71
|
-
import type { HarnessContractSubject, TurnScenario } from "./types.js";
|
|
84
|
+
import type { EngineView, HarnessContractSubject, TurnScenario } from "./types.js";
|
|
72
85
|
|
|
73
86
|
/**
|
|
74
87
|
* How long a turn may take to settle `interrupted` after its signal aborts.
|
|
@@ -79,7 +92,7 @@ import type { HarnessContractSubject, TurnScenario } from "./types.js";
|
|
|
79
92
|
*/
|
|
80
93
|
export const INTERRUPT_SETTLE_BOUND_MS = 5_000;
|
|
81
94
|
|
|
82
|
-
const OUTCOME_KINDS = ["completed", "awaiting_approval", "failed", "interrupted"] as const satisfies readonly TurnOutcome["kind"][];
|
|
95
|
+
const OUTCOME_KINDS = ["completed", "cancelled", "awaiting_approval", "failed", "interrupted"] as const satisfies readonly TurnOutcome["kind"][];
|
|
83
96
|
|
|
84
97
|
/** Representative gated action shared by the invariants. */
|
|
85
98
|
const WRITE_ALPHA: ProposedAction = { kind: "write", resource: "/work/alpha.txt" };
|
|
@@ -95,8 +108,12 @@ export interface TurnRun {
|
|
|
95
108
|
export interface TurnOptions {
|
|
96
109
|
/** A prepared sink (e.g. one whose bind rejects); defaults to a fresh one over the threaded status. */
|
|
97
110
|
readonly sink?: RecordingTurnSink;
|
|
98
|
-
/**
|
|
99
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Abort the sink's signal with this reason the moment the engine reports
|
|
113
|
+
* it is parked on a `hang` — the one way the kit stops a turn from the
|
|
114
|
+
* outside (see the header on why not sooner).
|
|
115
|
+
*/
|
|
116
|
+
readonly stopWhenHanging?: string;
|
|
100
117
|
}
|
|
101
118
|
|
|
102
119
|
export interface TurnInFlight {
|
|
@@ -105,25 +122,6 @@ export interface TurnInFlight {
|
|
|
105
122
|
readonly settled: Promise<TurnOutcome>;
|
|
106
123
|
}
|
|
107
124
|
|
|
108
|
-
/**
|
|
109
|
-
* The runtime's one reader of approval decisions, as the two harnesses agree
|
|
110
|
-
* on it today: every tool-call row still WAITING_APPROVAL whose
|
|
111
|
-
* `approvalAction` the server has set. The runtime extraction exports this
|
|
112
|
-
* from `src/harness/` and this kit imports it from there; until then the kit
|
|
113
|
-
* carries the definition so the data flow it drives is the real one.
|
|
114
|
-
*/
|
|
115
|
-
function approvalDecisionsOf(status: AgentExecutionStatus): ReadonlyMap<string, ApprovalAction> {
|
|
116
|
-
const decisions = new Map<string, ApprovalAction>();
|
|
117
|
-
for (const message of status.messages) {
|
|
118
|
-
for (const row of message.toolCalls) {
|
|
119
|
-
if (row.status === ToolCallStatus.TOOL_CALL_WAITING_APPROVAL && row.approvalAction !== ApprovalAction.UNSPECIFIED) {
|
|
120
|
-
decisions.set(row.id, row.approvalAction);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return decisions;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
125
|
/**
|
|
128
126
|
* One execution's worth of turns against one subject, doing the runtime's
|
|
129
127
|
* and the server's bookkeeping between them. A new driver per test: it
|
|
@@ -148,17 +146,34 @@ export class ExecutionDriver {
|
|
|
148
146
|
|
|
149
147
|
/** Start a turn without awaiting it, for invariants about turns in flight. */
|
|
150
148
|
begin(turn: TurnScenario, options: TurnOptions = {}): TurnInFlight {
|
|
151
|
-
this.subject.arrange(turn);
|
|
152
149
|
const sink = options.sink ?? new RecordingTurnSink({ status: this.seedStatus() });
|
|
153
|
-
|
|
150
|
+
// The engine's view and the record's copy both derive from the status
|
|
151
|
+
// this turn folds into, so a prepared sink's status is honoured too.
|
|
152
|
+
const view: EngineView = {
|
|
154
153
|
executionId: this.executionId,
|
|
155
|
-
threadId: this.threadId,
|
|
156
|
-
turnSeq: this.turnSeq,
|
|
157
154
|
sessionId: this.sessionId,
|
|
158
155
|
approvalDecisions: approvalDecisionsOf(sink.status),
|
|
159
156
|
};
|
|
157
|
+
this.subject.arrange(turn, view);
|
|
158
|
+
// Registered BEFORE the turn is entered so a hang reached synchronously
|
|
159
|
+
// is still observed; the abort itself lands when the engine reports.
|
|
160
|
+
const parked = options.stopWhenHanging !== undefined ? this.subject.whenHanging() : undefined;
|
|
161
|
+
// The whole resolved record: the subject's needs under the driver's five
|
|
162
|
+
// facts and the persisted status (`turn-input-fixture.ts`).
|
|
163
|
+
const input: TurnInput = turnInputFixture({
|
|
164
|
+
...this.subject.inputOverrides?.(view),
|
|
165
|
+
executionId: this.executionId,
|
|
166
|
+
threadId: this.threadId,
|
|
167
|
+
turnSeq: this.turnSeq,
|
|
168
|
+
sessionId: this.sessionId,
|
|
169
|
+
approvalDecisions: view.approvalDecisions,
|
|
170
|
+
persistedStatus: clone(AgentExecutionStatusSchema, sink.status),
|
|
171
|
+
});
|
|
160
172
|
const settled = this.subject.adapter.runTurn(input, sink);
|
|
161
|
-
options.
|
|
173
|
+
if (parked && options.stopWhenHanging !== undefined) {
|
|
174
|
+
const reason = options.stopWhenHanging;
|
|
175
|
+
void parked.then(() => sink.abort(reason));
|
|
176
|
+
}
|
|
162
177
|
return { input, sink, settled };
|
|
163
178
|
}
|
|
164
179
|
|
|
@@ -186,7 +201,11 @@ export class ExecutionDriver {
|
|
|
186
201
|
decide(toolCallId: string, action: ApprovalAction): void {
|
|
187
202
|
const row = this.persisted ? findToolCallRow(this.persisted, toolCallId) : undefined;
|
|
188
203
|
if (!row || row.status !== ToolCallStatus.TOOL_CALL_WAITING_APPROVAL) {
|
|
189
|
-
|
|
204
|
+
const rows = (this.persisted?.messages ?? [])
|
|
205
|
+
.flatMap((m) => m.toolCalls)
|
|
206
|
+
.map((tc) => `${tc.id}=${ToolCallStatus[tc.status]}`)
|
|
207
|
+
.join(", ");
|
|
208
|
+
throw new Error(`${this.subject.name}: kit bug — decide('${toolCallId}') but no WAITING row was persisted (rows: ${rows || "none"})`);
|
|
190
209
|
}
|
|
191
210
|
row.approvalAction = action;
|
|
192
211
|
}
|
|
@@ -233,13 +252,6 @@ async function settleWithinBound<T>(subject: HarnessContractSubject, settled: Pr
|
|
|
233
252
|
}
|
|
234
253
|
}
|
|
235
254
|
|
|
236
|
-
/** Stop the turn on the next macrotask, after the adapter has reached whatever it was going to park on. */
|
|
237
|
-
function stopSoon(reason: string): (sink: RecordingTurnSink) => void {
|
|
238
|
-
return (sink) => {
|
|
239
|
-
setImmediate(() => sink.abort(reason));
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
|
|
243
255
|
async function expectResolves(subject: HarnessContractSubject, call: Promise<void>, what: string): Promise<void> {
|
|
244
256
|
try {
|
|
245
257
|
await call;
|
|
@@ -263,7 +275,7 @@ export async function assertEveryExitIsAnOutcome(subject: HarnessContractSubject
|
|
|
263
275
|
{ when: "on a usage-only turn", turn: [scenario.usage({ inputTokens: 1, outputTokens: 1 })] },
|
|
264
276
|
{ when: "on an undecided proposal", turn: [scenario.propose("inv1-call", WRITE_ALPHA)] },
|
|
265
277
|
{ when: "on an engine failure", turn: [scenario.fail("engine exploded")] },
|
|
266
|
-
{ when: "on a hang stopped from the outside", turn: [scenario.hang()], options: {
|
|
278
|
+
{ when: "on a hang stopped from the outside", turn: [scenario.hang()], options: { stopWhenHanging: "kit: stop" } },
|
|
267
279
|
];
|
|
268
280
|
for (const play of plays) {
|
|
269
281
|
const { sink, settled } = driver.begin(play.turn, play.options);
|
|
@@ -304,10 +316,14 @@ export async function assertProposalIsWaitingAndUnexecuted(subject: HarnessContr
|
|
|
304
316
|
|
|
305
317
|
/**
|
|
306
318
|
* Invariant 3: the decision is honoured exactly. APPROVE executes once and
|
|
307
|
-
* only once, however many times the same decided row is seen again
|
|
308
|
-
* and SKIP never execute and
|
|
309
|
-
*
|
|
310
|
-
*
|
|
319
|
+
* only once, however many times the same decided row is seen again, and the
|
|
320
|
+
* engine's completion reaches the row; REJECT and SKIP never execute and the
|
|
321
|
+
* turn continues — the row's terminal status is NOT asserted here because
|
|
322
|
+
* it is not the adapter's to write (the decision is the server's field and
|
|
323
|
+
* the WAITING → SKIPPED transition follows from it with no engine knowledge,
|
|
324
|
+
* so it is the runtime's; S2 M4 Q-M4-1, the runtime's arm lands in S3). The
|
|
325
|
+
* reinvocation sees a CLONE of the persisted status, as it would from the
|
|
326
|
+
* server.
|
|
311
327
|
*/
|
|
312
328
|
export async function assertDecisionsExecuteExactlyOnce(subject: HarnessContractSubject): Promise<void> {
|
|
313
329
|
const driver = new ExecutionDriver(subject, "inv3");
|
|
@@ -334,7 +350,7 @@ export async function assertDecisionsExecuteExactlyOnce(subject: HarnessContract
|
|
|
334
350
|
const decided = await driver.turn([scenario.propose(id, WRITE_ALPHA), scenario.say("moving on")]);
|
|
335
351
|
expect(decided.outcome.kind, `${subject.name}: after ${label} the turn must continue to completion`).toBe("completed");
|
|
336
352
|
expect(subject.executionCount(id), `${subject.name}: a ${label}-ed action must never execute`).toBe(0);
|
|
337
|
-
expect(findToolCallRow(decided.sink.status, id)?.status, `${subject.name}: a ${label}-ed
|
|
353
|
+
expect(findToolCallRow(decided.sink.status, id)?.status, `${subject.name}: a ${label}-ed action must never be reported as executed`).not.toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
338
354
|
}
|
|
339
355
|
}
|
|
340
356
|
|
|
@@ -342,9 +358,11 @@ export async function assertDecisionsExecuteExactlyOnce(subject: HarnessContract
|
|
|
342
358
|
|
|
343
359
|
/**
|
|
344
360
|
* Invariant 4: the stop signal is honoured promptly and completely. A turn
|
|
345
|
-
* stopped
|
|
346
|
-
*
|
|
347
|
-
*
|
|
361
|
+
* stopped WHILE HANGING (the engine has reported it is parked, so the stop
|
|
362
|
+
* lands in the hang and not in the adapter's setup) settles `interrupted`
|
|
363
|
+
* within the bound and executes nothing that came after the hang, even an
|
|
364
|
+
* already-approved action; a turn entered under an aborted signal does
|
|
365
|
+
* nothing at all.
|
|
348
366
|
*/
|
|
349
367
|
export async function assertStopSignalInterrupts(subject: HarnessContractSubject, boundMs = INTERRUPT_SETTLE_BOUND_MS): Promise<void> {
|
|
350
368
|
const driver = new ExecutionDriver(subject, "inv4");
|
|
@@ -354,7 +372,7 @@ export async function assertStopSignalInterrupts(subject: HarnessContractSubject
|
|
|
354
372
|
expect(proposed.outcome.kind, `${subject.name}: proposal must end awaiting_approval`).toBe("awaiting_approval");
|
|
355
373
|
driver.decide(id, ApprovalAction.APPROVE);
|
|
356
374
|
|
|
357
|
-
const hanging = driver.begin([scenario.say("working"), scenario.hang(), scenario.propose(id, WRITE_ALPHA)], {
|
|
375
|
+
const hanging = driver.begin([scenario.say("working"), scenario.hang(), scenario.propose(id, WRITE_ALPHA)], { stopWhenHanging: "kit: user pause" });
|
|
358
376
|
const outcome = await settleWithinBound(subject, hanging.settled, "after stopSignal aborted mid-hang", boundMs);
|
|
359
377
|
expect(outcome.kind, `${subject.name}: a turn stopped mid-hang must settle interrupted`).toBe("interrupted");
|
|
360
378
|
expect(subject.executionCount(id), `${subject.name}: nothing after the stop may execute, even an approved action`).toBe(0);
|
|
@@ -371,22 +389,31 @@ export async function assertStopSignalInterrupts(subject: HarnessContractSubject
|
|
|
371
389
|
|
|
372
390
|
// ── Invariant 5 ─────────────────────────────────────────────────────────────
|
|
373
391
|
|
|
374
|
-
|
|
375
|
-
|
|
392
|
+
/** The four counts summed; the price and its basis are the adapter's per delta and are not summed here. */
|
|
393
|
+
interface UsageTotals {
|
|
394
|
+
inputTokens: number;
|
|
395
|
+
outputTokens: number;
|
|
396
|
+
cacheReadTokens: number;
|
|
397
|
+
cacheWriteTokens: number;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const TOKEN_COUNT_FIELDS = ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"] as const satisfies readonly (keyof UsageTotals)[];
|
|
401
|
+
|
|
402
|
+
function sumUsage(deltas: readonly UsageDelta[]): UsageTotals {
|
|
403
|
+
const total: UsageTotals = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };
|
|
376
404
|
for (const d of deltas) {
|
|
377
|
-
total
|
|
378
|
-
total.outputTokens += d.outputTokens ?? 0;
|
|
379
|
-
total.cacheReadTokens += d.cacheReadTokens ?? 0;
|
|
380
|
-
total.cacheWriteTokens += d.cacheWriteTokens ?? 0;
|
|
405
|
+
for (const field of TOKEN_COUNT_FIELDS) total[field] += d[field] ?? 0;
|
|
381
406
|
}
|
|
382
407
|
return total;
|
|
383
408
|
}
|
|
384
409
|
|
|
385
410
|
/**
|
|
386
|
-
* Invariant 5: usage reaches the sink as deltas —
|
|
387
|
-
* to what the engine emitted. An
|
|
388
|
-
*
|
|
389
|
-
*
|
|
411
|
+
* Invariant 5: usage reaches the sink as deltas — the token counts and the
|
|
412
|
+
* price non-negative, and the counts summing to what the engine emitted. An
|
|
413
|
+
* adapter that reports cumulative totals instead of deltas would
|
|
414
|
+
* double-count in the runtime's accumulator and trip the cost cap early; one
|
|
415
|
+
* that reports a negative number would credit it. The basis a delta names
|
|
416
|
+
* (`model`, `requestedModelParams`) is the adapter's own and not judged here.
|
|
390
417
|
*/
|
|
391
418
|
export async function assertUsageReachesSinkAsDeltas(subject: HarnessContractSubject): Promise<void> {
|
|
392
419
|
const driver = new ExecutionDriver(subject, "inv5");
|
|
@@ -399,7 +426,9 @@ export async function assertUsageReachesSinkAsDeltas(subject: HarnessContractSub
|
|
|
399
426
|
expect(outcome.kind, `${subject.name}: a usage-reporting turn must complete`).toBe("completed");
|
|
400
427
|
|
|
401
428
|
for (const delta of sink.usageDeltas) {
|
|
402
|
-
for (const [
|
|
429
|
+
for (const field of [...TOKEN_COUNT_FIELDS, "estimatedCostUsd"] as const) {
|
|
430
|
+
const value = delta[field];
|
|
431
|
+
if (value === undefined) continue;
|
|
403
432
|
expect(value, `${subject.name}: usage delta field ${field} must be non-negative`).toBeGreaterThanOrEqual(0);
|
|
404
433
|
}
|
|
405
434
|
}
|
|
@@ -455,15 +484,15 @@ export async function assertStateIdCapabilityAgrees(subject: HarnessContractSubj
|
|
|
455
484
|
// ── Invariant 7 ─────────────────────────────────────────────────────────────
|
|
456
485
|
|
|
457
486
|
/**
|
|
458
|
-
* Invariant 7: the
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
* session's resources,
|
|
487
|
+
* Invariant 7: the lifetimes resolve. On the booted adapter: serve a turn,
|
|
488
|
+
* release the served session and an unknown one, shut down, and boot again
|
|
489
|
+
* (a worker restart re-boots the same module state). Each must resolve — a
|
|
490
|
+
* rejection in any would leak a session's resources, hang a drain, or fail
|
|
491
|
+
* the next worker's boot. Boot itself is proven by every turn before this
|
|
492
|
+
* one having run.
|
|
462
493
|
*/
|
|
463
494
|
export async function assertLifetimesResolve(subject: HarnessContractSubject): Promise<void> {
|
|
464
495
|
const { adapter } = subject;
|
|
465
|
-
await expectResolves(subject, adapter.boot(subject.config), "boot(config)");
|
|
466
|
-
|
|
467
496
|
const driver = new ExecutionDriver(subject, "inv7");
|
|
468
497
|
const served = await driver.turn([scenario.say("served")]);
|
|
469
498
|
expect(served.outcome.kind, `${subject.name}: a plain turn must complete`).toBe("completed");
|
|
@@ -471,23 +500,28 @@ export async function assertLifetimesResolve(subject: HarnessContractSubject): P
|
|
|
471
500
|
await expectResolves(subject, adapter.releaseSession(served.input.sessionId), `releaseSession('${served.input.sessionId}') for a served session`);
|
|
472
501
|
await expectResolves(subject, adapter.releaseSession("ses-never-served"), "releaseSession for an unknown session");
|
|
473
502
|
await expectResolves(subject, adapter.shutdown(), "shutdown() after release");
|
|
503
|
+
await expectResolves(subject, adapter.boot(subject.config), "boot(config) after shutdown (a worker restart)");
|
|
474
504
|
}
|
|
475
505
|
|
|
476
506
|
// ── Invariant 8 ─────────────────────────────────────────────────────────────
|
|
477
507
|
|
|
478
508
|
/**
|
|
479
509
|
* Invariant 8: one adapter object, many turns. While one execution's turn
|
|
480
|
-
* hangs
|
|
481
|
-
*
|
|
482
|
-
*
|
|
483
|
-
*
|
|
510
|
+
* hangs (reported parked before the other execution starts, so the two are
|
|
511
|
+
* truly concurrent and not merely queued), another execution's turns
|
|
512
|
+
* propose, get decided and execute on the SAME adapter; stopping the hanging
|
|
513
|
+
* turn settles only it; nothing crosses. This is "the adapter holds no
|
|
514
|
+
* per-turn state" made observable — the property `maxConcurrentActivities`
|
|
515
|
+
* relies on.
|
|
484
516
|
*/
|
|
485
517
|
export async function assertConcurrentTurnsAreIndependent(subject: HarnessContractSubject, boundMs = INTERRUPT_SETTLE_BOUND_MS): Promise<void> {
|
|
486
518
|
const hangingExecution = new ExecutionDriver(subject, "inv8-hanging");
|
|
487
519
|
const busyExecution = new ExecutionDriver(subject, "inv8-busy");
|
|
488
520
|
const id = "inv8-busy-write";
|
|
489
521
|
|
|
522
|
+
const parked = subject.whenHanging();
|
|
490
523
|
const hanging = hangingExecution.begin([scenario.say("parked"), scenario.hang()]);
|
|
524
|
+
await settleWithinBound(subject, parked, "waiting for the first execution to park on its hang", boundMs);
|
|
491
525
|
|
|
492
526
|
const proposed = await settleWithinBound(subject, busyExecution.turn([scenario.propose(id, WRITE_ALPHA)]), "on a second execution while another turn hangs", boundMs);
|
|
493
527
|
expect(proposed.outcome.kind, `${subject.name}: a second execution's proposal must settle while another turn hangs`).toBe("awaiting_approval");
|
|
@@ -505,9 +539,20 @@ export async function assertConcurrentTurnsAreIndependent(subject: HarnessContra
|
|
|
505
539
|
|
|
506
540
|
// ── The runnable suite ──────────────────────────────────────────────────────
|
|
507
541
|
|
|
508
|
-
/**
|
|
542
|
+
/**
|
|
543
|
+
* Register the adapter-side contract against one subject. Boots the adapter
|
|
544
|
+
* once before the first invariant and shuts it down after the last, as the
|
|
545
|
+
* registry does around a worker's life (`bootHarnesses` / `shutdownHarnesses`).
|
|
546
|
+
*/
|
|
509
547
|
export function describeHarnessContract(subject: HarnessContractSubject): void {
|
|
510
548
|
describe(`harness contract — ${subject.name}`, () => {
|
|
549
|
+
beforeAll(async () => {
|
|
550
|
+
await subject.adapter.boot(subject.config);
|
|
551
|
+
});
|
|
552
|
+
afterAll(async () => {
|
|
553
|
+
await subject.adapter.shutdown();
|
|
554
|
+
});
|
|
555
|
+
|
|
511
556
|
it("settles every turn with a TurnOutcome and never rejects (invariant 1)", async () => {
|
|
512
557
|
await assertEveryExitIsAnOutcome(subject);
|
|
513
558
|
});
|
|
@@ -526,7 +571,7 @@ export function describeHarnessContract(subject: HarnessContractSubject): void {
|
|
|
526
571
|
it(`binds its state id as its ${subject.adapter.capabilities.stateIdSource} capability declares (invariant 6)`, async () => {
|
|
527
572
|
await assertStateIdCapabilityAgrees(subject);
|
|
528
573
|
});
|
|
529
|
-
it("
|
|
574
|
+
it("serves, releases, shuts down and re-boots without a rejection (invariant 7)", async () => {
|
|
530
575
|
await assertLifetimesResolve(subject);
|
|
531
576
|
});
|
|
532
577
|
it("serves concurrent turns on one adapter object independently (invariant 8)", async () => {
|
|
@@ -21,13 +21,15 @@
|
|
|
21
21
|
import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
22
22
|
|
|
23
23
|
import type { TurnSink, UsageDelta } from "../../harness/types.js";
|
|
24
|
+
import { TimingRecorder } from "../../shared/cold-start-timing.js";
|
|
24
25
|
import { emptyStatus } from "../proto-helpers.js";
|
|
25
26
|
|
|
26
27
|
/** One call the adapter made on the sink, in the order it made it. */
|
|
27
28
|
export type SinkEvent =
|
|
28
29
|
| { readonly kind: "persist" }
|
|
29
|
-
| { readonly kind: "activity" }
|
|
30
|
+
| { readonly kind: "activity"; readonly detail: string | undefined }
|
|
30
31
|
| { readonly kind: "usage"; readonly delta: UsageDelta }
|
|
32
|
+
| { readonly kind: "progress"; readonly label: string }
|
|
31
33
|
| { readonly kind: "bind"; readonly harnessStateId: string };
|
|
32
34
|
|
|
33
35
|
export interface RecordingTurnSinkOptions {
|
|
@@ -40,6 +42,7 @@ export interface RecordingTurnSinkOptions {
|
|
|
40
42
|
export class RecordingTurnSink implements TurnSink {
|
|
41
43
|
readonly status: AgentExecutionStatus;
|
|
42
44
|
readonly stopSignal: AbortSignal;
|
|
45
|
+
readonly setupTiming = new TimingRecorder();
|
|
43
46
|
|
|
44
47
|
private readonly controller = new AbortController();
|
|
45
48
|
private readonly log: SinkEvent[] = [];
|
|
@@ -56,18 +59,22 @@ export class RecordingTurnSink implements TurnSink {
|
|
|
56
59
|
this.controller.abort(reason);
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
requestPersist(): void {
|
|
62
|
+
async requestPersist(): Promise<void> {
|
|
60
63
|
this.log.push({ kind: "persist" });
|
|
61
64
|
}
|
|
62
65
|
|
|
63
|
-
recordActivity(): void {
|
|
64
|
-
this.log.push({ kind: "activity" });
|
|
66
|
+
recordActivity(detail?: string): void {
|
|
67
|
+
this.log.push({ kind: "activity", detail });
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
reportUsage(delta: UsageDelta): void {
|
|
68
71
|
this.log.push({ kind: "usage", delta });
|
|
69
72
|
}
|
|
70
73
|
|
|
74
|
+
async reportProgress(label: string): Promise<void> {
|
|
75
|
+
this.log.push({ kind: "progress", label });
|
|
76
|
+
}
|
|
77
|
+
|
|
71
78
|
async bindHarnessState(harnessStateId: string): Promise<void> {
|
|
72
79
|
if (this.bindRejectsWith) throw this.bindRejectsWith;
|
|
73
80
|
this.log.push({ kind: "bind", harnessStateId });
|
|
@@ -93,4 +100,8 @@ export class RecordingTurnSink implements TurnSink {
|
|
|
93
100
|
get boundStateIds(): readonly string[] {
|
|
94
101
|
return this.log.flatMap((e) => (e.kind === "bind" ? [e.harnessStateId] : []));
|
|
95
102
|
}
|
|
103
|
+
|
|
104
|
+
get progressLabels(): readonly string[] {
|
|
105
|
+
return this.log.flatMap((e) => (e.kind === "progress" ? [e.label] : []));
|
|
106
|
+
}
|
|
96
107
|
}
|