import { spawn, type ChildProcess } from "node:child_process"; import { EventEmitter } from "node:events"; import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { createServer } from "node:http"; import { createRequire } from "node:module"; import { tmpdir } from "node:os"; import { join } from "node:path"; import type { AcpAgentRegistry, AcpRuntime, AcpRuntimeHandle, AcpRuntimeOptions, AcpSessionStore, } from "acpx/runtime"; import { decodeAcpxRuntimeHandleState } from "acpx/runtime"; import { describe, expect, it, vi } from "vitest"; import type { VerifiedAcpxCommandLease } from "./installation-integrity.js"; import { openCodexAcpxRuntime } from "./codex-runtime-adapter.js"; import { cursorInstructionBinding } from "./cursor-instructions.js"; import { createAcpxCommandLeaseOwner } from "./command-lease-owner.js"; import { resolveQualifiedAcpxProfile } from "./qualified-profiles.js"; import type { AcpxRuntimePortOpenOptions } from "./runtime-host.js"; const HANDLE: AcpRuntimeHandle = { sessionKey: "session-key", backend: "acpx", runtimeSessionName: "runtime-name", cwd: "/workspace", acpxRecordId: "record-1", backendSessionId: "backend-1", agentSessionId: "agent-1", }; describe("Codex ACPX runtime adapter", () => { it("retains the provider cause before assigning the generic session failure code", async () => { const runtime = fakeRuntime(); const rejection = Object.assign(new Error("Internal error"), { code: -32603, data: { details: "API error: 429 This request would exceed your account's rate limit." } }); vi.mocked(runtime.ensureSession).mockRejectedValue(rejection); const options = openOptions(fakeCommand()); options.profile = { ...options.profile, agent: "claude" }; await expect(openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), createRuntime: () => runtime })).rejects.toMatchObject({ code: "ACPX_SESSION_ENSURE_FAILED", providerFailure: { provider: "anthropic", category: "rate_limit", httpStatus: 429, message: rejection.data.details, phase: "startup" }, }); expect(runtime.startTurn).not.toHaveBeenCalled(); }); it("keeps a redacted explanation for an ensure rejection that carries no ACP error", async () => { const runtime = fakeRuntime(); const options = openOptions(fakeCommand()); options.profile = { ...options.profile, agent: "claude" }; options.launchEnvironment = { ANTHROPIC_API_KEY: "fixture-anthropic-secret" }; const rejection = new Error("Claude Code exited during startup for fixture-anthropic-secret (sk-ant-api03-fixtureprivate)"); vi.mocked(runtime.ensureSession).mockRejectedValue(rejection); const failure = await openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), createRuntime: () => runtime }).catch((error: unknown) => error); expect(failure).toBe(rejection); expect(failure).toMatchObject({ code: "ACPX_SESSION_ENSURE_FAILED", providerFailure: { provider: "anthropic", phase: "startup", category: "unknown", message: "Claude Code exited during startup for [REDACTED] ([REDACTED])" } }); expect(JSON.stringify((failure as { providerFailure: unknown }).providerFailure)).not.toMatch(/fixture-anthropic-secret|fixtureprivate/); }); it("classifies an ACP authentication-required ensure rejection", async () => { const runtime = fakeRuntime(); const message = "Authentication required: This integration does not support using claude.ai subscriptions."; vi.mocked(runtime.ensureSession).mockRejectedValue(Object.assign(new Error(message), { code: -32000 })); const options = openOptions(fakeCommand()); options.profile = { ...options.profile, agent: "claude" }; await expect(openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), createRuntime: () => runtime })).rejects.toMatchObject({ code: "ACPX_SESSION_ENSURE_FAILED", providerFailure: { category: "authentication", message, phase: "startup", retryability: "requires_action" } }); }); it("attaches a provider cause to a non-error ensure rejection", async () => { const runtime = fakeRuntime(); vi.mocked(runtime.ensureSession).mockRejectedValue({ code: -32603, message: "Internal error", data: { errorKind: "billing_error" } }); const options = openOptions(fakeCommand()); options.profile = { ...options.profile, agent: "claude" }; await expect(openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), createRuntime: () => runtime })).rejects.toMatchObject({ code: "ACPX_SESSION_ENSURE_NON_ERROR", providerFailure: { category: "quota_exhausted", providerCode: "billing_error", phase: "startup" } }); }); it("does not report Runner cancellation as a provider explanation", async () => { const runtime = fakeRuntime(); const controller = new AbortController(); const reason = new Error("runtime admission cancelled"); const rejection = new Error("ACP connection closed"); vi.mocked(runtime.ensureSession).mockImplementation(async () => { controller.abort(reason); throw rejection; }); const options = openOptions(fakeCommand()); options.signal = controller.signal; await expect(openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), createRuntime: () => runtime })).rejects.toBe(reason); await vi.waitFor(() => expect(rejection).toHaveProperty("code", "ACPX_SESSION_ENSURE_FAILED")); expect(rejection).not.toHaveProperty("providerFailure"); expect(reason).not.toHaveProperty("providerFailure"); }); it.each([false, true])("acknowledges configured Claude effort before admission (restoring=%s)", async restoring => { const runtime = fakeRuntime(); const child = fakeChild(); const options = openOptions({ spawn: () => child, close: async () => undefined }); options.profile = { ...options.profile, agent: "claude" }; options.claudeEffort = "high"; options.restoringSession = restoring; options.refreshConsumedCommand = vi.fn(async () => undefined); let created!: AcpRuntimeOptions; const spawnProvider = () => created.spawnAgent!({ command: "ignored", args: [], options: {} }); vi.mocked(runtime.ensureSession).mockImplementation(async () => { if (!restoring) spawnProvider(); return HANDLE; }); vi.mocked(runtime.setConfigOption).mockImplementation(async input => { if (restoring) spawnProvider(); const guard = created.protocolGuardFactory!(); guard("outbound", { id: 0, method: "session/load", params: { sessionId: "backend-1" } }); guard("inbound", { id: 0, result: { configOptions: [{ id: "effort", currentValue: "default" }] } }); guard("outbound", { id: 1, method: "session/set_config_option", params: { sessionId: "backend-1", configId: input.key, value: input.value } }); guard("inbound", { id: 1, result: { configOptions: [{ id: "effort", currentValue: input.value }] } }); }); const port = await openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), createRuntime: value => { created = value; return runtime; }, awaitProviderOwnership: providerOwnershipEstablished, awaitProviderExit: providerOwnershipEstablished, }); expect(runtime.setConfigOption).toHaveBeenCalledExactlyOnceWith({ handle: HANDLE, key: "effort", value: "high" }); expect(await port.identity()).toMatchObject({ claudeEffort: "high" }); if (restoring) expect(options.refreshConsumedCommand).toHaveBeenCalledOnce(); else expect(options.refreshConsumedCommand).not.toHaveBeenCalled(); expect(runtime.startTurn).not.toHaveBeenCalled(); await port.close({ reason: "test complete" }); }); it.each([false, true])("acknowledges configured Grok effort before admission (restoring=%s)", async restoring => { const runtime = Object.assign(fakeRuntime(), { requestExtension: vi.fn(async () => ({})) }); const child = fakeChild(); const options = openOptions({ spawn: () => child, close: async () => undefined }); options.profile = resolveQualifiedAcpxProfile("grok", "grok-4.7"); options.grokEffort = "high"; options.restoringSession = restoring; options.refreshConsumedCommand = vi.fn(async () => undefined); let created!: AcpRuntimeOptions; const spawnProvider = () => created.spawnAgent!({ command: "ignored", args: [], options: {} }); const acknowledge = () => { const guard = created.protocolGuardFactory!(); guard("outbound", { id: 0, method: "session/load", params: { sessionId: "backend-1" } }); guard("inbound", { id: 0, result: { models: { currentModelId: "grok-4.7" }, _meta: { "x.ai/sessionConfig": { options: [{ id: "high", category: "mode", selected: true }] } } } }); }; vi.mocked(runtime.ensureSession).mockImplementation(async input => { expect(input.sessionOptions).toMatchObject({ model: "grok-4.7" }); if (!restoring) { spawnProvider(); acknowledge(); } return HANDLE; }); runtime.requestExtension.mockImplementation(async () => { spawnProvider(); acknowledge(); return {}; }); const port = await openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), createRuntime: value => { created = value; return runtime; }, awaitProviderOwnership: providerOwnershipEstablished, awaitProviderExit: providerOwnershipEstablished, }); expect(runtime.setConfigOption).not.toHaveBeenCalled(); expect(await port.identity()).toMatchObject({ grokEffort: "high" }); if (restoring) { expect(runtime.requestExtension).toHaveBeenCalledExactlyOnceWith({ handle: HANDLE, method: "_x.ai/session/info", params: { sessionId: "backend-1" }, sessionMode: "persistent" }); expect(options.refreshConsumedCommand).toHaveBeenCalledOnce(); } else { expect(runtime.requestExtension).not.toHaveBeenCalled(); expect(options.refreshConsumedCommand).not.toHaveBeenCalled(); } expect(runtime.startTurn).not.toHaveBeenCalled(); await port.close({ reason: "test complete" }); }); it("admits an exact Claude model through the real ACP without a user model allowlist", async () => { const root = await mkdtemp(join(tmpdir(), "preconfigured-claude-model-")); const home = join(root, "home"), cwd = join(root, "workspace"), config = join(home, ".claude"); await Promise.all([mkdir(config, { recursive: true }), mkdir(cwd)]); const settings = JSON.stringify({ model: "claude-sonnet-5-5", permissions: { deny: ["Bash"] } }); await writeFile(join(config, "settings.json"), settings); // Model validation can reach only this local fixture, never a provider. const api = createServer(async (request, response) => { for await (const _chunk of request) { /* drain validation input */ } response.writeHead(200, { "Content-Type": "application/json" }); response.end(JSON.stringify({ id: "msg_fixture", type: "message", role: "assistant", model: "claude-sonnet-5-5", content: [{ type: "text", text: "OK" }], stop_reason: "end_turn", usage: { input_tokens: 1, output_tokens: 1 } })); }); await new Promise(resolve => api.listen(0, "127.0.0.1", resolve)); const address = api.address() as { port: number }; const entry = createRequire(import.meta.url).resolve("@agentclientprotocol/claude-agent-acp/dist/index.js"); const command: VerifiedAcpxCommandLease = { spawn: (args, input) => spawn(process.execPath, [entry, ...(args ?? [])], { ...input, detached: true, stdio: ["pipe", "pipe", "pipe"] }), close: async () => undefined, }; const options = { ...openOptions(command), profile: resolveQualifiedAcpxProfile("claude", "claude-sonnet-5-5"), cwd, stateDirectory: join(root, "state"), permissionMode: "approve-all" as const, launchEnvironment: { HOME: home, CLAUDE_CONFIG_DIR: config, PATH: "/usr/bin:/bin", TMPDIR: root, PAPERCLIP_RUNNER_HOME_MODE: "preconfigured", ANTHROPIC_API_KEY: "local-fixture-only", ANTHROPIC_BASE_URL: `http://127.0.0.1:${address.port}`, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" }, }; let port: Awaited> | undefined; const priorSettingsFlag = process.env.ACPX_CLAUDE_INCLUDE_USER_SETTINGS; process.env.ACPX_CLAUDE_INCLUDE_USER_SETTINGS = "1"; try { port = await openCodexAcpxRuntime(options, { // This test exercises ACP/model admission; lifetime-fence tests are separate. awaitProviderOwnership: async () => undefined, awaitProviderExit: child => new Promise(resolve => child.once("exit", () => resolve())), }); expect(await port.getStatus()).toMatchObject({ models: { currentModelId: "claude-sonnet-5-5" } }); expect(await readFile(join(config, "settings.json"), "utf8")).toBe(settings); } finally { try { await port?.close({ reason: "exact model verified" }); } finally { if (priorSettingsFlag === undefined) delete process.env.ACPX_CLAUDE_INCLUDE_USER_SETTINGS; else process.env.ACPX_CLAUDE_INCLUDE_USER_SETTINGS = priorSettingsFlag; api.closeAllConnections(); await new Promise(resolve => api.close(() => resolve())); // Provider shutdown can flush metadata just after the ACP process exits. await rm(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 }); } } }, 30_000); it("rejects preconfigured Codex before creating a provider runtime", async () => { const createRuntime = vi.fn(); const options = openOptions(fakeCommand()); options.launchEnvironment = { HOME: "/users/executor", PAPERCLIP_RUNNER_HOME_MODE: "preconfigured" }; await expect(openCodexAcpxRuntime(options, { createRuntime })).rejects.toThrow("cannot protect Runner state"); expect(createRuntime).not.toHaveBeenCalled(); }); it.each(["blocked", "ended"] as const)("settles a %s Copilot stream on verified provider death without replaying the permission", async (streamState) => { const pending = pendingExtensionTurn("death-turn"); if (streamState === "ended") pending.turn.events = (async function* () {})(); const runtime = fakeRuntime(); vi.mocked(runtime.startTurn).mockReturnValue(pending.turn); const child = fakeChild(); const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValue(child); let exit!: () => void; const providerExit = new Promise(resolve => { exit = resolve; }); let created!: AcpRuntimeOptions; const options = openOptions(command); options.profile = { ...options.profile, agent: "copilot" }; options.permissionMode = "approve-paperclip"; const port = await openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), awaitProviderOwnership: providerOwnershipEstablished, awaitProviderExit: () => providerExit, createRuntime: value => { created = value; return runtimeWithProvider(runtime, value); }, }); let answer!: (value: { outcome: "allow_once" }) => void; let callbackSignal!: AbortSignal; const handler = vi.fn((_request, context) => { callbackSignal = context.signal; return new Promise<{ outcome: "allow_once" }>(resolve => { answer = resolve; }); }); const turn = port.startTurn({ text: "Write the file", requestId: "death-turn", onPermissionRequest: handler }); await turn.promptStarted; const events = turn.events[Symbol.asyncIterator](); const draining = expect(events.next()).rejects.toMatchObject({ code: "ACPX_PROVIDER_PROCESS_LOST" }); const settled = expect(turn.result).rejects.toMatchObject({ code: "ACPX_PROVIDER_PROCESS_LOST" }); const request = created.onPermissionRequest!({ sessionId: "backend-1", raw: { sessionId: "backend-1" }, inferredKind: "edit" } as never, { signal: new AbortController().signal }); await vi.waitFor(() => expect(handler).toHaveBeenCalledOnce()); // The guardian remains live: only independent provider EOF proves death. expect(child.exitCode).toBeNull(); exit(); await Promise.all([draining, settled]); expect(callbackSignal.aborted).toBe(true); answer({ outcome: "allow_once" }); await expect(request).resolves.toEqual({ outcome: "cancel" }); expect(() => port.startTurn({ text: "Replay", requestId: "replay" })).toThrow("provider exited"); expect(runtime.startTurn).toHaveBeenCalledOnce(); pending.settle(); await port.close({ reason: "test finished" }); expect(child.kill).toHaveBeenCalled(); }); it("refuses Copilot replay when native disconnection precedes the independent exit proof", async () => { const pending = pendingExtensionTurn("disconnect-turn"); let disconnect!: () => void; const failed = new Promise<{ status: "failed"; error: { code: "RUNTIME"; detailCode: "AGENT_DISCONNECTED"; message: string } }>(resolve => { disconnect = () => resolve({ status: "failed", error: { code: "RUNTIME", detailCode: "AGENT_DISCONNECTED", message: "Native ACP transport disconnected" } }); }); const runtime = fakeRuntime(); vi.mocked(runtime.startTurn).mockReturnValue({ ...pending.turn, result: failed }); const child = fakeChild(); const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValue(child); let exit!: () => void; const providerExit = new Promise(resolve => { exit = resolve; }); let created!: AcpRuntimeOptions; const options = openOptions(command); options.profile = { ...options.profile, agent: "copilot" }; options.permissionMode = "approve-paperclip"; const port = await openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), awaitProviderOwnership: providerOwnershipEstablished, awaitProviderExit: () => providerExit, createRuntime: value => { created = value; return runtimeWithProvider(runtime, value); }, }); let answer!: (value: { outcome: "allow_once" }) => void; let callbackSignal!: AbortSignal; const handler = vi.fn((_request, context) => { callbackSignal = context.signal; return new Promise<{ outcome: "allow_once" }>(resolve => { answer = resolve; }); }); try { const turn = port.startTurn({ text: "Write", requestId: "disconnect-turn", onPermissionRequest: handler }); await turn.promptStarted; const request = created.onPermissionRequest!({ sessionId: "backend-1", raw: { sessionId: "backend-1" }, inferredKind: "edit" } as never, { signal: new AbortController().signal }); await vi.waitFor(() => expect(handler).toHaveBeenCalledOnce()); disconnect(); await expect(turn.result).rejects.toMatchObject({ code: "ACPX_PROVIDER_PROCESS_LOST" }); await expect((async () => { for await (const _event of turn.events) { /* drain */ } })()) .rejects.toMatchObject({ code: "ACPX_PROVIDER_PROCESS_LOST" }); expect(callbackSignal.aborted).toBe(true); answer({ outcome: "allow_once" }); await expect(request).resolves.toEqual({ outcome: "cancel" }); expect(() => port.startTurn({ text: "Replay", requestId: "replay" })).toThrow("provider exited"); expect(runtime.startTurn).toHaveBeenCalledOnce(); // A typed disconnect does not manufacture independent process-exit proof. // Cleanup still owns the live guardian and waits for the separate proof. expect(child.exitCode).toBeNull(); } finally { pending.settle(); exit(); await port.close({ reason: "disconnect race verified" }); } }); it("keeps a Copilot callback deliverable when only the guardian exits", async () => { const pending = pendingExtensionTurn("preserved-turn"); const runtime = fakeRuntime(); vi.mocked(runtime.startTurn).mockReturnValue(pending.turn); let created!: AcpRuntimeOptions; const child = fakeChild(); const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValue(child); let exit!: () => void; const providerExit = new Promise(resolve => { exit = resolve; }); const options = openOptions(command); options.profile = { ...options.profile, agent: "copilot" }; options.permissionMode = "approve-paperclip"; const port = await openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), awaitProviderOwnership: providerOwnershipEstablished, awaitProviderExit: () => providerExit, createRuntime: value => { created = value; return runtimeWithProvider(runtime, value); }, }); let answer!: (value: { outcome: "allow_once" }) => void; const handler = vi.fn(() => new Promise<{ outcome: "allow_once" }>(resolve => { answer = resolve; })); const turn = port.startTurn({ text: "Write", requestId: "preserved-turn", onPermissionRequest: handler }); const request = created.onPermissionRequest!({ sessionId: "backend-1", raw: { sessionId: "backend-1" }, inferredKind: "edit" } as never, { signal: new AbortController().signal }); await vi.waitFor(() => expect(handler).toHaveBeenCalledOnce()); child.exitCode = 0; child.emit("exit", 0, null); // Guardian exit cannot replace the provider-only lifetime observation. await new Promise(resolve => setTimeout(resolve, 10)); answer({ outcome: "allow_once" }); await expect(request).resolves.toEqual({ outcome: "allow_once" }); pending.settle(); await expect(turn.result).resolves.toMatchObject({ status: "completed" }); exit(); await port.close({ reason: "test finished" }); }); it("installs per-connection policy authority only for Copilot and rejects native commands before a turn starts", async () => { for (const agent of ["copilot", "codex", "claude", "grok"] as const) { const runtime = fakeRuntime(); let created!: AcpRuntimeOptions; const options = openOptions(fakeCommand()); options.profile = { ...options.profile, agent }; const port = await openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), createRuntime: value => { created = value; return runtime; }, }); if (agent === "copilot") { expect(created.protocolGuardFactory).toBeTypeOf("function"); expect(created.protocolGuardFactory!()).not.toBe(created.protocolGuardFactory!()); expect(() => port.startTurn({ text: "\n/yolo on", requestId: "bad" })).toThrow(/admitted permission policy/); expect(runtime.startTurn).not.toHaveBeenCalled(); } else expect(created.protocolGuardFactory).toBeUndefined(); await port.close({ reason: "test complete" }); } }); it.each(["claude"] as const)("rejects approvals without a workflow in preconfigured %s sessions", async (agent) => { const runtime = fakeRuntime(); runtime.setMode = vi.fn().mockResolvedValue(undefined); let created!: AcpRuntimeOptions; const options = openOptions(fakeCommand()); options.profile = { ...options.profile, agent }; options.permissionMode = "approve-all"; options.launchEnvironment = { HOME: "/users/executor", PAPERCLIP_RUNNER_HOME_MODE: "preconfigured" }; options.mcpServers = [ { name: "paperclip_runner_fixture", url: "http://127.0.0.1:3211/mcp", bearerToken: "runner-fixture", runnerOwned: true }, { name: "paperclip", url: "http://127.0.0.1:3212/mcp", bearerToken: "external-fixture", runnerOwned: false }, ]; const port = await openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), createRuntime: value => { created = value; return runtime; }, }); const sessionOptions = vi.mocked(runtime.ensureSession).mock.calls[0]?.[0].sessionOptions; if (agent === "claude") expect(sessionOptions?.allowedTools).toEqual(["mcp__paperclip_runner_fixture__*"]); else expect(sessionOptions?.allowedTools).toBeUndefined(); expect(created.nonInteractivePermissions).toBe("deny"); expect(created.permissionPolicy).toMatchObject({ defaultAction: "deny", autoApprove: undefined, escalate: undefined }); expect(created.spawnEnvironment!().PAPERCLIP_ACPX_ISOLATED_CONTEXT).toBeUndefined(); if (agent === "claude") expect(created.spawnEnvironment!()).toMatchObject({ ANTHROPIC_MODEL: options.profile.reportedModelId, ANTHROPIC_CUSTOM_MODEL_OPTION: options.profile.reportedModelId, }); else expect(created.spawnEnvironment!().ANTHROPIC_CUSTOM_MODEL_OPTION).toBeUndefined(); await expect(created.onPermissionRequest!({ sessionId: "backend-1", raw: {}, inferredKind: "execute" } as never, { signal: new AbortController().signal })).resolves.toEqual({ outcome: "reject_once" }); expect(runtime.setMode).not.toHaveBeenCalled(); await port.close({ reason: "verified" }); }); it("rejects forged permission session identifiers before delegating or applying full-auto policy", async () => { const pending = pendingExtensionTurn("turn-1"); const runtime = fakeRuntime(); vi.mocked(runtime.startTurn).mockReturnValue(pending.turn); let created!: AcpRuntimeOptions; const options = openOptions(fakeCommand()); const port = await openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), createRuntime: (value) => { created = value; return runtime; }, }); const handler = vi.fn(async () => ({ outcome: "allow_once" as const })); const turn = port.startTurn({ text: "Write", requestId: "turn-1", onPermissionRequest: handler }); const signal = new AbortController().signal; for (const [sessionId, rawSessionId] of [["forged", "backend-1"], ["backend-1", "forged"]]) { const request = { sessionId, raw: { sessionId: rawSessionId }, inferredKind: "edit" as const }; await expect(created.onPermissionRequest!(request as never, { signal })).resolves.toEqual({ outcome: "reject_once" }); } expect(handler).not.toHaveBeenCalled(); const responseDelivery = Promise.resolve(); await expect(created.onPermissionRequest!({ sessionId: "backend-1", raw: { sessionId: "backend-1" }, inferredKind: "edit" } as never, { signal, responseDelivery })).resolves.toEqual({ outcome: "allow_once" }); expect(handler).toHaveBeenCalledOnce(); expect(handler).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ responseDelivery })); options.permissionMode = "approve-all"; await expect(created.onPermissionRequest!({ sessionId: "forged", raw: {}, inferredKind: "edit" } as never, { signal })).resolves.toEqual({ outcome: "reject_once" }); pending.settle(); await turn.result; await port.close({ reason: "session checks complete" }); }); it("cancels an outstanding native permission callback on the exact active turn", async () => { const pending = pendingExtensionTurn("turn-1"); const runtime = fakeRuntime(); vi.mocked(runtime.startTurn).mockReturnValue(pending.turn); let created!: AcpRuntimeOptions; const options = openOptions(fakeCommand()); const port = await openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), createRuntime: value => { created = value; return runtime; }, }); let callbackSignal!: AbortSignal; const handler = vi.fn((_request, context) => new Promise<{ outcome: "cancel" }>(resolve => { callbackSignal = context.signal; context.signal.addEventListener("abort", () => resolve({ outcome: "cancel" }), { once: true }); })); const turn = port.startTurn({ text: "Write", requestId: "turn-1", onPermissionRequest: handler }); const permission = created.onPermissionRequest!({ sessionId: "backend-1", raw: { sessionId: "backend-1" }, inferredKind: "edit" } as never, { signal: new AbortController().signal, responseDelivery: Promise.resolve() }); expect(callbackSignal.aborted).toBe(false); const oldMode = options.permissionMode; options.permissionMode = "approve-all"; const expiredContext = new AbortController(); expiredContext.abort(); await expect(created.onPermissionRequest!({ sessionId: "backend-1", raw: {}, inferredKind: "edit" } as never, { signal: expiredContext.signal })).resolves.toEqual({ outcome: "cancel" }); options.permissionMode = oldMode; await turn.cancel(); expect(callbackSignal.aborted).toBe(true); await expect(permission).resolves.toEqual({ outcome: "cancel" }); await expect(created.onPermissionRequest!({ sessionId: "backend-1", raw: {}, inferredKind: "edit" } as never, { signal: new AbortController().signal })).resolves.toEqual({ outcome: "cancel" }); expect(handler).toHaveBeenCalledOnce(); pending.settle(); await turn.result; // The active-turn pointer has been cleared. Even full-auto policy cannot // authorize a late callback from the retired turn. options.permissionMode = "approve-all"; await expect(created.onPermissionRequest!({ sessionId: "backend-1", raw: {}, inferredKind: "edit" } as never, { signal: new AbortController().signal })).resolves.toEqual({ outcome: "cancel" }); await port.close({ reason: "test complete" }); }); it("fails Cursor adapter admission when the provider never acknowledges instructions", async () => { const runtime = fakeRuntime(); const options = openOptions(fakeCommand()); options.profile = { ...options.profile, agent: "cursor" }; await expect(openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), createRuntime: () => runtime, })).rejects.toThrow("Cursor instruction admission failed"); expect(runtime.startTurn).not.toHaveBeenCalled(); expect(runtime.close).toHaveBeenCalled(); }); it.each(["valid", "missing", "unsupported"])("cold Cursor adapter admission requires %s load/config and renews only on success", async mode => { const runtime = fakeRuntime(); const options = openOptions(fakeCommand()); options.profile = { ...options.profile, agent: "cursor" }; options.refreshConsumedCommand = vi.fn(async () => undefined); let created!: AcpRuntimeOptions; vi.mocked(runtime.setConfigOption).mockImplementation(async () => { const guard = created.protocolGuardFactory!(); const binding = cursorInstructionBinding(options.systemInstructions); guard("outbound", { id: 0, method: "session/load", params: { sessionId: "backend-1" } }); guard("inbound", { id: 0, result: mode === "missing" ? {} : { modes: { currentModeId: "agent" }, configOptions: [{ id: "mode", currentValue: "agent" }], _meta: { paperclipCursorInstructions: { schema: "paperclip.cursor.instructions.v1", digest: binding.digest, byteLength: binding.byteLength, } } } }); if (mode === "unsupported") throw new Error("Exact model unsupported"); }); const opening = openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), createRuntime: value => { created = value; return runtime; }, }); if (mode === "valid") { const port = await opening; expect(options.refreshConsumedCommand).toHaveBeenCalledOnce(); await port.close({ reason: "fixture cleanup" }); } else { await expect(opening).rejects.toThrow(mode === "missing" ? /Cursor instruction admission failed/ : /Exact model unsupported/); expect(options.refreshConsumedCommand).not.toHaveBeenCalled(); expect(runtime.close).toHaveBeenCalled(); } expect(runtime.setConfigOption).toHaveBeenCalledExactlyOnceWith({ handle: HANDLE, key: "model", value: options.profile.reportedModelId }); expect(runtime.startTurn).not.toHaveBeenCalled(); }); it.each(["plan", "ask"] as const)("admission explicitly selects %s and renews temporary command authority", async selected => { const runtime = fakeRuntime(); const options = openOptions(fakeCommand()); options.profile = { ...options.profile, agent: "cursor" }; options.mode = selected; options.refreshConsumedCommand = vi.fn(async () => undefined); let created!: AcpRuntimeOptions; const binding = cursorInstructionBinding(options.systemInstructions); const configs = (mode: string) => [{ id: "mode", currentValue: mode }]; vi.mocked(runtime.setConfigOption).mockImplementation(async input => { const guard = created.protocolGuardFactory!(); guard("outbound", { id: 0, method: "session/load", params: { sessionId: "backend-1" } }); guard("inbound", { id: 0, result: { modes: { currentModeId: "agent" }, configOptions: configs("agent"), _meta: { paperclipCursorInstructions: { schema: "paperclip.cursor.instructions.v1", digest: binding.digest, byteLength: binding.byteLength, } } } }); guard("outbound", { id: 1, method: "session/set_config_option", params: { sessionId: "backend-1", configId: input.key, value: input.value } }); guard("inbound", { id: 1, result: { configOptions: configs(input.key === "mode" ? selected : "agent") } }); }); const port = await openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), createRuntime: value => { created = value; return runtime; }, }); expect(vi.mocked(runtime.setConfigOption).mock.calls.map(([call]) => [call.key, call.value])).toEqual([["model", options.profile.reportedModelId], ["mode", selected]]); expect(options.refreshConsumedCommand).toHaveBeenCalledTimes(2); expect(await port.identity()).toMatchObject({ mode: selected }); expect(runtime.startTurn).not.toHaveBeenCalled(); await port.close({ reason: "mode fixture cleanup" }); }); it("routes only profile-allowed extensions to the owning turn and expires late responses", async () => { const runtime = fakeRuntime(); const first = pendingExtensionTurn("turn-1"); const second = pendingExtensionTurn("turn-2"); vi.mocked(runtime.startTurn).mockReturnValueOnce(first.turn).mockReturnValueOnce(second.turn); let created!: AcpRuntimeOptions; const options = openOptions(fakeCommand()); options.profile = { ...options.profile, agent: "cursor" }; options.clientCapabilities = { _meta: { cursor: { test: true } } }; const port = await openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), createRuntime: (value) => { created = value; const guard = value.protocolGuardFactory!(); const binding = cursorInstructionBinding(options.systemInstructions); guard("outbound", { id: 0, method: "session/new", params: {} }); guard("inbound", { id: 0, result: { sessionId: "backend-1", modes: { currentModeId: "agent" }, configOptions: [{ id: "mode", currentValue: "agent" }], _meta: { paperclipCursorInstructions: { schema: "paperclip.cursor.instructions.v1", digest: binding.digest, byteLength: binding.byteLength } } } }); return runtime; }, }); expect(created.clientCapabilities).toEqual(options.clientCapabilities); expect(JSON.stringify(vi.mocked(runtime.ensureSession).mock.calls)).not.toContain("clientCapabilities"); const notification = vi.fn(); const request = vi.fn(async (_method, _params, context) => ({ requestId: context.requestId })); const admittedFirst = port.startTurn({ text: "First", requestId: "turn-1", onExtensionRequest: request, onExtensionNotification: notification }); const signal = new AbortController().signal; await expect(created.onExtensionRequest!("cursor/ask_question", { sessionId: "backend-1" }, { requestId: 0, signal })).resolves.toEqual({ requestId: 0 }); const sessionless = { toolCallId: "native-cursor-plan", plan: "Review the complete plan" }; const responseDelivery = Promise.resolve(); await expect(created.onExtensionRequest!("cursor/create_plan", sessionless, { requestId: "plan", signal, responseDelivery })).resolves.toEqual({ requestId: "plan" }); expect(request).toHaveBeenLastCalledWith("cursor/create_plan", { ...sessionless, sessionId: "backend-1" }, expect.objectContaining({ requestId: "plan", responseDelivery })); expect(sessionless).not.toHaveProperty("sessionId"); await expect(created.onExtensionRequest!("pi/steer", {}, { requestId: 1, signal })).rejects.toThrow("admitted active turn"); await expect(created.onExtensionRequest!("cursor/ask_question", { sessionId: "other" }, { requestId: 1, signal })).rejects.toThrow("admitted active turn"); created.onExtensionNotification!("cursor/task", { sessionId: "backend-1", taskId: "t" }); created.onExtensionNotification!("cursor/task", { sessionId: "other" }); created.onExtensionNotification!("cursor/ask_question", { sessionId: "backend-1" }); expect(notification).toHaveBeenCalledExactlyOnceWith("cursor/task", { sessionId: "backend-1", taskId: "t" }); created.onExtensionNotification!("cursor/task", { toolCallId: "native-child" }); expect(notification).toHaveBeenLastCalledWith("cursor/task", { sessionId: "backend-1", toolCallId: "native-child" }); created.onExtensionNotification!("cursor/task", { sessionId: "agent-1" }); expect(notification).toHaveBeenCalledTimes(2); request.mockImplementationOnce(() => new Promise(() => undefined)); const pending = created.onExtensionRequest!("cursor/ask_question", {}, { requestId: 2, signal }); const rejected = expect(pending).rejects.toThrow("expired"); first.settle(); await admittedFirst.result; await rejected; const nextRequest = vi.fn(async () => ({ accepted: true })); const admittedSecond = port.startTurn({ text: "Second", requestId: "turn-2", onExtensionRequest: nextRequest }); expect(nextRequest).not.toHaveBeenCalled(); await admittedSecond.cancel({ reason: "cancelled" }); await expect(created.onExtensionRequest!("cursor/ask_question", {}, { requestId: 3, signal })).rejects.toThrow("admitted active turn"); second.settle(); await admittedSecond.result; await port.close({ reason: "test finished" }); }); it("requires the Pi capability handshake and acknowledgments, preserves follow-up order, and never starts a second prompt", async () => { const pending = pendingExtensionTurn("turn-1"); const runtime = Object.assign(fakeRuntime(), { requestExtension: vi.fn(async () => ({ accepted: true })) }); vi.mocked(runtime.startTurn).mockReturnValue(pending.turn); let created!: AcpRuntimeOptions & { onAgentInitialize?: (result: unknown) => void }; const options = openOptions(fakeCommand()); options.profile = { ...options.profile, agent: "pi" }; options.piThinkingLevel = "low"; vi.mocked(runtime.setConfigOption).mockImplementation(async input => { const guard = created.protocolGuardFactory!(); guard("outbound", { id: 0, method: "session/load", params: { sessionId: "backend-1" } }); guard("inbound", { id: 0, result: { modes: { currentModeId: "high" }, configOptions: [{ id: "thought_level", currentValue: "high" }] } }); guard("outbound", { id: 1, method: "session/set_config_option", params: { sessionId: "backend-1", configId: input.key, value: input.value } }); guard("inbound", { id: 1, result: { configOptions: [{ id: "thought_level", currentValue: "low" }] } }); }); const port = await openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), createRuntime: (value) => { created = value; return runtime; }, }); const turn = port.startTurn({ text: "Initial", requestId: "turn-1" }); await expect(port.steerActiveTurn!("Update", "turn-1")).rejects.toThrow("supported active turn"); created.onAgentInitialize!({ agentCapabilities: { _meta: { paperclipPi: { version: 1, steering: true, queuedFollowUp: true } } } }); expect(port.steeringCapability!()).toEqual({ steering: true, queuedFollowUp: true }); await expect(port.steerActiveTurn!("Update", "wrong")).rejects.toThrow("supported active turn"); await port.steerActiveTurn!("Update", "turn-1"); await Promise.all([port.queueFollowUp!("One", "turn-1"), port.queueFollowUp!("Two", "turn-1")]); expect(runtime.requestExtension.mock.calls.map((call) => call[0])).toEqual([ { handle: HANDLE, method: "pi/steer", params: { sessionId: "backend-1", message: "Update" }, sessionMode: "persistent" }, { handle: HANDLE, method: "pi/follow_up", params: { sessionId: "backend-1", message: "One" }, sessionMode: "persistent" }, { handle: HANDLE, method: "pi/follow_up", params: { sessionId: "backend-1", message: "Two" }, sessionMode: "persistent" }, ]); expect(runtime.startTurn).toHaveBeenCalledTimes(1); runtime.requestExtension.mockResolvedValueOnce({ accepted: false }); await expect(port.steerActiveTurn!("Denied", "turn-1")).rejects.toThrow("not acknowledged"); runtime.requestExtension.mockImplementationOnce(() => new Promise(() => undefined)); const stale = port.queueFollowUp!("Stale", "turn-1"); const rejected = expect(stale).rejects.toThrow("expired"); pending.settle(); await turn.result; await rejected; await expect(port.queueFollowUp!("Late", "turn-1")).rejects.toThrow("supported active turn"); await port.close({ reason: "test finished" }); }); it("does not grant steering to Copilot or Cursor when an untrusted initialize result claims Pi support", async () => { const options = openOptions(fakeCommand()); options.profile = { ...options.profile, agent: "copilot" }; const runtime = fakeRuntime(); const port = await openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), createRuntime: (created) => { (created as AcpRuntimeOptions & { onAgentInitialize: (result: unknown) => void }).onAgentInitialize({ agentCapabilities: { _meta: { paperclipPi: { version: 1, steering: true, queuedFollowUp: true } } } }); return runtime; }, }); expect(port.steeringCapability!()).toBeNull(); await port.close({ reason: "test finished" }); }); it.each(["approve-reads", "approve-paperclip"] as const)("%s stops unassigned operations when approval has no handler", async (permissionMode) => { const runtime = fakeRuntime(); let runtimeOptions: AcpRuntimeOptions | undefined; let signal: AbortSignal | undefined; let settle!: () => void; const finished = new Promise((resolve) => { settle = resolve; }); vi.mocked(runtime.startTurn).mockImplementation((input) => { signal = input.signal; signal?.addEventListener("abort", settle, { once: true }); return { requestId: "permission-test", promptStarted: Promise.resolve(), events: (async function* () { await finished; })(), result: finished.then(() => ({ status: "cancelled" as const })), cancel: async () => settle(), closeStream: async () => settle(), } as ReturnType; }); const options = openOptions(fakeCommand()); options.permissionMode = permissionMode; const port = await openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), createRuntime: (created) => { runtimeOptions = created; return runtime; }, }); expect(runtimeOptions!.permissionMode).toBe("approve-reads"); expect(runtimeOptions!.permissionPolicy).toMatchObject({ defaultAction: "escalate" }); const turn = port.startTurn({ text: "Attempt a write.", requestId: "permission-test" }); try { await runtimeOptions!.onPermissionRequest!({ sessionId: "backend-1", inferredKind: "write", raw: {}, }, { signal: new AbortController().signal }); expect(signal?.aborted).toBe(true); await expect(turn.result).rejects.toThrow("Approval required"); await expect((async () => { for await (const _event of turn.events) { /* drain */ } })()) .rejects.toThrow("Approval required"); } finally { settle(); await port.close({ reason: "permission failure verified" }); } }); it.each(["PERMISSION_PROMPT_UNAVAILABLE", "AGENT_DISCONNECTED"])("preserves the meaning of ACPX terminal failure %s", async (code) => { const runtime = fakeRuntime(); const result = { status: "failed" as const, error: { code, message: "Provider failed" } }; vi.mocked(runtime.startTurn).mockReturnValue({ requestId: "permission-fallback", promptStarted: Promise.resolve(), events: { async *[Symbol.asyncIterator]() {} }, result: Promise.resolve(result), cancel: vi.fn(), closeStream: vi.fn(), }); const port = await openCodexAcpxRuntime(openOptions(fakeCommand()), { createRegistry: () => registry(), createStore: () => store(), createRuntime: () => runtime, }); try { const turn = port.startTurn({ text: "Attempt a write", requestId: "permission-fallback" }); if (code === "PERMISSION_PROMPT_UNAVAILABLE") { await expect(turn.result).rejects.toMatchObject({ code: "approval_required" }); } else { await expect(turn.result).resolves.toEqual(result); } } finally { await port.close({ reason: "terminal permission outcome verified" }); } }); it("rejects a pre-aborted admission before constructing or spawning ACPX", async () => { const cancellation = new Error("runtime admission cancelled"); const controller = new AbortController(); controller.abort(cancellation); const command = fakeCommand(); const createRuntime = vi.fn(); const retainFailedAdmissionCleanup = vi.fn(); await expect( openCodexAcpxRuntime( { ...openOptions(command), signal: controller.signal, retainFailedAdmissionCleanup, }, { createRuntime }, ), ).rejects.toBe(cancellation); expect(retainFailedAdmissionCleanup).toHaveBeenCalledOnce(); await expect( retainFailedAdmissionCleanup.mock.calls[0]?.[0], ).resolves.toBeUndefined(); expect(createRuntime).not.toHaveBeenCalled(); expect(command.spawn).not.toHaveBeenCalled(); }); it("opens a persistent Codex session without persisting launch secrets", async () => { const runtime = fakeRuntime(); let runtimeOptions: AcpRuntimeOptions | undefined; const command = fakeCommand(); const port = await openCodexAcpxRuntime(openOptions(command), { createRegistry: ({ overrides }) => { expect(overrides).toEqual({ codex: ["paperclip-verified-acpx-command"], }); return registry(); }, createStore: ({ stateDir }) => { expect(stateDir).toBe("/runtime/state"); return store(); }, createRuntime: (options) => { runtimeOptions = options; return runtime; }, }); expect(runtime.ensureSession).toHaveBeenCalledWith({ sessionKey: "provider-key", agent: "codex", mode: "persistent", cwd: "/workspace", sessionOptions: { model: "gpt-5.6-sol", systemPrompt: { append: "Use Paperclip tools." }, }, }); expect( JSON.stringify(vi.mocked(runtime.ensureSession).mock.calls[0]?.[0]), ).not.toContain("credential-secret"); expect(runtimeOptions?.spawnEnvironment?.()).toEqual({ CODEX_HOME: "/runtime/agent-home", OPENAI_API_KEY: "credential-secret", }); expect(runtimeOptions?.spawnCwd).toBe("/workspace"); expect(runtimeOptions?.elicitationModes).toEqual(["form"]); expect(await port.identity()).toEqual({ acpxRecordId: "record-1", backendSessionId: "backend-1", agentSessionId: "agent-1", }); }); it.each([["claude" as const, "claude-sonnet-5", "claude-sonnet-5"]])( "opens the qualified %s session through the verified lease", async (agent, model, providerModel) => { const runtime = fakeRuntime(); const command = fakeCommand(); const options = openOptions(command); let runtimeOptions: AcpRuntimeOptions | undefined; options.profile = resolveQualifiedAcpxProfile(agent, model); options.launchEnvironment = { PATH: "/verified/bin" }; await openCodexAcpxRuntime(options, { createRegistry: ({ overrides }) => { expect(overrides).toEqual({ [agent]: [agent === "claude" ? "/paperclip-verified/claude-agent-acp" : "paperclip-verified-acpx-command"], }); return registry(); }, createStore: () => store(), createRuntime: (createdOptions) => { runtimeOptions = createdOptions; return runtime; }, }); expect(runtimeOptions?.spawnEnvironment?.()).toEqual({ PATH: "/verified/bin", PAPERCLIP_ACPX_ISOLATED_CONTEXT: "1", PAPERCLIP_ACPX_TASK_TOOL_BRIDGE_URL: "", }); expect(runtime.ensureSession).toHaveBeenCalledWith( expect.objectContaining({ agent, sessionOptions: expect.objectContaining({ model: providerModel }), }), ); }, ); it.each(["runner-owned", "unowned", "absent"])( "pins Claude completion authority to the %s task bridge", async (binding) => { const options = openOptions(fakeCommand()); options.profile = resolveQualifiedAcpxProfile("claude", "claude-sonnet-5"); options.launchEnvironment = { PAPERCLIP_ACPX_TASK_TOOL_BRIDGE_URL: "http://untrusted.invalid/mcp" }; options.mcpServers = binding === "absent" ? [] : [{ name: "paperclip", url: "http://127.0.0.1:3210/mcp", bearerToken: "bridge-secret", runnerOwned: binding === "runner-owned", }]; let runtimeOptions: AcpRuntimeOptions | undefined; await openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), createRuntime: (created) => { runtimeOptions = created; return fakeRuntime(); }, }); expect(runtimeOptions?.spawnEnvironment?.()).toEqual({ PAPERCLIP_ACPX_ISOLATED_CONTEXT: "1", PAPERCLIP_ACPX_TASK_TOOL_BRIDGE_URL: binding === "runner-owned" ? "http://127.0.0.1:3210/mcp" : "", }); }, ); it("launches only through the verified command lease", async () => { const runtime = fakeRuntime(); const command = fakeCommand(); const child = fakeChild(); vi.mocked(command.spawn).mockReturnValue(child); const spawnOptions = { cwd: "/runtime/spawn" }; await openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), awaitProviderOwnership: providerOwnershipEstablished, awaitProviderExit: providerOwnershipEstablished, createRuntime: (options) => { vi.mocked(runtime.ensureSession).mockImplementation(async () => { expect( options.spawnAgent?.({ command: "/attacker/replacement", args: ["--stdio"], options: spawnOptions, }), ).toBe(child); return HANDLE; }); return runtime; }, }); expect(command.spawn).toHaveBeenCalledWith(["--stdio"], spawnOptions, { credentialFenceFds: [42, 43], activateCredentialFenceOwner: expect.any(Function), }); }); it("revalidates a recovered workspace immediately before provider spawn", async () => { const runtime = fakeRuntime(); let runtimeOptions: AcpRuntimeOptions | undefined; const command = fakeCommand(); const workspaceSubstituted = new Error("recovered workspace substituted"); const assertWorkspaceHeld = vi.fn(() => { throw workspaceSubstituted; }); await openCodexAcpxRuntime( { ...openOptions(command), assertWorkspaceHeld }, { createRegistry: () => registry(), createStore: () => store(), createRuntime: (options) => { runtimeOptions = options; return runtime; }, }, ); expect(() => runtimeOptions?.spawnAgent?.({ command: "/attacker/replacement", args: ["--stdio"], options: {}, }), ).toThrow(workspaceSubstituted); expect(assertWorkspaceHeld).toHaveBeenCalledOnce(); expect(command.spawn).not.toHaveBeenCalled(); }); it("reads verified status from durable state without draining live updates", async () => { const runtime = fakeRuntime(); vi.mocked(runtime.getStatus!).mockReturnValue(new Promise(() => {})); const durableRecord = { acpxRecordId: "record-1", acpSessionId: "backend-1", agentSessionId: "agent-1", lastRequestId: "run:turn-1", request_token_usage: { prompt: { input_tokens: 12, output_tokens: 30 } }, cumulative_cost: { amount: 0.1, currency: "USD" }, messages: [{ User: { id: "prompt", content: [] } }], cursor_prompt_usage: { request_id: "run:turn-1", prompt_message_id: "prompt", receipt: { diagnostic: "fixture" } }, 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: () => durableStore, createRuntime: () => runtime, }); expect(await port.getStatus()).toMatchObject({ acpxRecordId: "record-1", backendSessionId: "backend-1", agentSessionId: "agent-1", lastRequestId: "run:turn-1", requestTokenUsage: { prompt: { input_tokens: 12, output_tokens: 30 } }, usageCost: { amount: 0.1, currency: "USD" }, promptMessageIds: ["prompt"], cursorPromptUsage: { request_id: "run:turn-1", prompt_message_id: "prompt", receipt: { diagnostic: "fixture" } }, 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, key: "model", value: "gpt-5.6-sol", }); await port.close({ reason: "test complete" }); expect(runtime.close).toHaveBeenCalledWith({ handle: HANDLE, reason: "test complete", discardPersistentState: false, }); }); it("never overlaps a retained protocol close that has not settled", async () => { vi.useFakeTimers(); try { const runtime = fakeRuntime(); const runtimeClose = new Promise(() => {}); vi.mocked(runtime.close) .mockReturnValueOnce(runtimeClose) .mockResolvedValueOnce(undefined); const child = fakeChild(); const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValue(child); const port = await openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), awaitProviderOwnership: providerOwnershipEstablished, awaitProviderExit: providerOwnershipEstablished, createRuntime: (options) => { return runtimeWithProvider(runtime, options); }, }); const firstClose = expect( port.close({ reason: "runtime close stalled" }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"); await Promise.resolve(); expect(runtime.close).toHaveBeenCalledOnce(); await vi.advanceTimersByTimeAsync(2_000); expect(child.kill).toHaveBeenCalledWith("SIGTERM"); await firstClose; // Repeated callers inherit the same bounded observation. A permanently // pending exact close remains the sole protocol attempt for this handle. expect(runtime.close).toHaveBeenCalledOnce(); const secondClose = expect( port.close({ reason: "idempotent terminal close" }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"); await vi.advanceTimersByTimeAsync(2_000); await secondClose; expect(runtime.close).toHaveBeenCalledOnce(); } finally { vi.useRealTimers(); } }); it("allows a fresh close after a retained attempt rejects late", async () => { vi.useFakeTimers(); try { const runtime = fakeRuntime(); let rejectRuntimeClose!: (error: unknown) => void; const runtimeClose = new Promise((_resolve, reject) => { rejectRuntimeClose = reject; }); vi.mocked(runtime.close) .mockReturnValueOnce(runtimeClose) .mockResolvedValueOnce(undefined); const child = fakeChild(); const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValue(child); const port = await openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), awaitProviderOwnership: providerOwnershipEstablished, awaitProviderExit: providerOwnershipEstablished, createRuntime: (options) => { return runtimeWithProvider(runtime, options); }, }); const firstClose = expect( port.close({ reason: "runtime close stalled" }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"); await Promise.resolve(); await vi.advanceTimersByTimeAsync(2_000); await firstClose; expect(child.kill).toHaveBeenCalledWith("SIGTERM"); const protocolFailure = new Error("late protocol close failure"); rejectRuntimeClose(protocolFailure); // The late-settlement observer schedules reconciliation asynchronously. // Wait for that fresh attempt instead of racing another caller against // the already-settled retained failure. await vi.waitFor(() => expect(runtime.close).toHaveBeenCalledTimes(2)); await expect( port.close({ reason: "retry after retained failure" }), ).resolves.toBeUndefined(); expect(runtime.close).toHaveBeenLastCalledWith({ handle: HANDLE, reason: "ACPX late protocol cleanup reconciliation 1", discardPersistentState: false, }); } finally { vi.useRealTimers(); } }); it("reconciles a retained close only after its late failure settles", async () => { vi.useFakeTimers(); try { const runtime = fakeRuntime(); let rejectFirstClose!: (error: unknown) => void; const firstRuntimeClose = new Promise((_resolve, reject) => { rejectFirstClose = reject; }); vi.mocked(runtime.close) .mockReturnValueOnce(firstRuntimeClose) .mockResolvedValueOnce(undefined) .mockResolvedValueOnce(undefined); const child = fakeChild(); const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValue(child); const port = await openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), awaitProviderOwnership: providerOwnershipEstablished, awaitProviderExit: providerOwnershipEstablished, createRuntime: (options) => { return runtimeWithProvider(runtime, options); }, }); const firstClose = expect( port.close({ reason: "first protocol close stalls" }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"); await Promise.resolve(); await vi.advanceTimersByTimeAsync(2_000); await firstClose; const inheritedClose = expect( port.close({ reason: "observe pending protocol close" }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"); await vi.advanceTimersByTimeAsync(2_000); await inheritedClose; expect(runtime.close).toHaveBeenCalledOnce(); rejectFirstClose(new Error("older protocol close failed late")); await vi.waitFor(() => expect(runtime.close).toHaveBeenCalledTimes(2)); expect(runtime.close).toHaveBeenLastCalledWith({ handle: HANDLE, reason: "ACPX late protocol cleanup reconciliation 1", discardPersistentState: false, }); await expect( port.close({ reason: "observe reconciled cleanup" }), ).resolves.toBeUndefined(); expect(runtime.close).toHaveBeenCalledTimes(2); } finally { vi.useRealTimers(); } }); it("shares a pending close across concurrent callers before reconciliation", async () => { vi.useFakeTimers(); try { const runtime = fakeRuntime(); let rejectFirstClose!: (error: unknown) => void; const firstRuntimeClose = new Promise((_resolve, reject) => { rejectFirstClose = reject; }); let resolveFreshClose!: () => void; const freshRuntimeClose = new Promise((resolve) => { resolveFreshClose = resolve; }); vi.mocked(runtime.close) .mockReturnValueOnce(firstRuntimeClose) .mockReturnValueOnce(freshRuntimeClose) .mockResolvedValueOnce(undefined); const child = fakeChild(); const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValue(child); const port = await openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), awaitProviderOwnership: providerOwnershipEstablished, awaitProviderExit: providerOwnershipEstablished, createRuntime: (options) => { return runtimeWithProvider(runtime, options); }, }); const firstClose = expect( port.close({ reason: "first protocol close stalls" }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"); await Promise.resolve(); await vi.advanceTimersByTimeAsync(2_000); await firstClose; const inheritedClose = expect( port.close({ reason: "observe pending protocol close" }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"); expect(runtime.close).toHaveBeenCalledOnce(); rejectFirstClose(new Error("retained protocol close failed")); await inheritedClose; await vi.waitFor(() => expect(runtime.close).toHaveBeenCalledTimes(2)); resolveFreshClose(); await vi.advanceTimersByTimeAsync(0); expect(runtime.close).toHaveBeenCalledTimes(2); expect(runtime.close).toHaveBeenLastCalledWith({ handle: HANDLE, reason: "ACPX late protocol cleanup reconciliation 1", discardPersistentState: false, }); await expect( port.close({ reason: "observe reconciled cleanup" }), ).resolves.toBeUndefined(); expect(runtime.close).toHaveBeenCalledTimes(2); } finally { vi.useRealTimers(); } }); it("does not let late settlements bypass the reconciliation retry bound", async () => { vi.useFakeTimers(); try { const runtime = fakeRuntime(); let rejectInitialClose!: (error: unknown) => void; const initialClose = new Promise((_resolve, reject) => { rejectInitialClose = reject; }); vi.mocked(runtime.close) .mockReturnValueOnce(initialClose) .mockRejectedValueOnce(new Error("reconciliation 1 failed")) .mockRejectedValueOnce(new Error("reconciliation 2 failed")) .mockRejectedValueOnce(new Error("reconciliation 3 failed")); const port = await openCodexAcpxRuntime(openOptions(fakeCommand()), { createRegistry: () => registry(), createStore: () => store(), createRuntime: () => runtime, }); const firstClose = expect( port.close({ reason: "first protocol close stalls" }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"); await Promise.resolve(); await vi.advanceTimersByTimeAsync(2_000); await firstClose; rejectInitialClose(new Error("initial protocol close failed late")); await vi.waitFor(() => expect(runtime.close).toHaveBeenCalledTimes(4)); await vi.advanceTimersByTimeAsync(0); expect(runtime.close).toHaveBeenCalledTimes(4); } finally { vi.useRealTimers(); } }); it("keeps timed-out reconciliation failures in their originating budget", async () => { vi.useFakeTimers(); try { const runtime = fakeRuntime(); let rejectInitialClose!: (error: unknown) => void; const initialClose = new Promise((_resolve, reject) => { rejectInitialClose = reject; }); const reconciliationRejectors: Array<(error: unknown) => void> = []; const reconciliationAttempts = Array.from( { length: 3 }, () => new Promise((_resolve, reject) => { reconciliationRejectors.push(reject); }), ); vi.mocked(runtime.close) .mockReturnValueOnce(initialClose) .mockReturnValueOnce(reconciliationAttempts[0]!) .mockReturnValueOnce(reconciliationAttempts[1]!) .mockReturnValueOnce(reconciliationAttempts[2]!) .mockResolvedValueOnce(undefined); const port = await openCodexAcpxRuntime(openOptions(fakeCommand()), { createRegistry: () => registry(), createStore: () => store(), createRuntime: () => runtime, runtimeCloseTimeoutMs: 1, }); const firstClose = expect( port.close({ reason: "external protocol close stalls" }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"); await Promise.resolve(); await vi.advanceTimersByTimeAsync(1); await firstClose; rejectInitialClose(new Error("external close failed late")); for (let index = 0; index < reconciliationRejectors.length; index += 1) { await vi.waitFor(() => expect(runtime.close).toHaveBeenCalledTimes(index + 2), ); expect(runtime.close).toHaveBeenLastCalledWith({ handle: HANDLE, reason: `ACPX late protocol cleanup reconciliation ${index + 1}`, discardPersistentState: false, }); const overlappingExternalClose = index === 0 ? expect( port.close({ reason: "external observer of reconciliation", }), ).rejects.toThrow("ACPX runtime and provider cleanup failed") : null; // The external observer coalesces onto reconciliation attempt one. It // must not relabel that immutable attempt as an external generation. await Promise.resolve(); await vi.advanceTimersByTimeAsync(1); if (overlappingExternalClose) await overlappingExternalClose; reconciliationRejectors[index]!( new Error(`reconciliation ${index + 1} failed late`), ); } await vi.advanceTimersByTimeAsync(0); await Promise.resolve(); expect(runtime.close).toHaveBeenCalledTimes(4); } finally { vi.useRealTimers(); } }); it("renews one budget when an external close joins the final reconciliation", async () => { vi.useFakeTimers(); try { const runtime = fakeRuntime(); let rejectInitialClose!: (error: unknown) => void; const initialClose = new Promise((_resolve, reject) => { rejectInitialClose = reject; }); let rejectFinalReconciliation!: (error: unknown) => void; const finalReconciliation = new Promise((_resolve, reject) => { rejectFinalReconciliation = reject; }); vi.mocked(runtime.close) .mockReturnValueOnce(initialClose) .mockRejectedValueOnce(new Error("reconciliation 1 failed")) .mockRejectedValueOnce(new Error("reconciliation 2 failed")) .mockReturnValueOnce(finalReconciliation) .mockResolvedValueOnce(undefined); const port = await openCodexAcpxRuntime(openOptions(fakeCommand()), { createRegistry: () => registry(), createStore: () => store(), createRuntime: () => runtime, runtimeCloseTimeoutMs: 1, }); const initialObserver = expect( port.close({ reason: "external protocol close stalls" }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"); await Promise.resolve(); await vi.advanceTimersByTimeAsync(1); await initialObserver; rejectInitialClose(new Error("external close failed late")); await vi.waitFor(() => expect(runtime.close).toHaveBeenCalledTimes(4)); const finalObserver = expect( port.close({ reason: "external observer of final reconciliation" }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"); await Promise.resolve(); await vi.advanceTimersByTimeAsync(1); await finalObserver; expect(runtime.close).toHaveBeenCalledTimes(4); rejectFinalReconciliation(new Error("final reconciliation failed late")); await vi.waitFor(() => expect(runtime.close).toHaveBeenCalledTimes(5)); expect(runtime.close).toHaveBeenLastCalledWith({ handle: HANDLE, reason: "ACPX late protocol cleanup reconciliation 1", discardPersistentState: false, }); await vi.advanceTimersByTimeAsync(0); expect(runtime.close).toHaveBeenCalledTimes(5); } finally { vi.useRealTimers(); } }); it("batches coalesced external closes when the final reconciliation fails", async () => { vi.useFakeTimers(); try { const runtime = fakeRuntime(); let rejectInitialClose!: (error: unknown) => void; const initialClose = new Promise((_resolve, reject) => { rejectInitialClose = reject; }); let rejectFinalReconciliation!: (error: unknown) => void; const finalReconciliation = new Promise((_resolve, reject) => { rejectFinalReconciliation = reject; }); vi.mocked(runtime.close) .mockReturnValueOnce(initialClose) .mockRejectedValueOnce(new Error("reconciliation 1 failed")) .mockRejectedValueOnce(new Error("reconciliation 2 failed")) .mockReturnValueOnce(finalReconciliation) .mockRejectedValueOnce(new Error("renewed reconciliation 1 failed")) .mockRejectedValueOnce(new Error("renewed reconciliation 2 failed")) .mockRejectedValueOnce(new Error("renewed reconciliation 3 failed")); const port = await openCodexAcpxRuntime(openOptions(fakeCommand()), { createRegistry: () => registry(), createStore: () => store(), createRuntime: () => runtime, runtimeCloseTimeoutMs: 1, }); const initialObserver = expect( port.close({ reason: "external protocol close stalls" }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"); await Promise.resolve(); await vi.advanceTimersByTimeAsync(1); await initialObserver; rejectInitialClose(new Error("external close failed late")); await vi.waitFor(() => expect(runtime.close).toHaveBeenCalledTimes(4)); const coalescedObservers = ["first", "second", "third"].map((label) => expect( port.close({ reason: `${label} external observer` }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"), ); rejectFinalReconciliation( new Error("final reconciliation failed with joined observers"), ); expect(runtime.close).toHaveBeenCalledTimes(4); await Promise.all(coalescedObservers); await vi.waitFor(() => expect(runtime.close).toHaveBeenCalledTimes(7)); expect( vi .mocked(runtime.close) .mock.calls.slice(4) .map(([input]) => input.reason), ).toEqual([ "ACPX late protocol cleanup reconciliation 1", "ACPX late protocol cleanup reconciliation 2", "ACPX late protocol cleanup reconciliation 3", ]); await vi.advanceTimersByTimeAsync(0); await Promise.resolve(); expect(runtime.close).toHaveBeenCalledTimes(7); } finally { vi.useRealTimers(); } }); it("consumes an external intent when a timed-out final reconciliation succeeds late", async () => { vi.useFakeTimers(); try { const runtime = fakeRuntime(); let rejectInitialClose!: (error: unknown) => void; const initialClose = new Promise((_resolve, reject) => { rejectInitialClose = reject; }); let resolveFinalReconciliation!: () => void; const finalReconciliation = new Promise((resolve) => { resolveFinalReconciliation = resolve; }); vi.mocked(runtime.close) .mockReturnValueOnce(initialClose) .mockRejectedValueOnce(new Error("reconciliation 1 failed")) .mockRejectedValueOnce(new Error("reconciliation 2 failed")) .mockReturnValueOnce(finalReconciliation); const port = await openCodexAcpxRuntime(openOptions(fakeCommand()), { createRegistry: () => registry(), createStore: () => store(), createRuntime: () => runtime, runtimeCloseTimeoutMs: 1, }); const initialObserver = expect( port.close({ reason: "external protocol close stalls" }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"); await Promise.resolve(); await vi.advanceTimersByTimeAsync(1); await initialObserver; rejectInitialClose(new Error("external close failed late")); await vi.waitFor(() => expect(runtime.close).toHaveBeenCalledTimes(4)); const finalObserver = expect( port.close({ reason: "external observer of successful reconciliation", }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"); await Promise.resolve(); await vi.advanceTimersByTimeAsync(1); await finalObserver; expect(runtime.close).toHaveBeenCalledTimes(4); resolveFinalReconciliation(); await vi.advanceTimersByTimeAsync(0); await Promise.resolve(); expect(runtime.close).toHaveBeenCalledTimes(4); } finally { vi.useRealTimers(); } }); it("renews an external intent when bounded protocol success cannot terminate the provider", async () => { vi.useFakeTimers(); try { const runtime = fakeRuntime(); let rejectInitialClose!: (error: unknown) => void; const initialClose = new Promise((_resolve, reject) => { rejectInitialClose = reject; }); let resolveFinalReconciliation!: () => void; const finalReconciliation = new Promise((resolve) => { resolveFinalReconciliation = resolve; }); let resolveFinalReconciliationStarted!: () => void; const finalReconciliationStarted = new Promise((resolve) => { resolveFinalReconciliationStarted = resolve; }); let resolveRenewedReconciliation!: () => void; const renewedReconciliation = new Promise((resolve) => { resolveRenewedReconciliation = resolve; }); let resolveRenewedReconciliationStarted!: () => void; const renewedReconciliationStarted = new Promise((resolve) => { resolveRenewedReconciliationStarted = resolve; }); vi.mocked(runtime.close) .mockReturnValueOnce(initialClose) .mockRejectedValueOnce(new Error("reconciliation 1 failed")) .mockRejectedValueOnce(new Error("reconciliation 2 failed")) .mockImplementationOnce(() => { resolveFinalReconciliationStarted(); return finalReconciliation; }) .mockImplementationOnce(() => { resolveRenewedReconciliationStarted(); return renewedReconciliation; }); const child = failingSignalChild(); const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValue(child.child); const port = await openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), awaitProviderOwnership: providerOwnershipEstablished, awaitProviderExit: providerOwnershipEstablished, createRuntime: (options) => { return runtimeWithProvider(runtime, options); }, runtimeCloseTimeoutMs: 1, }); const initialObserver = expect( port.close({ reason: "external protocol close stalls" }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"); await Promise.resolve(); await vi.advanceTimersByTimeAsync(1); await initialObserver; rejectInitialClose(new Error("external close failed late")); await finalReconciliationStarted; expect(runtime.close).toHaveBeenCalledTimes(4); const finalObserver = expect( port.close({ reason: "external observer of failed process cleanup" }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"); resolveFinalReconciliation(); await finalObserver; await renewedReconciliationStarted; expect(runtime.close).toHaveBeenCalledTimes(5); expect(runtime.close).toHaveBeenLastCalledWith({ handle: HANDLE, reason: "ACPX late protocol cleanup reconciliation 1", discardPersistentState: false, }); child.child.signalCode = "SIGKILL"; child.child.emit("exit", null, "SIGKILL"); resolveRenewedReconciliation(); await vi.advanceTimersByTimeAsync(0); await Promise.resolve(); expect(runtime.close).toHaveBeenCalledTimes(5); } finally { vi.useRealTimers(); } }); it("renews an external intent when late protocol success follows process cleanup failure", async () => { vi.useFakeTimers(); try { const runtime = fakeRuntime(); let rejectInitialClose!: (error: unknown) => void; const initialClose = new Promise((_resolve, reject) => { rejectInitialClose = reject; }); let resolveFinalReconciliation!: () => void; const finalReconciliation = new Promise((resolve) => { resolveFinalReconciliation = resolve; }); let resolveFinalReconciliationStarted!: () => void; const finalReconciliationStarted = new Promise((resolve) => { resolveFinalReconciliationStarted = resolve; }); let resolveRenewedReconciliationStarted!: () => void; const renewedReconciliationStarted = new Promise((resolve) => { resolveRenewedReconciliationStarted = resolve; }); vi.mocked(runtime.close) .mockReturnValueOnce(initialClose) .mockRejectedValueOnce(new Error("reconciliation 1 failed")) .mockRejectedValueOnce(new Error("reconciliation 2 failed")) .mockImplementationOnce(() => { resolveFinalReconciliationStarted(); return finalReconciliation; }) .mockImplementationOnce(() => { resolveRenewedReconciliationStarted(); return Promise.resolve(); }); const child = failingSignalChild(); const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValue(child.child); const port = await openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), awaitProviderOwnership: providerOwnershipEstablished, awaitProviderExit: providerOwnershipEstablished, createRuntime: (options) => { return runtimeWithProvider(runtime, options); }, runtimeCloseTimeoutMs: 1, }); const initialObserver = expect( port.close({ reason: "external protocol close stalls" }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"); await Promise.resolve(); await vi.advanceTimersByTimeAsync(1); await initialObserver; rejectInitialClose(new Error("external close failed late")); await finalReconciliationStarted; expect(runtime.close).toHaveBeenCalledTimes(4); const finalObserver = expect( port.close({ reason: "external observer of failed process cleanup" }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"); await Promise.resolve(); await vi.advanceTimersByTimeAsync(1); await vi.advanceTimersByTimeAsync(4_000); await finalObserver; expect(runtime.close).toHaveBeenCalledTimes(4); child.child.signalCode = "SIGKILL"; child.child.emit("exit", null, "SIGKILL"); resolveFinalReconciliation(); await renewedReconciliationStarted; expect(runtime.close).toHaveBeenCalledTimes(5); expect(runtime.close).toHaveBeenLastCalledWith({ handle: HANDLE, reason: "ACPX late protocol cleanup reconciliation 1", discardPersistentState: false, }); await vi.advanceTimersByTimeAsync(0); expect(runtime.close).toHaveBeenCalledTimes(5); } finally { vi.useRealTimers(); } }); it("shares one reconciliation budget across repeated bounded observers", async () => { vi.useFakeTimers(); try { const runtime = fakeRuntime(); let rejectRetainedClose!: (error: unknown) => void; const retainedClose = new Promise((_resolve, reject) => { rejectRetainedClose = reject; }); vi.mocked(runtime.close) .mockReturnValueOnce(retainedClose) .mockRejectedValueOnce(new Error("reconciliation 1 failed")) .mockRejectedValueOnce(new Error("reconciliation 2 failed")) .mockRejectedValueOnce(new Error("reconciliation 3 failed")); const port = await openCodexAcpxRuntime(openOptions(fakeCommand()), { createRegistry: () => registry(), createStore: () => store(), createRuntime: () => runtime, runtimeCloseTimeoutMs: 1, }); for (const reason of [ "first observer", "second observer", "third observer", ]) { const close = expect(port.close({ reason })).rejects.toThrow( "ACPX runtime and provider cleanup failed", ); await Promise.resolve(); await vi.advanceTimersByTimeAsync(1); await close; } expect(runtime.close).toHaveBeenCalledOnce(); rejectRetainedClose(new Error("retained close failed late")); await vi.waitFor(() => expect(runtime.close).toHaveBeenCalledTimes(4)); await vi.advanceTimersByTimeAsync(0); expect(runtime.close).toHaveBeenCalledTimes(4); } finally { vi.useRealTimers(); } }); it("gives each late failure generation a bounded reconciliation budget", async () => { vi.useFakeTimers(); try { const runtime = fakeRuntime(); let rejectInitialClose!: (error: unknown) => void; const initialClose = new Promise((_resolve, reject) => { rejectInitialClose = reject; }); let rejectNewerClose!: (error: unknown) => void; const newerClose = new Promise((_resolve, reject) => { rejectNewerClose = reject; }); vi.mocked(runtime.close) .mockReturnValueOnce(initialClose) .mockRejectedValueOnce(new Error("reconciliation 1 failed")) .mockRejectedValueOnce(new Error("reconciliation 2 failed")) .mockRejectedValueOnce(new Error("reconciliation 3 failed")) .mockReturnValueOnce(newerClose) .mockResolvedValueOnce(undefined); const port = await openCodexAcpxRuntime(openOptions(fakeCommand()), { createRegistry: () => registry(), createStore: () => store(), createRuntime: () => runtime, runtimeCloseTimeoutMs: 1, }); const firstClose = expect( port.close({ reason: "first protocol close stalls" }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"); await Promise.resolve(); await vi.advanceTimersByTimeAsync(1); await firstClose; rejectInitialClose(new Error("initial close failed late")); await vi.waitFor(() => expect(runtime.close).toHaveBeenCalledTimes(4)); await vi.advanceTimersByTimeAsync(0); const secondClose = expect( port.close({ reason: "newer protocol close stalls" }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"); await Promise.resolve(); await vi.advanceTimersByTimeAsync(1); await secondClose; expect(runtime.close).toHaveBeenCalledTimes(5); rejectNewerClose(new Error("newer close failed late")); await vi.waitFor(() => expect(runtime.close).toHaveBeenCalledTimes(6)); expect(runtime.close).toHaveBeenLastCalledWith({ handle: HANDLE, reason: "ACPX late protocol cleanup reconciliation 1", discardPersistentState: false, }); } finally { vi.useRealTimers(); } }); it("marks a retained protocol close terminal when it succeeds late", async () => { vi.useFakeTimers(); try { const runtime = fakeRuntime(); let resolveRetainedClose!: () => void; const retainedClose = new Promise((resolve) => { resolveRetainedClose = resolve; }); vi.mocked(runtime.close).mockReturnValueOnce(retainedClose); const port = await openCodexAcpxRuntime(openOptions(fakeCommand()), { createRegistry: () => registry(), createStore: () => store(), createRuntime: () => runtime, runtimeCloseTimeoutMs: 1, }); const close = expect( port.close({ reason: "protocol close stalls" }), ).rejects.toThrow("ACPX runtime and provider cleanup failed"); await Promise.resolve(); await vi.advanceTimersByTimeAsync(1); await close; expect(runtime.close).toHaveBeenCalledOnce(); resolveRetainedClose(); await vi.advanceTimersByTimeAsync(0); await expect( port.close({ reason: "observe late success" }), ).resolves.toBeUndefined(); expect(runtime.close).toHaveBeenCalledOnce(); } finally { vi.useRealTimers(); } }); it("retains cleanup after guardian exit until provider exit is proven", async () => { const runtime = fakeRuntime(); const child = fakeChild(); const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValue(child); let proveProviderExit!: () => void; const providerExit = new Promise((resolve) => { proveProviderExit = resolve; }); const port = await openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), awaitProviderOwnership: providerOwnershipEstablished, awaitProviderExit: async () => await providerExit, createRuntime: (options) => runtimeWithProvider(runtime, options), }); let settled = false; const closing = port.close({ reason: "guardian exited first" }); void closing.then( () => { settled = true; }, () => { settled = true; }, ); await vi.waitFor(() => expect(child.kill).toHaveBeenCalledWith("SIGTERM")); await Promise.resolve(); expect(child.signalCode).toBe("SIGTERM"); expect(settled).toBe(false); proveProviderExit(); await expect(closing).resolves.toBeUndefined(); expect(settled).toBe(true); }); it("kills a TERM-resistant provider through its live guardian", async () => { vi.useFakeTimers(); try { const runtime = fakeRuntime(); const child = childThatExitsOnKill(); const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValue(child); const port = await openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), awaitProviderOwnership: providerOwnershipEstablished, awaitProviderExit: providerOwnershipEstablished, createRuntime: (options) => { return runtimeWithProvider(runtime, options); }, }); const closing = expect( port.close({ reason: "provider ignored shutdown" }), ).rejects.toMatchObject({ errors: [ expect.objectContaining({ message: "ACPX provider did not exit after SIGTERM", }), ], }); await vi.advanceTimersByTimeAsync(4_000); await closing; expect(child.kill).toHaveBeenNthCalledWith(1, "SIGTERM"); expect(child.kill).toHaveBeenNthCalledWith(2, "SIGKILL"); expect(child.kill).toHaveBeenCalledTimes(2); expect(child.signalCode).toBe("SIGKILL"); // Cleanup never transfers a copied numeric PGID or releases local // ownership before the verified guardian's exit is observed. expect(child.unref).not.toHaveBeenCalled(); } finally { vi.useRealTimers(); } }); it("retains local ownership when guardian group exit cannot be confirmed", async () => { vi.useFakeTimers(); try { const runtime = fakeRuntime(); const child = stubbornChild(); const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValue(child); const port = await openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), awaitProviderOwnership: providerOwnershipEstablished, awaitProviderExit: providerOwnershipEstablished, createRuntime: (options) => { return runtimeWithProvider(runtime, options); }, }); const closing = expect( port.close({ reason: "provider group exit unconfirmed" }), ).rejects.toMatchObject({ errors: expect.arrayContaining([ expect.objectContaining({ message: "ACPX provider did not exit after SIGKILL", }), ]), }); await vi.advanceTimersByTimeAsync(4_000); await closing; expect(child.kill).toHaveBeenNthCalledWith(1, "SIGTERM"); expect(child.kill).toHaveBeenNthCalledWith(2, "SIGKILL"); expect(child.unref).not.toHaveBeenCalled(); } finally { vi.useRealTimers(); } }); it("rejects Windows before runtime construction or provider spawn", async () => { const command = fakeCommand(); const createRuntime = vi.fn(() => fakeRuntime()); await expect( openCodexAcpxRuntime(openOptions(command), { platform: "win32", createRuntime, }), ).rejects.toThrow( "The production ACPX runtime is unavailable on Windows because verified provider launch requires atomic no-follow file opening", ); expect(createRuntime).not.toHaveBeenCalled(); expect(command.spawn).not.toHaveBeenCalled(); }); it.each([ ["one fence", [42]], ["duplicate fences", [42, 42]], ["an invalid fence", [42, -1]], ])("rejects %s before runtime construction", async (_label, fences) => { const command = fakeCommand(); const createRuntime = vi.fn(() => fakeRuntime()); await expect( openCodexAcpxRuntime( { ...openOptions(command), credentialFenceFds: fences as unknown as readonly [number, number], }, { createRuntime }, ), ).rejects.toThrow( "The production ACPX runtime requires an inherited provider-lifetime fence", ); expect(createRuntime).not.toHaveBeenCalled(); expect(command.spawn).not.toHaveBeenCalled(); }); it("rejects a missing credential owner activation callback", async () => { const command = fakeCommand(); const createRuntime = vi.fn(() => fakeRuntime()); await expect( openCodexAcpxRuntime( { ...openOptions(command), activateCredentialFenceOwner: null, }, { createRuntime }, ), ).rejects.toThrow( "The production ACPX runtime requires an inherited provider-lifetime fence", ); expect(createRuntime).not.toHaveBeenCalled(); expect(command.spawn).not.toHaveBeenCalled(); }); it("rejects and independently cleans providers spawned during termination", async () => { vi.useFakeTimers(); try { const runtime = fakeRuntime(); const firstChild = childThatExitsOnKill(); const lateChild = childThatExitsOnKill(); const command = fakeCommand(); vi.mocked(command.spawn) .mockReturnValueOnce(firstChild) .mockReturnValueOnce(lateChild); const retainedCleanups: Promise[] = []; let runtimeOptions: AcpRuntimeOptions | undefined; const port = await openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), awaitProviderOwnership: providerOwnershipEstablished, awaitProviderExit: providerOwnershipEstablished, retainCleanup: (cleanup) => retainedCleanups.push(cleanup), createRuntime: (options) => { runtimeOptions = options; return runtimeWithProvider(runtime, options); }, }); let settled = false; const closing = port.close({ reason: "join late provider" }).then( () => { settled = true; }, () => { settled = true; }, ); await vi.advanceTimersByTimeAsync(0); expect(firstChild.kill).toHaveBeenCalledWith("SIGTERM"); await vi.advanceTimersByTimeAsync(1_500); expect(() => runtimeOptions?.spawnAgent?.({ command: "ignored", args: ["--stdio"], options: {}, }), ).toThrow("provider spawned after cleanup was sealed"); expect(lateChild.kill).toHaveBeenCalledWith("SIGKILL"); expect(retainedCleanups).toHaveLength(1); await vi.advanceTimersByTimeAsync(500); expect(firstChild.kill).toHaveBeenCalledWith("SIGKILL"); await closing; await retainedCleanups[0]; expect(settled).toBe(true); } finally { vi.useRealTimers(); } }); it("maps prompt turns to the admitted ACPX handle", async () => { const runtime = fakeRuntime(); const turn = { requestId: "turn-1", promptStarted: Promise.resolve(), events: { async *[Symbol.asyncIterator]() {} }, result: Promise.resolve({ status: "completed" as const }), cancel: vi.fn(), closeStream: vi.fn(), }; vi.mocked(runtime.startTurn).mockReturnValue(turn); const port = await openCodexAcpxRuntime(openOptions(fakeCommand()), { createRegistry: () => registry(), createStore: () => store(), createRuntime: () => runtime, }); const signal = new AbortController().signal; const onElicitation = vi.fn(); const admittedTurn = port.startTurn({ text: "Complete the task.", requestId: "turn-1", signal, onElicitation, }); expect(admittedTurn.requestId).toBe(turn.requestId); await expect(admittedTurn.promptStarted).resolves.toBeUndefined(); await expect(admittedTurn.result).resolves.toEqual({ status: "completed" }); expect(runtime.startTurn).toHaveBeenCalledWith({ handle: HANDLE, text: "Complete the task.", mode: "prompt", requestId: "turn-1", signal: expect.any(AbortSignal), onElicitation, }); }); it("observes prompt admission rejection when the sidecar consumes only events and the result", async () => { const runtime = fakeRuntime(); const failure = new Error("Recovered provider could not start the prompt"); vi.mocked(runtime.startTurn).mockImplementation(() => ({ requestId: "turn-recovered-failure", promptStarted: Promise.reject(failure), events: { async *[Symbol.asyncIterator]() { throw failure; } }, result: Promise.reject(failure), cancel: vi.fn(), closeStream: vi.fn(), })); const port = await openCodexAcpxRuntime(openOptions(fakeCommand()), { createRegistry: () => registry(), createStore: () => store(), createRuntime: () => runtime, }); const turn = port.startTurn({ text: "Resume", requestId: "turn-recovered-failure" }); const eventDrain = (async () => { for await (const _event of turn.events) { /* drain */ } })(); await expect(eventDrain).rejects.toBe(failure); // The sidecar does not await promptStarted. Leave it unconsumed across a // full event-loop turn so an unobserved derived rejection fails this test. await new Promise((resolve) => setImmediate(resolve)); // Observing internally must not replace failure with successful admission. await expect(turn.result).rejects.toBe(failure); await expect(turn.promptStarted).rejects.toBe(failure); await port.close({ reason: "test complete" }); }); it("verifies a lazy recovered provider spawned by model selection before returning", async () => { const runtime = fakeRuntime(); const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValue(fakeChild()); let runtimeOptions: AcpRuntimeOptions | undefined; let acknowledgeOwnership!: () => void; const ownership = new Promise((resolve) => { acknowledgeOwnership = resolve; }); vi.mocked(runtime.setConfigOption!).mockImplementation(async () => { await Promise.resolve(); runtimeOptions?.spawnAgent?.({ command: "ignored", args: ["--stdio"], options: {} }); }); const port = await openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), awaitProviderOwnership: () => ownership, awaitProviderExit: providerOwnershipEstablished, createRuntime: (options) => { runtimeOptions = options; return runtime; }, }); let admitted = false; const selection = port.setModel!("gpt-5.6-sol").then(() => { admitted = true; }); void selection.catch(() => undefined); await vi.waitFor(() => expect(command.spawn).toHaveBeenCalledOnce()); expect(admitted).toBe(false); acknowledgeOwnership(); await selection; expect(admitted).toBe(true); expect(() => runtimeOptions?.spawnAgent?.({ command: "ignored", args: [], options: {} })) .toThrow("provider spawned after ownership admission was sealed"); await port.close({ reason: "test complete" }); }); it("uses a fresh single-use command after a cold model control consumes its launch", async () => { const runtime = fakeRuntime(); const freshCommand = () => { const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValueOnce(fakeChild()).mockImplementation(() => { throw new Error("Verified ACPX command lease is closed"); }); return command; }; const first = freshCommand(); const second = freshCommand(); const openCommand = vi.fn(async () => second); const owner = createAcpxCommandLeaseOwner(first, openCommand); let runtimeOptions: AcpRuntimeOptions; vi.mocked(runtime.setConfigOption!).mockImplementation(async () => { runtimeOptions.spawnAgent!({ command: "ignored", args: [], options: {} }); }); vi.mocked(runtime.startTurn).mockImplementation(() => { runtimeOptions.spawnAgent!({ command: "ignored", args: [], options: {} }); return { requestId: "cold-turn", promptStarted: Promise.resolve(), events: { async *[Symbol.asyncIterator]() {} }, result: Promise.resolve({ status: "completed" }), cancel: vi.fn(), closeStream: vi.fn(), }; }); const port = await openCodexAcpxRuntime( { ...openOptions(owner.command), refreshConsumedCommand: owner.refreshConsumedCommand, }, { createRegistry: () => registry(), createStore: () => store(), awaitProviderOwnership: providerOwnershipEstablished, awaitProviderExit: providerOwnershipEstablished, createRuntime: (options) => { runtimeOptions = options; return runtime; }, }, ); await port.setModel!("gpt-5.6-sol"); expect(openCommand).toHaveBeenCalledOnce(); const turn = port.startTurn({ text: "Resume", requestId: "cold-turn" }); await expect(turn.result).resolves.toMatchObject({ status: "completed" }); expect(first.spawn).toHaveBeenCalledOnce(); expect(second.spawn).toHaveBeenCalledOnce(); await port.close({ reason: "test complete" }); await owner.command.close(); }); it("admits a verified provider that starts with the first recovered turn", async () => { const runtime = fakeRuntime(); const child = fakeChild(); const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValue(child); let runtimeOptions: AcpRuntimeOptions | undefined; let resolvePromptStarted: (() => void) | undefined; const promptStarted = new Promise((resolve) => { resolvePromptStarted = resolve; }); const rawTurn = { requestId: "turn-recovered", promptStarted, events: { async *[Symbol.asyncIterator]() {} }, result: new Promise(() => undefined), cancel: vi.fn(), closeStream: vi.fn(), }; vi.mocked(runtime.startTurn).mockImplementation(() => { queueMicrotask(() => { runtimeOptions?.spawnAgent?.({ command: "ignored", args: ["--stdio"], options: {}, }); resolvePromptStarted?.(); }); return rawTurn; }); const port = await openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), awaitProviderOwnership: providerOwnershipEstablished, awaitProviderExit: providerOwnershipEstablished, createRuntime: (options) => { runtimeOptions = options; return runtime; }, }); const turn = port.startTurn({ text: "Resume the task.", requestId: "turn-recovered", }); await expect(turn.promptStarted).resolves.toBeUndefined(); expect(command.spawn).toHaveBeenCalledTimes(1); expect(() => runtimeOptions?.spawnAgent?.({ command: "ignored", args: ["--stdio"], options: {}, }), ).toThrow("provider spawned after ownership admission was sealed"); }); it("projects only ephemeral MCP bindings and applies fail-closed permissions", async () => { const runtime = fakeRuntime(); let runtimeOptions: AcpRuntimeOptions | undefined; const port = await openCodexAcpxRuntime( { ...openOptions(fakeCommand()), permissionMode: "deny-all", mcpServers: [ { name: "paperclip", url: "http://127.0.0.1:3210/mcp", bearerToken: "bridge-secret", runnerOwned: true, }, ], }, { createRegistry: () => registry(), createStore: () => store(), createRuntime: (options) => { runtimeOptions = options; return runtime; }, }, ); expect(runtimeOptions?.mcpServers).toEqual([ { type: "http", name: "paperclip", url: "http://127.0.0.1:3210/mcp", headers: [{ name: "Authorization", value: "Bearer bridge-secret" }], }, ]); await expect( runtimeOptions?.onPermissionRequest?.( { sessionId: "session-1", inferredKind: "execute", raw: { _meta: { is_mcp_tool_approval: true } }, }, { signal: new AbortController().signal }, ), ).resolves.toEqual({ outcome: "reject_once" }); await expect( runtimeOptions?.onPermissionRequest?.( { sessionId: "session-1", inferredKind: "execute", raw: {}, }, { signal: new AbortController().signal }, ), ).resolves.toEqual({ outcome: "reject_once" }); expect( JSON.stringify(vi.mocked(runtime.ensureSession).mock.calls), ).not.toContain("bridge-secret"); await port.close({ reason: "complete" }); }); it("rejects permissions that require an unavailable coordinator", async () => { const runtime = fakeRuntime(); let runtimeOptions: AcpRuntimeOptions | undefined; const port = await openCodexAcpxRuntime(openOptions(fakeCommand()), { createRegistry: () => registry(), createStore: () => store(), createRuntime: (options) => { runtimeOptions = options; return runtime; }, }); await expect( runtimeOptions?.onPermissionRequest?.( { sessionId: "session-1", inferredKind: "write", raw: {}, }, { signal: new AbortController().signal }, ), ).resolves.toEqual({ outcome: "reject_once" }); await port.close({ reason: "complete" }); }); it.each([ ["codex", "gpt-5.6-sol", "approve-all", { outcome: "allow_once" }], ["codex", "gpt-5.6-sol", "approve-reads", { outcome: "reject_once" }], ["codex", "gpt-5.6-sol", "deny-all", { outcome: "reject_once" }], ["claude", "claude-sonnet-5", "approve-all", { outcome: "allow_once" }], ["claude", "claude-sonnet-5", "approve-reads", { outcome: "reject_once" }], ["claude", "claude-sonnet-5", "approve-paperclip", { outcome: "reject_once" }], ["claude", "claude-sonnet-5", "deny-all", { outcome: "reject_once" }], ] as const)( "applies the %s/%s ACPX profile's %s mode without an implicit prompt bridge", async (agent, model, permissionMode, expected) => { const runtime = fakeRuntime(); let runtimeOptions: AcpRuntimeOptions | undefined; const options = openOptions(fakeCommand()); options.profile = resolveQualifiedAcpxProfile(agent, model); options.permissionMode = permissionMode; const port = await openCodexAcpxRuntime(options, { createRegistry: () => registry(), createStore: () => store(), createRuntime: (createdOptions) => { runtimeOptions = createdOptions; return runtime; }, }); expect(runtimeOptions?.nonInteractivePermissions).toBe("fail"); await expect( runtimeOptions?.onPermissionRequest?.( { sessionId: "backend-1", inferredKind: "write", raw: {}, }, { signal: new AbortController().signal }, ), ).resolves.toEqual(expected); await port.close({ reason: "permission policy verified" }); }, ); it("uses the real ACP session when no second agent identity is advertised", async () => { const runtime = fakeRuntime({ ...HANDLE, agentSessionId: undefined }); const durableStore: AcpSessionStore = { load: vi.fn(async () => structuredClone({ acpxRecordId: "record-1", acpSessionId: "backend-1", acpx: { current_model_id: "gpt-5.6-sol" }, } as never), ), save: vi.fn(), }; const port = await openCodexAcpxRuntime(openOptions(fakeCommand()), { createRegistry: () => registry(), createStore: () => durableStore, createRuntime: () => runtime, }); await expect(port.identity()).resolves.toEqual({ acpxRecordId: "record-1", backendSessionId: "backend-1", agentSessionId: "backend-1", }); await expect(port.getStatus()).resolves.toMatchObject({ backendSessionId: "backend-1", agentSessionId: "backend-1", models: { currentModelId: "gpt-5.6-sol" }, }); expect(runtime.close).not.toHaveBeenCalled(); }); it("bounds invalid-identity cleanup before terminating the provider", async () => { vi.useFakeTimers(); try { const runtime = fakeRuntime({ ...HANDLE, backendSessionId: undefined, agentSessionId: undefined, }); vi.mocked(runtime.close).mockImplementation( () => new Promise(() => undefined), ); const child = fakeChild(); const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValue(child); const opening = openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), awaitProviderExit: providerOwnershipEstablished, createRuntime: (options) => ({ ...runtime, ensureSession: vi.fn(async () => { options.spawnAgent?.({ command: "ignored", args: ["--stdio"], options: {}, }); return { ...HANDLE, backendSessionId: undefined, agentSessionId: undefined, }; }), }), }); const rejected = expect(opening).rejects.toThrow( "identity validation and cleanup failed", ); await vi.advanceTimersByTimeAsync(2_000); await rejected; expect(child.kill).toHaveBeenCalledWith("SIGTERM"); } finally { vi.useRealTimers(); } }); it("terminates a provider spawned before the session handshake rejects", async () => { const child = fakeChild(); const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValue(child); const failure = new Error("ACP handshake rejected"); const runtime = fakeRuntime(); await expect( openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), awaitProviderExit: providerOwnershipEstablished, createRuntime: (options) => { vi.mocked(runtime.ensureSession).mockImplementation(async () => { options.spawnAgent?.({ command: "ignored", args: ["--stdio"], options: {}, }); throw failure; }); return runtime; }, }), ).rejects.toBe(failure); expect(runtime.close).not.toHaveBeenCalled(); expect(child.kill).toHaveBeenCalledWith("SIGTERM"); }); it("aborts a blocked handshake, reaps its provider, and closes a late session", async () => { let resolveHandshake: ((handle: AcpRuntimeHandle) => void) | undefined; const blockedHandshake = new Promise((resolve) => { resolveHandshake = resolve; }); const child = fakeChild(); const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValue(child); const runtime = fakeRuntime(); const controller = new AbortController(); const cancellation = new Error("runtime admission cancelled"); const opening = openCodexAcpxRuntime( { ...openOptions(command), signal: controller.signal }, { createRegistry: () => registry(), createStore: () => store(), awaitProviderExit: providerOwnershipEstablished, createRuntime: (runtimeOptions) => { vi.mocked(runtime.ensureSession).mockImplementation(async () => { runtimeOptions.spawnAgent?.({ command: "ignored", args: ["--stdio"], options: {}, }); return await blockedHandshake; }); return runtime; }, }, ); await vi.waitFor(() => expect(command.spawn).toHaveBeenCalledOnce()); controller.abort(cancellation); await expect(opening).rejects.toBe(cancellation); expect(child.kill).toHaveBeenCalledWith("SIGTERM"); resolveHandshake?.(HANDLE); await vi.waitFor(() => expect(runtime.close).toHaveBeenCalledOnce()); expect(runtime.close).toHaveBeenCalledWith({ handle: HANDLE, reason: "ACPX runtime admission aborted", discardPersistentState: false, }); }); it("retries retained admission cleanup after the first close times out", async () => { let rejectHandshake: ((error: Error) => void) | undefined; let firstCloseSettled = false; let overlappingClose = false; const blockedHandshake = new Promise( (_resolve, reject) => { rejectHandshake = reject; }, ); const runtime = fakeRuntime(); vi.mocked(runtime.ensureSession).mockReturnValue(blockedHandshake); vi.mocked(runtime.close) .mockImplementationOnce( () => new Promise((_resolve, reject) => { setTimeout(() => { firstCloseSettled = true; reject(new Error("late close rejected after its timeout")); }, 8); }), ) .mockImplementationOnce(async () => { overlappingClose = !firstCloseSettled; }); const controller = new AbortController(); const cancellation = new Error("runtime admission cancelled"); let runtimeOptions: AcpRuntimeOptions | undefined; const opening = openCodexAcpxRuntime( { ...openOptions(fakeCommand()), signal: controller.signal, }, { createRegistry: () => registry(), createStore: () => store(), createRuntime: (options) => { runtimeOptions = options; return runtime; }, runtimeCloseTimeoutMs: 5, }, ); await vi.waitFor(() => expect(runtime.ensureSession).toHaveBeenCalledOnce(), ); controller.abort(cancellation); const openingFailure = opening.catch((error: unknown) => error); await runtimeOptions!.sessionStore.save({ acpxRecordId: "late-record", acpSessionId: "late-backend-session", agentSessionId: "late-agent-session", name: "late-runtime-name", cwd: "/workspace", } as never); await expect(openingFailure).resolves.toBeInstanceOf(Error); await vi.waitFor(() => expect(runtime.close).toHaveBeenCalledTimes(2)); expect(firstCloseSettled).toBe(true); expect(overlappingClose).toBe(false); expect(runtime.close).toHaveBeenCalledTimes(2); expect(vi.mocked(runtime.close).mock.calls[1]?.[0]).toEqual( vi.mocked(runtime.close).mock.calls[0]?.[0], ); rejectHandshake?.(new Error("test handshake stopped")); }); it("retries a rejected cleanup for a session returned after admission aborts", async () => { let resolveHandshake: ((handle: AcpRuntimeHandle) => void) | undefined; const blockedHandshake = new Promise((resolve) => { resolveHandshake = resolve; }); const firstCloseFailure = new Error("late runtime close failed"); const runtime = fakeRuntime(); vi.mocked(runtime.ensureSession).mockReturnValue(blockedHandshake); vi.mocked(runtime.close) .mockRejectedValueOnce(firstCloseFailure) .mockResolvedValueOnce(undefined); const controller = new AbortController(); const cancellation = new Error("runtime admission cancelled"); const retainedAdmissionCleanups: Promise[] = []; const opening = openCodexAcpxRuntime( { ...openOptions(fakeCommand()), signal: controller.signal, retainFailedAdmissionCleanup: (cleanup) => retainedAdmissionCleanups.push(cleanup), }, { createRegistry: () => registry(), createStore: () => store(), createRuntime: () => runtime, }, ); await vi.waitFor(() => expect(runtime.ensureSession).toHaveBeenCalledOnce(), ); controller.abort(cancellation); await expect(opening).rejects.toBe(cancellation); expect(retainedAdmissionCleanups).toHaveLength(1); resolveHandshake?.(HANDLE); await vi.waitFor(() => expect(runtime.close).toHaveBeenCalledTimes(2)); await expect(retainedAdmissionCleanups[0]).resolves.toBeUndefined(); expect(vi.mocked(runtime.close).mock.calls[0]?.[0]).toEqual({ handle: HANDLE, reason: "ACPX runtime admission aborted", discardPersistentState: false, }); expect(vi.mocked(runtime.close).mock.calls[1]?.[0]).toEqual( vi.mocked(runtime.close).mock.calls[0]?.[0], ); }); it("terminalizes a never-settling late close without overlapping it", async () => { let resolveHandshake: ((handle: AcpRuntimeHandle) => void) | undefined; const blockedHandshake = new Promise((resolve) => { resolveHandshake = resolve; }); const runtime = fakeRuntime(); vi.mocked(runtime.ensureSession).mockReturnValue(blockedHandshake); vi.mocked(runtime.close).mockImplementation( () => new Promise(() => undefined), ); const controller = new AbortController(); const cancellation = new Error("runtime admission cancelled"); const retainedCleanups: Promise[] = []; const opening = openCodexAcpxRuntime( { ...openOptions(fakeCommand()), signal: controller.signal }, { createRegistry: () => registry(), createStore: () => store(), createRuntime: () => runtime, runtimeCloseTimeoutMs: 5, retainCleanup: (cleanup) => retainedCleanups.push(cleanup), }, ); await vi.waitFor(() => expect(runtime.ensureSession).toHaveBeenCalledOnce(), ); controller.abort(cancellation); await expect(opening).rejects.toBe(cancellation); // The exact late-handshake owner and the host-facing aggregate proof are // distinct retained promises over the same cleanup obligation. expect(retainedCleanups).toHaveLength(2); resolveHandshake?.(HANDLE); await expect(retainedCleanups[0]).rejects.toMatchObject({ name: "AcpxRuntimeCloseFinalTimeoutError", }); expect(runtime.close).toHaveBeenCalledOnce(); await new Promise((resolve) => setTimeout(resolve, 20)); expect(runtime.close).toHaveBeenCalledOnce(); }); it("coalesces store and late-handshake cleanup while a timed-out close is active", async () => { let resolveHandshake: ((handle: AcpRuntimeHandle) => void) | undefined; let firstCloseSettled = false; let overlappingClose = false; const blockedHandshake = new Promise((resolve) => { resolveHandshake = resolve; }); const runtime = fakeRuntime(); vi.mocked(runtime.ensureSession).mockReturnValue(blockedHandshake); vi.mocked(runtime.close) .mockImplementationOnce( () => new Promise((_resolve, reject) => { setTimeout(() => { firstCloseSettled = true; reject(new Error("timed-out close eventually rejected")); }, 8); }), ) .mockImplementationOnce(async () => { overlappingClose = !firstCloseSettled; }); const controller = new AbortController(); const cancellation = new Error("runtime admission cancelled"); let runtimeOptions: AcpRuntimeOptions | undefined; const opening = openCodexAcpxRuntime( { ...openOptions(fakeCommand()), signal: controller.signal, }, { createRegistry: () => registry(), createStore: () => store(), createRuntime: (options) => { runtimeOptions = options; return runtime; }, runtimeCloseTimeoutMs: 5, }, ); await vi.waitFor(() => expect(runtime.ensureSession).toHaveBeenCalledOnce(), ); controller.abort(cancellation); await expect(opening).rejects.toBe(cancellation); await runtimeOptions!.sessionStore.save({ acpxRecordId: "record-1", acpSessionId: "backend-1", agentSessionId: "agent-1", name: "stored-runtime-name", cwd: "/workspace", } as never); // The returned handle uses a different runtimeSessionName representation, // but its durable record identity must join the store-published cleanup. resolveHandshake?.(HANDLE); await vi.waitFor(() => expect(runtime.close).toHaveBeenCalledTimes(2)); expect(overlappingClose).toBe(false); }); it("promotes a session fallback into the later durable record lifecycle", async () => { let resolveHandshake: ((handle: AcpRuntimeHandle) => void) | undefined; let firstCloseSettled = false; let overlappingClose = false; const blockedHandshake = new Promise((resolve) => { resolveHandshake = resolve; }); const runtime = fakeRuntime(); vi.mocked(runtime.ensureSession).mockReturnValue(blockedHandshake); vi.mocked(runtime.close) .mockImplementationOnce( () => new Promise((_resolve, reject) => { setTimeout(() => { firstCloseSettled = true; reject(new Error("fallback close rejected after timeout")); }, 15); }), ) .mockImplementationOnce(async () => { overlappingClose = !firstCloseSettled; }); const controller = new AbortController(); const cancellation = new Error("runtime admission cancelled"); const retainedCleanups: Promise[] = []; let runtimeOptions: AcpRuntimeOptions | undefined; const opening = openCodexAcpxRuntime( { ...openOptions(fakeCommand()), signal: controller.signal, }, { createRegistry: () => registry(), createStore: () => store(), createRuntime: (options) => { runtimeOptions = options; return runtime; }, runtimeCloseTimeoutMs: 10, retainCleanup: (cleanup) => retainedCleanups.push(cleanup), }, ); await vi.waitFor(() => expect(runtime.ensureSession).toHaveBeenCalledOnce(), ); controller.abort(cancellation); await expect(opening).rejects.toBe(cancellation); resolveHandshake?.({ ...HANDLE, sessionKey: "provider-key", acpxRecordId: undefined, agentSessionId: "fallback-agent-session", } as never); for (let turn = 0; turn < 5; turn += 1) await Promise.resolve(); await runtimeOptions!.sessionStore.save({ acpxRecordId: "promoted-record", acpSessionId: "promoted-backend-session", name: "promoted-runtime-name", cwd: "/workspace", } as never); await vi.waitFor(() => expect(runtime.close).toHaveBeenCalledTimes(2)); expect(overlappingClose).toBe(false); expect(vi.mocked(runtime.close).mock.calls[1]?.[0].handle).toMatchObject({ acpxRecordId: "promoted-record", backendSessionId: "promoted-backend-session", agentSessionId: "fallback-agent-session", }); expect( decodeAcpxRuntimeHandleState( vi.mocked(runtime.close).mock.calls[1]![0].handle.runtimeSessionName, ), ).toMatchObject({ name: "promoted-runtime-name" }); await Promise.all(retainedCleanups); }); it("keeps exact record ids distinct when whitespace differs", async () => { let rejectHandshake: ((error: Error) => void) | undefined; const blockedHandshake = new Promise( (_resolve, reject) => { rejectHandshake = reject; }, ); const runtime = fakeRuntime(); vi.mocked(runtime.ensureSession).mockReturnValue(blockedHandshake); const controller = new AbortController(); const cancellation = new Error("runtime admission cancelled"); let runtimeOptions: AcpRuntimeOptions | undefined; const opening = openCodexAcpxRuntime( { ...openOptions(fakeCommand()), signal: controller.signal }, { createRegistry: () => registry(), createStore: () => store(), createRuntime: (options) => { runtimeOptions = options; return runtime; }, }, ); await vi.waitFor(() => expect(runtime.ensureSession).toHaveBeenCalledOnce(), ); controller.abort(cancellation); await expect(opening).rejects.toBe(cancellation); for (const acpxRecordId of ["record-id", " record-id "]) { await runtimeOptions!.sessionStore.save({ acpxRecordId, acpSessionId: `${acpxRecordId}-backend`, agentSessionId: `${acpxRecordId}-agent`, name: `${acpxRecordId}-runtime`, cwd: "/workspace", } as never); } await vi.waitFor(() => expect(runtime.close).toHaveBeenCalledTimes(2)); expect( vi .mocked(runtime.close) .mock.calls.map(([input]) => input.handle.acpxRecordId), ).toEqual(["record-id", " record-id "]); rejectHandshake?.(new Error("test handshake stopped")); }); it("exhausts the full retained admission close retry budget", async () => { let rejectHandshake: ((error: Error) => void) | undefined; const blockedHandshake = new Promise( (_resolve, reject) => { rejectHandshake = reject; }, ); const runtime = fakeRuntime(); const closeFailure = new Error("runtime close failed"); const retainedCleanups: Promise[] = []; vi.mocked(runtime.ensureSession).mockReturnValue(blockedHandshake); vi.mocked(runtime.close).mockRejectedValue(closeFailure); const controller = new AbortController(); const cancellation = new Error("runtime admission cancelled"); let runtimeOptions: AcpRuntimeOptions | undefined; const opening = openCodexAcpxRuntime( { ...openOptions(fakeCommand()), signal: controller.signal, }, { createRegistry: () => registry(), createStore: () => store(), createRuntime: (options) => { runtimeOptions = options; return runtime; }, retainCleanup: (cleanup) => retainedCleanups.push(cleanup), }, ); await vi.waitFor(() => expect(runtime.ensureSession).toHaveBeenCalledOnce(), ); controller.abort(cancellation); await runtimeOptions!.sessionStore.save({ acpxRecordId: "late-record", acpSessionId: "late-backend-session", agentSessionId: "late-agent-session", name: "late-runtime-name", cwd: "/workspace", } as never); await expect(opening).rejects.toBeInstanceOf(Error); expect(retainedCleanups).toHaveLength(3); const recoveryOutcome = retainedCleanups[0]!.catch( (error: unknown) => error, ); const compositeOutcome = retainedCleanups[2]!.catch( (error: unknown) => error, ); await vi.waitFor(() => expect(runtime.close).toHaveBeenCalledTimes(4)); await expect(recoveryOutcome).resolves.toMatchObject({ message: "ACPX failed-admission cleanup exhausted 3 retry attempts", }); await new Promise((resolve) => setTimeout(resolve, 100)); expect(runtime.close).toHaveBeenCalledTimes(4); rejectHandshake?.(new Error("test handshake stopped")); await expect(retainedCleanups[1]).resolves.toBeUndefined(); await expect(compositeOutcome).resolves.toMatchObject({ message: "ACPX failed-admission cleanup exhausted 3 retry attempts", }); }); it("aggregates asynchronous provider signal errors after a failed handshake", async () => { const child = failingSignalChild(); const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValue(child.child); const handshakeError = new Error("ACP handshake rejected"); const runtime = fakeRuntime(); const retainedCleanups: Promise[] = []; const result = openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), awaitProviderExit: providerOwnershipEstablished, retainCleanup: (cleanup) => retainedCleanups.push(cleanup), createRuntime: (options) => { vi.mocked(runtime.ensureSession).mockImplementation(async () => { options.spawnAgent?.({ command: "ignored", args: ["--stdio"], options: {}, }); throw handshakeError; }); return runtime; }, }); await expect(result).rejects.toMatchObject({ errors: [ handshakeError, child.errors[0], expect.objectContaining({ message: "ACPX provider did not exit after SIGTERM", }), child.errors[1], expect.objectContaining({ message: "ACPX provider did not exit after SIGKILL", }), ], }); expect(child.child.kill).toHaveBeenNthCalledWith(1, "SIGTERM"); expect(child.child.kill).toHaveBeenNthCalledWith(2, "SIGKILL"); expect(retainedCleanups).toHaveLength(1); child.child.signalCode = "SIGKILL"; child.child.emit("exit", null, "SIGKILL"); await expect(retainedCleanups[0]).resolves.toBeUndefined(); }); it("retains failed-admission cleanup until the provider exits", async () => { const child = childThatExitsAfterSignalCalls(12); const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValue(child); const handshakeError = new Error("ACP handshake rejected"); const runtime = fakeRuntime(); const retainedCleanups: Promise[] = []; await expect( openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), awaitProviderExit: providerOwnershipEstablished, retainCleanup: (cleanup) => retainedCleanups.push(cleanup), createRuntime: (options) => { vi.mocked(runtime.ensureSession).mockImplementation(async () => { options.spawnAgent?.({ command: "ignored", args: ["--stdio"], options: {}, }); throw handshakeError; }); return runtime; }, }), ).rejects.toThrow("session handshake and runtime cleanup failed"); expect(retainedCleanups).toHaveLength(1); await expect(retainedCleanups[0]).resolves.toBeUndefined(); expect(child.kill).toHaveBeenCalledTimes(12); expect(child.signalCode).toBe("SIGKILL"); }); it("closes a recovered session when its handshake rejects before another save", async () => { const runtime = fakeRuntime(); const recoveredStore = store(); vi.mocked(recoveredStore.load).mockResolvedValue({ acpxRecordId: "recovered-record", acpSessionId: "recovered-backend-session", agentSessionId: "recovered-agent-session", name: "recovered-runtime-name", cwd: "/workspace", } as never); const failure = new Error("recovered ACP handshake rejected"); await expect( openCodexAcpxRuntime(openOptions(fakeCommand()), { createRegistry: () => registry(), createStore: () => recoveredStore, createRuntime: (runtimeOptions) => { vi.mocked(runtime.ensureSession).mockImplementation(async () => { await runtimeOptions.sessionStore.load("provider-key"); throw failure; }); return runtime; }, }), ).rejects.toBe(failure); expect(failure).toMatchObject({ code: "ACPX_SESSION_ENSURE_FAILED" }); expect(runtime.close).toHaveBeenCalledOnce(); const recoveredClose = vi.mocked(runtime.close).mock.calls[0]![0]; expect(recoveredClose).toMatchObject({ handle: { sessionKey: "provider-key", backend: "acpx", cwd: "/workspace", acpxRecordId: "recovered-record", backendSessionId: "recovered-backend-session", agentSessionId: "recovered-agent-session", }, reason: "ACPX session handshake failed", discardPersistentState: false, }); expect( decodeAcpxRuntimeHandleState(recoveredClose.handle.runtimeSessionName), ).toEqual({ name: "recovered-runtime-name", agent: "codex", cwd: "/workspace", mode: "persistent", acpxRecordId: "recovered-record", backendSessionId: "recovered-backend-session", agentSessionId: "recovered-agent-session", }); expect(recoveredStore.save).not.toHaveBeenCalled(); }); it("closes a newly created session when its record save rejects", async () => { const runtime = fakeRuntime(); const failingStore = store(); const failure = new Error("session store unavailable"); vi.mocked(failingStore.save).mockRejectedValue(failure); await expect( openCodexAcpxRuntime(openOptions(fakeCommand()), { createRegistry: () => registry(), createStore: () => failingStore, createRuntime: (runtimeOptions) => { vi.mocked(runtime.ensureSession).mockImplementation(async () => { await runtimeOptions.sessionStore.save({ acpxRecordId: "new-record", acpSessionId: "new-backend-session", agentSessionId: "new-agent-session", name: "new-runtime-name", cwd: "/workspace", } as never); return HANDLE; }); return runtime; }, }), ).rejects.toBe(failure); expect(runtime.close).toHaveBeenCalledOnce(); const failedSaveClose = vi.mocked(runtime.close).mock.calls[0]![0]; expect(failedSaveClose).toMatchObject({ handle: { sessionKey: "provider-key", backend: "acpx", cwd: "/workspace", acpxRecordId: "new-record", backendSessionId: "new-backend-session", agentSessionId: "new-agent-session", }, reason: "ACPX session handshake failed", discardPersistentState: false, }); expect( decodeAcpxRuntimeHandleState(failedSaveClose.handle.runtimeSessionName), ).toMatchObject({ name: "new-runtime-name", agent: "codex" }); }); it("bounds a stalled runtime close before terminating a failed-handshake provider", async () => { const child = fakeChild(); const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValue(child); const runtime = fakeRuntime(); vi.mocked(runtime.close).mockImplementation( () => new Promise(() => undefined), ); await expect( openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), awaitProviderExit: providerOwnershipEstablished, createRuntime: (runtimeOptions) => { vi.mocked(runtime.ensureSession).mockImplementation(async () => { await runtimeOptions.sessionStore.save({ acpxRecordId: "actual-record", acpSessionId: "backend-session", agentSessionId: "agent-session", name: "actual-runtime-name", cwd: "/workspace", } as never); runtimeOptions.spawnAgent?.({ command: "ignored", args: ["--stdio"], options: {}, }); throw new Error("ACP handshake rejected"); }); return runtime; }, runtimeCloseTimeoutMs: 5, }), ).rejects.toThrow("ACPX session handshake and runtime cleanup failed"); expect(runtime.close).toHaveBeenCalledOnce(); const stalledClose = vi.mocked(runtime.close).mock.calls[0]![0]; expect(stalledClose).toMatchObject({ handle: { sessionKey: "provider-key", backend: "acpx", cwd: "/workspace", acpxRecordId: "actual-record", backendSessionId: "backend-session", agentSessionId: "agent-session", }, reason: "ACPX session handshake failed", discardPersistentState: false, }); expect( decodeAcpxRuntimeHandleState(stalledClose.handle.runtimeSessionName), ).toMatchObject({ name: "actual-runtime-name", agent: "codex" }); expect(child.kill).toHaveBeenCalledWith("SIGTERM"); }); it("waits for a timed-out admission close before retrying it sequentially", async () => { let rejectFirstClose!: (error: Error) => void; const firstClose = new Promise((_resolve, reject) => { rejectFirstClose = reject; }); const runtime = fakeRuntime(); vi.mocked(runtime.close) .mockImplementationOnce(() => firstClose) .mockResolvedValueOnce(undefined); const retainedCleanups: Promise[] = []; await expect( openCodexAcpxRuntime(openOptions(fakeCommand()), { createRegistry: () => registry(), createStore: () => store(), runtimeCloseTimeoutMs: 1, retainCleanup: (cleanup) => retainedCleanups.push(cleanup), createRuntime: (runtimeOptions) => { vi.mocked(runtime.ensureSession).mockImplementation(async () => { await runtimeOptions.sessionStore.save({ acpxRecordId: "retry-record", acpSessionId: "retry-backend-session", agentSessionId: "retry-agent-session", name: "retry-runtime-name", cwd: "/workspace", } as never); throw new Error("handshake failed before cleanup retry"); }); return runtime; }, }), ).rejects.toThrow("session handshake and runtime cleanup failed"); expect(runtime.close).toHaveBeenCalledOnce(); expect(retainedCleanups).toHaveLength(1); let recoverySettled = false; void retainedCleanups[0]!.then(() => { recoverySettled = true; }); await Promise.resolve(); expect(recoverySettled).toBe(false); rejectFirstClose(new Error("late first close failure")); await vi.waitFor(() => expect(runtime.close).toHaveBeenCalledTimes(2)); await expect(retainedCleanups[0]).resolves.toBeUndefined(); await new Promise((resolve) => setTimeout(resolve, 25)); expect(runtime.close).toHaveBeenCalledTimes(2); expect(runtime.close).toHaveBeenNthCalledWith( 2, expect.objectContaining({ reason: "ACPX session handshake failed", discardPersistentState: false, }), ); }); it("rejects close with asynchronous provider signal errors", async () => { const runtime = fakeRuntime(); const command = fakeCommand(); const child = failingSignalChild(); vi.mocked(command.spawn).mockReturnValue(child.child); const port = await openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), awaitProviderOwnership: providerOwnershipEstablished, awaitProviderExit: providerOwnershipEstablished, createRuntime: (options) => { return runtimeWithProvider(runtime, options); }, }); await expect(port.close({ reason: "test complete" })).rejects.toMatchObject( { errors: [ child.errors[0], expect.objectContaining({ message: "ACPX provider did not exit after SIGTERM", }), child.errors[1], expect.objectContaining({ message: "ACPX provider did not exit after SIGKILL", }), ], }, ); expect(child.child.kill).toHaveBeenNthCalledWith(1, "SIGTERM"); expect(child.child.kill).toHaveBeenNthCalledWith(2, "SIGKILL"); expect(child.child.kill).toHaveBeenCalledTimes(2); child.child.signalCode = "SIGKILL"; child.child.emit("exit", null, "SIGKILL"); }); it("retains a provider error after close removes the child", async () => { const runtime = fakeRuntime(); const command = fakeCommand(); const child = fakeChild(); const providerError = new Error("provider spawn failed"); vi.mocked(command.spawn).mockReturnValue(child); const port = await openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), awaitProviderOwnership: providerOwnershipEstablished, awaitProviderExit: providerOwnershipEstablished, createRuntime: (options) => { return runtimeWithProvider(runtime, options); }, }); child.emit("error", providerError); // A real ChildProcess has committed its terminal status before `close`. // Model that ordering so the process tracker can prove this child no // longer needs a cleanup signal while retaining its earlier error. child.exitCode = 1; child.emit("close", 1, null); await expect(port.close({ reason: "test complete" })).rejects.toMatchObject( { errors: [providerError], }, ); expect(child.kill).not.toHaveBeenCalled(); }); it("propagates ownership failure from a provider spawned during verification", async () => { const firstChild = fakeChild(); const racingChild = fakeChild(); const command = fakeCommand(); vi.mocked(command.spawn) .mockReturnValueOnce(firstChild) .mockReturnValueOnce(racingChild); let resolveFirstOwnership!: () => void; const firstOwnership = new Promise((resolve) => { resolveFirstOwnership = resolve; }); const racingOwnershipFailure = new Error( "racing guardian ownership failed", ); const runtime = fakeRuntime(); let runtimeOptions: AcpRuntimeOptions | undefined; const awaitProviderOwnership = vi.fn((child: ChildProcess) => { if (child !== firstChild) return Promise.reject(racingOwnershipFailure); return firstOwnership.then(() => { // This callback runs while verification still awaits its initial // ownership batch, reproducing the exact append-after-splice race. runtimeOptions?.spawnAgent?.({ command: "ignored", args: ["--stdio"], options: {}, }); }); }); vi.mocked(runtime.ensureSession).mockImplementation(async () => { runtimeOptions?.spawnAgent?.({ command: "ignored", args: ["--stdio"], options: {}, }); return HANDLE; }); const opening = openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), awaitProviderOwnership, awaitProviderExit: providerOwnershipEstablished, createRuntime: (options) => { runtimeOptions = options; return runtime; }, }); await vi.waitFor(() => expect(awaitProviderOwnership).toHaveBeenCalledOnce(), ); resolveFirstOwnership(); await expect(opening).rejects.toBe(racingOwnershipFailure); expect(awaitProviderOwnership).toHaveBeenCalledTimes(2); expect(firstChild.kill).toHaveBeenCalledWith("SIGTERM"); expect(racingChild.kill).toHaveBeenCalledWith("SIGTERM"); }); it("bounds a stalled session handshake and terminates its provider", async () => { const child = fakeChild(); const command = fakeCommand(); vi.mocked(command.spawn).mockReturnValue(child); const runtime = fakeRuntime(); await expect( openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), sessionHandshakeTimeoutMs: 1, awaitProviderExit: providerOwnershipEstablished, createRuntime: (options) => { vi.mocked(runtime.ensureSession).mockImplementation(() => { options.spawnAgent?.({ command: "ignored", args: ["--stdio"], options: {}, }); return new Promise(() => undefined); }); return runtime; }, }), ).rejects.toThrow("session handshake exceeded its admission deadline"); expect(runtime.close).not.toHaveBeenCalled(); expect(child.kill).toHaveBeenCalledWith("SIGTERM"); }); it("rejects provider children created after handshake cleanup is sealed", async () => { const child = fakeChild(); const postCleanupChild = fakeChild(); const command = fakeCommand(); vi.mocked(command.spawn) .mockReturnValueOnce(child) .mockReturnValueOnce(postCleanupChild); const runtime = fakeRuntime(); const retainCleanup = vi.fn<(cleanup: Promise) => void>(); let runtimeOptions: AcpRuntimeOptions | undefined; let resolveHandshake: ((handle: AcpRuntimeHandle) => void) | undefined; vi.mocked(runtime.ensureSession).mockImplementation( () => new Promise((resolve) => { resolveHandshake = resolve; }), ); await expect( openCodexAcpxRuntime(openOptions(command), { createRegistry: () => registry(), createStore: () => store(), sessionHandshakeTimeoutMs: 1, awaitProviderExit: providerOwnershipEstablished, retainCleanup, createRuntime: (options) => { runtimeOptions = options; return runtime; }, }), ).rejects.toThrow("session handshake exceeded its admission deadline"); expect(retainCleanup).toHaveBeenCalledTimes(2); const lateHandshakeCleanup = retainCleanup.mock.calls[0]?.[0]; const cleanupProof = retainCleanup.mock.calls[1]?.[0]; expect(lateHandshakeCleanup).toBeDefined(); expect(cleanupProof).toBeDefined(); expect(() => runtimeOptions?.spawnAgent?.({ command: "ignored", args: ["--stdio"], options: {}, }), ).toThrow("provider spawned after cleanup was sealed"); expect(child.kill).toHaveBeenCalledWith("SIGKILL"); expect(retainCleanup).toHaveBeenCalledTimes(3); await retainCleanup.mock.calls[2]?.[0]; resolveHandshake?.(HANDLE); await lateHandshakeCleanup; await cleanupProof; expect(runtime.close).toHaveBeenCalledWith({ handle: HANDLE, reason: "ACPX session handshake completed after its admission deadline", discardPersistentState: false, }); expect(() => runtimeOptions?.spawnAgent?.({ command: "ignored", args: ["--stdio"], options: {}, }), ).toThrow("provider spawned after cleanup was sealed"); expect(postCleanupChild.kill).toHaveBeenCalledWith("SIGKILL"); expect(retainCleanup).toHaveBeenCalledTimes(4); await retainCleanup.mock.calls[3]?.[0]; }); }); function openOptions( command: VerifiedAcpxCommandLease, ): AcpxRuntimePortOpenOptions { return { command, profile: { driverKind: "acpx_runtime", protocolVersion: 1, acpxVersion: "0.13.1", agent: "codex", agentProfileVersion: 1, agentServerPackage: "@agentclientprotocol/codex-acp", agentServerVersion: "1.6.2", agentRuntimePackage: null, agentRuntimeVersion: null, commandDigest: "sha256:test", qualificationModel: "gpt-5.6-sol", reportedModelId: "gpt-5.6-sol", permissionPolicy: "interactive", }, cwd: "/workspace", stateDirectory: "/runtime/state", providerSessionKey: "provider-key", permissionMode: "approve-reads", permissionPolicy: { autoApprove: ["read"], escalate: ["write"], defaultAction: "escalate", }, launchEnvironment: { CODEX_HOME: "/runtime/agent-home", OPENAI_API_KEY: "credential-secret", OMITTED: undefined, }, // Fake command leases do not inherit these descriptors; production host // supplies both live credential-quorum listener descriptors. credentialFenceFds: [42, 43] as const, activateCredentialFenceOwner: async () => undefined, systemInstructions: "Use Paperclip tools.", mcpServers: [], retainFailedAdmissionCleanup: vi.fn(), }; } function fakeRuntime(handle: AcpRuntimeHandle = HANDLE): AcpRuntime { return { ensureSession: vi.fn().mockResolvedValue(handle), startTurn: vi.fn(), runTurn: vi.fn(), getStatus: vi.fn(), setConfigOption: vi.fn(), cancel: vi.fn(), close: vi.fn(), }; } function pendingExtensionTurn(requestId: string) { let settle!: () => void; const result = new Promise((resolve) => { settle = resolve; }); return { settle, turn: { requestId, promptStarted: Promise.resolve(), events: (async function* () { await result; })(), result: result.then(() => ({ status: "completed" as const })), cancel: async () => undefined, closeStream: async () => undefined, } }; } function runtimeWithProvider( runtime: AcpRuntime, options: AcpRuntimeOptions, ): AcpRuntime { vi.mocked(runtime.ensureSession).mockImplementationOnce(async () => { options.spawnAgent?.({ command: "ignored", args: ["--stdio"], options: {}, }); return HANDLE; }); return runtime; } async function providerOwnershipEstablished(): Promise {} function fakeCommand(): VerifiedAcpxCommandLease { return { spawn: vi.fn(), close: vi.fn() }; } function fakeChild(): ChildProcess { const child = new EventEmitter() as ChildProcess; Object.defineProperties(child, { exitCode: { value: null, writable: true }, signalCode: { value: null, writable: true }, }); child.kill = vi.fn(() => { child.signalCode = "SIGTERM"; queueMicrotask(() => child.emit("exit", null, "SIGTERM")); return true; }); return child; } function failingSignalChild(): { child: ChildProcess; errors: [Error, Error]; } { const child = new EventEmitter() as ChildProcess; const errors: [Error, Error] = [ new Error("SIGTERM delivery failed"), new Error("SIGKILL delivery failed"), ]; Object.defineProperties(child, { exitCode: { value: null, writable: true }, signalCode: { value: null, writable: true }, }); child.kill = vi.fn((signal) => { const error = signal === "SIGTERM" ? errors[0] : errors[1]; queueMicrotask(() => child.emit("error", error)); return true; }); child.unref = vi.fn(() => child); return { child, errors }; } function childThatExitsAfterSignalCalls(exitAfter: number): ChildProcess { const child = new EventEmitter() as ChildProcess; Object.defineProperties(child, { exitCode: { value: null, writable: true }, signalCode: { value: null, writable: true }, }); child.kill = vi.fn((signal?: NodeJS.Signals | number) => { const calls = vi.mocked(child.kill).mock.calls.length; if (calls >= exitAfter) { child.signalCode = signal as NodeJS.Signals; queueMicrotask(() => child.emit("exit", null, signal)); } else { queueMicrotask(() => child.emit("error", new Error(`signal attempt ${calls} failed`)), ); } return true; }); child.unref = vi.fn(() => child); return child; } function stubbornChild(): ChildProcess { const child = new EventEmitter() as ChildProcess; Object.defineProperties(child, { pid: { value: 71_001 }, exitCode: { value: null, writable: true }, signalCode: { value: null, writable: true }, stdin: { value: { destroy: vi.fn() } }, stdout: { value: { destroy: vi.fn() } }, stderr: { value: { destroy: vi.fn() } }, }); child.kill = vi.fn(() => true); child.unref = vi.fn(() => child); return child; } function childThatExitsOnKill(): ChildProcess { const child = new EventEmitter() as ChildProcess; Object.defineProperties(child, { pid: { value: 71_002 }, exitCode: { value: null, writable: true }, signalCode: { value: null, writable: true }, }); child.kill = vi.fn((signal?: NodeJS.Signals | number) => { if (signal === "SIGKILL") { child.signalCode = "SIGKILL"; queueMicrotask(() => child.emit("exit", null, "SIGKILL")); } return true; }); child.unref = vi.fn(() => child); return child; } function registry(): AcpAgentRegistry { return { resolve: vi.fn(), list: vi.fn() }; } function store(): AcpSessionStore { return { load: vi.fn(), save: vi.fn() }; }