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 b0ea480a234..6c8f9295f73 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 @@ -952,6 +952,45 @@ describe("Codex ACPX runtime adapter", () => { } finally { await port.close({ reason: "fixture done" }); } }); + it.each(["cancel", "closeStream"] as const)("retains the bound Grok usage response during %s until the prompt settles", async (stop) => { + const runtime = fakeRuntime(); + const durableRecord = { acpxRecordId: "record-1", acpSessionId: "backend-1", agentSessionId: "agent-1", lastRequestId: "previous" }; + const durableStore: AcpSessionStore = { load: vi.fn(async () => structuredClone(durableRecord) as never), save: vi.fn() }; + let created!: AcpRuntimeOptions; + let settle!: (value: { status: "completed" }) => void; + const result = new Promise<{ status: "completed" }>(resolve => { settle = resolve; }); + const options = openOptions(fakeCommand()); + options.profile = { ...options.profile, agent: "grok" }; + const port = await openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => durableStore, + createRuntime: value => { created = value; return runtime; } }); + vi.mocked(runtime.startTurn).mockImplementation(input => { + durableRecord.lastRequestId = input.requestId; + created.onAcpMessage?.("outbound", { id: 1, method: "session/prompt", params: { sessionId: "backend-1" } }); + return { requestId: input.requestId, promptStarted: Promise.resolve(), events: (async function* () {})(), result, + cancel: async () => undefined, closeStream: async () => undefined }; + }); + try { + const before = await port.getStatus(); + const turn = port.startTurn({ requestId: "runner-cancelled", text: "fixture" }); + await turn[stop]({ reason: "fixture stopped" }); + created.onAcpMessage?.("outbound", { id: 2, method: "session/prompt", params: { sessionId: "backend-1" } }); + created.onAcpMessage?.("inbound", { id: 1, result: { _meta: { + sessionId: "backend-1", promptId: "prompt-1", requestId: "prompt-1", + usage: { inputTokens: 50, outputTokens: 5, totalTokens: 55, cachedReadTokens: 20, cacheCreationTokens: 0, + reasoningTokens: 2, modelCalls: 1, costUsdTicks: 10000000 }, + } } }); + settle({ status: "completed" }); + await turn.result; + const after = await port.getStatus(); + expect(persistedAcpxTurnUsage(before, after, "runner-cancelled")).toMatchObject({ + cost: { amount: 0.001, currency: "USD", basis: "turn" }, + breakdown: { inputTokens: 30, outputTokens: 5, cachedReadTokens: 20, thoughtTokens: 0 }, + }); + created.onAcpMessage?.("inbound", { id: 1, error: { message: "late conflicting response" } }); + expect(await port.getStatus()).toEqual(after); + } finally { settle({ status: "completed" }); await port.close({ reason: "fixture done" }); } + }); + it("reads verified status from durable state without draining live updates", async () => { const runtime = fakeRuntime(); vi.mocked(runtime.getStatus!).mockReturnValue(new Promise(() => {})); 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 ba1e97ce2ac..48d3eea115c 100644 --- a/packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.ts +++ b/packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.ts @@ -470,7 +470,7 @@ export async function openQualifiedAcpxRuntime( onSessionNotification: acceptGoalNotification, onAcpMessage: (direction, message) => { const active = extensionBoundary.active; - if (active && !active.signal.aborted) promptUsage?.observe(direction, message); + if (active && (direction === "inbound" || !active.signal.aborted)) promptUsage?.observe(direction, message); }, spawnEnvironment: () => ({ ...definedEnvironment(options.launchEnvironment),