diff --git a/packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.test.ts b/packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.test.ts index 2d73db9f73..c3a6418251 100644 --- a/packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.test.ts @@ -199,26 +199,39 @@ describe("Codex ACPX runtime adapter", () => { expect(assertWorkspaceHeld).toHaveBeenCalledOnce(); expect(command.spawn).not.toHaveBeenCalled(); }); - it("maps status, model selection, and state-preserving close", async () => { + it("reads verified status from durable state without draining live updates", async () => { const runtime = fakeRuntime(); - vi.mocked(runtime.getStatus!).mockResolvedValue({ - models: { - currentModelId: "gpt-5.6-sol", - availableModelIds: ["gpt-5.6-sol"], + vi.mocked(runtime.getStatus!).mockReturnValue(new Promise(() => {})); + const durableRecord = { + acpxRecordId: "record-1", + acpSessionId: "backend-1", + agentSessionId: "agent-1", + acpx: { + current_model_id: "gpt-5.6-sol", + available_models: ["gpt-5.6-sol"], }, - }); + } as never; + const durableStore: AcpSessionStore = { + load: vi.fn(async () => structuredClone(durableRecord)), + save: vi.fn(), + }; const port = await openCodexAcpxRuntime(openOptions(fakeCommand()), { createRegistry: () => registry(), - createStore: () => store(), + createStore: () => durableStore, createRuntime: () => runtime, }); - expect(await port.getStatus()).toEqual({ + expect(await port.getStatus()).toMatchObject({ + acpxRecordId: "record-1", + backendSessionId: "backend-1", + agentSessionId: "agent-1", models: { currentModelId: "gpt-5.6-sol", availableModelIds: ["gpt-5.6-sol"], }, }); + expect(durableStore.load).toHaveBeenCalledWith("record-1"); + expect(runtime.getStatus).not.toHaveBeenCalled(); await port.setModel?.("gpt-5.6-sol"); expect(runtime.setConfigOption).toHaveBeenCalledWith({ handle: HANDLE, diff --git a/packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.ts b/packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.ts index ee5e4f0d68..83ab1efc08 100644 --- a/packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.ts +++ b/packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.ts @@ -23,6 +23,7 @@ import { awaitVerifiedAcpxProviderExit, awaitVerifiedAcpxProviderOwnership, } from "./installation-integrity.js"; +import type { AcpxModelStatus } from "./model-verification.js"; import { decideAcpxPermission } from "./permission-policy.js"; const VERIFIED_COMMAND_SENTINEL = "paperclip-verified-acpx-command"; @@ -366,6 +367,7 @@ export async function openQualifiedAcpxRuntime( runtime, handle, requireIdentity(handle), + baseStore, children, runtimeCloseTimeoutMs, ); @@ -774,6 +776,7 @@ function runtimePort( runtime: AcpRuntime, handle: AcpRuntimeHandle, identity: AcpxRuntimePortIdentity, + sessionStore: AcpSessionStore, children: SpawnedChildSet, runtimeCloseTimeoutMs: number, ): AcpxRuntimePort { @@ -1004,10 +1007,7 @@ function runtimePort( return structuredClone(identity); }, async getStatus() { - if (!runtime.getStatus) { - throw new Error("The pinned ACPX runtime cannot report session status"); - } - return structuredClone(await runtime.getStatus({ handle })); + return await persistedRuntimeStatus(sessionStore, handle, identity); }, ...(runtime.setConfigOption ? { @@ -1035,6 +1035,50 @@ function runtimePort( return port; } +async function persistedRuntimeStatus( + sessionStore: AcpSessionStore, + handle: AcpRuntimeHandle, + identity: AcpxRuntimePortIdentity, +): Promise { + const recordId = handle.acpxRecordId ?? handle.sessionKey; + const record = await sessionStore.load(recordId); + if (!record) { + throw new Error( + "The pinned ACPX runtime omitted its persisted session record", + ); + } + if ( + record.acpxRecordId !== identity.acpxRecordId || + record.acpSessionId !== identity.backendSessionId || + record.agentSessionId !== identity.agentSessionId + ) { + throw new Error( + "The persisted ACPX session identity changed after admission", + ); + } + const currentModelId = record.acpx?.current_model_id; + const availableModelIds = record.acpx?.available_models; + return { + summary: [ + `session=${record.acpxRecordId}`, + `backendSessionId=${record.acpSessionId}`, + `agentSessionId=${record.agentSessionId}`, + record.closed === true ? "closed" : "open", + ].join(" "), + acpxRecordId: record.acpxRecordId, + backendSessionId: record.acpSessionId, + agentSessionId: record.agentSessionId, + ...(currentModelId === undefined && !availableModelIds?.length + ? {} + : { + models: { + ...(currentModelId === undefined ? {} : { currentModelId }), + availableModelIds: availableModelIds ? [...availableModelIds] : [], + }, + }), + }; +} + function runtimeCloseOutcome( runtime: AcpRuntime, input: Parameters[0],