@stigmer/runner 3.14.0 → 3.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-fingerprint +1 -1
- package/dist/__test-utils__/execution-record-fixture.d.ts +47 -0
- package/dist/__test-utils__/execution-record-fixture.js +68 -0
- package/dist/__test-utils__/execution-record-fixture.js.map +1 -0
- package/dist/__test-utils__/harness-contract/types.d.ts +166 -0
- package/dist/__test-utils__/harness-contract/types.js +49 -0
- package/dist/__test-utils__/harness-contract/types.js.map +1 -0
- package/dist/__test-utils__/hermetic-activity.d.ts +268 -0
- package/dist/__test-utils__/hermetic-activity.js +385 -0
- package/dist/__test-utils__/hermetic-activity.js.map +1 -0
- package/dist/__test-utils__/mock-client.d.ts +13 -0
- package/dist/__test-utils__/mock-client.js +45 -0
- package/dist/__test-utils__/mock-client.js.map +1 -0
- package/dist/__test-utils__/mock-workspace.d.ts +15 -0
- package/dist/__test-utils__/mock-workspace.js +21 -0
- package/dist/__test-utils__/mock-workspace.js.map +1 -0
- package/dist/__test-utils__/model-registry-fixture.d.ts +56 -0
- package/dist/__test-utils__/model-registry-fixture.js +66 -0
- package/dist/__test-utils__/model-registry-fixture.js.map +1 -0
- package/dist/__test-utils__/turn-input-fixture.d.ts +60 -0
- package/dist/__test-utils__/turn-input-fixture.js +118 -0
- package/dist/__test-utils__/turn-input-fixture.js.map +1 -0
- package/dist/activities/execute-cursor/__test-utils__/contract-subject.d.ts +107 -0
- package/dist/activities/execute-cursor/__test-utils__/contract-subject.js +344 -0
- package/dist/activities/execute-cursor/__test-utils__/contract-subject.js.map +1 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +20 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +58 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +2 -32
- package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.d.ts +148 -0
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +257 -0
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -0
- package/dist/activities/execute-cursor/__test-utils__/scripted-agent.d.ts +183 -0
- package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js +262 -0
- package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js.map +1 -0
- package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.d.ts +138 -0
- package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js +191 -0
- package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js.map +1 -0
- package/dist/activities/execute-cursor/adapter.d.ts +55 -0
- package/dist/activities/execute-cursor/adapter.js +119 -0
- package/dist/activities/execute-cursor/adapter.js.map +1 -0
- package/dist/activities/execute-cursor/approval-state.d.ts +2 -2
- package/dist/activities/execute-cursor/approval-state.js.map +1 -1
- package/dist/activities/execute-cursor/capture-flow.d.ts +15 -30
- package/dist/activities/execute-cursor/capture-flow.js +17 -26
- package/dist/activities/execute-cursor/capture-flow.js.map +1 -1
- package/dist/activities/execute-cursor/cursor-capabilities.d.ts +23 -0
- package/dist/activities/execute-cursor/cursor-capabilities.js +33 -0
- package/dist/activities/execute-cursor/cursor-capabilities.js.map +1 -0
- package/dist/activities/execute-cursor/cursor-mcp-config.d.ts +2 -2
- package/dist/activities/execute-cursor/cursor-mcp-config.js.map +1 -1
- package/dist/activities/execute-cursor/fetch-interceptor.d.ts +15 -25
- package/dist/activities/execute-cursor/fetch-interceptor.js +39 -33
- package/dist/activities/execute-cursor/fetch-interceptor.js.map +1 -1
- package/dist/activities/execute-cursor/http2-interceptor.d.ts +21 -17
- package/dist/activities/execute-cursor/http2-interceptor.js +38 -31
- package/dist/activities/execute-cursor/http2-interceptor.js.map +1 -1
- package/dist/activities/execute-cursor/message-translator.d.ts +28 -19
- package/dist/activities/execute-cursor/message-translator.js +76 -27
- package/dist/activities/execute-cursor/message-translator.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +180 -11
- package/dist/activities/execute-cursor/prompt-builder.js +171 -0
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/rejection-capture.d.ts +3 -6
- package/dist/activities/execute-cursor/rejection-capture.js +5 -7
- package/dist/activities/execute-cursor/rejection-capture.js.map +1 -1
- package/dist/activities/execute-cursor/session-lifecycle.d.ts +1 -4
- package/dist/activities/execute-cursor/session-lifecycle.js.map +1 -1
- package/dist/activities/execute-cursor/turn-boundary.d.ts +1 -1
- package/dist/activities/execute-cursor/turn-settle.d.ts +49 -0
- package/dist/activities/execute-cursor/turn-settle.js +507 -0
- package/dist/activities/execute-cursor/turn-settle.js.map +1 -0
- package/dist/activities/execute-cursor/turn-setup.d.ts +173 -0
- package/dist/activities/execute-cursor/turn-setup.js +496 -0
- package/dist/activities/execute-cursor/turn-setup.js.map +1 -0
- package/dist/activities/execute-cursor/turn-stream.d.ts +41 -71
- package/dist/activities/execute-cursor/turn-stream.js +70 -135
- package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
- package/dist/activities/execute-cursor/turn.d.ts +43 -0
- package/dist/activities/execute-cursor/turn.js +222 -0
- package/dist/activities/execute-cursor/turn.js.map +1 -0
- package/dist/activities/execute-cursor/usage-pricing.d.ts +38 -0
- package/dist/activities/execute-cursor/usage-pricing.js +50 -0
- package/dist/activities/execute-cursor/usage-pricing.js.map +1 -0
- package/dist/activities/execute-deep-agent/approval-file-change.d.ts +1 -1
- package/dist/activities/execute-deep-agent/approval-file-change.js +1 -1
- package/dist/activities/execute-deep-agent/environment.js +1 -1
- package/dist/activities/execute-deep-agent/environment.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.js +3 -2
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/activities/hydrate-workflow-execution.js +1 -1
- package/dist/activities/hydrate-workflow-execution.js.map +1 -1
- package/dist/config.d.ts +15 -0
- package/dist/config.js.map +1 -1
- package/dist/harness/capabilities.d.ts +87 -0
- package/dist/harness/capabilities.js +37 -0
- package/dist/harness/capabilities.js.map +1 -0
- package/dist/harness/persist-chokepoint.d.ts +74 -0
- package/dist/harness/persist-chokepoint.js +88 -0
- package/dist/harness/persist-chokepoint.js.map +1 -0
- package/dist/harness/registry.d.ts +106 -0
- package/dist/harness/registry.js +168 -0
- package/dist/harness/registry.js.map +1 -0
- package/dist/harness/run-turn.d.ts +53 -0
- package/dist/harness/run-turn.js +528 -0
- package/dist/harness/run-turn.js.map +1 -0
- package/dist/harness/stop-controller.d.ts +50 -0
- package/dist/harness/stop-controller.js +62 -0
- package/dist/harness/stop-controller.js.map +1 -0
- package/dist/harness/terminal-table.d.ts +141 -0
- package/dist/harness/terminal-table.js +239 -0
- package/dist/harness/terminal-table.js.map +1 -0
- package/dist/harness/turn-context.d.ts +523 -0
- package/dist/harness/turn-context.js +739 -0
- package/dist/harness/turn-context.js.map +1 -0
- package/dist/harness/types.d.ts +448 -0
- package/dist/harness/types.js +64 -0
- package/dist/harness/types.js.map +1 -0
- package/dist/harness/usage-accumulator.d.ts +68 -0
- package/dist/harness/usage-accumulator.js +101 -0
- package/dist/harness/usage-accumulator.js.map +1 -0
- package/dist/harness-adapters.d.ts +27 -0
- package/dist/harness-adapters.js +28 -0
- package/dist/harness-adapters.js.map +1 -0
- package/dist/main.js.map +1 -1
- package/dist/runner-manager.js +64 -48
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.d.ts +5 -4
- package/dist/runner.js +65 -64
- package/dist/runner.js.map +1 -1
- package/dist/shared/approval-policy.d.ts +1 -1
- package/dist/shared/approval-policy.js.map +1 -1
- package/dist/{activities/execute-cursor → shared}/attachment-resolver.d.ts +2 -2
- package/dist/{activities/execute-cursor → shared}/attachment-resolver.js +5 -5
- package/dist/shared/attachment-resolver.js.map +1 -0
- package/dist/{activities/execute-cursor → shared}/blueprint-resolver.d.ts +7 -5
- package/dist/{activities/execute-cursor → shared}/blueprint-resolver.js +3 -63
- package/dist/shared/blueprint-resolver.js.map +1 -0
- package/dist/shared/channel-attachment.d.ts +1 -1
- package/dist/shared/channel-attachment.js.map +1 -1
- package/dist/{activities/execute-cursor → shared}/cost-guard.d.ts +5 -1
- package/dist/{activities/execute-cursor → shared}/cost-guard.js +5 -1
- package/dist/shared/cost-guard.js.map +1 -0
- package/dist/{activities/execute-cursor → shared}/env-resolver.d.ts +1 -1
- package/dist/shared/env-resolver.js.map +1 -0
- package/dist/{activities/execute-cursor → shared}/exact-apply.d.ts +1 -1
- package/dist/{activities/execute-cursor → shared}/exact-apply.js +6 -6
- package/dist/shared/exact-apply.js.map +1 -0
- package/dist/shared/execution-context.d.ts +35 -0
- package/dist/shared/execution-context.js +38 -0
- package/dist/shared/execution-context.js.map +1 -0
- package/dist/{activities/execute-cursor → shared}/extract-structured-output.d.ts +5 -2
- package/dist/{activities/execute-cursor → shared}/extract-structured-output.js +9 -6
- package/dist/shared/extract-structured-output.js.map +1 -0
- package/dist/shared/filereview/capture.d.ts +1 -1
- package/dist/shared/filereview/capture.js +1 -1
- package/dist/shared/heartbeat.d.ts +13 -11
- package/dist/shared/heartbeat.js +14 -29
- package/dist/shared/heartbeat.js.map +1 -1
- package/dist/{activities/execute-cursor → shared}/persist-decision.d.ts +5 -2
- package/dist/{activities/execute-cursor → shared}/persist-decision.js +4 -1
- package/dist/shared/persist-decision.js.map +1 -0
- package/dist/{activities/execute-cursor → shared}/skill-resolver.d.ts +16 -3
- package/dist/{activities/execute-cursor → shared}/skill-resolver.js +12 -4
- package/dist/shared/skill-resolver.js.map +1 -0
- package/dist/shared/subagent-rows.d.ts +21 -0
- package/dist/shared/subagent-rows.js +34 -0
- package/dist/shared/subagent-rows.js.map +1 -0
- package/dist/shared/worker-shutdown.d.ts +43 -16
- package/dist/shared/worker-shutdown.js +28 -13
- package/dist/shared/worker-shutdown.js.map +1 -1
- package/dist/shared/workspace/platform-dir.d.ts +1 -1
- package/dist/shared/workspace/platform-dir.js +1 -1
- package/dist/shared/workspace/session-provision.d.ts +54 -0
- package/dist/{activities/execute-cursor/workspace-provision.js → shared/workspace/session-provision.js} +15 -12
- package/dist/shared/workspace/session-provision.js.map +1 -0
- package/package.json +4 -4
- package/src/__test-utils__/__tests__/harness-contract-self-check.test.ts +325 -0
- package/src/__test-utils__/config-fixture.ts +63 -0
- package/src/__test-utils__/execution-record-fixture.ts +104 -0
- package/src/__test-utils__/harness-boot-order-child.ts +58 -0
- package/src/__test-utils__/harness-contract/contract.ts +581 -0
- package/src/__test-utils__/harness-contract/recording-sink.ts +107 -0
- package/src/__test-utils__/harness-contract/runtime-contract.ts +666 -0
- package/src/__test-utils__/harness-contract/scripted-adapter.ts +349 -0
- package/src/__test-utils__/harness-contract/types.ts +176 -0
- package/src/__test-utils__/hermetic-activity.ts +502 -0
- package/src/__test-utils__/model-registry-fixture.ts +72 -0
- package/src/__test-utils__/module-specifiers.ts +82 -0
- package/src/__test-utils__/proto-helpers.ts +25 -0
- package/src/__test-utils__/turn-input-fixture.ts +155 -0
- package/src/__tests__/harness-boot-order.test.ts +77 -0
- package/src/__tests__/harness-contract.test.ts +27 -0
- package/src/__tests__/runner-manager.test.ts +18 -0
- package/src/activities/execute-cursor/__test-utils__/__tests__/scripted-double.test.ts +102 -0
- package/src/activities/execute-cursor/__test-utils__/contract-subject.ts +396 -0
- package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +66 -0
- package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +2 -43
- package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +342 -0
- package/src/activities/execute-cursor/__test-utils__/scripted-agent.ts +368 -0
- package/src/activities/execute-cursor/__test-utils__/scripted-sdk.ts +254 -0
- package/src/activities/execute-cursor/__tests__/adapter-is-temporal-free.test.ts +45 -0
- package/src/activities/execute-cursor/__tests__/approval-decisions-agree.test.ts +90 -0
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +7 -8
- package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +28 -1
- package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/fetch-interceptor.test.ts +64 -6
- package/src/activities/execute-cursor/__tests__/hermetic/deny-and-retry.test.ts +228 -0
- package/src/activities/execute-cursor/__tests__/hermetic/file-review-capture.test.ts +180 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/cost-cap.status.json +53 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/deny-and-retry.turn1.status.json +55 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/deny-and-retry.turn2.status.json +77 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/file-review-capture.status.json +126 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/pause.status.json +40 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/plain-turn.status.json +48 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/platform-stop.status.json +56 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/recovery-fresh-agent.status.json +53 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/recovery-transport-timeout.status.json +48 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/resolution-error.status.json +18 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/run-cancelled.status.json +36 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/run-error-non-retryable.status.json +37 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/sdk-error-at-create.status.json +38 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/stall.status.json +59 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/tool-call.status.json +68 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/unattributed-hook-block.status.json +79 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/worker-shutdown.status.json +42 -0
- package/src/activities/execute-cursor/__tests__/hermetic/harness-contract.test.ts +170 -0
- package/src/activities/execute-cursor/__tests__/hermetic/pause-vs-shutdown.test.ts +205 -0
- package/src/activities/execute-cursor/__tests__/hermetic/plain-turn.test.ts +171 -0
- package/src/activities/execute-cursor/__tests__/hermetic/recovery-fresh-agent.test.ts +156 -0
- package/src/activities/execute-cursor/__tests__/hermetic/run-wait-arms.test.ts +286 -0
- package/src/activities/execute-cursor/__tests__/hermetic/stream-self-stop-arms.test.ts +332 -0
- package/src/activities/execute-cursor/__tests__/hermetic/thrown-error-arms.test.ts +193 -0
- package/src/activities/execute-cursor/__tests__/hermetic/tool-call.test.ts +137 -0
- package/src/activities/execute-cursor/__tests__/hermetic/unattributed-hook-block.test.ts +185 -0
- package/src/activities/execute-cursor/__tests__/hermetic/workspace-lock-timeout.test.ts +151 -0
- package/src/activities/execute-cursor/__tests__/http2-interceptor.test.ts +65 -35
- package/src/activities/execute-cursor/__tests__/message-translator.test.ts +0 -40
- package/src/activities/execute-cursor/__tests__/same-identity-reproposal.test.ts +171 -0
- package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +118 -243
- package/src/activities/execute-cursor/__tests__/usage-pricing.test.ts +84 -0
- package/src/activities/execute-cursor/adapter.ts +138 -0
- package/src/activities/execute-cursor/approval-state.ts +2 -2
- package/src/activities/execute-cursor/capture-flow.ts +18 -41
- package/src/activities/execute-cursor/cursor-capabilities.ts +35 -0
- package/src/activities/execute-cursor/cursor-mcp-config.ts +2 -2
- package/src/activities/execute-cursor/fetch-interceptor.ts +48 -45
- package/src/activities/execute-cursor/http2-interceptor.ts +47 -34
- package/src/activities/execute-cursor/message-translator.ts +90 -45
- package/src/activities/execute-cursor/prompt-builder.ts +327 -12
- package/src/activities/execute-cursor/rejection-capture.ts +5 -12
- package/src/activities/execute-cursor/session-lifecycle.ts +1 -5
- package/src/activities/execute-cursor/turn-boundary.ts +1 -1
- package/src/activities/execute-cursor/turn-settle.ts +601 -0
- package/src/activities/execute-cursor/turn-setup.ts +645 -0
- package/src/activities/execute-cursor/turn-stream.ts +84 -210
- package/src/activities/execute-cursor/turn.ts +240 -0
- package/src/activities/execute-cursor/usage-pricing.ts +67 -0
- package/src/activities/execute-deep-agent/approval-file-change.ts +1 -1
- package/src/activities/execute-deep-agent/environment.ts +1 -1
- package/src/activities/execute-deep-agent/setup.ts +3 -2
- package/src/activities/hydrate-workflow-execution.ts +1 -1
- package/src/config.ts +13 -0
- package/src/harness/__tests__/import-direction.test.ts +168 -0
- package/src/harness/__tests__/persist-chokepoint.test.ts +146 -0
- package/src/harness/__tests__/registry.test.ts +168 -0
- package/src/harness/__tests__/run-turn.test.ts +165 -0
- package/src/harness/__tests__/stop-controller.test.ts +49 -0
- package/src/harness/__tests__/turn-context.test.ts +178 -0
- package/src/harness/__tests__/usage-accumulator.test.ts +83 -0
- package/src/harness/capabilities.ts +92 -0
- package/src/harness/persist-chokepoint.ts +112 -0
- package/src/harness/registry.ts +197 -0
- package/src/harness/run-turn.ts +618 -0
- package/src/harness/stop-controller.ts +83 -0
- package/src/harness/terminal-table.ts +276 -0
- package/src/harness/turn-context.ts +1028 -0
- package/src/harness/types.ts +473 -0
- package/src/harness/usage-accumulator.ts +122 -0
- package/src/harness-adapters.ts +30 -0
- package/src/main.ts +1 -1
- package/src/runner-manager.ts +68 -61
- package/src/runner.ts +67 -78
- package/src/shared/__tests__/attachment-naming.test.ts +1 -1
- package/src/{activities/execute-cursor → shared}/__tests__/attachment-resolver.test.ts +6 -4
- package/src/{activities/execute-cursor → shared}/__tests__/cost-guard.test.ts +2 -1
- package/src/{activities/execute-cursor → shared}/__tests__/exact-apply.test.ts +3 -3
- package/src/{activities/execute-cursor → shared}/__tests__/extract-structured-output.test.ts +8 -8
- package/src/{activities/execute-cursor → shared}/__tests__/persist-decision.test.ts +4 -2
- package/src/{activities/execute-cursor → shared}/__tests__/skill-resolver.test.ts +1 -1
- package/src/shared/__tests__/subagent-rows.test.ts +34 -0
- package/src/shared/__tests__/worker-shutdown.test.ts +44 -39
- package/src/shared/approval-policy.ts +1 -1
- package/src/{activities/execute-cursor → shared}/attachment-resolver.ts +6 -6
- package/src/{activities/execute-cursor → shared}/blueprint-resolver.ts +7 -78
- package/src/shared/channel-attachment.ts +1 -1
- package/src/{activities/execute-cursor → shared}/cost-guard.ts +5 -1
- package/src/{activities/execute-cursor → shared}/env-resolver.ts +1 -1
- package/src/{activities/execute-cursor → shared}/exact-apply.ts +7 -7
- package/src/shared/execution-context.ts +45 -0
- package/src/{activities/execute-cursor → shared}/extract-structured-output.ts +10 -7
- package/src/shared/filereview/capture.ts +1 -1
- package/src/shared/heartbeat.ts +13 -33
- package/src/{activities/execute-cursor → shared}/persist-decision.ts +5 -2
- package/src/{activities/execute-cursor → shared}/skill-resolver.ts +20 -6
- package/src/shared/subagent-rows.ts +39 -0
- package/src/shared/worker-shutdown.ts +55 -25
- package/src/{activities/execute-cursor/__tests__/workspace-provision.test.ts → shared/workspace/__tests__/session-provision.test.ts} +25 -28
- package/src/shared/workspace/platform-dir.ts +1 -1
- package/src/{activities/execute-cursor/workspace-provision.ts → shared/workspace/session-provision.ts} +29 -16
- package/dist/activities/execute-cursor/attachment-resolver.js.map +0 -1
- package/dist/activities/execute-cursor/blueprint-resolver.js.map +0 -1
- package/dist/activities/execute-cursor/cost-guard.js.map +0 -1
- package/dist/activities/execute-cursor/env-resolver.js.map +0 -1
- package/dist/activities/execute-cursor/exact-apply.js.map +0 -1
- package/dist/activities/execute-cursor/extract-structured-output.js.map +0 -1
- package/dist/activities/execute-cursor/index.d.ts +0 -209
- package/dist/activities/execute-cursor/index.js +0 -2345
- package/dist/activities/execute-cursor/index.js.map +0 -1
- package/dist/activities/execute-cursor/persist-decision.js.map +0 -1
- package/dist/activities/execute-cursor/skill-resolver.js.map +0 -1
- package/dist/activities/execute-cursor/usage-accumulator.d.ts +0 -89
- package/dist/activities/execute-cursor/usage-accumulator.js +0 -121
- package/dist/activities/execute-cursor/usage-accumulator.js.map +0 -1
- package/dist/activities/execute-cursor/workspace-provision.d.ts +0 -42
- package/dist/activities/execute-cursor/workspace-provision.js.map +0 -1
- package/src/activities/execute-cursor/__tests__/usage-accumulator.test.ts +0 -164
- package/src/activities/execute-cursor/index.ts +0 -2876
- package/src/activities/execute-cursor/usage-accumulator.ts +0 -169
- /package/dist/{activities/execute-cursor → shared}/env-resolver.js +0 -0
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The turn runtime — one agent turn as a Temporal activity, written once for
|
|
3
|
+
* every harness, with the engine behind the adapter contract (`types.ts`).
|
|
4
|
+
*
|
|
5
|
+
* What runs here and nowhere else: the activity's identity and its
|
|
6
|
+
* execution-scoped context; the status and the single persist chokepoint;
|
|
7
|
+
* the periodic heartbeat; the stop controller and its four producers (the
|
|
8
|
+
* stall watchdog, the cost cap, the platform STOP, Temporal's cancellation);
|
|
9
|
+
* the resolution phases (`turn-context.ts`) composed into the `TurnInput`;
|
|
10
|
+
* the sink the adapter is handed; the terminal table (`terminal-table.ts`)
|
|
11
|
+
* with its throw-vs-return rule, applied by `settleWith`, the one writer of
|
|
12
|
+
* a terminal; the completion epilogue (final text,
|
|
13
|
+
* structured output, plan artifact, write-back); the finally that releases
|
|
14
|
+
* the workspace lock last. The adapter owns its SDK slice inside `runTurn`
|
|
15
|
+
* and its own teardown inside its own `finally`, which runs BEFORE this
|
|
16
|
+
* module's (the gate is torn down before the lock is released, as it always
|
|
17
|
+
* was).
|
|
18
|
+
*
|
|
19
|
+
* Shape: one activity per registry row (`createHarnessActivities`), each a
|
|
20
|
+
* closure over the adapter, the activity's byte-pinned name and one shared
|
|
21
|
+
* `StigmerClient`; `runTurnActivity` is the body. Every `console` line here
|
|
22
|
+
* names the activity (`ExecuteCursor started: …`) so the log reads as it did
|
|
23
|
+
* before the extraction, and never a harness literal — the harness is a
|
|
24
|
+
* registry row, not a branch.
|
|
25
|
+
*
|
|
26
|
+
* Extracted from `activities/execute-cursor/index.ts` `executeCursorInner`
|
|
27
|
+
* (S2 M3; the milestone log in stigmer-cloud
|
|
28
|
+
* `_projects/2026-09/20260911.03.sp.turn-runtime-extraction/` has every
|
|
29
|
+
* ruling). Its twelve resolution phases came out at M2; what came out here is
|
|
30
|
+
* everything between them and after the engine: the seventeen hermetic
|
|
31
|
+
* goldens under `execute-cursor/__tests__/hermetic/` pin the result byte
|
|
32
|
+
* for byte, and `__tests__/run-turn.test.ts` proves the table against the
|
|
33
|
+
* scripted fake without any engine at all.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { setMaxListeners } from "node:events";
|
|
37
|
+
import { heartbeat, CancelledFailure, Context } from "@temporalio/activity";
|
|
38
|
+
import { create, type JsonObject } from "@bufbuild/protobuf";
|
|
39
|
+
import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
40
|
+
import { ExecutionPhase, InteractionMode, MessageType } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
41
|
+
|
|
42
|
+
import type { Config } from "../config.js";
|
|
43
|
+
import type { StigmerClient } from "../client/stigmer-client.js";
|
|
44
|
+
import type { NormalizedActivityInput } from "../shared/activity-input.js";
|
|
45
|
+
import { loadArtifactStorageConfig, resolveUsableArtifactStorage } from "../shared/artifact-storage.js";
|
|
46
|
+
import { TimingRecorder } from "../shared/cold-start-timing.js";
|
|
47
|
+
import { costCapExceeded } from "../shared/cost-guard.js";
|
|
48
|
+
import { runWithExecutionContext } from "../shared/execution-context.js";
|
|
49
|
+
import { startHeartbeat } from "../shared/heartbeat.js";
|
|
50
|
+
import { describeExecutionError } from "../shared/model-error.js";
|
|
51
|
+
import { publishPlanArtifact } from "../shared/plan-artifact.js";
|
|
52
|
+
import { StallTimeoutError, startStallWatchdog, type StallWatchdog } from "../shared/stall-watchdog.js";
|
|
53
|
+
import { reportSetupProgress, slimStatus, utcTimestamp } from "../shared/status.js";
|
|
54
|
+
import { cancelInProgressSubAgentProtos } from "../shared/subagent-rows.js";
|
|
55
|
+
import { removeStigmerSymlink } from "../shared/workspace/stigmer-link.js";
|
|
56
|
+
import {
|
|
57
|
+
cancellationReasonOf,
|
|
58
|
+
classifyTurnInterruption,
|
|
59
|
+
getShutdownSignalForQueue,
|
|
60
|
+
type TurnInterruption,
|
|
61
|
+
} from "../shared/worker-shutdown.js";
|
|
62
|
+
import { PersistChokepoint } from "./persist-chokepoint.js";
|
|
63
|
+
import { StopController } from "./stop-controller.js";
|
|
64
|
+
import {
|
|
65
|
+
applyTerminalArm,
|
|
66
|
+
costCapArm,
|
|
67
|
+
failedArm,
|
|
68
|
+
fileReviewResolvedArm,
|
|
69
|
+
infrastructureCancelArm,
|
|
70
|
+
pauseArm,
|
|
71
|
+
platformStopArm,
|
|
72
|
+
rejectedByUserArm,
|
|
73
|
+
stallArm,
|
|
74
|
+
TERMINAL_COPY,
|
|
75
|
+
unexpectedErrorArm,
|
|
76
|
+
workerShutdownArm,
|
|
77
|
+
workspaceLockTimeoutArm,
|
|
78
|
+
type TerminalArm,
|
|
79
|
+
} from "./terminal-table.js";
|
|
80
|
+
import { resolveTurnContext, type ResolutionDeps, type TurnFrame, type TurnSettlement } from "./turn-context.js";
|
|
81
|
+
import type { HarnessAdapter, TurnInput, TurnSink } from "./types.js";
|
|
82
|
+
import { UsageAccumulator } from "./usage-accumulator.js";
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Interval of the activity-wide periodic heartbeat. Started before any phase
|
|
86
|
+
* runs: setup phases make network calls (blueprint resolution, workspace
|
|
87
|
+
* clone, MCP backfill, engine create) that can stall, and a pulse only
|
|
88
|
+
* between them leaves every individual call uncovered — the production
|
|
89
|
+
* stale-proxy incident hung inside `Agent.create` with zero heartbeats and
|
|
90
|
+
* surfaced as an opaque five-minute Temporal timeout. The details name the
|
|
91
|
+
* current step so a stall is attributed. Safe ONLY because every call below
|
|
92
|
+
* is itself bounded (the resolve timeout, the stall watchdog): an unbounded
|
|
93
|
+
* hang under a live heartbeat would keep a dead activity alive forever.
|
|
94
|
+
*/
|
|
95
|
+
const PERIODIC_HEARTBEAT_MS = 30_000;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Vendor SDKs register abort listeners on the cancellation signal for each
|
|
99
|
+
* concurrent tool call (fetch, MCP, shell). With 10+ parallel tools, Node's
|
|
100
|
+
* default limit of 10 triggers `MaxListenersExceededWarning` — a diagnostic
|
|
101
|
+
* warning, not a functional error, but it pollutes logs and creates false
|
|
102
|
+
* alarm fatigue. 25 covers observed peaks (~12 concurrent tools + the
|
|
103
|
+
* heartbeat + the shutdown signal + SDK internals) with headroom.
|
|
104
|
+
*/
|
|
105
|
+
const CANCELLATION_SIGNAL_MAX_LISTENERS = 25;
|
|
106
|
+
|
|
107
|
+
/** What every activity of the registry shares: the adapter it drives, its wire name, and the one client. */
|
|
108
|
+
export interface TurnRuntimeDeps {
|
|
109
|
+
readonly adapter: HarnessAdapter;
|
|
110
|
+
readonly activityName: string;
|
|
111
|
+
readonly client: StigmerClient;
|
|
112
|
+
readonly config: Config;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Run one turn: the activity body behind every harness's registry row. Sets
|
|
117
|
+
* the execution-scoped context for the WHOLE activity (the interceptors stamp
|
|
118
|
+
* every proxy-bound request from it, and much of that traffic is the
|
|
119
|
+
* runtime's own: offload, attachments, the epilogue's extraction) and runs
|
|
120
|
+
* the turn inside it.
|
|
121
|
+
*/
|
|
122
|
+
export async function runTurnActivity(deps: TurnRuntimeDeps, input: NormalizedActivityInput): Promise<unknown> {
|
|
123
|
+
const { executionId, threadId, turnSeq } = input;
|
|
124
|
+
console.log(
|
|
125
|
+
`${deps.activityName} started: execution=${executionId}, threadId=${threadId || "(new)"}, turnSeq=${turnSeq}`,
|
|
126
|
+
);
|
|
127
|
+
return runWithExecutionContext(executionId, () => runTurn(deps, input));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Where the activity ends up once the table has been applied: return the
|
|
132
|
+
* slim status, or throw after the finally. The thrown value is always a
|
|
133
|
+
* `CancelledFailure` — the table's rule ("THROW when the workflow must see
|
|
134
|
+
* the activity as cancelled", `terminal-table.ts`) as a type.
|
|
135
|
+
*/
|
|
136
|
+
type Settled = { readonly kind: "return"; readonly value: unknown } | { readonly kind: "throw"; readonly error: CancelledFailure };
|
|
137
|
+
|
|
138
|
+
async function runTurn(deps: TurnRuntimeDeps, input: NormalizedActivityInput): Promise<unknown> {
|
|
139
|
+
const { adapter, activityName, client, config } = deps;
|
|
140
|
+
const { executionId } = input;
|
|
141
|
+
|
|
142
|
+
const status = create(AgentExecutionStatusSchema, {
|
|
143
|
+
phase: ExecutionPhase.EXECUTION_IN_PROGRESS,
|
|
144
|
+
startedAt: utcTimestamp(),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Cold-start timeline of this turn's setup: one mark after each phase, the
|
|
148
|
+
// adapter's own segments marked through the sink, emitted by the adapter
|
|
149
|
+
// once its engine is resolved (an early return skips it — partial setups
|
|
150
|
+
// are not comparable cold-start samples).
|
|
151
|
+
const setupTiming = new TimingRecorder();
|
|
152
|
+
|
|
153
|
+
// Artifact storage for offloading oversized tool outputs out of the
|
|
154
|
+
// persisted status and for publishing the plan artifact. Resolved once so
|
|
155
|
+
// every persist sees it. Best-effort: `undefined`, never a throw, when no
|
|
156
|
+
// substrate works; an absent store disables offload (the aggregate size cap
|
|
157
|
+
// still applies) and flips capture mode off (deny-gate fallback).
|
|
158
|
+
const artifactStorage = await resolveUsableArtifactStorage(loadArtifactStorageConfig(config), { executionId });
|
|
159
|
+
setupTiming.mark("resolve_artifact_storage");
|
|
160
|
+
|
|
161
|
+
const frame: TurnFrame = { primaryDir: undefined, releaseWorkspaceLock: undefined, writeback: null };
|
|
162
|
+
const stop = new StopController();
|
|
163
|
+
let usage: UsageAccumulator | undefined;
|
|
164
|
+
let heartbeatPhase = "setup";
|
|
165
|
+
let lastActivityDetail: string | undefined;
|
|
166
|
+
let watchdog: StallWatchdog | undefined;
|
|
167
|
+
|
|
168
|
+
const cancellationSignal = Context.current().cancellationSignal;
|
|
169
|
+
const shutdownSignal = getShutdownSignalForQueue(Context.current().info.taskQueue);
|
|
170
|
+
const periodicHeartbeat = startHeartbeat(PERIODIC_HEARTBEAT_MS, () => ({ phase: heartbeatPhase, execution: executionId }));
|
|
171
|
+
|
|
172
|
+
const chokepoint = new PersistChokepoint({
|
|
173
|
+
client,
|
|
174
|
+
executionId,
|
|
175
|
+
status,
|
|
176
|
+
offload: artifactStorage ? { artifactStorage, executionId } : undefined,
|
|
177
|
+
usage: () => usage,
|
|
178
|
+
heartbeat,
|
|
179
|
+
onPlatformStop: () => stop.stop({ kind: "platform-stop" }),
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
setMaxListeners(CANCELLATION_SIGNAL_MAX_LISTENERS, cancellationSignal);
|
|
184
|
+
} catch {
|
|
185
|
+
// An AbortSignal that does not support setMaxListeners (an older SDK):
|
|
186
|
+
// the warning is harmless, so this is not.
|
|
187
|
+
}
|
|
188
|
+
const onCancellation = (): void => stop.stop({ kind: "cancellation" });
|
|
189
|
+
if (cancellationSignal.aborted) onCancellation();
|
|
190
|
+
else cancellationSignal.addEventListener("abort", onCancellation, { once: true });
|
|
191
|
+
|
|
192
|
+
/** The interruption Temporal delivered, read when the table needs it. */
|
|
193
|
+
const interruption = (): TurnInterruption =>
|
|
194
|
+
classifyTurnInterruption({
|
|
195
|
+
cancellationReason: cancellationReasonOf(cancellationSignal),
|
|
196
|
+
shutdownSignalAborted: shutdownSignal?.aborted ?? false,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The one writer of a terminal: apply the arm, persist it ONCE, and end
|
|
201
|
+
* the activity the way the arm's disposition says. A thrown arm also marks
|
|
202
|
+
* the in-progress sub-agent rows cancelled before that persist, so the
|
|
203
|
+
* status the workflow reads back is the whole terminal state in one write.
|
|
204
|
+
* (The file-review resume in `settleResolution` is the one place that
|
|
205
|
+
* applies an arm itself: its write-back must land between the arm and the
|
|
206
|
+
* persist.)
|
|
207
|
+
*
|
|
208
|
+
* The table decides what is written and whether the activity throws; the
|
|
209
|
+
* caller may supply the `CancelledFailure` to throw. The catch does, to
|
|
210
|
+
* rethrow the failure it caught (the caught failure IS the evidence, #776)
|
|
211
|
+
* or to throw the after-error variant of the copy; every other caller lets
|
|
212
|
+
* the arm's own message stand.
|
|
213
|
+
*/
|
|
214
|
+
const settleWith = async (arm: TerminalArm, failure?: CancelledFailure): Promise<Settled> => {
|
|
215
|
+
applyTerminalArm(status, arm);
|
|
216
|
+
if (arm.disposition.kind === "return") {
|
|
217
|
+
await chokepoint.write();
|
|
218
|
+
return { kind: "return", value: slimStatus(status) };
|
|
219
|
+
}
|
|
220
|
+
cancelInProgressSubAgentProtos(status.subAgentExecutions);
|
|
221
|
+
await chokepoint.write();
|
|
222
|
+
return { kind: "throw", error: failure ?? new CancelledFailure(arm.disposition.message) };
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// ── The turn ended during resolution ──────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
async function settleResolution(settlement: TurnSettlement): Promise<Settled> {
|
|
228
|
+
switch (settlement.kind) {
|
|
229
|
+
case "workspace-lock-timeout": {
|
|
230
|
+
const outcome = await settleWith(workspaceLockTimeoutArm(settlement.error));
|
|
231
|
+
console.warn(`${activityName} workspace lock timeout: execution=${executionId}`);
|
|
232
|
+
return outcome;
|
|
233
|
+
}
|
|
234
|
+
case "rejected-by-user":
|
|
235
|
+
// A reject of an irreversible action (shell/MCP) fails the execution.
|
|
236
|
+
return settleWith(rejectedByUserArm());
|
|
237
|
+
case "file-review-resolved": {
|
|
238
|
+
// Push the APPROVED tree — reconcile snapped rejected files back to
|
|
239
|
+
// baseline, so what finalize commits is exactly what the user kept.
|
|
240
|
+
// After reconcile, before persist, never on a failed reconcile
|
|
241
|
+
// (diverged bytes must not reach the remote).
|
|
242
|
+
applyTerminalArm(status, fileReviewResolvedArm(settlement));
|
|
243
|
+
if (!settlement.failed && frame.writeback) {
|
|
244
|
+
await frame.writeback.finalize();
|
|
245
|
+
}
|
|
246
|
+
await chokepoint.write();
|
|
247
|
+
console.log(
|
|
248
|
+
`${activityName} file-review resume short-circuit: execution=${executionId}, ` +
|
|
249
|
+
`failed=${settlement.failed}, discarded=${settlement.discardedPaths.length}`,
|
|
250
|
+
);
|
|
251
|
+
return { kind: "return", value: slimStatus(status) };
|
|
252
|
+
}
|
|
253
|
+
default: {
|
|
254
|
+
const exhaustive: never = settlement;
|
|
255
|
+
throw new Error(`${activityName}: unknown resolution settlement ${String(exhaustive)}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ── The engine's turn and its outcome ─────────────────────────────────────
|
|
261
|
+
|
|
262
|
+
async function runEngineTurn(turn: TurnInput): Promise<Settled> {
|
|
263
|
+
const maxCostUsd = turn.execution.spec?.executionConfig?.maxCostUsd ?? 0;
|
|
264
|
+
const accumulator = new UsageAccumulator(turn.model.serviceTier, turn.model.thinkingMode);
|
|
265
|
+
usage = accumulator;
|
|
266
|
+
|
|
267
|
+
// The stall watchdog: the periodic heartbeat proves the process is alive,
|
|
268
|
+
// not that the engine is progressing; if the engine wedges, nothing
|
|
269
|
+
// arrives and the turn would hang forever. Armed for the adapter's whole
|
|
270
|
+
// stretch (Q-M3-13) and reset by every `recordActivity`; on stall the
|
|
271
|
+
// signal aborts, the adapter cancels its run, and the table reports the
|
|
272
|
+
// stall with the last detail the adapter named.
|
|
273
|
+
watchdog = startStallWatchdog(config.cursorStreamStallTimeoutMs, (idleMs) => {
|
|
274
|
+
const error = new StallTimeoutError(idleMs, lastActivityDetail ? `last tool: ${lastActivityDetail}` : undefined);
|
|
275
|
+
console.warn(
|
|
276
|
+
`${activityName} stall detected: execution=${executionId}, idleMs=${idleMs}, lastTool=${lastActivityDetail ?? "none"}`,
|
|
277
|
+
);
|
|
278
|
+
stop.stop({ kind: "stall", error });
|
|
279
|
+
});
|
|
280
|
+
const armedWatchdog = watchdog;
|
|
281
|
+
|
|
282
|
+
const sink: TurnSink = {
|
|
283
|
+
status,
|
|
284
|
+
stopSignal: stop.signal,
|
|
285
|
+
setupTiming,
|
|
286
|
+
requestPersist: () => chokepoint.request(),
|
|
287
|
+
recordActivity: (detail) => {
|
|
288
|
+
if (detail !== undefined) lastActivityDetail = detail;
|
|
289
|
+
armedWatchdog.recordActivity();
|
|
290
|
+
},
|
|
291
|
+
reportUsage: (delta) => {
|
|
292
|
+
accumulator.addTurn(delta);
|
|
293
|
+
// max_cost_usd enforcement (cost-guard.ts): the running estimate only
|
|
294
|
+
// advances here, so this is the single check point; the abort is how
|
|
295
|
+
// the adapter learns to end its run.
|
|
296
|
+
const estimated = accumulator.snapshot().estimatedCostUsd;
|
|
297
|
+
if (!stop.evidence.costCapExceeded && costCapExceeded(maxCostUsd, estimated)) {
|
|
298
|
+
console.warn(
|
|
299
|
+
`${activityName} cost cap exceeded: execution=${executionId}, ` +
|
|
300
|
+
`estimatedCostUsd=${estimated.toFixed(4)}, maxCostUsd=${maxCostUsd.toFixed(2)}`,
|
|
301
|
+
);
|
|
302
|
+
stop.stop({ kind: "cost-cap" });
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
reportProgress: (label) => reportSetupProgress(client, executionId, label),
|
|
306
|
+
bindHarnessState: async (harnessStateId) => {
|
|
307
|
+
turn.session.spec!.harnessStateId = harnessStateId;
|
|
308
|
+
// Clear slug to avoid re-validation of potentially invalid
|
|
309
|
+
// server-generated slugs. BuildUpdateStateStep preserves the existing
|
|
310
|
+
// slug from the database record.
|
|
311
|
+
if (turn.session.metadata) turn.session.metadata.slug = "";
|
|
312
|
+
await client.updateSession(turn.session);
|
|
313
|
+
console.log(`Stored ${harnessStateId} as harness_state_id on session ${turn.sessionId}`);
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
heartbeatPhase = "harness";
|
|
318
|
+
const outcome = await adapter.runTurn(turn, sink);
|
|
319
|
+
armedWatchdog.stop();
|
|
320
|
+
heartbeatPhase = "epilogue";
|
|
321
|
+
|
|
322
|
+
switch (outcome.kind) {
|
|
323
|
+
case "completed":
|
|
324
|
+
return completeTurn(turn, ExecutionPhase.EXECUTION_COMPLETED);
|
|
325
|
+
case "cancelled":
|
|
326
|
+
return completeTurn(turn, ExecutionPhase.EXECUTION_CANCELLED);
|
|
327
|
+
case "awaiting_approval": {
|
|
328
|
+
// Pause for review exactly like the native harness: the adapter put
|
|
329
|
+
// the WAITING rows on the transcript; flip the phase, persist, and
|
|
330
|
+
// RETURN to the workflow, which waits for the approval signal and
|
|
331
|
+
// reinvokes.
|
|
332
|
+
status.phase = ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL;
|
|
333
|
+
await chokepoint.write();
|
|
334
|
+
return { kind: "return", value: slimStatus(status) };
|
|
335
|
+
}
|
|
336
|
+
case "failed": {
|
|
337
|
+
const settledFailure = await settleWith(failedArm(outcome.message, outcome.surface));
|
|
338
|
+
console.error(
|
|
339
|
+
`${activityName} failed (${outcome.surface}): execution=${executionId}, error=${outcome.message}` +
|
|
340
|
+
(outcome.cause instanceof Error ? `, cause=${outcome.cause.message}` : ""),
|
|
341
|
+
);
|
|
342
|
+
return settledFailure;
|
|
343
|
+
}
|
|
344
|
+
case "interrupted":
|
|
345
|
+
return settleInterrupted(maxCostUsd, accumulator);
|
|
346
|
+
default: {
|
|
347
|
+
const exhaustive: never = outcome;
|
|
348
|
+
throw new Error(`${activityName}: unknown turn outcome ${JSON.stringify(exhaustive)}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* The table for an `interrupted` outcome, in the orchestrator's precedence:
|
|
355
|
+
* a stall (FAILED, return) outranks everything; a cost cap (TERMINATED,
|
|
356
|
+
* return); then Temporal's cancellation as the signal's reason reads it —
|
|
357
|
+
* the runner's drain, the orchestrator's pause, an infrastructure cancel
|
|
358
|
+
* (all thrown); last a platform stop (COMPLETED, return).
|
|
359
|
+
*/
|
|
360
|
+
async function settleInterrupted(maxCostUsd: number, accumulator: UsageAccumulator): Promise<Settled> {
|
|
361
|
+
const evidence = stop.evidence;
|
|
362
|
+
if (evidence.stall) {
|
|
363
|
+
const outcome = await settleWith(stallArm(evidence.stall));
|
|
364
|
+
console.warn(`${activityName} stalled: execution=${executionId}, error=${status.error}`);
|
|
365
|
+
return outcome;
|
|
366
|
+
}
|
|
367
|
+
if (evidence.costCapExceeded) {
|
|
368
|
+
const estimated = accumulator.snapshot().estimatedCostUsd;
|
|
369
|
+
const outcome = await settleWith(costCapArm(maxCostUsd, estimated));
|
|
370
|
+
console.warn(
|
|
371
|
+
`${activityName} terminated (cost cap): execution=${executionId}, ` +
|
|
372
|
+
`estimatedCostUsd=${estimated.toFixed(4)}, maxCostUsd=${maxCostUsd.toFixed(2)}`,
|
|
373
|
+
);
|
|
374
|
+
return outcome;
|
|
375
|
+
}
|
|
376
|
+
const delivered = interruption();
|
|
377
|
+
switch (delivered) {
|
|
378
|
+
case "worker-shutdown":
|
|
379
|
+
console.log(`${activityName} interrupted (worker shutdown): execution=${executionId}`);
|
|
380
|
+
return settleWith(workerShutdownArm());
|
|
381
|
+
case "pause":
|
|
382
|
+
console.log(`${activityName} paused: execution=${executionId}`);
|
|
383
|
+
return settleWith(pauseArm());
|
|
384
|
+
case "infrastructure":
|
|
385
|
+
console.log(`${activityName} interrupted (infrastructure cancel): execution=${executionId}`);
|
|
386
|
+
return settleWith(infrastructureCancelArm());
|
|
387
|
+
case "none":
|
|
388
|
+
break;
|
|
389
|
+
default: {
|
|
390
|
+
const exhaustive: never = delivered;
|
|
391
|
+
throw new Error(`${activityName}: unknown interruption ${String(exhaustive)}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (evidence.platformStop) {
|
|
395
|
+
console.log(`${activityName} completed (platform stop): execution=${executionId}`);
|
|
396
|
+
return settleWith(platformStopArm());
|
|
397
|
+
}
|
|
398
|
+
// The adapter settled `interrupted` with the signal live: a contract
|
|
399
|
+
// violation, reported as the runner's own failure rather than guessed at.
|
|
400
|
+
return settleWith(unexpectedErrorArm("HarnessContractError", `${adapter.name} settled interrupted with no stop`));
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* A turn the engine finished (COMPLETED) or ended cancelled on its own
|
|
405
|
+
* (CANCELLED): the final text from the last AI row; the structured output
|
|
406
|
+
* the execution asked for, extracted from that text unless the adapter
|
|
407
|
+
* already folded it onto the status; the plan artifact in plan mode; the
|
|
408
|
+
* write-back safety net; the one persist that makes COMPLETED and the
|
|
409
|
+
* structured output visible atomically; the slim return with `final_text`
|
|
410
|
+
* and `structured` beside it.
|
|
411
|
+
*/
|
|
412
|
+
async function completeTurn(turn: TurnInput, phase: ExecutionPhase): Promise<Settled> {
|
|
413
|
+
status.completedAt = utcTimestamp();
|
|
414
|
+
status.phase = phase;
|
|
415
|
+
|
|
416
|
+
let structuredOutput: unknown = status.structuredOutput;
|
|
417
|
+
let finalText: string | undefined;
|
|
418
|
+
|
|
419
|
+
if (phase === ExecutionPhase.EXECUTION_COMPLETED) {
|
|
420
|
+
finalText = [...status.messages].reverse().find((m) => m.type === MessageType.MESSAGE_AI)?.content;
|
|
421
|
+
|
|
422
|
+
if (structuredOutput === undefined && turn.structuredOutputSchema && finalText) {
|
|
423
|
+
structuredOutput = await extractStructuredOutputFromText(turn, finalText);
|
|
424
|
+
if (structuredOutput !== undefined) {
|
|
425
|
+
status.structuredOutput = structuredOutput as JsonObject;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Plan mode: publish the final plan message as a plan artifact (named
|
|
430
|
+
// from the plan's title); the only artifact path a harness without an
|
|
431
|
+
// auto-publish pipeline has.
|
|
432
|
+
const interactionMode = turn.execution.spec?.executionConfig?.interactionMode ?? InteractionMode.UNSPECIFIED;
|
|
433
|
+
if (interactionMode === InteractionMode.PLAN && finalText && turn.artifactStorage) {
|
|
434
|
+
try {
|
|
435
|
+
await publishPlanArtifact({ status, executionId, planText: finalText, artifactStorage: turn.artifactStorage });
|
|
436
|
+
} catch (err) {
|
|
437
|
+
console.warn(`${activityName} plan artifact publish skipped (non-fatal): execution=${executionId}, error=${err}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Write-back safety net on terminal completion. A capture-mode turn
|
|
442
|
+
// with captured changes paused for review instead, so reaching here
|
|
443
|
+
// means no reviewable delta this turn and this is normally a no-op —
|
|
444
|
+
// it exists for stragglers outside the capture, and never runs
|
|
445
|
+
// mid-turn.
|
|
446
|
+
if (frame.writeback) {
|
|
447
|
+
await frame.writeback.finalize();
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// NOW persist — the subscriber sees the phase and structured_output atomically.
|
|
452
|
+
await chokepoint.write();
|
|
453
|
+
console.log(
|
|
454
|
+
`${activityName} completed: execution=${executionId}, phase=${ExecutionPhase[status.phase]}, ` +
|
|
455
|
+
`hasStructuredOutput=${structuredOutput !== undefined}` +
|
|
456
|
+
(status.error ? `, error=${status.error}` : ""),
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
const slim = slimStatus(status) as Record<string, unknown>;
|
|
460
|
+
if (finalText !== undefined) slim.final_text = finalText;
|
|
461
|
+
if (structuredOutput !== undefined) slim.structured = structuredOutput;
|
|
462
|
+
return { kind: "return", value: slim };
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Tier 1 + 1.5: JSON.parse, code-fence extraction, heuristic brace match.
|
|
467
|
+
* Tier 2: LLM extraction with withStructuredOutput — deterministic,
|
|
468
|
+
* function-calling guarantees schema-conformant output. Each tier logs
|
|
469
|
+
* its verdict; a failed LLM extraction is logged and yields nothing.
|
|
470
|
+
*/
|
|
471
|
+
async function extractStructuredOutputFromText(turn: TurnInput, finalText: string): Promise<unknown> {
|
|
472
|
+
const { extractJsonFromText } = await import("../shared/extract-json.js");
|
|
473
|
+
const fromText = extractJsonFromText(finalText);
|
|
474
|
+
if (fromText !== undefined) {
|
|
475
|
+
console.log(
|
|
476
|
+
`${activityName} structured output extracted (text): execution=${executionId}, finalTextLength=${finalText.length}`,
|
|
477
|
+
);
|
|
478
|
+
return fromText;
|
|
479
|
+
}
|
|
480
|
+
console.log(
|
|
481
|
+
`${activityName} text extraction failed, trying LLM extraction: execution=${executionId}, ` +
|
|
482
|
+
`finalTextLength=${finalText.length}`,
|
|
483
|
+
);
|
|
484
|
+
try {
|
|
485
|
+
const { extractStructuredOutput } = await import("../shared/extract-structured-output.js");
|
|
486
|
+
const extracted = await extractStructuredOutput(finalText, turn.structuredOutputSchema!, config, turn.model.requested);
|
|
487
|
+
if (extracted !== undefined) {
|
|
488
|
+
console.log(`${activityName} structured output extracted (LLM): execution=${executionId}`);
|
|
489
|
+
}
|
|
490
|
+
return extracted;
|
|
491
|
+
} catch (extractErr) {
|
|
492
|
+
const errMsg = extractErr instanceof Error ? extractErr.message : String(extractErr);
|
|
493
|
+
console.error(
|
|
494
|
+
`${activityName} structured output extraction FAILED: execution=${executionId}, ` +
|
|
495
|
+
`requestedModel=${turn.model.requested}, finalTextLength=${finalText.length}, error=${errMsg}`,
|
|
496
|
+
);
|
|
497
|
+
return undefined;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// ── Something threw ───────────────────────────────────────────────────────
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* The catch. An adapter never throws (its contract), so what lands here is
|
|
505
|
+
* the runtime's own: a `CancelledFailure` from a resolution phase (the
|
|
506
|
+
* lock wait), an unexpected error during resolution or the epilogue, or a
|
|
507
|
+
* contract violation. The cancellation arms are the table's throw arms
|
|
508
|
+
* through `settleWith`, with the failure this catch chooses to throw; the
|
|
509
|
+
* generic arm is the `internal` failure surface, returned.
|
|
510
|
+
*/
|
|
511
|
+
async function settleThrown(err: unknown): Promise<Settled> {
|
|
512
|
+
const cancelledArm = (): TerminalArm | undefined => {
|
|
513
|
+
const delivered = interruption();
|
|
514
|
+
switch (delivered) {
|
|
515
|
+
case "worker-shutdown":
|
|
516
|
+
console.log(`${activityName} cancelled (worker shutdown) for execution ${executionId}`);
|
|
517
|
+
return workerShutdownArm();
|
|
518
|
+
case "pause":
|
|
519
|
+
console.log(`${activityName} cancelled (pause) for execution ${executionId}`);
|
|
520
|
+
return pauseArm();
|
|
521
|
+
case "infrastructure":
|
|
522
|
+
console.log(`${activityName} cancelled (infrastructure) for execution ${executionId}`);
|
|
523
|
+
return infrastructureCancelArm();
|
|
524
|
+
case "none":
|
|
525
|
+
return undefined;
|
|
526
|
+
default: {
|
|
527
|
+
const exhaustive: never = delivered;
|
|
528
|
+
throw new Error(`${activityName}: unknown interruption ${String(exhaustive)}`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
if (err instanceof CancelledFailure) {
|
|
534
|
+
// The cancellation arrived as a throw (the lock wait observes the
|
|
535
|
+
// signal); the caught failure IS the evidence (#776). A thrown
|
|
536
|
+
// cancellation with no delivered cancellation on the signal can only be
|
|
537
|
+
// the runtime's own `checkCancellation`-style throw: infrastructure.
|
|
538
|
+
return settleWith(cancelledArm() ?? infrastructureCancelArm(), err);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const errDetail = err instanceof Error ? err.message : String(err);
|
|
542
|
+
const arm = cancelledArm();
|
|
543
|
+
if (arm !== undefined) {
|
|
544
|
+
// A non-cancellation error while cancelled was most likely caused by the
|
|
545
|
+
// cancellation itself (a teardown mid-flight) and must not overwrite the
|
|
546
|
+
// state the workflow already expects: a pause stays PAUSED; anything
|
|
547
|
+
// else reads as the interruption it was.
|
|
548
|
+
if (arm.phase === ExecutionPhase.EXECUTION_PAUSED) {
|
|
549
|
+
console.log(`${activityName} error during pause (treating as pause): execution=${executionId}, error=${errDetail}`);
|
|
550
|
+
return settleWith(arm, new CancelledFailure(TERMINAL_COPY.pause.throwMessageAfterError));
|
|
551
|
+
}
|
|
552
|
+
console.log(`${activityName} error during infrastructure cancel: execution=${executionId}, error=${errDetail}`);
|
|
553
|
+
return settleWith(
|
|
554
|
+
{ ...infrastructureCancelArm(), error: `Execution interrupted: ${errDetail}` },
|
|
555
|
+
new CancelledFailure(TERMINAL_COPY.infrastructureCancel.throwMessageAfterError),
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Unwrap + classify before formatting: a model error arrives
|
|
560
|
+
// MiddlewareError-wrapped with raw provider prose; non-model errors keep
|
|
561
|
+
// the root error's own identity.
|
|
562
|
+
const { errorType, errorMessage } = describeExecutionError(err, { proxyMode: !!config.proxyEndpoint });
|
|
563
|
+
console.error(`${activityName} failed: execution=${executionId}, [${errorType}] ${errorMessage}`);
|
|
564
|
+
return settleWith(unexpectedErrorArm(errorType, errorMessage));
|
|
565
|
+
}
|
|
566
|
+
// ── Drive ─────────────────────────────────────────────────────────────────
|
|
567
|
+
|
|
568
|
+
let settled: Settled;
|
|
569
|
+
try {
|
|
570
|
+
const resolutionDeps: ResolutionDeps = {
|
|
571
|
+
input,
|
|
572
|
+
client,
|
|
573
|
+
config,
|
|
574
|
+
status,
|
|
575
|
+
artifactStorage,
|
|
576
|
+
timing: setupTiming,
|
|
577
|
+
signal: cancellationSignal,
|
|
578
|
+
heartbeat,
|
|
579
|
+
enterPhase: (step) => {
|
|
580
|
+
heartbeatPhase = step;
|
|
581
|
+
heartbeat();
|
|
582
|
+
},
|
|
583
|
+
reportProgress: (label) => reportSetupProgress(client, executionId, label),
|
|
584
|
+
};
|
|
585
|
+
const resolution = await resolveTurnContext(resolutionDeps, adapter.capabilities, frame, () => chokepoint.write());
|
|
586
|
+
if (resolution.kind === "settled") {
|
|
587
|
+
settled = await settleResolution(resolution.settlement);
|
|
588
|
+
} else {
|
|
589
|
+
settled = await runEngineTurn(resolution.input);
|
|
590
|
+
}
|
|
591
|
+
} catch (err) {
|
|
592
|
+
settled = await settleThrown(err);
|
|
593
|
+
} finally {
|
|
594
|
+
periodicHeartbeat.stop();
|
|
595
|
+
watchdog?.stop();
|
|
596
|
+
cancellationSignal.removeEventListener("abort", onCancellation);
|
|
597
|
+
// Remove the workspace's `.stigmer` link the skill and attachment phases
|
|
598
|
+
// created, so attaching a real repo leaves it untouched between turns
|
|
599
|
+
// (issue #173). After the adapter's own teardown (its gate), before the
|
|
600
|
+
// lock. Idempotent and non-throwing.
|
|
601
|
+
if (frame.primaryDir) await removeStigmerSymlink(frame.primaryDir);
|
|
602
|
+
// Release the workspace turn lock LAST — every mutation of this turn has
|
|
603
|
+
// landed, and the next queued turn must not baseline before that.
|
|
604
|
+
// Idempotent and non-throwing, so it can never mask the turn's outcome.
|
|
605
|
+
await frame.releaseWorkspaceLock?.();
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
switch (settled.kind) {
|
|
609
|
+
case "return":
|
|
610
|
+
return settled.value;
|
|
611
|
+
case "throw":
|
|
612
|
+
throw settled.error;
|
|
613
|
+
default: {
|
|
614
|
+
const exhaustive: never = settled;
|
|
615
|
+
throw new Error(`${activityName}: unknown settlement ${JSON.stringify(exhaustive)}`);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|