@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,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pins the turn context's pure decisions — the three places `turn-context.ts`
|
|
3
|
+
* decides something rather than fetching it:
|
|
4
|
+
*
|
|
5
|
+
* - `isReinvocation`: the one reading of "does the engine already hold state
|
|
6
|
+
* for this execution", keyed on how the harness mints its state id. The
|
|
7
|
+
* engine-minted arm is today's `!!threadId`; the deterministic arm refuses
|
|
8
|
+
* until the native adapter lands (S3), and the refusal is pinned so a
|
|
9
|
+
* future caller cannot get a silent `false`.
|
|
10
|
+
* - `approvalDecisionsOf`: the runtime's reader of adjudicated approvals,
|
|
11
|
+
* over a transcript that carries every row shape it must include or
|
|
12
|
+
* exclude. Its agreement with the Cursor adapter's own reader
|
|
13
|
+
* (`reconstructAdjudicatedApprovals`) is pinned from the adapter's side
|
|
14
|
+
* (`activities/execute-cursor/__tests__/approval-state.test.ts`), because
|
|
15
|
+
* `src/harness/` never imports `src/activities/`, tests included.
|
|
16
|
+
* - `decideReinvocation`: the four ways a resume proceeds (reject fails,
|
|
17
|
+
* approvals run, a clean file-review resume completes, a failed or
|
|
18
|
+
* partially discarded one completes with its facts carried).
|
|
19
|
+
*
|
|
20
|
+
* The phases that fetch and provision are exercised end to end by the
|
|
21
|
+
* hermetic goldens (`activities/execute-cursor/__tests__/hermetic/`), which
|
|
22
|
+
* pin their observable behavior byte for byte; nothing here re-drives a
|
|
23
|
+
* client.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { describe, expect, it } from "vitest";
|
|
27
|
+
import { create } from "@bufbuild/protobuf";
|
|
28
|
+
import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
29
|
+
import { AgentMessageSchema, ToolCallSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
30
|
+
import { SubAgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
|
|
31
|
+
import { ApprovalAction, MessageType, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
32
|
+
|
|
33
|
+
import { approvalDecisionsOf, decideReinvocation, isReinvocation, type ReinvocationFacts } from "../turn-context.js";
|
|
34
|
+
|
|
35
|
+
const INPUT = { executionId: "aex_1", threadId: "", turnSeq: 0 } as const;
|
|
36
|
+
|
|
37
|
+
describe("isReinvocation", () => {
|
|
38
|
+
it("engine-minted: an empty state id is a first turn, any id is a resume", () => {
|
|
39
|
+
expect(isReinvocation("engine-minted", INPUT)).toBe(false);
|
|
40
|
+
expect(isReinvocation("engine-minted", { ...INPUT, threadId: "agent-42" })).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("engine-minted: turnSeq is NOT the signal (a pure-reconcile turn does not advance it; the legacy wire shape yields 0)", () => {
|
|
44
|
+
expect(isReinvocation("engine-minted", { ...INPUT, turnSeq: 3 })).toBe(false);
|
|
45
|
+
expect(isReinvocation("engine-minted", { ...INPUT, threadId: "agent-42", turnSeq: 0 })).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("deterministic: refuses rather than guess until the native adapter lands (S3)", () => {
|
|
49
|
+
expect(() => isReinvocation("deterministic", { ...INPUT, threadId: "thread-1" })).toThrow(
|
|
50
|
+
/deterministic arm lands with the native adapter \(S3\)/,
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
/** A transcript carrying every row shape the reader must include or exclude. */
|
|
56
|
+
function transcriptWithEveryRowShape() {
|
|
57
|
+
const waitingApproved = create(ToolCallSchema, {
|
|
58
|
+
id: "tc-approved",
|
|
59
|
+
name: "Shell",
|
|
60
|
+
status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
|
|
61
|
+
approvalAction: ApprovalAction.APPROVE,
|
|
62
|
+
approvalContentDigest: "sha256:approved",
|
|
63
|
+
});
|
|
64
|
+
const waitingRejected = create(ToolCallSchema, {
|
|
65
|
+
id: "tc-rejected",
|
|
66
|
+
name: "Write",
|
|
67
|
+
status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
|
|
68
|
+
approvalAction: ApprovalAction.REJECT,
|
|
69
|
+
});
|
|
70
|
+
const waitingUndecided = create(ToolCallSchema, {
|
|
71
|
+
id: "tc-undecided",
|
|
72
|
+
name: "Shell",
|
|
73
|
+
status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
|
|
74
|
+
approvalAction: ApprovalAction.UNSPECIFIED,
|
|
75
|
+
});
|
|
76
|
+
const completedWithStaleAction = create(ToolCallSchema, {
|
|
77
|
+
id: "tc-completed",
|
|
78
|
+
name: "Read",
|
|
79
|
+
status: ToolCallStatus.TOOL_CALL_COMPLETED,
|
|
80
|
+
approvalAction: ApprovalAction.APPROVE,
|
|
81
|
+
});
|
|
82
|
+
const subAgentWaiting = create(ToolCallSchema, {
|
|
83
|
+
id: "tc-sub",
|
|
84
|
+
name: "Shell",
|
|
85
|
+
status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
|
|
86
|
+
approvalAction: ApprovalAction.APPROVE,
|
|
87
|
+
});
|
|
88
|
+
return create(AgentExecutionStatusSchema, {
|
|
89
|
+
messages: [
|
|
90
|
+
create(AgentMessageSchema, { type: MessageType.MESSAGE_AI, toolCalls: [waitingApproved, waitingUndecided] }),
|
|
91
|
+
create(AgentMessageSchema, { type: MessageType.MESSAGE_AI, toolCalls: [completedWithStaleAction, waitingRejected] }),
|
|
92
|
+
],
|
|
93
|
+
subAgentExecutions: [
|
|
94
|
+
create(SubAgentExecutionSchema, {
|
|
95
|
+
messages: [create(AgentMessageSchema, { type: MessageType.MESSAGE_AI, toolCalls: [subAgentWaiting] })],
|
|
96
|
+
}),
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
describe("approvalDecisionsOf", () => {
|
|
102
|
+
it("reads every WAITING row with a verdict, in transcript order, and nothing else", () => {
|
|
103
|
+
const decisions = approvalDecisionsOf(transcriptWithEveryRowShape());
|
|
104
|
+
expect([...decisions.entries()]).toEqual([
|
|
105
|
+
["tc-approved", ApprovalAction.APPROVE],
|
|
106
|
+
["tc-rejected", ApprovalAction.REJECT],
|
|
107
|
+
]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("an undecided WAITING row, a COMPLETED row with a stale verdict, and a sub-agent row are all excluded", () => {
|
|
111
|
+
const decisions = approvalDecisionsOf(transcriptWithEveryRowShape());
|
|
112
|
+
expect(decisions.has("tc-undecided"), "no verdict yet").toBe(false);
|
|
113
|
+
expect(decisions.has("tc-completed"), "already executed; the verdict on it is history").toBe(false);
|
|
114
|
+
expect(decisions.has("tc-sub"), "sub-agent transcripts are not the top-level rows").toBe(false);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("is empty on a fresh status", () => {
|
|
118
|
+
expect(approvalDecisionsOf(create(AgentExecutionStatusSchema, {})).size).toBe(0);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
function facts(overrides: Partial<ReinvocationFacts> = {}): ReinvocationFacts {
|
|
123
|
+
return {
|
|
124
|
+
approvalDecisions: new Map(),
|
|
125
|
+
reconciledFileReview: false,
|
|
126
|
+
fileReviewFailed: false,
|
|
127
|
+
fileReviewFailureDetail: "",
|
|
128
|
+
discardedPaths: [],
|
|
129
|
+
...overrides,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
describe("decideReinvocation", () => {
|
|
134
|
+
it("a first turn, or a resume that decided nothing and reconciled nothing, runs the agent", () => {
|
|
135
|
+
expect(decideReinvocation(facts())).toBeUndefined();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("any REJECT among the adjudicated decisions fails the turn, whatever else was decided", () => {
|
|
139
|
+
const decisions = new Map([
|
|
140
|
+
["tc-1", ApprovalAction.APPROVE],
|
|
141
|
+
["tc-2", ApprovalAction.REJECT],
|
|
142
|
+
]);
|
|
143
|
+
expect(decideReinvocation(facts({ approvalDecisions: decisions }))).toEqual({ kind: "rejected-by-user" });
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("approvals without a reject run the agent, even when file review was also reconciled", () => {
|
|
147
|
+
const decisions = new Map([["tc-1", ApprovalAction.APPROVE]]);
|
|
148
|
+
expect(decideReinvocation(facts({ approvalDecisions: decisions, reconciledFileReview: true }))).toBeUndefined();
|
|
149
|
+
expect(
|
|
150
|
+
decideReinvocation(facts({ approvalDecisions: new Map([["tc-1", ApprovalAction.SKIP]]) })),
|
|
151
|
+
"a SKIP is not a reject",
|
|
152
|
+
).toBeUndefined();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("a pure file-review resume completes, carrying the reconcile's facts", () => {
|
|
156
|
+
expect(decideReinvocation(facts({ reconciledFileReview: true }))).toEqual({
|
|
157
|
+
kind: "file-review-resolved",
|
|
158
|
+
failed: false,
|
|
159
|
+
failureDetail: "",
|
|
160
|
+
discardedPaths: [],
|
|
161
|
+
});
|
|
162
|
+
expect(
|
|
163
|
+
decideReinvocation(
|
|
164
|
+
facts({
|
|
165
|
+
reconciledFileReview: true,
|
|
166
|
+
fileReviewFailed: true,
|
|
167
|
+
fileReviewFailureDetail: "src/a.ts changed after review",
|
|
168
|
+
discardedPaths: ["src/b.ts"],
|
|
169
|
+
}),
|
|
170
|
+
),
|
|
171
|
+
).toEqual({
|
|
172
|
+
kind: "file-review-resolved",
|
|
173
|
+
failed: true,
|
|
174
|
+
failureDetail: "src/a.ts changed after review",
|
|
175
|
+
discardedPaths: ["src/b.ts"],
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runtime's usage accounting over priced deltas: the token-accounting
|
|
3
|
+
* convention the Usage widget and the billing parity test depend on, the
|
|
4
|
+
* audit-trail fields, and the rule that the accumulator adds and never
|
|
5
|
+
* prices. The pricing half is `execute-cursor/__tests__/usage-pricing.test.ts`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect } from "vitest";
|
|
9
|
+
import { ServiceTier, ThinkingMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
10
|
+
|
|
11
|
+
import { UsageAccumulator } from "../usage-accumulator.js";
|
|
12
|
+
|
|
13
|
+
describe("UsageAccumulator", () => {
|
|
14
|
+
it("reports total_tokens as input + output (cache is a subset of input, not additive)", () => {
|
|
15
|
+
const acc = new UsageAccumulator();
|
|
16
|
+
|
|
17
|
+
// Two turns. cacheRead/cacheWrite are subsets of inputTokens.
|
|
18
|
+
acc.addTurn({ inputTokens: 100, outputTokens: 50, cacheReadTokens: 30, cacheWriteTokens: 10 });
|
|
19
|
+
acc.addTurn({ inputTokens: 300, outputTokens: 20, cacheReadTokens: 200, cacheWriteTokens: 0 });
|
|
20
|
+
|
|
21
|
+
const snap = acc.snapshot();
|
|
22
|
+
|
|
23
|
+
expect(snap.inputTokens).toBe(400n);
|
|
24
|
+
expect(snap.outputTokens).toBe(70n);
|
|
25
|
+
expect(snap.cacheReadTokens).toBe(230n);
|
|
26
|
+
expect(snap.cacheWriteTokens).toBe(10n);
|
|
27
|
+
// True total = input + output = 470. NOT 470 + 230 + 10 (which would
|
|
28
|
+
// double-count the cached tokens already inside inputTokens).
|
|
29
|
+
expect(snap.totalTokens).toBe(470n);
|
|
30
|
+
expect(snap.turnCount).toBe(2);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("returns an empty snapshot when no turns were observed", () => {
|
|
34
|
+
const acc = new UsageAccumulator();
|
|
35
|
+
const snap = acc.snapshot();
|
|
36
|
+
expect(acc.hasTurns).toBe(false);
|
|
37
|
+
expect(snap.totalTokens).toBe(0n);
|
|
38
|
+
expect(snap.turnCount).toBe(0);
|
|
39
|
+
expect(snap.estimatedCostUsd).toBe(0);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("treats missing token fields as zero", () => {
|
|
43
|
+
const acc = new UsageAccumulator();
|
|
44
|
+
acc.addTurn({ inputTokens: 42 });
|
|
45
|
+
const snap = acc.snapshot();
|
|
46
|
+
expect(snap.inputTokens).toBe(42n);
|
|
47
|
+
expect(snap.outputTokens).toBe(0n);
|
|
48
|
+
expect(snap.totalTokens).toBe(42n);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("sums the adapter's stated cost and never prices on its own", () => {
|
|
52
|
+
const acc = new UsageAccumulator();
|
|
53
|
+
acc.addTurn({ inputTokens: 1_000_000, estimatedCostUsd: 0.25 });
|
|
54
|
+
acc.addTurn({ inputTokens: 1_000_000, estimatedCostUsd: 0.5 });
|
|
55
|
+
acc.addTurn({ inputTokens: 1_000_000 });
|
|
56
|
+
expect(acc.snapshot().estimatedCostUsd).toBeCloseTo(0.75, 9);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("records the latest stated price basis; a delta without one inherits it (#357 audit trail)", () => {
|
|
60
|
+
const acc = new UsageAccumulator(ServiceTier.FAST, ThinkingMode.DISABLED);
|
|
61
|
+
acc.addTurn({ inputTokens: 10, model: "composer-2.5", requestedModelParams: '[{"id":"fast","value":"true"}]' });
|
|
62
|
+
acc.addTurn({ inputTokens: 5 });
|
|
63
|
+
const snap = acc.snapshot();
|
|
64
|
+
expect(snap.model).toBe("composer-2.5");
|
|
65
|
+
expect(snap.requestedModelParams).toBe('[{"id":"fast","value":"true"}]');
|
|
66
|
+
expect(snap.requestedServiceTier).toBe(ServiceTier.FAST);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("records an empty basis when no delta named one", () => {
|
|
70
|
+
const acc = new UsageAccumulator(ServiceTier.STANDARD, ThinkingMode.DISABLED);
|
|
71
|
+
acc.addTurn({ inputTokens: 1 });
|
|
72
|
+
const snap = acc.snapshot();
|
|
73
|
+
expect(snap.model).toBe("");
|
|
74
|
+
expect(snap.requestedModelParams).toBe("");
|
|
75
|
+
expect(snap.requestedServiceTier).toBe(ServiceTier.STANDARD);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("records the requested thinking mode into the snapshot (#772 audit trail)", () => {
|
|
79
|
+
const acc = new UsageAccumulator(ServiceTier.STANDARD, ThinkingMode.ENABLED);
|
|
80
|
+
acc.addTurn({ inputTokens: 10, outputTokens: 5 });
|
|
81
|
+
expect(acc.snapshot().requestedThinkingMode).toBe(ThinkingMode.ENABLED);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness capability flags — the facts about an engine the runtime branches
|
|
3
|
+
* on, declared once per adapter and read nowhere else.
|
|
4
|
+
*
|
|
5
|
+
* The runtime is written against the flags, not against harness names: a
|
|
6
|
+
* phase that must differ per engine asks "does this harness accept a system
|
|
7
|
+
* prompt?" rather than "is this Cursor?". That is what keeps a new harness a
|
|
8
|
+
* registry row and an SDK slice instead of a new branch in every phase.
|
|
9
|
+
* Every row of the matrix below is either a flag here or internal to one
|
|
10
|
+
* adapter; nothing in it needed a third kind of thing.
|
|
11
|
+
*
|
|
12
|
+
* The matrix the contract was designed against (2026-09; Claude and Codex are
|
|
13
|
+
* the surveyed SDKs, not built harnesses):
|
|
14
|
+
*
|
|
15
|
+
* | Capability | Native (LangGraph) | Cursor (`@cursor/sdk`) | Claude (`@anthropic-ai/claude-agent-sdk`) | Codex (`@openai/codex-sdk`) |
|
|
16
|
+
* |-------------------|-----------------------------|-----------------------------------------|-----------------------------------------------|------------------------------------------------|
|
|
17
|
+
* | `pausePrimitive` | `interrupt` (checkpoint) | `deny-and-retry` (hook, ledger, cancel) | `callback` (`canUseTool`, in-process) | `none` (approvalPolicy only) → capture-only |
|
|
18
|
+
* | `stateIdSource` | `deterministic` (thread id) | `engine-minted` (agent id) | either (caller-supplied or minted) | `engine-minted` (thread id) |
|
|
19
|
+
* | `systemPrompt` | yes | no (rides the first message) | yes | no (`AGENTS.md` or first message) |
|
|
20
|
+
* | `subAgents` | yes (compiled sub-graphs) | yes (`agents` option) | yes (`AgentDefinition`) | no |
|
|
21
|
+
* | `toolRestriction` | yes (tool list) | no (the hook enforces) | yes (`disallowedTools`, `tools`) | per-server config |
|
|
22
|
+
* | `visionProfile` | PNG, JPEG, WebP, GIF | PNG, JPEG (transport re-sniffs) | (surveyed later) | (surveyed later) |
|
|
23
|
+
* | `fileReview` | `deep-agent`, no excludes | `cursor`, `.cursor/hooks.json` excluded | (surveyed later) | (surveyed later) |
|
|
24
|
+
*
|
|
25
|
+
* Adapter-internal, deliberately NOT flags: how MCP servers are bound, how
|
|
26
|
+
* metering is routed, how the cost cap is applied inside the engine, how a
|
|
27
|
+
* run is cancelled. Those differ per engine but the runtime never needs to
|
|
28
|
+
* know.
|
|
29
|
+
*
|
|
30
|
+
* Two flags matter to the contract kit directly: `pausePrimitive` (the kit
|
|
31
|
+
* proves the two real primitives are indistinguishable above the contract
|
|
32
|
+
* line — both end a turn `awaiting_approval` and both take the decisions on
|
|
33
|
+
* reinvocation) and `stateIdSource` (an `engine-minted` adapter must bind its
|
|
34
|
+
* id before its first persist; a `deterministic` one must never bind).
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import type { VisionProfile } from "../shared/attachment-vision.js";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* How the engine can be made to stop before a gated side effect.
|
|
41
|
+
*
|
|
42
|
+
* - `interrupt`: the engine checkpoints and stops at the gate (LangGraph
|
|
43
|
+
* `interrupt`); the adapter resumes it with the decisions.
|
|
44
|
+
* - `deny-and-retry`: an out-of-process hook denies the tool, the adapter
|
|
45
|
+
* records the denial, cancels the run, and re-runs with grants on the next
|
|
46
|
+
* invocation.
|
|
47
|
+
* - `callback`: an in-process callback decides per tool; the runtime still
|
|
48
|
+
* returns `awaiting_approval` and reinvokes, so the adapter answers the
|
|
49
|
+
* callback with "deny, stop" and resumes with the decisions.
|
|
50
|
+
* - `none`: the engine offers no gate; the runtime confines it to
|
|
51
|
+
* capture-only work (a read-only sandbox) and gated actions never reach
|
|
52
|
+
* the engine.
|
|
53
|
+
*/
|
|
54
|
+
export type PausePrimitive = "interrupt" | "deny-and-retry" | "callback" | "none";
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Who mints the engine's state id. `deterministic`: the runtime derives it
|
|
58
|
+
* from the session before the first turn (`EnsureThread`), so it is known
|
|
59
|
+
* before any engine exists. `engine-minted`: the engine issues it on first
|
|
60
|
+
* use and the adapter must hand it to the runtime at once
|
|
61
|
+
* (`TurnSink.bindHarnessState`) so a crash mid-turn still resumes.
|
|
62
|
+
*/
|
|
63
|
+
export type StateIdSource = "deterministic" | "engine-minted";
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The two facts the runtime's file-review reconcile needs from the harness
|
|
67
|
+
* and cannot know: the id the projection reads from the BASELINE payload
|
|
68
|
+
* (`shared/filereview/capture.ts` `applyCaptureDecisions`), and the
|
|
69
|
+
* workspace-relative paths the harness writes into the repo that a turn's
|
|
70
|
+
* diff must never show (Cursor's transient `.cursor/hooks.json`). Declared
|
|
71
|
+
* once per adapter, read by `turn-context.ts` (ruled Q-M2-6 as an argument,
|
|
72
|
+
* homed here at M3).
|
|
73
|
+
*/
|
|
74
|
+
export interface FileReviewIdentity {
|
|
75
|
+
readonly harnessId: string;
|
|
76
|
+
readonly excludePaths: readonly string[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface HarnessCapabilities {
|
|
80
|
+
readonly pausePrimitive: PausePrimitive;
|
|
81
|
+
readonly stateIdSource: StateIdSource;
|
|
82
|
+
/** The engine accepts a system prompt; otherwise instructions ride the first user message. */
|
|
83
|
+
readonly systemPrompt: boolean;
|
|
84
|
+
/** The engine runs delegated sub-agents from a definition map. */
|
|
85
|
+
readonly subAgents: boolean;
|
|
86
|
+
/** The engine can hide or deny tools by name; otherwise the gate enforces `enabledTools`. */
|
|
87
|
+
readonly toolRestriction: boolean;
|
|
88
|
+
/** Which image types the engine can display inline; the runtime degrades the rest before the turn. */
|
|
89
|
+
readonly visionProfile: VisionProfile;
|
|
90
|
+
/** How the harness's captured file changes are identified and what its own repo writes are excluded from a diff. */
|
|
91
|
+
readonly fileReview: FileReviewIdentity;
|
|
92
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The turn runtime's single persist chokepoint: every write of the
|
|
3
|
+
* execution status during a turn — the adapter's streaming requests, the
|
|
4
|
+
* runtime's own terminal writes — goes through here and nowhere else, so
|
|
5
|
+
* the guards that must never be skipped cannot be skipped by a call site
|
|
6
|
+
* that forgot them.
|
|
7
|
+
*
|
|
8
|
+
* What one write does, in order:
|
|
9
|
+
* 1. The never-persist-secret backstop (DD-26 #2): content is withheld from
|
|
10
|
+
* any built-in write row targeting a secret-like path, top-level and
|
|
11
|
+
* sub-agent. The single airtight choke point for a deny-gate harness and
|
|
12
|
+
* the only guarantee under `auto_approve_all`. Idempotent.
|
|
13
|
+
* 2. `streaming_usage` is refreshed from the accumulator when it has turns,
|
|
14
|
+
* so the summary the UI shows is the one the write carries.
|
|
15
|
+
* 3. `persistStatus` (`shared/status.ts`): tool-output offload, the
|
|
16
|
+
* aggregate size cap, transient retry, oversize recovery. Never rejects.
|
|
17
|
+
* 4. A Temporal heartbeat, so a long streaming turn's liveness is proven by
|
|
18
|
+
* the writes it makes (Q-S2-10).
|
|
19
|
+
* 5. The control plane's answer: STOP becomes the runtime's platform-stop
|
|
20
|
+
* cause on the stop controller, which aborts the adapter's signal.
|
|
21
|
+
*
|
|
22
|
+
* Single-flight with coalescing: an adapter's `requestPersist()` while a
|
|
23
|
+
* write is in flight does not start a second concurrent write (two writes of
|
|
24
|
+
* one status racing on the wire is the drift the chokepoint exists to
|
|
25
|
+
* prevent); it schedules ONE follow-up write after the current one lands,
|
|
26
|
+
* and resolves when that follow-up has. The promise it returns therefore
|
|
27
|
+
* always means "the state as of my request is on the control plane", which
|
|
28
|
+
* is what lets the Cursor loop await it and see a STOP before it pulls the
|
|
29
|
+
* next event, exactly as its own inline persist did (Q-M3-2).
|
|
30
|
+
*
|
|
31
|
+
* The runtime's terminal writes use the same path (`write()`), so the
|
|
32
|
+
* secret backstop and the usage summary ride the final status too, as they
|
|
33
|
+
* did when the orchestrator's `persist` closure was the one place to write.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { create } from "@bufbuild/protobuf";
|
|
37
|
+
import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
38
|
+
import { ExecutionControlSignal } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
39
|
+
import { StreamingUsageSummarySchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/usage_pb";
|
|
40
|
+
|
|
41
|
+
import type { StigmerClient } from "../client/stigmer-client.js";
|
|
42
|
+
import { persistStatus } from "../shared/status.js";
|
|
43
|
+
import type { ToolOutputOffloadContext } from "../shared/status-offload.js";
|
|
44
|
+
import { withholdSecretContentFromMessages } from "../shared/tool-row.js";
|
|
45
|
+
import type { UsageAccumulator } from "./usage-accumulator.js";
|
|
46
|
+
|
|
47
|
+
export interface PersistChokepointDeps {
|
|
48
|
+
readonly client: StigmerClient;
|
|
49
|
+
readonly executionId: string;
|
|
50
|
+
readonly status: AgentExecutionStatus;
|
|
51
|
+
/** Present when artifact storage resolved; enables tool-output offload on every write. */
|
|
52
|
+
readonly offload: ToolOutputOffloadContext | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* The accumulator, read at WRITE time: it exists only once the runtime has
|
|
55
|
+
* the turn's model preferences, after resolution, so it arrives as a thunk
|
|
56
|
+
* and is `undefined` for a write made before then (no usage yet anyway).
|
|
57
|
+
*/
|
|
58
|
+
readonly usage: () => UsageAccumulator | undefined;
|
|
59
|
+
/** The Temporal heartbeat, pulsed after each write. */
|
|
60
|
+
readonly heartbeat: () => void;
|
|
61
|
+
/** The control plane answered STOP to this write. */
|
|
62
|
+
readonly onPlatformStop: () => void;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export class PersistChokepoint {
|
|
66
|
+
private inFlight: Promise<void> | undefined;
|
|
67
|
+
private followUp: Promise<void> | undefined;
|
|
68
|
+
|
|
69
|
+
constructor(private readonly deps: PersistChokepointDeps) {}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The adapter's `requestPersist()`: a write now, or one follow-up write
|
|
73
|
+
* after the in-flight one. Resolves when the state as of this call has
|
|
74
|
+
* been written. Never rejects.
|
|
75
|
+
*/
|
|
76
|
+
request(): Promise<void> {
|
|
77
|
+
if (this.inFlight === undefined) {
|
|
78
|
+
const write = this.write().finally(() => {
|
|
79
|
+
this.inFlight = undefined;
|
|
80
|
+
});
|
|
81
|
+
this.inFlight = write;
|
|
82
|
+
return write;
|
|
83
|
+
}
|
|
84
|
+
if (this.followUp === undefined) {
|
|
85
|
+
this.followUp = this.inFlight.then(() => {
|
|
86
|
+
this.followUp = undefined;
|
|
87
|
+
return this.request();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return this.followUp;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* One write, now. The runtime's own writes (a settlement, a terminal
|
|
95
|
+
* status) call this directly: they run when no adapter write can be in
|
|
96
|
+
* flight (before `runTurn` or after it settled), so they need no
|
|
97
|
+
* coalescing, and they must not be deferred behind one.
|
|
98
|
+
*/
|
|
99
|
+
async write(): Promise<void> {
|
|
100
|
+
const { client, executionId, status, offload, heartbeat } = this.deps;
|
|
101
|
+
withholdSecretContentFromMessages(status.messages, status.subAgentExecutions);
|
|
102
|
+
const usage = this.deps.usage();
|
|
103
|
+
if (usage?.hasTurns) {
|
|
104
|
+
status.streamingUsage = create(StreamingUsageSummarySchema, usage.snapshot());
|
|
105
|
+
}
|
|
106
|
+
const signal = await persistStatus(client, executionId, status, { offload });
|
|
107
|
+
heartbeat();
|
|
108
|
+
if (signal === ExecutionControlSignal.STOP) {
|
|
109
|
+
this.deps.onPlatformStop();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The harness registry — the lifecycle fan-out over the adapters a worker
|
|
3
|
+
* runs, and the byte-pinned binding from harness to Temporal activity name.
|
|
4
|
+
*
|
|
5
|
+
* Plain functions over an `adapters` argument, no module state and no DI
|
|
6
|
+
* (the composition roots are staged plain functions; a missing adapter is a
|
|
7
|
+
* compile error or a loud boot throw, never a silent no-op). ORDER IS
|
|
8
|
+
* LOAD-BEARING: `bootHarnesses` runs adapters in declaration order because
|
|
9
|
+
* the Cursor harness's interceptors must patch `node:http2` before anything
|
|
10
|
+
* dials the control plane, and `shutdownHarnesses` runs them in reverse so
|
|
11
|
+
* what was set up last is torn down first.
|
|
12
|
+
*
|
|
13
|
+
* Error posture, ruled at the entry's gate (Q-S1-10):
|
|
14
|
+
* - Boot validates the whole table BEFORE booting anything (a duplicate name
|
|
15
|
+
* is a configuration defect, and a half-booted worker is the worst state to
|
|
16
|
+
* discover it in), then fails fast at the first adapter that rejects. A
|
|
17
|
+
* worker that cannot boot a harness must not start.
|
|
18
|
+
* - Shutdown and session release CONTINUE past a failing adapter and throw
|
|
19
|
+
* one `AggregateError` at the end naming each failure, so one bad teardown
|
|
20
|
+
* never leaks the others' resources.
|
|
21
|
+
*
|
|
22
|
+
* `createHarnessActivities` binds each row to its Temporal activity over the
|
|
23
|
+
* turn runtime (`run-turn.ts`). The table of real rows is NOT here: it lives
|
|
24
|
+
* at the source root (`src/harness-adapters.ts`), because a row imports its
|
|
25
|
+
* adapter from `activities/` and nothing under `src/harness/` may
|
|
26
|
+
* (`__tests__/import-direction.test.ts`; Q-M3-1). The registry knows rows,
|
|
27
|
+
* never which adapters exist.
|
|
28
|
+
*
|
|
29
|
+
* `HarnessName` lives here and not in `types.ts` on purpose: the wire
|
|
30
|
+
* vocabulary is the registry's concern. An adapter never declares the
|
|
31
|
+
* activity it is bound to (its `name` is a diagnostic identity); the registry
|
|
32
|
+
* row does.
|
|
33
|
+
*
|
|
34
|
+
* THIS MODULE'S STATIC GRAPH MUST STAY CONNECT-FREE. Both composition roots
|
|
35
|
+
* import it BEFORE they boot the harnesses, and the Cursor adapter's boot
|
|
36
|
+
* patches `node:http2` (its HTTP/2 auth interceptor); a static import of
|
|
37
|
+
* anything that loads `@connectrpc/connect-node` here would snapshot the
|
|
38
|
+
* `node:http2` ESM facade to the unpatched `connect` before that boot ran,
|
|
39
|
+
* and every BiDi stream would silently 401. So `StigmerClient` is loaded
|
|
40
|
+
* with a dynamic import inside `createHarnessActivities`, the runner's
|
|
41
|
+
* dynamic-import-for-order convention (`bootstrap.ts` documents the same
|
|
42
|
+
* omission for the same reason). `__tests__/harness-boot-order.test.ts`
|
|
43
|
+
* boots a fresh process through this module and fails if the facade froze.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
import type { Config } from "../config.js";
|
|
47
|
+
import type { StigmerClient } from "../client/stigmer-client.js";
|
|
48
|
+
import { activityFinished, activityStarted } from "../idle-watchdog.js";
|
|
49
|
+
import { normalizeActivityInput, type ExecuteActivityInput } from "../shared/activity-input.js";
|
|
50
|
+
import { runTurnActivity } from "./run-turn.js";
|
|
51
|
+
import type { HarnessAdapter } from "./types.js";
|
|
52
|
+
|
|
53
|
+
/** The harnesses the control plane can dispatch to, as the registry knows them. */
|
|
54
|
+
export type HarnessName = "cursor" | "deep-agent";
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Byte-pinned Temporal activity names, one per harness. The server side of
|
|
58
|
+
* the pin is `stigmer-server/src/temporal/agentexecution/names.ts`
|
|
59
|
+
* (`EXECUTE_CURSOR_ACTIVITY_NAME`, `EXECUTE_DEEP_AGENT_ACTIVITY_NAME`); the
|
|
60
|
+
* two must stay byte-identical or the workflow schedules an activity no
|
|
61
|
+
* worker registers. Never "cleaned up".
|
|
62
|
+
*/
|
|
63
|
+
export const HARNESS_ACTIVITY_NAMES = {
|
|
64
|
+
cursor: "ExecuteCursor",
|
|
65
|
+
"deep-agent": "ExecuteDeepAgent",
|
|
66
|
+
} as const satisfies Record<HarnessName, string>;
|
|
67
|
+
|
|
68
|
+
export type HarnessActivityName = (typeof HARNESS_ACTIVITY_NAMES)[HarnessName];
|
|
69
|
+
|
|
70
|
+
/** One harness the worker serves: the wire name it answers to and the adapter that runs its turns. */
|
|
71
|
+
export interface HarnessRow {
|
|
72
|
+
readonly harness: HarnessName;
|
|
73
|
+
readonly adapter: HarnessAdapter;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The legacy positional activity signature, kept as the wire shape both
|
|
78
|
+
* control planes send until they both send the typed object
|
|
79
|
+
* (`shared/activity-input.ts`). Every harness's activity has it.
|
|
80
|
+
*/
|
|
81
|
+
export type HarnessActivity = (arg0: ExecuteActivityInput | string, arg1?: string) => Promise<unknown>;
|
|
82
|
+
|
|
83
|
+
/** The adapters of a row list, for the lifecycle functions below. */
|
|
84
|
+
export function adaptersOf(rows: readonly HarnessRow[]): HarnessAdapter[] {
|
|
85
|
+
return rows.map((row) => row.adapter);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* One Temporal activity per row, under the row's byte-pinned name, over ONE
|
|
90
|
+
* `StigmerClient` for all of them (each harness used to build its own). The
|
|
91
|
+
* activity normalizes the wire input, brackets the turn with the idle
|
|
92
|
+
* watchdog, and hands the turn runtime the adapter. The roots spread the
|
|
93
|
+
* result into the worker's activity map beside the activities the runtime
|
|
94
|
+
* does not own.
|
|
95
|
+
*
|
|
96
|
+
* Async because the client is loaded here, not at the top of the module (see
|
|
97
|
+
* the header): the roots call this after the harnesses have booted, when
|
|
98
|
+
* connect-node may load.
|
|
99
|
+
*/
|
|
100
|
+
export async function createHarnessActivities(
|
|
101
|
+
rows: readonly HarnessRow[],
|
|
102
|
+
config: Config,
|
|
103
|
+
): Promise<Partial<Record<HarnessActivityName, HarnessActivity>>> {
|
|
104
|
+
const { StigmerClient } = await import("../client/stigmer-client.js");
|
|
105
|
+
const client: StigmerClient = new StigmerClient({
|
|
106
|
+
endpoint: config.stigmerBackendEndpoint,
|
|
107
|
+
token: config.stigmerToken,
|
|
108
|
+
tokenRef: config.stigmerTokenRef,
|
|
109
|
+
runnerTokenRef: config.stigmerRunnerTokenRef,
|
|
110
|
+
});
|
|
111
|
+
const activities: Partial<Record<HarnessActivityName, HarnessActivity>> = {};
|
|
112
|
+
for (const row of rows) {
|
|
113
|
+
const activityName = HARNESS_ACTIVITY_NAMES[row.harness];
|
|
114
|
+
const deps = { adapter: row.adapter, activityName, client, config };
|
|
115
|
+
activities[activityName] = async (arg0, arg1) => {
|
|
116
|
+
const input = normalizeActivityInput(arg0, arg1);
|
|
117
|
+
activityStarted();
|
|
118
|
+
try {
|
|
119
|
+
return await runTurnActivity(deps, input);
|
|
120
|
+
} finally {
|
|
121
|
+
activityFinished();
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return activities;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Refuse a table two of whose adapters share a name. Names are the registry's
|
|
130
|
+
* identity for diagnostics and for this check; a duplicate means two adapters
|
|
131
|
+
* would be indistinguishable in every log line and kit message.
|
|
132
|
+
*/
|
|
133
|
+
function assertUniqueNames(adapters: readonly HarnessAdapter[]): void {
|
|
134
|
+
const seen = new Set<string>();
|
|
135
|
+
for (const adapter of adapters) {
|
|
136
|
+
if (seen.has(adapter.name)) {
|
|
137
|
+
throw new Error(`harness registry: duplicate adapter name '${adapter.name}'; every adapter must have a unique name`);
|
|
138
|
+
}
|
|
139
|
+
seen.add(adapter.name);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Boot every adapter in declaration order, one at a time, awaiting each. The
|
|
145
|
+
* table is validated first; the first rejection stops the boot and propagates
|
|
146
|
+
* (adapters after it are never booted; adapters before it stay booted for the
|
|
147
|
+
* caller's shutdown path to release).
|
|
148
|
+
*/
|
|
149
|
+
export async function bootHarnesses(adapters: readonly HarnessAdapter[], config: Config): Promise<void> {
|
|
150
|
+
assertUniqueNames(adapters);
|
|
151
|
+
for (const adapter of adapters) {
|
|
152
|
+
await adapter.boot(config);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Shut every adapter down in reverse declaration order, continuing past
|
|
158
|
+
* failures. Rejects with one `AggregateError` carrying every failure once all
|
|
159
|
+
* adapters have been given their chance.
|
|
160
|
+
*/
|
|
161
|
+
export async function shutdownHarnesses(adapters: readonly HarnessAdapter[]): Promise<void> {
|
|
162
|
+
const failures: Error[] = [];
|
|
163
|
+
for (const adapter of [...adapters].reverse()) {
|
|
164
|
+
try {
|
|
165
|
+
await adapter.shutdown();
|
|
166
|
+
} catch (err) {
|
|
167
|
+
failures.push(describeFailure(adapter, "shutdown", err));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
throwIfAny(failures, "harness registry: shutdown failed for one or more adapters");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Tell every adapter the session is done on this host, in declaration order,
|
|
175
|
+
* continuing past failures. An adapter that parks nothing per session
|
|
176
|
+
* resolves as a no-op; the registry does not know which do.
|
|
177
|
+
*/
|
|
178
|
+
export async function releaseHarnessSession(adapters: readonly HarnessAdapter[], sessionId: string): Promise<void> {
|
|
179
|
+
const failures: Error[] = [];
|
|
180
|
+
for (const adapter of adapters) {
|
|
181
|
+
try {
|
|
182
|
+
await adapter.releaseSession(sessionId);
|
|
183
|
+
} catch (err) {
|
|
184
|
+
failures.push(describeFailure(adapter, `releaseSession('${sessionId}')`, err));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
throwIfAny(failures, `harness registry: releaseSession('${sessionId}') failed for one or more adapters`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function describeFailure(adapter: HarnessAdapter, call: string, err: unknown): Error {
|
|
191
|
+
const cause = err instanceof Error ? err : new Error(String(err));
|
|
192
|
+
return new Error(`${adapter.name}: ${call} rejected: ${cause.message}`, { cause });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function throwIfAny(failures: readonly Error[], message: string): void {
|
|
196
|
+
if (failures.length > 0) throw new AggregateError(failures, `${message}: ${failures.map((f) => f.message).join("; ")}`);
|
|
197
|
+
}
|