From 1162aa5d5f27cc567240127f2fee2ba7f3fc9793 Mon Sep 17 00:00:00 2001 From: Microck Date: Thu, 5 Nov 2026 04:14:57 +0100 Subject: [PATCH 1/9] test(release): add ACP load compatibility guard --- scripts/personal-release-load.ts | 39 ++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 scripts/personal-release-load.ts diff --git a/scripts/personal-release-load.ts b/scripts/personal-release-load.ts new file mode 100644 index 000000000000..69c71c312cad --- /dev/null +++ b/scripts/personal-release-load.ts @@ -0,0 +1,39 @@ +// Preserve the carried fix: replay silence is not a session/load response. +export function normalizeLoadResponse(source: string): string { + const marker = " const runLoadSessionWithReplayIdle = ("; + if (!source.includes(marker)) { + if ( + source.includes("waitForSessionLoadReplayIdle") || + source.includes("runLoadSessionWithReplayIdle") + ) + throw new Error("Unexpected ACP load compatibility shape"); + return source; + } + const before = ` const idleFiber = yield* waitForSessionLoadReplayIdle({ + gateRef: sessionLoadGateRef, + }).pipe(Effect.forkIn(runtimeScope)); + const loaded = yield* Effect.raceFirst( + acp.agent.loadSession(loadPayload), + Fiber.join(idleFiber), + ).pipe( + Effect.ensuring(Fiber.interrupt(idleFiber).pipe(Effect.ignore)),`; + const after = ` // Replay silence does not acknowledge session/load or its configuration. + const loaded = yield* acp.agent.loadSession(loadPayload).pipe(`; + const replacements: ReadonlyArray = [ + [" waitForSessionLoadReplayIdle,\n", ""], + [before, after], + [ + "session/load timed out waiting for RPC response or replay idle gap", + "session/load timed out waiting for RPC response", + ], + ]; + let result = source; + for (const [from, to] of replacements) { + if (result.split(from).length !== 2) + throw new Error(`Unexpected ACP load compatibility shape: ${from}`); + result = result.replace(from, to); + } + if (result.includes("waitForSessionLoadReplayIdle") || result.includes("Fiber.join(idleFiber)")) + throw new Error("Unexpected ACP load compatibility shape: residual idle race"); + return result; +} From 0bcdfd719bcda37dfe85dc851e67ff4a2a7497ff Mon Sep 17 00:00:00 2001 From: Microck Date: Thu, 5 Nov 2026 04:15:03 +0100 Subject: [PATCH 2/9] test(release): add ACP load compatibility tests --- scripts/personal-release-load.node-check.ts | 37 +++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 scripts/personal-release-load.node-check.ts diff --git a/scripts/personal-release-load.node-check.ts b/scripts/personal-release-load.node-check.ts new file mode 100644 index 000000000000..104c7e7b8be9 --- /dev/null +++ b/scripts/personal-release-load.node-check.ts @@ -0,0 +1,37 @@ +import * as Assert from "node:assert/strict"; +import * as Test from "node:test"; +import { normalizeLoadResponse } from "./personal-release-load.ts"; + +const legacy = ` waitForSessionLoadReplayIdle, + const runLoadSessionWithReplayIdle = ( + const idleFiber = yield* waitForSessionLoadReplayIdle({ + gateRef: sessionLoadGateRef, + }).pipe(Effect.forkIn(runtimeScope)); + const loaded = yield* Effect.raceFirst( + acp.agent.loadSession(loadPayload), + Fiber.join(idleFiber), + ).pipe( + Effect.ensuring(Fiber.interrupt(idleFiber).pipe(Effect.ignore)), + detail: "session/load timed out waiting for RPC response or replay idle gap", +`; +Test.test("restores response-bound session loading", () => { + const result = normalizeLoadResponse(legacy); + Assert.match(result, /yield\* acp\.agent\.loadSession\(loadPayload\)\.pipe/); + Assert.doesNotMatch(result, /Fiber\.join\(idleFiber\)|waitForSessionLoadReplayIdle/); + Assert.match(result, /timed out waiting for RPC response"/); +}); +Test.test("leaves already-correct runtime alone", () => { + const current = "const runLoadSession = () => acp.agent.loadSession(loadPayload);"; + Assert.equal(normalizeLoadResponse(current), current); +}); +Test.test("rejects unrecognized race shape", () => { + Assert.throws(() => + normalizeLoadResponse(legacy.replace("Fiber.join(idleFiber)", "Fiber.join(otherFiber)")), + ); + Assert.throws(() => + normalizeLoadResponse(legacy.replace("runLoadSessionWithReplayIdle", "runLoadSessionChanged")), + ); + Assert.throws(() => + normalizeLoadResponse(legacy + "\nconst extra = waitForSessionLoadReplayIdle;"), + ); +}); From c3908700fbf902212b8b45344bc69bea79186bff Mon Sep 17 00:00:00 2001 From: Microck Date: Thu, 5 Nov 2026 04:15:16 +0100 Subject: [PATCH 3/9] fix(release): preserve response-bound ACP loading --- scripts/personal-release.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scripts/personal-release.ts b/scripts/personal-release.ts index 3ad3486937a3..c6462895acab 100644 --- a/scripts/personal-release.ts +++ b/scripts/personal-release.ts @@ -3,6 +3,8 @@ import * as NodeChildProcess from "node:child_process"; import * as NodeFS from "node:fs"; +import { normalizeLoadResponse } from "./personal-release-load.ts"; + const upstream = "pingdotgg/t3code"; const fork = "Microck/t3code"; const fixes: ReadonlyArray<{ @@ -377,6 +379,14 @@ function prepare() { git("add", acpDriver); git("commit", "-m", "fix(acp): retain current random and digest services"); } + const acpRuntime = "packages/provider-acp/src/server/AcpSessionRuntime.ts"; + const runtimeSource = NodeFS.readFileSync(acpRuntime, "utf8"); + const normalizedRuntime = normalizeLoadResponse(runtimeSource); + if (normalizedRuntime !== runtimeSource) { + NodeFS.writeFileSync(acpRuntime, normalizedRuntime); + git("add", acpRuntime); + git("commit", "-m", "fix(acp): await the session load response"); + } // The CLI release index and downloads must point at the same distribution. replaceRequired( "packages/shared/src/cliRelease.ts", From b6c110e339dce53b61723f26222b98c03e907483 Mon Sep 17 00:00:00 2001 From: Microck Date: Thu, 5 Nov 2026 04:15:29 +0100 Subject: [PATCH 4/9] test(release): verify ACP load compatibility guard --- .github/workflows/personal-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/personal-release.yml b/.github/workflows/personal-release.yml index 6c4aa8651b22..176d72d051f2 100644 --- a/.github/workflows/personal-release.yml +++ b/.github/workflows/personal-release.yml @@ -47,7 +47,7 @@ jobs: node-version: "24" - run: npm install --global @github/copilot@1.0.101 - name: Test release compatibility normalization - run: node --test scripts/personal-release-random.node-check.ts + run: node --test scripts/personal-release-random.node-check.ts scripts/personal-release-load.node-check.ts - name: Prepare official source with pending fixes id: prepare env: From b3d655cb97cbf761fd88309d5f5be7f20444a2fe Mon Sep 17 00:00:00 2001 From: Microck Date: Thu, 5 Nov 2026 04:23:46 +0100 Subject: [PATCH 5/9] test(release): register load compatibility test separately --- .github/workflows/personal-release.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/personal-release.yml b/.github/workflows/personal-release.yml index 176d72d051f2..b8db55806691 100644 --- a/.github/workflows/personal-release.yml +++ b/.github/workflows/personal-release.yml @@ -47,7 +47,9 @@ jobs: node-version: "24" - run: npm install --global @github/copilot@1.0.101 - name: Test release compatibility normalization - run: node --test scripts/personal-release-random.node-check.ts scripts/personal-release-load.node-check.ts + run: node --test scripts/personal-release-random.node-check.ts + - name: Test ACP load compatibility + run: node --test scripts/personal-release-load.node-check.ts - name: Prepare official source with pending fixes id: prepare env: From 27a22959518228e62de45e5156e84932cd1030b3 Mon Sep 17 00:00:00 2001 From: Microck Date: Thu, 5 Nov 2026 04:32:54 +0100 Subject: [PATCH 6/9] test(release): follow node import conventions --- scripts/personal-release-load.node-check.ts | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/scripts/personal-release-load.node-check.ts b/scripts/personal-release-load.node-check.ts index 104c7e7b8be9..a5b17a8b0027 100644 --- a/scripts/personal-release-load.node-check.ts +++ b/scripts/personal-release-load.node-check.ts @@ -1,5 +1,5 @@ -import * as Assert from "node:assert/strict"; -import * as Test from "node:test"; +import * as NodeAssert from "node:assert/strict"; +import * as NodeTest from "node:test"; import { normalizeLoadResponse } from "./personal-release-load.ts"; const legacy = ` waitForSessionLoadReplayIdle, @@ -14,24 +14,24 @@ const legacy = ` waitForSessionLoadReplayIdle, Effect.ensuring(Fiber.interrupt(idleFiber).pipe(Effect.ignore)), detail: "session/load timed out waiting for RPC response or replay idle gap", `; -Test.test("restores response-bound session loading", () => { +NodeTest.test("restores response-bound session loading", () => { const result = normalizeLoadResponse(legacy); - Assert.match(result, /yield\* acp\.agent\.loadSession\(loadPayload\)\.pipe/); - Assert.doesNotMatch(result, /Fiber\.join\(idleFiber\)|waitForSessionLoadReplayIdle/); - Assert.match(result, /timed out waiting for RPC response"/); + NodeAssert.match(result, /yield\* acp\.agent\.loadSession\(loadPayload\)\.pipe/); + NodeAssert.doesNotMatch(result, /Fiber\.join\(idleFiber\)|waitForSessionLoadReplayIdle/); + NodeAssert.match(result, /timed out waiting for RPC response"/); }); -Test.test("leaves already-correct runtime alone", () => { +NodeTest.test("leaves already-correct runtime alone", () => { const current = "const runLoadSession = () => acp.agent.loadSession(loadPayload);"; - Assert.equal(normalizeLoadResponse(current), current); + NodeAssert.equal(normalizeLoadResponse(current), current); }); -Test.test("rejects unrecognized race shape", () => { - Assert.throws(() => +NodeTest.test("rejects unrecognized race shape", () => { + NodeAssert.throws(() => normalizeLoadResponse(legacy.replace("Fiber.join(idleFiber)", "Fiber.join(otherFiber)")), ); - Assert.throws(() => + NodeAssert.throws(() => normalizeLoadResponse(legacy.replace("runLoadSessionWithReplayIdle", "runLoadSessionChanged")), ); - Assert.throws(() => + NodeAssert.throws(() => normalizeLoadResponse(legacy + "\nconst extra = waitForSessionLoadReplayIdle;"), ); }); From ec585c6992ddef449f7a5fc293d19576673aae60 Mon Sep 17 00:00:00 2001 From: Microck Date: Thu, 5 Nov 2026 04:33:43 +0100 Subject: [PATCH 7/9] fix(release): make load guard idempotent --- scripts/personal-release-load.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scripts/personal-release-load.ts b/scripts/personal-release-load.ts index 69c71c312cad..5ddd724c7d7d 100644 --- a/scripts/personal-release-load.ts +++ b/scripts/personal-release-load.ts @@ -9,6 +9,16 @@ export function normalizeLoadResponse(source: string): string { throw new Error("Unexpected ACP load compatibility shape"); return source; } + if (!source.includes("waitForSessionLoadReplayIdle")) { + const directLoad = " const loaded = yield* acp.agent.loadSession(loadPayload).pipe("; + if ( + source.split(directLoad).length === 2 && + !source.includes("Fiber.join(idleFiber)") && + !source.includes("RPC response or replay idle gap") + ) + return source; + throw new Error("Unexpected ACP load compatibility shape: missing idle helper"); + } const before = ` const idleFiber = yield* waitForSessionLoadReplayIdle({ gateRef: sessionLoadGateRef, }).pipe(Effect.forkIn(runtimeScope)); From 63e769c0157d00820704446adc99487d66965909 Mon Sep 17 00:00:00 2001 From: Microck Date: Thu, 5 Nov 2026 04:33:55 +0100 Subject: [PATCH 8/9] test(release): verify load guard is idempotent --- scripts/personal-release-load.node-check.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/personal-release-load.node-check.ts b/scripts/personal-release-load.node-check.ts index a5b17a8b0027..6646c4adc76a 100644 --- a/scripts/personal-release-load.node-check.ts +++ b/scripts/personal-release-load.node-check.ts @@ -19,6 +19,7 @@ NodeTest.test("restores response-bound session loading", () => { NodeAssert.match(result, /yield\* acp\.agent\.loadSession\(loadPayload\)\.pipe/); NodeAssert.doesNotMatch(result, /Fiber\.join\(idleFiber\)|waitForSessionLoadReplayIdle/); NodeAssert.match(result, /timed out waiting for RPC response"/); + NodeAssert.equal(normalizeLoadResponse(result), result); }); NodeTest.test("leaves already-correct runtime alone", () => { const current = "const runLoadSession = () => acp.agent.loadSession(loadPayload);"; From 7c8c03da9a79efac75adaef475bb27ea328fc1a6 Mon Sep 17 00:00:00 2001 From: Microck Date: Thu, 5 Nov 2026 04:55:02 +0100 Subject: [PATCH 9/9] fix(release): include ACP load helper in runtime copy --- .github/workflows/personal-release.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/personal-release.yml b/.github/workflows/personal-release.yml index b8db55806691..ba31cbe348b1 100644 --- a/.github/workflows/personal-release.yml +++ b/.github/workflows/personal-release.yml @@ -49,7 +49,12 @@ jobs: - name: Test release compatibility normalization run: node --test scripts/personal-release-random.node-check.ts - name: Test ACP load compatibility - run: node --test scripts/personal-release-load.node-check.ts + run: | + node --test scripts/personal-release-load.node-check.ts + temp="$(mktemp -d)" + trap 'rm -rf "$temp"' EXIT + cp scripts/personal-release.ts scripts/personal-release-load.ts "$temp/" + node --input-type=module -e 'await import(process.argv[1])' "$temp/personal-release.ts" - name: Prepare official source with pending fixes id: prepare env: @@ -59,6 +64,7 @@ jobs: run: | gh auth setup-git cp scripts/personal-release.ts "$RUNNER_TEMP/personal-release.ts" + cp scripts/personal-release-load.ts "$RUNNER_TEMP/" cp scripts/personal-return.* "$RUNNER_TEMP/" node "$RUNNER_TEMP/personal-release.ts" - uses: actions/upload-artifact@v7