@stigmer/runner 3.14.1 → 3.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-fingerprint +1 -1
- package/dist/__test-utils__/execution-record-fixture.d.ts +47 -0
- package/dist/__test-utils__/execution-record-fixture.js +68 -0
- package/dist/__test-utils__/execution-record-fixture.js.map +1 -0
- package/dist/__test-utils__/harness-contract/types.d.ts +166 -0
- package/dist/__test-utils__/harness-contract/types.js +49 -0
- package/dist/__test-utils__/harness-contract/types.js.map +1 -0
- package/dist/__test-utils__/hermetic-activity.d.ts +28 -5
- package/dist/__test-utils__/hermetic-activity.js +23 -7
- package/dist/__test-utils__/hermetic-activity.js.map +1 -1
- package/dist/__test-utils__/mock-workspace.d.ts +15 -0
- package/dist/__test-utils__/mock-workspace.js +21 -0
- package/dist/__test-utils__/mock-workspace.js.map +1 -0
- package/dist/__test-utils__/model-registry-fixture.d.ts +56 -0
- package/dist/__test-utils__/model-registry-fixture.js +66 -0
- package/dist/__test-utils__/model-registry-fixture.js.map +1 -0
- package/dist/__test-utils__/turn-input-fixture.d.ts +60 -0
- package/dist/__test-utils__/turn-input-fixture.js +118 -0
- package/dist/__test-utils__/turn-input-fixture.js.map +1 -0
- package/dist/activities/execute-cursor/__test-utils__/contract-subject.d.ts +107 -0
- package/dist/activities/execute-cursor/__test-utils__/contract-subject.js +344 -0
- package/dist/activities/execute-cursor/__test-utils__/contract-subject.js.map +1 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +20 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +58 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +2 -32
- package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.d.ts +38 -62
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +47 -121
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/scripted-agent.d.ts +17 -1
- package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js +27 -4
- package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.d.ts +52 -11
- package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js +66 -7
- package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js.map +1 -1
- package/dist/activities/execute-cursor/adapter.d.ts +55 -0
- package/dist/activities/execute-cursor/adapter.js +119 -0
- package/dist/activities/execute-cursor/adapter.js.map +1 -0
- package/dist/activities/execute-cursor/approval-state.d.ts +2 -2
- package/dist/activities/execute-cursor/approval-state.js.map +1 -1
- package/dist/activities/execute-cursor/capture-flow.d.ts +15 -30
- package/dist/activities/execute-cursor/capture-flow.js +17 -26
- package/dist/activities/execute-cursor/capture-flow.js.map +1 -1
- package/dist/activities/execute-cursor/cursor-capabilities.d.ts +23 -0
- package/dist/activities/execute-cursor/cursor-capabilities.js +33 -0
- package/dist/activities/execute-cursor/cursor-capabilities.js.map +1 -0
- package/dist/activities/execute-cursor/cursor-mcp-config.d.ts +2 -2
- package/dist/activities/execute-cursor/cursor-mcp-config.js.map +1 -1
- package/dist/activities/execute-cursor/fetch-interceptor.d.ts +15 -25
- package/dist/activities/execute-cursor/fetch-interceptor.js +39 -33
- package/dist/activities/execute-cursor/fetch-interceptor.js.map +1 -1
- package/dist/activities/execute-cursor/http2-interceptor.d.ts +21 -17
- package/dist/activities/execute-cursor/http2-interceptor.js +38 -31
- package/dist/activities/execute-cursor/http2-interceptor.js.map +1 -1
- package/dist/activities/execute-cursor/message-translator.d.ts +28 -19
- package/dist/activities/execute-cursor/message-translator.js +76 -27
- package/dist/activities/execute-cursor/message-translator.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +180 -11
- package/dist/activities/execute-cursor/prompt-builder.js +171 -0
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/rejection-capture.d.ts +3 -6
- package/dist/activities/execute-cursor/rejection-capture.js +5 -7
- package/dist/activities/execute-cursor/rejection-capture.js.map +1 -1
- package/dist/activities/execute-cursor/session-lifecycle.d.ts +1 -4
- package/dist/activities/execute-cursor/session-lifecycle.js.map +1 -1
- package/dist/activities/execute-cursor/turn-boundary.d.ts +1 -1
- package/dist/activities/execute-cursor/turn-settle.d.ts +49 -0
- package/dist/activities/execute-cursor/turn-settle.js +507 -0
- package/dist/activities/execute-cursor/turn-settle.js.map +1 -0
- package/dist/activities/execute-cursor/turn-setup.d.ts +173 -0
- package/dist/activities/execute-cursor/turn-setup.js +496 -0
- package/dist/activities/execute-cursor/turn-setup.js.map +1 -0
- package/dist/activities/execute-cursor/turn-stream.d.ts +41 -71
- package/dist/activities/execute-cursor/turn-stream.js +70 -135
- package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
- package/dist/activities/execute-cursor/turn.d.ts +43 -0
- package/dist/activities/execute-cursor/turn.js +222 -0
- package/dist/activities/execute-cursor/turn.js.map +1 -0
- package/dist/activities/execute-cursor/usage-pricing.d.ts +38 -0
- package/dist/activities/execute-cursor/usage-pricing.js +50 -0
- package/dist/activities/execute-cursor/usage-pricing.js.map +1 -0
- package/dist/activities/execute-deep-agent/approval-file-change.d.ts +1 -1
- package/dist/activities/execute-deep-agent/approval-file-change.js +1 -1
- package/dist/activities/execute-deep-agent/environment.js +1 -1
- package/dist/activities/execute-deep-agent/environment.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.js +3 -2
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/activities/hydrate-workflow-execution.js +1 -1
- package/dist/activities/hydrate-workflow-execution.js.map +1 -1
- package/dist/config.d.ts +15 -0
- package/dist/config.js.map +1 -1
- package/dist/harness/capabilities.d.ts +16 -0
- package/dist/harness/capabilities.js +1 -0
- package/dist/harness/capabilities.js.map +1 -1
- package/dist/harness/persist-chokepoint.d.ts +74 -0
- package/dist/harness/persist-chokepoint.js +88 -0
- package/dist/harness/persist-chokepoint.js.map +1 -0
- package/dist/harness/registry.d.ts +44 -5
- package/dist/harness/registry.js +61 -5
- package/dist/harness/registry.js.map +1 -1
- package/dist/harness/run-turn.d.ts +53 -0
- package/dist/harness/run-turn.js +528 -0
- package/dist/harness/run-turn.js.map +1 -0
- package/dist/harness/stop-controller.d.ts +50 -0
- package/dist/harness/stop-controller.js +62 -0
- package/dist/harness/stop-controller.js.map +1 -0
- package/dist/harness/terminal-table.d.ts +141 -0
- package/dist/harness/terminal-table.js +239 -0
- package/dist/harness/terminal-table.js.map +1 -0
- package/dist/harness/turn-context.d.ts +523 -0
- package/dist/harness/turn-context.js +739 -0
- package/dist/harness/turn-context.js.map +1 -0
- package/dist/harness/types.d.ts +232 -52
- package/dist/harness/types.js +17 -8
- package/dist/harness/types.js.map +1 -1
- package/dist/harness/usage-accumulator.d.ts +68 -0
- package/dist/harness/usage-accumulator.js +101 -0
- package/dist/harness/usage-accumulator.js.map +1 -0
- package/dist/harness-adapters.d.ts +27 -0
- package/dist/harness-adapters.js +28 -0
- package/dist/harness-adapters.js.map +1 -0
- package/dist/main.js.map +1 -1
- package/dist/runner-manager.js +64 -48
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.d.ts +5 -4
- package/dist/runner.js +65 -64
- package/dist/runner.js.map +1 -1
- package/dist/shared/approval-policy.d.ts +1 -1
- package/dist/shared/approval-policy.js.map +1 -1
- package/dist/{activities/execute-cursor → shared}/attachment-resolver.d.ts +2 -2
- package/dist/{activities/execute-cursor → shared}/attachment-resolver.js +5 -5
- package/dist/shared/attachment-resolver.js.map +1 -0
- package/dist/{activities/execute-cursor → shared}/blueprint-resolver.d.ts +7 -5
- package/dist/{activities/execute-cursor → shared}/blueprint-resolver.js +3 -63
- package/dist/shared/blueprint-resolver.js.map +1 -0
- package/dist/shared/channel-attachment.d.ts +1 -1
- package/dist/shared/channel-attachment.js.map +1 -1
- package/dist/{activities/execute-cursor → shared}/cost-guard.d.ts +5 -1
- package/dist/{activities/execute-cursor → shared}/cost-guard.js +5 -1
- package/dist/shared/cost-guard.js.map +1 -0
- package/dist/{activities/execute-cursor → shared}/env-resolver.d.ts +1 -1
- package/dist/shared/env-resolver.js.map +1 -0
- package/dist/{activities/execute-cursor → shared}/exact-apply.d.ts +1 -1
- package/dist/{activities/execute-cursor → shared}/exact-apply.js +6 -6
- package/dist/shared/exact-apply.js.map +1 -0
- package/dist/shared/execution-context.d.ts +35 -0
- package/dist/shared/execution-context.js +38 -0
- package/dist/shared/execution-context.js.map +1 -0
- package/dist/{activities/execute-cursor → shared}/extract-structured-output.d.ts +5 -2
- package/dist/{activities/execute-cursor → shared}/extract-structured-output.js +9 -6
- package/dist/shared/extract-structured-output.js.map +1 -0
- package/dist/shared/filereview/capture.d.ts +1 -1
- package/dist/shared/filereview/capture.js +1 -1
- package/dist/shared/heartbeat.d.ts +13 -11
- package/dist/shared/heartbeat.js +14 -29
- package/dist/shared/heartbeat.js.map +1 -1
- package/dist/{activities/execute-cursor → shared}/persist-decision.d.ts +5 -2
- package/dist/{activities/execute-cursor → shared}/persist-decision.js +4 -1
- package/dist/shared/persist-decision.js.map +1 -0
- package/dist/{activities/execute-cursor → shared}/skill-resolver.d.ts +16 -3
- package/dist/{activities/execute-cursor → shared}/skill-resolver.js +12 -4
- package/dist/shared/skill-resolver.js.map +1 -0
- package/dist/shared/subagent-rows.d.ts +21 -0
- package/dist/shared/subagent-rows.js +34 -0
- package/dist/shared/subagent-rows.js.map +1 -0
- package/dist/shared/worker-shutdown.d.ts +43 -16
- package/dist/shared/worker-shutdown.js +28 -13
- package/dist/shared/worker-shutdown.js.map +1 -1
- package/dist/shared/workspace/platform-dir.d.ts +1 -1
- package/dist/shared/workspace/platform-dir.js +1 -1
- package/dist/shared/workspace/session-provision.d.ts +54 -0
- package/dist/{activities/execute-cursor/workspace-provision.js → shared/workspace/session-provision.js} +15 -12
- package/dist/shared/workspace/session-provision.js.map +1 -0
- package/package.json +4 -4
- package/src/__test-utils__/__tests__/harness-contract-self-check.test.ts +110 -14
- package/src/__test-utils__/execution-record-fixture.ts +104 -0
- package/src/__test-utils__/harness-boot-order-child.ts +58 -0
- package/src/__test-utils__/harness-contract/contract.ts +128 -83
- package/src/__test-utils__/harness-contract/recording-sink.ts +15 -4
- package/src/__test-utils__/harness-contract/runtime-contract.ts +666 -0
- package/src/__test-utils__/harness-contract/scripted-adapter.ts +92 -32
- package/src/__test-utils__/harness-contract/types.ts +85 -9
- package/src/__test-utils__/hermetic-activity.ts +33 -8
- package/src/__test-utils__/model-registry-fixture.ts +72 -0
- package/src/__test-utils__/module-specifiers.ts +82 -0
- package/src/__test-utils__/turn-input-fixture.ts +155 -0
- package/src/__tests__/harness-boot-order.test.ts +77 -0
- package/src/__tests__/harness-contract.test.ts +7 -5
- package/src/__tests__/runner-manager.test.ts +18 -0
- package/src/activities/execute-cursor/__test-utils__/__tests__/scripted-double.test.ts +102 -0
- package/src/activities/execute-cursor/__test-utils__/contract-subject.ts +396 -0
- package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +66 -0
- package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +2 -43
- package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +65 -145
- package/src/activities/execute-cursor/__test-utils__/scripted-agent.ts +30 -4
- package/src/activities/execute-cursor/__test-utils__/scripted-sdk.ts +96 -8
- package/src/activities/execute-cursor/__tests__/adapter-is-temporal-free.test.ts +45 -0
- package/src/activities/execute-cursor/__tests__/approval-decisions-agree.test.ts +90 -0
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +7 -8
- package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +28 -1
- package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/fetch-interceptor.test.ts +64 -6
- package/src/activities/execute-cursor/__tests__/hermetic/deny-and-retry.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/hermetic/file-review-capture.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/cost-cap.status.json +53 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/pause.status.json +1 -6
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/platform-stop.status.json +56 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/recovery-transport-timeout.status.json +48 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/resolution-error.status.json +18 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/run-cancelled.status.json +36 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/run-error-non-retryable.status.json +37 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/sdk-error-at-create.status.json +38 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/stall.status.json +59 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/unattributed-hook-block.status.json +79 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/worker-shutdown.status.json +2 -7
- package/src/activities/execute-cursor/__tests__/hermetic/harness-contract.test.ts +170 -0
- package/src/activities/execute-cursor/__tests__/hermetic/pause-vs-shutdown.test.ts +22 -18
- package/src/activities/execute-cursor/__tests__/hermetic/plain-turn.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/hermetic/recovery-fresh-agent.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/hermetic/run-wait-arms.test.ts +286 -0
- package/src/activities/execute-cursor/__tests__/hermetic/stream-self-stop-arms.test.ts +332 -0
- package/src/activities/execute-cursor/__tests__/hermetic/thrown-error-arms.test.ts +193 -0
- package/src/activities/execute-cursor/__tests__/hermetic/tool-call.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/hermetic/unattributed-hook-block.test.ts +185 -0
- package/src/activities/execute-cursor/__tests__/hermetic/workspace-lock-timeout.test.ts +151 -0
- package/src/activities/execute-cursor/__tests__/http2-interceptor.test.ts +65 -35
- package/src/activities/execute-cursor/__tests__/message-translator.test.ts +0 -40
- package/src/activities/execute-cursor/__tests__/same-identity-reproposal.test.ts +171 -0
- package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +118 -243
- package/src/activities/execute-cursor/__tests__/usage-pricing.test.ts +84 -0
- package/src/activities/execute-cursor/adapter.ts +138 -0
- package/src/activities/execute-cursor/approval-state.ts +2 -2
- package/src/activities/execute-cursor/capture-flow.ts +18 -41
- package/src/activities/execute-cursor/cursor-capabilities.ts +35 -0
- package/src/activities/execute-cursor/cursor-mcp-config.ts +2 -2
- package/src/activities/execute-cursor/fetch-interceptor.ts +48 -45
- package/src/activities/execute-cursor/http2-interceptor.ts +47 -34
- package/src/activities/execute-cursor/message-translator.ts +90 -45
- package/src/activities/execute-cursor/prompt-builder.ts +327 -12
- package/src/activities/execute-cursor/rejection-capture.ts +5 -12
- package/src/activities/execute-cursor/session-lifecycle.ts +1 -5
- package/src/activities/execute-cursor/turn-boundary.ts +1 -1
- package/src/activities/execute-cursor/turn-settle.ts +601 -0
- package/src/activities/execute-cursor/turn-setup.ts +645 -0
- package/src/activities/execute-cursor/turn-stream.ts +84 -210
- package/src/activities/execute-cursor/turn.ts +240 -0
- package/src/activities/execute-cursor/usage-pricing.ts +67 -0
- package/src/activities/execute-deep-agent/approval-file-change.ts +1 -1
- package/src/activities/execute-deep-agent/environment.ts +1 -1
- package/src/activities/execute-deep-agent/setup.ts +3 -2
- package/src/activities/hydrate-workflow-execution.ts +1 -1
- package/src/config.ts +13 -0
- package/src/harness/__tests__/import-direction.test.ts +168 -0
- package/src/harness/__tests__/persist-chokepoint.test.ts +146 -0
- package/src/harness/__tests__/registry.test.ts +1 -0
- package/src/harness/__tests__/run-turn.test.ts +165 -0
- package/src/harness/__tests__/stop-controller.test.ts +49 -0
- package/src/harness/__tests__/turn-context.test.ts +178 -0
- package/src/harness/__tests__/usage-accumulator.test.ts +83 -0
- package/src/harness/capabilities.ts +17 -0
- package/src/harness/persist-chokepoint.ts +112 -0
- package/src/harness/registry.ts +79 -5
- package/src/harness/run-turn.ts +618 -0
- package/src/harness/stop-controller.ts +83 -0
- package/src/harness/terminal-table.ts +276 -0
- package/src/harness/turn-context.ts +1028 -0
- package/src/harness/types.ts +248 -53
- package/src/harness/usage-accumulator.ts +122 -0
- package/src/harness-adapters.ts +30 -0
- package/src/main.ts +1 -1
- package/src/runner-manager.ts +68 -61
- package/src/runner.ts +67 -78
- package/src/shared/__tests__/attachment-naming.test.ts +1 -1
- package/src/{activities/execute-cursor → shared}/__tests__/attachment-resolver.test.ts +6 -4
- package/src/{activities/execute-cursor → shared}/__tests__/cost-guard.test.ts +2 -1
- package/src/{activities/execute-cursor → shared}/__tests__/exact-apply.test.ts +3 -3
- package/src/{activities/execute-cursor → shared}/__tests__/extract-structured-output.test.ts +8 -8
- package/src/{activities/execute-cursor → shared}/__tests__/persist-decision.test.ts +4 -2
- package/src/{activities/execute-cursor → shared}/__tests__/skill-resolver.test.ts +1 -1
- package/src/shared/__tests__/subagent-rows.test.ts +34 -0
- package/src/shared/__tests__/worker-shutdown.test.ts +44 -39
- package/src/shared/approval-policy.ts +1 -1
- package/src/{activities/execute-cursor → shared}/attachment-resolver.ts +6 -6
- package/src/{activities/execute-cursor → shared}/blueprint-resolver.ts +7 -78
- package/src/shared/channel-attachment.ts +1 -1
- package/src/{activities/execute-cursor → shared}/cost-guard.ts +5 -1
- package/src/{activities/execute-cursor → shared}/env-resolver.ts +1 -1
- package/src/{activities/execute-cursor → shared}/exact-apply.ts +7 -7
- package/src/shared/execution-context.ts +45 -0
- package/src/{activities/execute-cursor → shared}/extract-structured-output.ts +10 -7
- package/src/shared/filereview/capture.ts +1 -1
- package/src/shared/heartbeat.ts +13 -33
- package/src/{activities/execute-cursor → shared}/persist-decision.ts +5 -2
- package/src/{activities/execute-cursor → shared}/skill-resolver.ts +20 -6
- package/src/shared/subagent-rows.ts +39 -0
- package/src/shared/worker-shutdown.ts +55 -25
- package/src/{activities/execute-cursor/__tests__/workspace-provision.test.ts → shared/workspace/__tests__/session-provision.test.ts} +25 -28
- package/src/shared/workspace/platform-dir.ts +1 -1
- package/src/{activities/execute-cursor/workspace-provision.ts → shared/workspace/session-provision.ts} +29 -16
- package/dist/activities/execute-cursor/attachment-resolver.js.map +0 -1
- package/dist/activities/execute-cursor/blueprint-resolver.js.map +0 -1
- package/dist/activities/execute-cursor/cost-guard.js.map +0 -1
- package/dist/activities/execute-cursor/env-resolver.js.map +0 -1
- package/dist/activities/execute-cursor/exact-apply.js.map +0 -1
- package/dist/activities/execute-cursor/extract-structured-output.js.map +0 -1
- package/dist/activities/execute-cursor/index.d.ts +0 -209
- package/dist/activities/execute-cursor/index.js +0 -2345
- package/dist/activities/execute-cursor/index.js.map +0 -1
- package/dist/activities/execute-cursor/persist-decision.js.map +0 -1
- package/dist/activities/execute-cursor/skill-resolver.js.map +0 -1
- package/dist/activities/execute-cursor/usage-accumulator.d.ts +0 -89
- package/dist/activities/execute-cursor/usage-accumulator.js +0 -121
- package/dist/activities/execute-cursor/usage-accumulator.js.map +0 -1
- package/dist/activities/execute-cursor/workspace-provision.d.ts +0 -42
- package/dist/activities/execute-cursor/workspace-provision.js.map +0 -1
- package/src/activities/execute-cursor/__tests__/usage-accumulator.test.ts +0 -164
- package/src/activities/execute-cursor/index.ts +0 -2876
- package/src/activities/execute-cursor/usage-accumulator.ts +0 -169
- /package/dist/{activities/execute-cursor → shared}/env-resolver.js +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import type http2Type from "node:http2";
|
|
4
|
-
import { installHttp2Interceptor, uninstallHttp2Interceptor,
|
|
5
|
-
import { getExecutionContext } from "
|
|
4
|
+
import { installHttp2Interceptor, uninstallHttp2Interceptor, assertHttp2ConnectPatched } from "../http2-interceptor.js";
|
|
5
|
+
import { getExecutionContext } from "../../../shared/execution-context.js";
|
|
6
6
|
|
|
7
7
|
const require = createRequire(import.meta.url);
|
|
8
8
|
const http2: typeof http2Type = require("node:http2");
|
|
@@ -53,27 +53,56 @@ describe("http2-interceptor", () => {
|
|
|
53
53
|
describe("installHttp2Interceptor", () => {
|
|
54
54
|
it("is a no-op when proxyEndpoint is undefined", () => {
|
|
55
55
|
const before = http2.connect;
|
|
56
|
-
installHttp2Interceptor({ proxyEndpoint: undefined,
|
|
56
|
+
installHttp2Interceptor({ proxyEndpoint: undefined, proxyTokenRef: { current: STIGMER_TOKEN } });
|
|
57
57
|
expect(http2.connect).toBe(before);
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
it("is a no-op when proxyEndpoint is empty string", () => {
|
|
61
61
|
const before = http2.connect;
|
|
62
|
-
installHttp2Interceptor({ proxyEndpoint: "",
|
|
62
|
+
installHttp2Interceptor({ proxyEndpoint: "", proxyTokenRef: { current: STIGMER_TOKEN } });
|
|
63
63
|
expect(http2.connect).toBe(before);
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
it("is a no-op when
|
|
66
|
+
it("is a no-op when no token ref is bound", () => {
|
|
67
67
|
const before = http2.connect;
|
|
68
|
-
installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT,
|
|
68
|
+
installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: undefined });
|
|
69
69
|
expect(http2.connect).toBe(before);
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
-
it("
|
|
72
|
+
it("is a no-op when the bound ref is empty at install", () => {
|
|
73
73
|
const before = http2.connect;
|
|
74
|
-
installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT,
|
|
74
|
+
installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: { current: null } });
|
|
75
|
+
expect(http2.connect).toBe(before);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("replaces http2.connect when proxyEndpoint and a bound ref are valid", () => {
|
|
79
|
+
const before = http2.connect;
|
|
80
|
+
installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: { current: STIGMER_TOKEN } });
|
|
75
81
|
expect(http2.connect).not.toBe(before);
|
|
76
82
|
});
|
|
83
|
+
|
|
84
|
+
it("is idempotent: a second install rebinds the ref and does not wrap a proxy session twice", async () => {
|
|
85
|
+
const mock = createMockConnect();
|
|
86
|
+
http2.connect = mock.mockConnect;
|
|
87
|
+
const first = { current: "first-token" };
|
|
88
|
+
const second = { current: "second-token" };
|
|
89
|
+
installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: first });
|
|
90
|
+
const patched = http2.connect;
|
|
91
|
+
|
|
92
|
+
installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: second });
|
|
93
|
+
// The one patch stays; a re-capture here would have made the patched
|
|
94
|
+
// connect the "original" and wrapped every proxy session twice.
|
|
95
|
+
expect(http2.connect).toBe(patched);
|
|
96
|
+
|
|
97
|
+
const session = http2.connect(PROXY_ENDPOINT);
|
|
98
|
+
session.request({ ":path": "/agent.v1.AgentService/Run" });
|
|
99
|
+
expect(mock.calls).toHaveLength(1);
|
|
100
|
+
expect(mock.calls[0].headers).toHaveProperty("x-stigmer-auth", "Bearer second-token");
|
|
101
|
+
|
|
102
|
+
// Uninstall restores the connect that was live at the FIRST install.
|
|
103
|
+
uninstallHttp2Interceptor();
|
|
104
|
+
expect(http2.connect).toBe(mock.mockConnect);
|
|
105
|
+
});
|
|
77
106
|
});
|
|
78
107
|
|
|
79
108
|
describe("uninstallHttp2Interceptor", () => {
|
|
@@ -81,7 +110,7 @@ describe("http2-interceptor", () => {
|
|
|
81
110
|
const { mockConnect } = createMockConnect();
|
|
82
111
|
http2.connect = mockConnect;
|
|
83
112
|
|
|
84
|
-
installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT,
|
|
113
|
+
installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: { current: STIGMER_TOKEN } });
|
|
85
114
|
expect(http2.connect).not.toBe(mockConnect);
|
|
86
115
|
|
|
87
116
|
uninstallHttp2Interceptor();
|
|
@@ -95,7 +124,7 @@ describe("http2-interceptor", () => {
|
|
|
95
124
|
beforeEach(() => {
|
|
96
125
|
mock = createMockConnect();
|
|
97
126
|
http2.connect = mock.mockConnect;
|
|
98
|
-
installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT,
|
|
127
|
+
installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: { current: STIGMER_TOKEN } });
|
|
99
128
|
});
|
|
100
129
|
|
|
101
130
|
it("injects x-stigmer-execution-id and x-stigmer-auth when context is active and target is proxy", async () => {
|
|
@@ -188,7 +217,7 @@ describe("http2-interceptor", () => {
|
|
|
188
217
|
beforeEach(() => {
|
|
189
218
|
mock = createMockConnect();
|
|
190
219
|
http2.connect = mock.mockConnect;
|
|
191
|
-
installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT,
|
|
220
|
+
installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: { current: STIGMER_TOKEN } });
|
|
192
221
|
});
|
|
193
222
|
|
|
194
223
|
it("different requests on same session get execution IDs from their respective ALS contexts", async () => {
|
|
@@ -229,7 +258,7 @@ describe("http2-interceptor", () => {
|
|
|
229
258
|
it("matches https with explicit port", async () => {
|
|
230
259
|
const mock = createMockConnect();
|
|
231
260
|
http2.connect = mock.mockConnect;
|
|
232
|
-
installHttp2Interceptor({ proxyEndpoint: "https://proxy.example.com:9443",
|
|
261
|
+
installHttp2Interceptor({ proxyEndpoint: "https://proxy.example.com:9443", proxyTokenRef: { current: STIGMER_TOKEN } });
|
|
233
262
|
|
|
234
263
|
const executionContext = getExecutionContext();
|
|
235
264
|
await executionContext.run({ executionId: "exec-port" }, async () => {
|
|
@@ -243,7 +272,7 @@ describe("http2-interceptor", () => {
|
|
|
243
272
|
it("matches https with default port (443)", async () => {
|
|
244
273
|
const mock = createMockConnect();
|
|
245
274
|
http2.connect = mock.mockConnect;
|
|
246
|
-
installHttp2Interceptor({ proxyEndpoint: "https://proxy.example.com",
|
|
275
|
+
installHttp2Interceptor({ proxyEndpoint: "https://proxy.example.com", proxyTokenRef: { current: STIGMER_TOKEN } });
|
|
247
276
|
|
|
248
277
|
const executionContext = getExecutionContext();
|
|
249
278
|
await executionContext.run({ executionId: "exec-default-port" }, async () => {
|
|
@@ -257,7 +286,7 @@ describe("http2-interceptor", () => {
|
|
|
257
286
|
it("matches http with explicit port", async () => {
|
|
258
287
|
const mock = createMockConnect();
|
|
259
288
|
http2.connect = mock.mockConnect;
|
|
260
|
-
installHttp2Interceptor({ proxyEndpoint: "http://localhost:9090",
|
|
289
|
+
installHttp2Interceptor({ proxyEndpoint: "http://localhost:9090", proxyTokenRef: { current: STIGMER_TOKEN } });
|
|
261
290
|
|
|
262
291
|
const executionContext = getExecutionContext();
|
|
263
292
|
await executionContext.run({ executionId: "exec-http" }, async () => {
|
|
@@ -271,7 +300,7 @@ describe("http2-interceptor", () => {
|
|
|
271
300
|
it("does not match when port differs", async () => {
|
|
272
301
|
const mock = createMockConnect();
|
|
273
302
|
http2.connect = mock.mockConnect;
|
|
274
|
-
installHttp2Interceptor({ proxyEndpoint: "https://proxy.example.com:8443",
|
|
303
|
+
installHttp2Interceptor({ proxyEndpoint: "https://proxy.example.com:8443", proxyTokenRef: { current: STIGMER_TOKEN } });
|
|
275
304
|
|
|
276
305
|
const executionContext = getExecutionContext();
|
|
277
306
|
await executionContext.run({ executionId: "exec-wrong-port" }, async () => {
|
|
@@ -283,31 +312,30 @@ describe("http2-interceptor", () => {
|
|
|
283
312
|
});
|
|
284
313
|
});
|
|
285
314
|
|
|
286
|
-
describe("
|
|
315
|
+
describe("the token ref is read per request (the root rotates it in place; Q-S2-7)", () => {
|
|
287
316
|
let mock: ReturnType<typeof createMockConnect>;
|
|
317
|
+
let proxyTokenRef: { current: string | null };
|
|
288
318
|
|
|
289
319
|
beforeEach(() => {
|
|
290
320
|
mock = createMockConnect();
|
|
291
321
|
http2.connect = mock.mockConnect;
|
|
292
|
-
|
|
322
|
+
proxyTokenRef = { current: STIGMER_TOKEN };
|
|
323
|
+
installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef });
|
|
293
324
|
});
|
|
294
325
|
|
|
295
|
-
it("
|
|
326
|
+
it("a session opened BEFORE a rotation carries the new token on its next request", () => {
|
|
296
327
|
const session = http2.connect(PROXY_ENDPOINT);
|
|
297
328
|
session.request({ ":path": "/agent.v1.AgentService/Run" });
|
|
298
|
-
|
|
299
329
|
expect(mock.calls[0].headers).toHaveProperty("x-stigmer-auth", `Bearer ${STIGMER_TOKEN}`);
|
|
300
330
|
|
|
301
|
-
|
|
302
|
-
updateHttp2InterceptorToken(refreshedToken);
|
|
303
|
-
|
|
331
|
+
proxyTokenRef.current = "refreshed-stigmer-jwt-token";
|
|
304
332
|
session.request({ ":path": "/agent.v1.AgentService/Run" });
|
|
305
333
|
|
|
306
|
-
expect(mock.calls[1].headers).toHaveProperty("x-stigmer-auth",
|
|
334
|
+
expect(mock.calls[1].headers).toHaveProperty("x-stigmer-auth", "Bearer refreshed-stigmer-jwt-token");
|
|
307
335
|
});
|
|
308
336
|
|
|
309
|
-
it("
|
|
310
|
-
|
|
337
|
+
it("a session opened AFTER a rotation carries the new token", () => {
|
|
338
|
+
proxyTokenRef.current = "new-token-value";
|
|
311
339
|
|
|
312
340
|
const session = http2.connect(PROXY_ENDPOINT);
|
|
313
341
|
session.request({ ":path": "/aiserver.v1.AnalyticsService/BootstrapStatsig" });
|
|
@@ -315,15 +343,16 @@ describe("http2-interceptor", () => {
|
|
|
315
343
|
expect(mock.calls[0].headers).toHaveProperty("x-stigmer-auth", "Bearer new-token-value");
|
|
316
344
|
});
|
|
317
345
|
|
|
318
|
-
it("
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
346
|
+
it("an emptied ref omits x-stigmer-auth and keeps the execution id (the proxy's 401 is the diagnostic)", async () => {
|
|
347
|
+
proxyTokenRef.current = null;
|
|
348
|
+
const executionContext = getExecutionContext();
|
|
349
|
+
await executionContext.run({ executionId: "exec-empty-ref" }, async () => {
|
|
350
|
+
const session = http2.connect(PROXY_ENDPOINT);
|
|
351
|
+
session.request({ ":path": "/agent.v1.AgentService/Run" });
|
|
352
|
+
});
|
|
325
353
|
|
|
326
354
|
expect(mock.calls[0].headers).not.toHaveProperty("x-stigmer-auth");
|
|
355
|
+
expect(mock.calls[0].headers).toHaveProperty("x-stigmer-execution-id", "exec-empty-ref");
|
|
327
356
|
});
|
|
328
357
|
});
|
|
329
358
|
|
|
@@ -333,8 +362,9 @@ describe("http2-interceptor", () => {
|
|
|
333
362
|
// and CASE A/B in the probe). That makes the positive "configured + in-sync"
|
|
334
363
|
// path impossible to assert deterministically inside a shared test process
|
|
335
364
|
// — it is guaranteed by load order (interceptor installed before connect-node
|
|
336
|
-
// imports node:http2)
|
|
337
|
-
//
|
|
365
|
+
// imports node:http2), exercised by the boot guard inside the Cursor
|
|
366
|
+
// adapter's boot, and proven in a FRESH process by
|
|
367
|
+
// src/__tests__/harness-boot-order.test.ts. Here we cover the two deterministic contracts: it is a no-op
|
|
338
368
|
// when unconfigured (no import, so it never freezes the facade), and it
|
|
339
369
|
// throws when the frozen facade is out of sync with the patched connect.
|
|
340
370
|
//
|
|
@@ -359,7 +389,7 @@ describe("http2-interceptor", () => {
|
|
|
359
389
|
|
|
360
390
|
const mock = createMockConnect();
|
|
361
391
|
http2.connect = mock.mockConnect;
|
|
362
|
-
installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT,
|
|
392
|
+
installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: { current: STIGMER_TOKEN } });
|
|
363
393
|
|
|
364
394
|
await expect(assertHttp2ConnectPatched()).rejects.toThrow(
|
|
365
395
|
/node:http2 ESM facade is unpatched/,
|
|
@@ -26,9 +26,7 @@ import type { SDKMessage } from "@cursor/sdk";
|
|
|
26
26
|
import {
|
|
27
27
|
MessageAccumulator,
|
|
28
28
|
extractConversationSteps,
|
|
29
|
-
cancelInProgressSubAgentProtos,
|
|
30
29
|
} from "../message-translator.js";
|
|
31
|
-
import { SubAgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
|
|
32
30
|
|
|
33
31
|
function toolCallEvent(
|
|
34
32
|
callId: string,
|
|
@@ -1032,42 +1030,4 @@ describe("MessageAccumulator tool call status transitions", () => {
|
|
|
1032
1030
|
});
|
|
1033
1031
|
});
|
|
1034
1032
|
|
|
1035
|
-
describe("cancelInProgressSubAgentProtos standalone", () => {
|
|
1036
|
-
it("cancels IN_PROGRESS/PENDING protos in place and reports whether anything changed", () => {
|
|
1037
|
-
const running = create(SubAgentExecutionSchema, {
|
|
1038
|
-
id: "a",
|
|
1039
|
-
status: SubAgentStatus.SUB_AGENT_IN_PROGRESS,
|
|
1040
|
-
});
|
|
1041
|
-
const pending = create(SubAgentExecutionSchema, {
|
|
1042
|
-
id: "b",
|
|
1043
|
-
status: SubAgentStatus.SUB_AGENT_PENDING,
|
|
1044
|
-
});
|
|
1045
|
-
const completed = create(SubAgentExecutionSchema, {
|
|
1046
|
-
id: "c",
|
|
1047
|
-
status: SubAgentStatus.SUB_AGENT_COMPLETED,
|
|
1048
|
-
completedAt: "2026-01-01T00:00:00Z",
|
|
1049
|
-
});
|
|
1050
|
-
|
|
1051
|
-
const list = [running, pending, completed];
|
|
1052
|
-
const changed = cancelInProgressSubAgentProtos(list);
|
|
1053
|
-
|
|
1054
|
-
expect(changed).toBe(true);
|
|
1055
|
-
expect(running.status).toBe(SubAgentStatus.SUB_AGENT_CANCELLED);
|
|
1056
|
-
expect(running.completedAt).not.toBe("");
|
|
1057
|
-
expect(pending.status).toBe(SubAgentStatus.SUB_AGENT_CANCELLED);
|
|
1058
|
-
// Terminal sub-agents are untouched.
|
|
1059
|
-
expect(completed.status).toBe(SubAgentStatus.SUB_AGENT_COMPLETED);
|
|
1060
|
-
expect(completed.completedAt).toBe("2026-01-01T00:00:00Z");
|
|
1061
|
-
});
|
|
1062
|
-
|
|
1063
|
-
it("returns false when there is nothing to cancel", () => {
|
|
1064
|
-
const completed = create(SubAgentExecutionSchema, {
|
|
1065
|
-
id: "c",
|
|
1066
|
-
status: SubAgentStatus.SUB_AGENT_COMPLETED,
|
|
1067
|
-
});
|
|
1068
|
-
expect(cancelInProgressSubAgentProtos([completed])).toBe(false);
|
|
1069
|
-
expect(cancelInProgressSubAgentProtos([])).toBe(false);
|
|
1070
|
-
});
|
|
1071
|
-
});
|
|
1072
|
-
|
|
1073
1033
|
});
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A later, same-identity proposal is a NEW act: it never lands on a row the
|
|
3
|
+
* user already decided (S2 M4 finding F9).
|
|
4
|
+
*
|
|
5
|
+
* The defect the harness contract kit found against this adapter: within one
|
|
6
|
+
* execution, after the user APPROVED `edit a.txt` and the resumed agent ran
|
|
7
|
+
* it (its row COMPLETED, still carrying `approval_action = APPROVE`), the
|
|
8
|
+
* model proposed the SAME edit again in a later turn. The hook denied it
|
|
9
|
+
* (correct: no grant). The denial overlay then matched the denial to the
|
|
10
|
+
* FIRST same-identity row in the whole transcript — the completed, approved
|
|
11
|
+
* one — flipped it back to WAITING_APPROVAL with its APPROVE still on it and
|
|
12
|
+
* wiped its result; the actual streamed call was hidden as a twin. On the next
|
|
13
|
+
* invocation `approvalDecisionsOf` read that row as a decision and the new,
|
|
14
|
+
* never-approved proposal ran under the old approval. The accumulator had the
|
|
15
|
+
* sibling flaw: a re-issue after a SKIP or REJECT was reconciled onto the
|
|
16
|
+
* declined row by identity, inheriting its decision.
|
|
17
|
+
*
|
|
18
|
+
* The rule, in `message-translator.ts`: a row the server has adjudicated
|
|
19
|
+
* (`isAdjudicatedRow`) is never the home of THIS turn's denial and never a
|
|
20
|
+
* twin to collapse; a row the user DECLINED (`isDeclinedRow`) is never the
|
|
21
|
+
* home of a later same-identity call. The approved re-issue (the
|
|
22
|
+
* `sequential-gate-resume` shape) still reconciles onto its APPROVE row — the
|
|
23
|
+
* one case where the later call IS the decided act.
|
|
24
|
+
*
|
|
25
|
+
* Each arm ends by deriving the decisions the runtime would read from the
|
|
26
|
+
* reconciled transcript (`approvalDecisionsOf`): the assertion that matters
|
|
27
|
+
* is that nothing decided bleeds onto the new proposal.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { describe, it, expect } from "vitest";
|
|
31
|
+
import { create, clone } from "@bufbuild/protobuf";
|
|
32
|
+
import { AgentMessageSchema, ToolCallSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
33
|
+
import type { AgentMessage, ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
34
|
+
import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
35
|
+
import { ApprovalAction, MessageType, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
36
|
+
import type { SDKMessage } from "@cursor/sdk";
|
|
37
|
+
|
|
38
|
+
import { approvalDecisionsOf } from "../../../harness/turn-context.js";
|
|
39
|
+
import { MessageAccumulator, reconcileDeniedToolCalls, toolCallIdentityToken } from "../message-translator.js";
|
|
40
|
+
import type { DeniedLedgerEntry } from "../approval-state.js";
|
|
41
|
+
|
|
42
|
+
const PATH = "/work/a.txt";
|
|
43
|
+
const CONTENT = "A\n";
|
|
44
|
+
const FIRST_ID = "tool_edit_first";
|
|
45
|
+
const LATER_ID = "tool_edit_later";
|
|
46
|
+
const RESULT_OF_FIRST = "wrote a.txt";
|
|
47
|
+
|
|
48
|
+
function toolCallEvent(
|
|
49
|
+
callId: string,
|
|
50
|
+
status: "running" | "completed" | "error",
|
|
51
|
+
result?: unknown,
|
|
52
|
+
): Extract<SDKMessage, { type: "tool_call" }> {
|
|
53
|
+
return { type: "tool_call", agent_id: "agent-1", run_id: "r3", call_id: callId, name: "edit", status, args: { path: PATH, content: CONTENT }, result };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The transcript after the first gate was decided `action` and, for an APPROVE, run. */
|
|
57
|
+
function decidedFirstGate(action: ApprovalAction): AgentMessage[] {
|
|
58
|
+
const executed = action === ApprovalAction.APPROVE;
|
|
59
|
+
return [
|
|
60
|
+
create(AgentMessageSchema, {
|
|
61
|
+
type: MessageType.MESSAGE_AI,
|
|
62
|
+
content: "I'll write file A.",
|
|
63
|
+
toolCalls: [
|
|
64
|
+
create(ToolCallSchema, {
|
|
65
|
+
id: FIRST_ID,
|
|
66
|
+
name: "edit",
|
|
67
|
+
status: executed ? ToolCallStatus.TOOL_CALL_COMPLETED : ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
|
|
68
|
+
requiresApproval: true,
|
|
69
|
+
approvalAction: action,
|
|
70
|
+
args: { path: PATH, content: CONTENT },
|
|
71
|
+
argsPreview: JSON.stringify({ path: PATH, content: CONTENT }),
|
|
72
|
+
result: executed ? RESULT_OF_FIRST : "",
|
|
73
|
+
completedAt: executed ? "2026-06-20T00:00:05.000Z" : "",
|
|
74
|
+
}),
|
|
75
|
+
],
|
|
76
|
+
}),
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function allToolCalls(messages: AgentMessage[]): ToolCall[] {
|
|
81
|
+
return messages.flatMap((m) => m.toolCalls);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The later turn: the model proposes the same edit under a fresh id, the hook denies it, the turn boundary reconciles. */
|
|
85
|
+
async function laterTurnProposesSameEdit(seeded: AgentMessage[]): Promise<ToolCall[]> {
|
|
86
|
+
const acc = new MessageAccumulator(seeded, { seededSubAgents: [] });
|
|
87
|
+
acc.processEvent(toolCallEvent(LATER_ID, "running"));
|
|
88
|
+
acc.processEvent(toolCallEvent(LATER_ID, "error", "Blocked by hook"));
|
|
89
|
+
acc.finalize();
|
|
90
|
+
const ledger: DeniedLedgerEntry[] = [{ toolName: "Write", token: toolCallIdentityToken(allToolCalls(seeded).find((tc) => tc.id === LATER_ID)!) }];
|
|
91
|
+
return reconcileDeniedToolCalls(seeded, ledger);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function decisionsTheRuntimeWouldRead(messages: AgentMessage[]): ReadonlyMap<string, ApprovalAction> {
|
|
95
|
+
return approvalDecisionsOf(create(AgentExecutionStatusSchema, { messages }));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
describe("a later same-identity proposal never lands on a decided row (F9)", () => {
|
|
99
|
+
it("after APPROVE and execution: the completed row keeps its result and decision; the new call gets its own gate; nothing bleeds", async () => {
|
|
100
|
+
const seeded = decidedFirstGate(ApprovalAction.APPROVE).map((m) => clone(AgentMessageSchema, m));
|
|
101
|
+
|
|
102
|
+
const gated = await laterTurnProposesSameEdit(seeded);
|
|
103
|
+
|
|
104
|
+
const byId = new Map(allToolCalls(seeded).map((tc) => [tc.id, tc]));
|
|
105
|
+
const first = byId.get(FIRST_ID)!;
|
|
106
|
+
expect(first.status, "the executed row is the transcript's record and is never rewritten").toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
107
|
+
expect(first.result).toBe(RESULT_OF_FIRST);
|
|
108
|
+
expect(first.approvalAction).toBe(ApprovalAction.APPROVE);
|
|
109
|
+
|
|
110
|
+
const later = byId.get(LATER_ID);
|
|
111
|
+
expect(later, "the new act has its own row").toBeDefined();
|
|
112
|
+
expect(later!.status, "and it is the turn's one gate").toBe(ToolCallStatus.TOOL_CALL_WAITING_APPROVAL);
|
|
113
|
+
expect(later!.approvalAction, "undecided — the user has not been asked yet").toBe(ApprovalAction.UNSPECIFIED);
|
|
114
|
+
expect(gated.map((tc) => tc.id)).toEqual([LATER_ID]);
|
|
115
|
+
|
|
116
|
+
expect(decisionsTheRuntimeWouldRead(seeded).size, "no decision bleeds onto the new proposal").toBe(0);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it.each([
|
|
120
|
+
["SKIP", ApprovalAction.SKIP],
|
|
121
|
+
["REJECT", ApprovalAction.REJECT],
|
|
122
|
+
] as const)("after %s: the re-issue is not reconciled onto the declined row; it gets its own gate and inherits nothing", async (_label, action) => {
|
|
123
|
+
const seeded = decidedFirstGate(action).map((m) => clone(AgentMessageSchema, m));
|
|
124
|
+
|
|
125
|
+
const gated = await laterTurnProposesSameEdit(seeded);
|
|
126
|
+
|
|
127
|
+
const byId = new Map(allToolCalls(seeded).map((tc) => [tc.id, tc]));
|
|
128
|
+
const first = byId.get(FIRST_ID)!;
|
|
129
|
+
expect(first.status, "the declined gate is untouched").toBe(ToolCallStatus.TOOL_CALL_WAITING_APPROVAL);
|
|
130
|
+
expect(first.approvalAction).toBe(action);
|
|
131
|
+
expect(first.error, "the later denial was not merged onto it").toBe("");
|
|
132
|
+
|
|
133
|
+
const later = byId.get(LATER_ID);
|
|
134
|
+
expect(later, "the re-issue is a new act with its own row").toBeDefined();
|
|
135
|
+
expect(later!.status).toBe(ToolCallStatus.TOOL_CALL_WAITING_APPROVAL);
|
|
136
|
+
expect(later!.approvalAction).toBe(ApprovalAction.UNSPECIFIED);
|
|
137
|
+
expect(gated.map((tc) => tc.id)).toEqual([LATER_ID]);
|
|
138
|
+
|
|
139
|
+
const decisions = decisionsTheRuntimeWouldRead(seeded);
|
|
140
|
+
expect(decisions.get(FIRST_ID), "the declined row's decision stays its own").toBe(action);
|
|
141
|
+
expect(decisions.has(LATER_ID), "and is not the new proposal's").toBe(false);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("the approved re-issue itself still reconciles onto its APPROVE row (the sequential-gate-resume shape is unchanged)", () => {
|
|
145
|
+
const seeded = [
|
|
146
|
+
create(AgentMessageSchema, {
|
|
147
|
+
type: MessageType.MESSAGE_AI,
|
|
148
|
+
toolCalls: [
|
|
149
|
+
create(ToolCallSchema, {
|
|
150
|
+
id: FIRST_ID,
|
|
151
|
+
name: "edit",
|
|
152
|
+
status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
|
|
153
|
+
requiresApproval: true,
|
|
154
|
+
approvalAction: ApprovalAction.APPROVE,
|
|
155
|
+
argsPreview: JSON.stringify({ path: PATH, content: CONTENT }),
|
|
156
|
+
}),
|
|
157
|
+
],
|
|
158
|
+
}),
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
const acc = new MessageAccumulator(seeded, { seededSubAgents: [] });
|
|
162
|
+
acc.processEvent(toolCallEvent(`${FIRST_ID}_RESUME`, "running"));
|
|
163
|
+
acc.processEvent(toolCallEvent(`${FIRST_ID}_RESUME`, "completed", RESULT_OF_FIRST));
|
|
164
|
+
acc.finalize();
|
|
165
|
+
|
|
166
|
+
const tools = allToolCalls(seeded);
|
|
167
|
+
expect(tools.map((tc) => tc.id), "one row, the committed id").toEqual([FIRST_ID]);
|
|
168
|
+
expect(tools[0]!.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
169
|
+
expect(tools[0]!.result).toBe(RESULT_OF_FIRST);
|
|
170
|
+
});
|
|
171
|
+
});
|