@stigmer/runner 3.14.1 → 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 +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
|
@@ -29,9 +29,20 @@
|
|
|
29
29
|
* approval exactly once, refusing to resume a state it never minted — because
|
|
30
30
|
* those are what the kit measures.
|
|
31
31
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
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`.
|
|
35
46
|
*/
|
|
36
47
|
|
|
37
48
|
import { ApprovalAction, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
@@ -43,14 +54,21 @@ import type { HarnessAdapter, TurnInput, TurnOutcome, TurnSink } from "../../har
|
|
|
43
54
|
import { DEEP_AGENT_VISION_PROFILE } from "../../shared/attachment-vision.js";
|
|
44
55
|
import type { ProposedAction } from "../approval-contract/types.js";
|
|
45
56
|
import { testConfig } from "../config-fixture.js";
|
|
46
|
-
import { aiMessage, findToolCallRow, waitingToolCall } from "../proto-helpers.js";
|
|
47
|
-
import type { HarnessContractSubject, ScenarioStep, TurnScenario } from "./types.js";
|
|
57
|
+
import { aiMessage, findToolCallRow, toolCall, waitingToolCall } from "../proto-helpers.js";
|
|
58
|
+
import type { EngineView, HarnessContractSubject, ScenarioStep, TurnScenario } from "./types.js";
|
|
48
59
|
|
|
49
60
|
export interface ScriptedHarnessOptions {
|
|
50
61
|
/** Diagnostic name; defaults to a name that says which primitives this instance runs under. */
|
|
51
62
|
readonly name?: string;
|
|
52
63
|
readonly pausePrimitive: PausePrimitive;
|
|
53
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>;
|
|
54
72
|
}
|
|
55
73
|
|
|
56
74
|
/**
|
|
@@ -74,9 +92,11 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
|
|
|
74
92
|
readonly name: string;
|
|
75
93
|
readonly capabilities: HarnessCapabilities;
|
|
76
94
|
|
|
77
|
-
|
|
95
|
+
/** The next turn each session plays; arranging a session replaces its entry. */
|
|
96
|
+
private readonly arranged = new Map<string, TurnScenario>();
|
|
78
97
|
private readonly mintedStateIds = new Set<string>();
|
|
79
98
|
private readonly executions = new Map<string, number>();
|
|
99
|
+
private readonly hangWaiters: Array<() => void> = [];
|
|
80
100
|
private mintCounter = 0;
|
|
81
101
|
|
|
82
102
|
constructor(options: ScriptedHarnessOptions) {
|
|
@@ -88,14 +108,29 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
|
|
|
88
108
|
subAgents: false,
|
|
89
109
|
toolRestriction: true,
|
|
90
110
|
visionProfile: DEEP_AGENT_VISION_PROFILE,
|
|
111
|
+
fileReview: { harnessId: "scripted", excludePaths: [] },
|
|
91
112
|
};
|
|
92
113
|
}
|
|
93
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
|
+
|
|
94
124
|
// ── Engine controls (the subject's side of the kit seam) ──────────────────
|
|
95
125
|
|
|
96
|
-
/**
|
|
97
|
-
|
|
98
|
-
|
|
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);
|
|
99
134
|
}
|
|
100
135
|
|
|
101
136
|
executionCount(toolCallId: string): number {
|
|
@@ -114,8 +149,9 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
|
|
|
114
149
|
async releaseSession(_sessionId: string): Promise<void> {}
|
|
115
150
|
|
|
116
151
|
async runTurn(input: TurnInput, sink: TurnSink): Promise<TurnOutcome> {
|
|
117
|
-
const turn = this.
|
|
118
|
-
if (!turn) throw new Error(`${this.name}: runTurn called with no scenario arranged (test bug)`);
|
|
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);
|
|
119
155
|
|
|
120
156
|
// The signal may be aborted before the turn is entered; do no work then.
|
|
121
157
|
if (sink.stopSignal.aborted) return { kind: "interrupted" };
|
|
@@ -153,7 +189,7 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
|
|
|
153
189
|
} catch (err) {
|
|
154
190
|
return {
|
|
155
191
|
kind: "ended",
|
|
156
|
-
outcome: { kind: "failed", message: `${this.name}: could not bind engine state`, cause: err },
|
|
192
|
+
outcome: { kind: "failed", surface: "internal", message: `${this.name}: could not bind engine state`, cause: err },
|
|
157
193
|
};
|
|
158
194
|
}
|
|
159
195
|
return { kind: "ok" };
|
|
@@ -162,7 +198,7 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
|
|
|
162
198
|
if (!this.mintedStateIds.has(input.threadId)) {
|
|
163
199
|
return {
|
|
164
200
|
kind: "ended",
|
|
165
|
-
outcome: { kind: "failed", message: `${this.name}: no engine state '${input.threadId}' to resume` },
|
|
201
|
+
outcome: { kind: "failed", surface: "internal", message: `${this.name}: no engine state '${input.threadId}' to resume` },
|
|
166
202
|
};
|
|
167
203
|
}
|
|
168
204
|
return { kind: "ok" };
|
|
@@ -174,22 +210,42 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
|
|
|
174
210
|
case "say": {
|
|
175
211
|
sink.status.messages.push(aiMessage(step.text));
|
|
176
212
|
sink.recordActivity();
|
|
177
|
-
|
|
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();
|
|
178
217
|
return undefined;
|
|
179
218
|
}
|
|
180
219
|
case "propose":
|
|
181
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
|
+
}
|
|
182
235
|
case "usage": {
|
|
183
236
|
sink.reportUsage(step.delta);
|
|
184
237
|
sink.recordActivity();
|
|
185
238
|
return undefined;
|
|
186
239
|
}
|
|
187
240
|
case "hang": {
|
|
241
|
+
for (const waiter of this.hangWaiters.splice(0)) waiter();
|
|
188
242
|
await whenAborted(sink.stopSignal);
|
|
189
243
|
return { kind: "interrupted" };
|
|
190
244
|
}
|
|
191
245
|
case "fail":
|
|
192
|
-
return { kind: "failed", message: step.message };
|
|
246
|
+
return { kind: "failed", surface: step.surface, message: step.message };
|
|
247
|
+
case "cancelled":
|
|
248
|
+
return { kind: "cancelled" };
|
|
193
249
|
default: {
|
|
194
250
|
const exhaustive: never = step;
|
|
195
251
|
throw new Error(`${this.name}: unknown scenario step ${JSON.stringify(exhaustive)}`);
|
|
@@ -230,12 +286,14 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
|
|
|
230
286
|
case ApprovalAction.APPROVE:
|
|
231
287
|
case ApprovalAction.APPROVE_ALL: {
|
|
232
288
|
this.executions.set(toolCallId, this.executionCount(toolCallId) + 1);
|
|
233
|
-
this.
|
|
289
|
+
this.completeRow(existing, toolCallId, action, sink);
|
|
234
290
|
return undefined;
|
|
235
291
|
}
|
|
236
292
|
case ApprovalAction.SKIP:
|
|
237
293
|
case ApprovalAction.REJECT: {
|
|
238
|
-
|
|
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();
|
|
239
297
|
return undefined;
|
|
240
298
|
}
|
|
241
299
|
default: {
|
|
@@ -246,24 +304,19 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
|
|
|
246
304
|
}
|
|
247
305
|
|
|
248
306
|
/**
|
|
249
|
-
*
|
|
250
|
-
* (the runtime seeded the status with
|
|
251
|
-
* with no row is a runtime that decided
|
|
252
|
-
* records what happened rather than
|
|
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.
|
|
253
312
|
*/
|
|
254
|
-
private
|
|
255
|
-
existing: ToolCall | undefined,
|
|
256
|
-
toolCallId: string,
|
|
257
|
-
action: ProposedAction,
|
|
258
|
-
status: ToolCallStatus,
|
|
259
|
-
sink: TurnSink,
|
|
260
|
-
): void {
|
|
313
|
+
private completeRow(existing: ToolCall | undefined, toolCallId: string, action: ProposedAction, sink: TurnSink): void {
|
|
261
314
|
if (existing) {
|
|
262
|
-
existing.status =
|
|
315
|
+
existing.status = ToolCallStatus.TOOL_CALL_COMPLETED;
|
|
263
316
|
} else {
|
|
264
317
|
const message = aiMessage("");
|
|
265
318
|
const row = waitingToolCall(toolCallId, action.kind, "");
|
|
266
|
-
row.status =
|
|
319
|
+
row.status = ToolCallStatus.TOOL_CALL_COMPLETED;
|
|
267
320
|
message.toolCalls.push(row);
|
|
268
321
|
sink.status.messages.push(message);
|
|
269
322
|
}
|
|
@@ -277,13 +330,20 @@ export interface ScriptedSubject extends HarnessContractSubject {
|
|
|
277
330
|
readonly adapter: ScriptedHarnessAdapter;
|
|
278
331
|
}
|
|
279
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
|
+
*/
|
|
280
338
|
export function scriptedSubject(options: ScriptedHarnessOptions): ScriptedSubject {
|
|
281
339
|
const adapter = new ScriptedHarnessAdapter(options);
|
|
282
340
|
return {
|
|
283
341
|
name: adapter.name,
|
|
342
|
+
harness: "deep-agent",
|
|
284
343
|
adapter,
|
|
285
|
-
config: testConfig(),
|
|
286
|
-
arrange: (turn) => adapter.arrange(turn),
|
|
344
|
+
config: testConfig(options.config),
|
|
345
|
+
arrange: (turn, view) => adapter.arrange(turn, view),
|
|
346
|
+
whenHanging: () => adapter.whenHanging(),
|
|
287
347
|
executionCount: (toolCallId) => adapter.executionCount(toolCallId),
|
|
288
348
|
};
|
|
289
349
|
}
|
|
@@ -23,9 +23,13 @@
|
|
|
23
23
|
* HITL taxonomy.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
+
import type { ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
27
|
+
|
|
26
28
|
import type { Config } from "../../config.js";
|
|
27
|
-
import type {
|
|
29
|
+
import type { HarnessName } from "../../harness/registry.js";
|
|
30
|
+
import type { FailureSurface, HarnessAdapter, UsageDelta } from "../../harness/types.js";
|
|
28
31
|
import type { ProposedAction } from "../approval-contract/types.js";
|
|
32
|
+
import type { TurnInputFixtureOverrides } from "../turn-input-fixture.js";
|
|
29
33
|
|
|
30
34
|
/**
|
|
31
35
|
* One thing the engine does during a turn. A turn plays its steps in order
|
|
@@ -35,20 +39,33 @@ import type { ProposedAction } from "../approval-contract/types.js";
|
|
|
35
39
|
* - `propose`: the engine reaches a gated side effect identified by
|
|
36
40
|
* `toolCallId`. Undecided → the turn ends `awaiting_approval` and later
|
|
37
41
|
* steps do not run; APPROVE → the effect runs once and the turn continues;
|
|
38
|
-
* REJECT / SKIP → the effect never runs 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).
|
|
39
51
|
* - `usage`: the engine reports one turn's token counts.
|
|
40
52
|
* - `hang`: the engine makes no further progress until told to stop. The
|
|
41
53
|
* step that the stop-signal invariants and the runtime's stall watchdog
|
|
42
54
|
* are built around; it must be parked on the signal, never on a timer.
|
|
43
55
|
* - `fail`: the engine fails with a message the adapter can name; the turn
|
|
44
|
-
* ends `failed
|
|
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`.
|
|
45
60
|
*/
|
|
46
61
|
export type ScenarioStep =
|
|
47
62
|
| { readonly kind: "say"; readonly text: string }
|
|
48
63
|
| { readonly kind: "propose"; readonly toolCallId: string; readonly action: ProposedAction }
|
|
64
|
+
| { readonly kind: "read"; readonly toolCallId: string; readonly path: string }
|
|
49
65
|
| { readonly kind: "usage"; readonly delta: UsageDelta }
|
|
50
66
|
| { readonly kind: "hang" }
|
|
51
|
-
| { readonly kind: "fail"; readonly message: string }
|
|
67
|
+
| { readonly kind: "fail"; readonly message: string; readonly surface: FailureSurface }
|
|
68
|
+
| { readonly kind: "cancelled" };
|
|
52
69
|
|
|
53
70
|
/** One turn's worth of engine behaviour. */
|
|
54
71
|
export type TurnScenario = readonly ScenarioStep[];
|
|
@@ -61,36 +78,95 @@ export const scenario = {
|
|
|
61
78
|
propose(toolCallId: string, action: ProposedAction): ScenarioStep {
|
|
62
79
|
return { kind: "propose", toolCallId, action };
|
|
63
80
|
},
|
|
81
|
+
read(toolCallId: string, path: string): ScenarioStep {
|
|
82
|
+
return { kind: "read", toolCallId, path };
|
|
83
|
+
},
|
|
64
84
|
usage(delta: UsageDelta): ScenarioStep {
|
|
65
85
|
return { kind: "usage", delta };
|
|
66
86
|
},
|
|
67
87
|
hang(): ScenarioStep {
|
|
68
88
|
return { kind: "hang" };
|
|
69
89
|
},
|
|
70
|
-
fail(message: string): ScenarioStep {
|
|
71
|
-
return { kind: "fail", message };
|
|
90
|
+
fail(message: string, surface: FailureSurface = "engine"): ScenarioStep {
|
|
91
|
+
return { kind: "fail", message, surface };
|
|
92
|
+
},
|
|
93
|
+
cancelled(): ScenarioStep {
|
|
94
|
+
return { kind: "cancelled" };
|
|
72
95
|
},
|
|
73
96
|
} as const;
|
|
74
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
|
+
|
|
75
127
|
/**
|
|
76
128
|
* One adapter under test, with the engine controls the kit needs. Adapters
|
|
77
129
|
* translate scenarios into their own double's drives and report what the
|
|
78
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.
|
|
79
136
|
*/
|
|
80
137
|
export interface HarnessContractSubject {
|
|
81
138
|
/** Stable name, used in suite titles and every assertion message. */
|
|
82
139
|
readonly name: string;
|
|
140
|
+
/** The registry row this adapter fills; the runtime half selects the activity by it. */
|
|
141
|
+
readonly harness: HarnessName;
|
|
83
142
|
/** The adapter under test — the real implementation of the contract. */
|
|
84
143
|
readonly adapter: HarnessAdapter;
|
|
85
144
|
/** What `adapter.boot` needs. The subject knows its adapter's fields; the kit does not. */
|
|
86
145
|
readonly config: Config;
|
|
87
146
|
/**
|
|
88
|
-
* Arrange the engine so that the NEXT `runTurn`
|
|
89
|
-
* `turn
|
|
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
|
|
90
151
|
* reinvocation (the engine re-reaches the same gated call on resume, so the
|
|
91
152
|
* same proposal is arranged again).
|
|
92
153
|
*/
|
|
93
|
-
arrange(turn: TurnScenario): void;
|
|
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>;
|
|
94
170
|
/**
|
|
95
171
|
* How many times the side effect behind `toolCallId` actually ran, as the
|
|
96
172
|
* subject's double observed it. The safety-critical observable; zero for an
|
|
@@ -77,12 +77,13 @@ import {
|
|
|
77
77
|
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
78
78
|
import {
|
|
79
79
|
ApprovalAction,
|
|
80
|
+
ExecutionControlSignal,
|
|
80
81
|
ExecutionPhase,
|
|
81
82
|
ToolCallStatus,
|
|
82
83
|
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
83
84
|
import { UpdateStatusResponseSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/io_pb";
|
|
84
85
|
import type { ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
85
|
-
import type
|
|
86
|
+
import { SessionSchema, type Session } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
|
|
86
87
|
import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
87
88
|
import type { AgentInstance } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/api_pb";
|
|
88
89
|
import type { StigmerClient } from "../client/stigmer-client.js";
|
|
@@ -107,11 +108,20 @@ export interface ExecutionRecordInput {
|
|
|
107
108
|
readonly session: Session;
|
|
108
109
|
readonly agentInstance: AgentInstance;
|
|
109
110
|
readonly agent: Agent;
|
|
111
|
+
/**
|
|
112
|
+
* What `UpdateStatus` answers for each FULL status write — the platform's
|
|
113
|
+
* STOP lever (`ExecutionControlSignal`), decided by the control plane per
|
|
114
|
+
* write and read by the harness stream loop on its mid-stream persist.
|
|
115
|
+
* Evaluated AFTER the write lands, on the snapshot just persisted, so a
|
|
116
|
+
* policy can key on the transcript ("stop once the first assistant message
|
|
117
|
+
* is in"). Defaults to UNSPECIFIED: keep going.
|
|
118
|
+
*/
|
|
119
|
+
readonly controlSignal?: (status: AgentExecutionStatus) => ExecutionControlSignal;
|
|
110
120
|
}
|
|
111
121
|
|
|
112
122
|
/**
|
|
113
123
|
* An in-memory stand-in for the server's execution row, with the TWO merge
|
|
114
|
-
* rules the activity depends on and no others:
|
|
124
|
+
* rules and the ONE control lever the activity depends on, and no others:
|
|
115
125
|
*
|
|
116
126
|
* - A status whose phase is UNSPECIFIED is a setup-progress report
|
|
117
127
|
* (`reportSetupProgress`): the server keeps `setup_progress` and leaves the
|
|
@@ -120,6 +130,10 @@ export interface ExecutionRecordInput {
|
|
|
120
130
|
* writer of the transcript; the server's own field-ownership merge — approval
|
|
121
131
|
* fields it owns — is exercised by the test SETTING `approvalAction` on the
|
|
122
132
|
* record between invocations, exactly as `SubmitApproval` would).
|
|
133
|
+
* - Every `UpdateStatus` answers a control signal ({@link ExecutionRecordInput.controlSignal});
|
|
134
|
+
* the server's STOP is the one instruction that travels back to the runner
|
|
135
|
+
* on this channel, and it is a server decision, so it is modelled here and
|
|
136
|
+
* not by the test hand-writing the client.
|
|
123
137
|
*
|
|
124
138
|
* Every persisted status is also kept in order (`persisted`) so a test can
|
|
125
139
|
* assert the phase sequence the activity wrote, independent of the final state.
|
|
@@ -129,11 +143,16 @@ export class ExecutionRecord {
|
|
|
129
143
|
readonly session: Session;
|
|
130
144
|
readonly agentInstance: AgentInstance;
|
|
131
145
|
readonly agent: Agent;
|
|
146
|
+
private readonly controlSignal: (status: AgentExecutionStatus) => ExecutionControlSignal;
|
|
132
147
|
/** Every `updateStatus` payload, in order, snapshotted at write time. */
|
|
133
148
|
readonly persisted: AgentExecutionStatus[] = [];
|
|
134
149
|
/** Setup-progress labels reported before the stream started, in order. */
|
|
135
150
|
readonly setupProgress: string[] = [];
|
|
136
|
-
/**
|
|
151
|
+
/**
|
|
152
|
+
* Every `updateSession` payload, in order (the `harness_state_id` write-back),
|
|
153
|
+
* snapshotted at write time: the activity re-binds the SAME session object on
|
|
154
|
+
* a fresh-agent recovery, so a live reference would show the second id twice.
|
|
155
|
+
*/
|
|
137
156
|
readonly sessionUpdates: Session[] = [];
|
|
138
157
|
|
|
139
158
|
constructor(input: ExecutionRecordInput) {
|
|
@@ -141,6 +160,7 @@ export class ExecutionRecord {
|
|
|
141
160
|
this.session = input.session;
|
|
142
161
|
this.agentInstance = input.agentInstance;
|
|
143
162
|
this.agent = input.agent;
|
|
163
|
+
this.controlSignal = input.controlSignal ?? (() => ExecutionControlSignal.UNSPECIFIED);
|
|
144
164
|
}
|
|
145
165
|
|
|
146
166
|
get executionId(): string {
|
|
@@ -201,16 +221,22 @@ export class ExecutionRecord {
|
|
|
201
221
|
return waiting.length;
|
|
202
222
|
}
|
|
203
223
|
|
|
204
|
-
|
|
224
|
+
/**
|
|
225
|
+
* Apply one `UpdateStatus` write and answer the control signal the server
|
|
226
|
+
* would. A setup-progress report never carries a signal (the runner ignores
|
|
227
|
+
* the response there; the server has nothing to say about a label).
|
|
228
|
+
*/
|
|
229
|
+
applyStatusUpdate(status: AgentExecutionStatus): ExecutionControlSignal {
|
|
205
230
|
const snapshot = clone(AgentExecutionStatusSchema, status);
|
|
206
231
|
this.persisted.push(snapshot);
|
|
207
232
|
if (snapshot.phase === ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED) {
|
|
208
233
|
if (snapshot.setupProgress?.currentPhase) {
|
|
209
234
|
this.setupProgress.push(snapshot.setupProgress.currentPhase);
|
|
210
235
|
}
|
|
211
|
-
return;
|
|
236
|
+
return ExecutionControlSignal.UNSPECIFIED;
|
|
212
237
|
}
|
|
213
238
|
this.execution.status = clone(AgentExecutionStatusSchema, snapshot);
|
|
239
|
+
return this.controlSignal(snapshot);
|
|
214
240
|
}
|
|
215
241
|
|
|
216
242
|
/**
|
|
@@ -228,12 +254,11 @@ export class ExecutionRecord {
|
|
|
228
254
|
getAgentInstance: vi.fn(async () => this.agentInstance),
|
|
229
255
|
getAgent: vi.fn(async () => this.agent),
|
|
230
256
|
updateStatus: vi.fn(async (_id: string, status: AgentExecutionStatus) => {
|
|
231
|
-
this.applyStatusUpdate(status);
|
|
232
257
|
// The activity reads only `.signal`; UNSPECIFIED means "keep going".
|
|
233
|
-
return create(UpdateStatusResponseSchema, {});
|
|
258
|
+
return create(UpdateStatusResponseSchema, { signal: this.applyStatusUpdate(status) });
|
|
234
259
|
}),
|
|
235
260
|
updateSession: vi.fn(async (session: Session) => {
|
|
236
|
-
this.sessionUpdates.push(session);
|
|
261
|
+
this.sessionUpdates.push(clone(SessionSchema, session));
|
|
237
262
|
return session;
|
|
238
263
|
}),
|
|
239
264
|
getExecutionContextByExecutionId: vi.fn(async () => {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model registry document a hermetic activity run reads, and the `fetch`
|
|
3
|
+
* stub that serves it and refuses everything else.
|
|
4
|
+
*
|
|
5
|
+
* The activity's only HTTP fetch is the model registry (`shared/model-
|
|
6
|
+
* registry.ts` for vision capability; the Cursor harness's `model-pricing-
|
|
7
|
+
* data.ts` for pricing). Both fail SOFT to defaults with a 60 s failure
|
|
8
|
+
* cache, which would make a golden depend on DEFAULT_PRICING and log a
|
|
9
|
+
* warning on every run, so a hermetic run stubs `fetch` to answer one
|
|
10
|
+
* registry document — and to THROW for any other URL, so a new network
|
|
11
|
+
* dependency on the activity path fails the run instead of leaking.
|
|
12
|
+
*
|
|
13
|
+
* One entry, `composer-2.5`, priced at round numbers so a golden's
|
|
14
|
+
* `estimatedCostUsd` is legible (600 000 input tokens = $0.60). The entry is
|
|
15
|
+
* a Cursor-harness model because that is what the registry lists; the
|
|
16
|
+
* document itself is the control plane's, read by every harness's runtime.
|
|
17
|
+
* `parsePricingTable` wants `harness: "cursor"` + `pricing`; `parseRegistry`
|
|
18
|
+
* wants `id` + `provider` and reads `capabilities.vision`.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { vi } from "vitest";
|
|
22
|
+
|
|
23
|
+
/** The pinned model of every hermetic run; in the registry document AND in the Cursor double's catalog. */
|
|
24
|
+
export const FIXTURE_MODEL = "composer-2.5";
|
|
25
|
+
|
|
26
|
+
export const REGISTRY_DOCUMENT = {
|
|
27
|
+
models: [
|
|
28
|
+
{
|
|
29
|
+
id: FIXTURE_MODEL,
|
|
30
|
+
displayName: "Composer 2.5 (hermetic fixture)",
|
|
31
|
+
provider: "cursor",
|
|
32
|
+
harness: "cursor",
|
|
33
|
+
costTier: "standard",
|
|
34
|
+
featured: true,
|
|
35
|
+
capabilities: { vision: true },
|
|
36
|
+
pricing: {
|
|
37
|
+
inputPricePerMillion: 1.0,
|
|
38
|
+
outputPricePerMillion: 4.0,
|
|
39
|
+
cacheWritePricePerMillion: 1.0,
|
|
40
|
+
cacheReadPricePerMillion: 0.1,
|
|
41
|
+
},
|
|
42
|
+
pricingVariants: {
|
|
43
|
+
fast: {
|
|
44
|
+
inputPricePerMillion: 3.0,
|
|
45
|
+
outputPricePerMillion: 12.0,
|
|
46
|
+
cacheWritePricePerMillion: 3.0,
|
|
47
|
+
cacheReadPricePerMillion: 0.3,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
} as const;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Stub `fetch` to answer the registry document and refuse everything else.
|
|
56
|
+
* Returns the URLs fetched, for the "no other network" assertion.
|
|
57
|
+
*/
|
|
58
|
+
export function stubRegistryFetch(): { readonly urls: string[]; restore(): void } {
|
|
59
|
+
const urls: string[] = [];
|
|
60
|
+
vi.stubGlobal(
|
|
61
|
+
"fetch",
|
|
62
|
+
vi.fn(async (input: string | URL | Request) => {
|
|
63
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
64
|
+
urls.push(url);
|
|
65
|
+
if (!url.includes("/model-registry")) {
|
|
66
|
+
throw new Error(`hermetic run attempted a non-registry network call: ${url}`);
|
|
67
|
+
}
|
|
68
|
+
return { ok: true, status: 200, json: async () => REGISTRY_DOCUMENT } as unknown as Response;
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
return { urls, restore: () => vi.unstubAllGlobals() };
|
|
72
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The module specifiers a TypeScript file names, read off its syntax tree —
|
|
3
|
+
* the primitive behind the runner's import fences.
|
|
4
|
+
*
|
|
5
|
+
* Two fences stand on it: `src/harness/` and production `src/shared/` never
|
|
6
|
+
* import `src/activities/` (`harness/__tests__/import-direction.test.ts`,
|
|
7
|
+
* the rule the turn runtime is written under), and the Cursor adapter never
|
|
8
|
+
* imports `@temporalio/*` (`execute-cursor/__tests__/adapter-is-temporal-
|
|
9
|
+
* free.test.ts`, what lets the contract kit run it outside an activity).
|
|
10
|
+
*
|
|
11
|
+
* A syntax tree, not a regex, so a path quoted in a header comment cannot
|
|
12
|
+
* trip a fence, and no import form can slip past one: static, type-only,
|
|
13
|
+
* side-effect, re-export and dynamic `import()` are all module specifiers to
|
|
14
|
+
* the parser. A dynamic import whose argument is not a string literal names
|
|
15
|
+
* nothing and is ignored (no fence has a literal to match).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
19
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
20
|
+
import ts from "typescript";
|
|
21
|
+
|
|
22
|
+
/** Every module specifier a file names, in source order, whatever the form. */
|
|
23
|
+
export function moduleSpecifiers(fileName: string, source: string): string[] {
|
|
24
|
+
const sourceFile = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, /* setParentNodes */ false);
|
|
25
|
+
const specifiers: string[] = [];
|
|
26
|
+
const visit = (node: ts.Node): void => {
|
|
27
|
+
if (
|
|
28
|
+
(ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) &&
|
|
29
|
+
node.moduleSpecifier !== undefined &&
|
|
30
|
+
ts.isStringLiteral(node.moduleSpecifier)
|
|
31
|
+
) {
|
|
32
|
+
specifiers.push(node.moduleSpecifier.text);
|
|
33
|
+
} else if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
34
|
+
const [argument] = node.arguments;
|
|
35
|
+
if (argument !== undefined && ts.isStringLiteralLike(argument)) {
|
|
36
|
+
specifiers.push(argument.text);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
ts.forEachChild(node, visit);
|
|
40
|
+
};
|
|
41
|
+
visit(sourceFile);
|
|
42
|
+
return specifiers;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The relative specifiers in `source` that resolve to a path inside
|
|
47
|
+
* `forbiddenRoot`. Package specifiers are ignored: no alias maps a package
|
|
48
|
+
* name onto a source directory.
|
|
49
|
+
*/
|
|
50
|
+
export function forbiddenImports(filePath: string, source: string, forbiddenRoot: string): string[] {
|
|
51
|
+
return moduleSpecifiers(filePath, source).filter((specifier) => {
|
|
52
|
+
if (!specifier.startsWith(".")) return false;
|
|
53
|
+
const target = resolve(dirname(filePath), specifier);
|
|
54
|
+
return target === forbiddenRoot || target.startsWith(forbiddenRoot + sep);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The package specifiers in `source` whose name is `pkg` or starts with `${pkg}/`. */
|
|
59
|
+
export function packageImports(filePath: string, source: string, pkg: string): string[] {
|
|
60
|
+
return moduleSpecifiers(filePath, source).filter((specifier) => specifier === pkg || specifier.startsWith(`${pkg}/`));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Every `.ts` file under `dir`, sorted, skipping the named directories at any depth. */
|
|
64
|
+
export function typeScriptFilesUnder(dir: string, skipDirectories: ReadonlySet<string>): string[] {
|
|
65
|
+
const files: string[] = [];
|
|
66
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
67
|
+
const path = join(dir, entry.name);
|
|
68
|
+
if (entry.isDirectory()) {
|
|
69
|
+
if (!skipDirectories.has(entry.name)) {
|
|
70
|
+
files.push(...typeScriptFilesUnder(path, skipDirectories));
|
|
71
|
+
}
|
|
72
|
+
} else if (entry.name.endsWith(".ts")) {
|
|
73
|
+
files.push(path);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return files.sort();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Read a file for a sweep; one place so every fence reads the same bytes the compiler does. */
|
|
80
|
+
export function readSource(filePath: string): string {
|
|
81
|
+
return readFileSync(filePath, "utf-8");
|
|
82
|
+
}
|