diff --git a/packages/server/src/server/agent/providers/codex-goal-provenance.local.e2e.test.ts b/packages/server/src/server/agent/providers/codex-goal-provenance.local.e2e.test.ts index c4f427a371..44bfdf0593 100644 --- a/packages/server/src/server/agent/providers/codex-goal-provenance.local.e2e.test.ts +++ b/packages/server/src/server/agent/providers/codex-goal-provenance.local.e2e.test.ts @@ -5,7 +5,7 @@ import path from "node:path"; import { expect, test } from "vitest"; import { createTestLogger } from "../../../test-utils/test-logger.js"; import { CodexAppServerAgentClient } from "./codex-app-server-agent.js"; -import type { AgentStreamEvent } from "../agent-sdk-types.js"; +import type { AgentSession, AgentStreamEvent } from "../agent-sdk-types.js"; // Native Codex owns goals; the fixture supplies inference responses only. test("records all four /goal commands as user edits in the next model request", async () => { @@ -45,13 +45,15 @@ test("records all four /goal commands as user edits in the next model request", }); } const server = createServer(handleRequest); - server.listen(0, "127.0.0.1"); - await once(server, "listening"); - const address = server.address(); - if (!address || typeof address === "string") throw new Error("missing fixture address"); - writeFileSync( - path.join(root, "config.toml"), - ` + let session: AgentSession | null = null; + try { + server.listen(0, "127.0.0.1"); + await once(server, "listening"); + const address = server.address(); + if (!address || typeof address === "string") throw new Error("missing fixture address"); + writeFileSync( + path.join(root, "config.toml"), + ` model = "gpt-6.1-sol" model_reasoning_effort = "low" model_provider = "fixture" @@ -68,61 +70,75 @@ supports_websockets = false request_max_retries = 0 stream_max_retries = 0 `, - ); - const client = new CodexAppServerAgentClient(createTestLogger(), { env: { CODEX_HOME: root } }); - const session = await client.createSession({ - provider: "codex", - cwd: root, - model: "gpt-6.1-sol", - thinkingOptionId: "low", - }); - let active = false; - const unsubscribe = session.subscribe((event) => { - if (event.type === "turn_started") active = true; - if (["turn_completed", "turn_canceled", "turn_failed"].includes(event.type)) active = false; - }); - try { - expect((await session.listCommands?.())?.some((command) => command.name === "goal")).toBe(true); - for (const command of [ - "/goal AUDIT_GOAL: summarize local evidence", - "/goal pause", - "/goal resume", - "/goal clear", - ]) { - const handler = session.tryHandleOutOfBand?.(command); - expect(handler).toBeTruthy(); - const events: AgentStreamEvent[] = []; - await handler!.run({ emit: (event) => events.push(event) }); - expect(events).not.toEqual( - expect.arrayContaining([ - expect.objectContaining({ - item: expect.objectContaining({ - text: expect.stringContaining("Failed to update goal"), - }), - }), - ]), + ); + const client = new CodexAppServerAgentClient(createTestLogger(), { env: { CODEX_HOME: root } }); + session = await client.createSession({ + provider: "codex", + cwd: root, + model: "gpt-6.1-sol", + thinkingOptionId: "low", + }); + let active = false; + const unsubscribe = session.subscribe((event) => { + if (event.type === "turn_started") active = true; + if (["turn_completed", "turn_canceled", "turn_failed"].includes(event.type)) active = false; + }); + try { + expect((await session.listCommands?.())?.some((command) => command.name === "goal")).toBe( + true, ); + for (const command of [ + "/goal AUDIT_GOAL: summarize local evidence", + "/goal pause", + "/goal resume", + "/goal clear", + ]) { + const handler = session.tryHandleOutOfBand?.(command); + expect(handler).toBeTruthy(); + const events: AgentStreamEvent[] = []; + await handler!.run({ emit: (event) => events.push(event) }); + expect(events).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + item: expect.objectContaining({ + text: expect.stringContaining("Failed to update goal"), + }), + }), + ]), + ); + } + await expect.poll(() => active, { timeout: 30_000 }).toBe(false); + expect((await session.run("PROBE_AFTER_GOAL_CLEAR")).finalText).toBe( + "LOCAL_FIXTURE_RESPONSE", + ); + const body = JSON.parse(requests.at(-1)!); + const goalContext = body.input + .filter((item: { role: string }) => item.role === "user") + .flatMap((item: { content: Array<{ text?: string }> }) => + item.content.map((part) => part.text ?? ""), + ) + .filter((text: string) => text.includes('')) + .join("\n"); + expect(goalContext).toContain('User set the goal: "AUDIT_GOAL: summarize local evidence"'); + expect(goalContext).toContain('User set goal status: "paused".'); + expect(goalContext).toContain('User set goal status: "active".'); + expect(goalContext).toContain("User cleared the goal."); + } finally { + unsubscribe(); } - await expect.poll(() => active, { timeout: 30_000 }).toBe(false); - expect((await session.run("PROBE_AFTER_GOAL_CLEAR")).finalText).toBe("LOCAL_FIXTURE_RESPONSE"); - const body = JSON.parse(requests.at(-1)!); - const goalContext = body.input - .filter((item: { role: string }) => item.role === "user") - .flatMap((item: { content: Array<{ text?: string }> }) => - item.content.map((part) => part.text ?? ""), - ) - .filter((text: string) => text.includes('')) - .join("\n"); - expect(goalContext).toContain('User set the goal: "AUDIT_GOAL: summarize local evidence"'); - expect(goalContext).toContain('User set goal status: "paused".'); - expect(goalContext).toContain('User set goal status: "active".'); - expect(goalContext).toContain("User cleared the goal."); } finally { - unsubscribe(); - await session.close(); - server.closeAllConnections(); - server.close(); - await once(server, "close"); - rmSync(root, { recursive: true, force: true }); + try { + await session?.close(); + } finally { + try { + server.closeAllConnections(); + if (server.listening) { + server.close(); + await once(server, "close"); + } + } finally { + rmSync(root, { recursive: true, force: true }); + } + } } }, 60_000); diff --git a/packages/server/src/server/agent/providers/codex-goal-provenance.test.ts b/packages/server/src/server/agent/providers/codex-goal-provenance.test.ts index 2faa8f3705..d0e46d43a7 100644 --- a/packages/server/src/server/agent/providers/codex-goal-provenance.test.ts +++ b/packages/server/src/server/agent/providers/codex-goal-provenance.test.ts @@ -4,6 +4,7 @@ import { expect, test } from "vitest"; import { createTestLogger } from "../../../test-utils/test-logger.js"; import { CodexAppServerAgentClient } from "./codex-app-server-agent.js"; +import type { AgentSession } from "../agent-sdk-types.js"; // A protocol peer exercises the public session interface without a Codex install. test("marks set, pause, resume and clear as user goal mutations", async () => { @@ -35,13 +36,14 @@ readline.createInterface({ input: process.stdin }).on("line", (line) => { command: { mode: "replace", argv: [process.execPath, script] }, env: { CODEX_HOME: root, CAPTURE: capture }, }); - const session = await client.createSession({ - provider: "codex", - cwd: root, - model: "test-model", - thinkingOptionId: "low", - }); + let session: AgentSession | null = null; try { + session = await client.createSession({ + provider: "codex", + cwd: root, + model: "test-model", + thinkingOptionId: "low", + }); for (const command of ["/goal ship feature", "/goal pause", "/goal resume", "/goal clear"]) { const handler = session.tryHandleOutOfBand?.(command); if (!handler) throw new Error(`Missing handler for ${command}`); @@ -73,7 +75,10 @@ readline.createInterface({ input: process.stdin }).on("line", (line) => { { method: "thread/goal/clear", params: { threadId: "goal-thread", origin: "user" } }, ]); } finally { - await session.close(); - rmSync(root, { recursive: true, force: true }); + try { + await session?.close(); + } finally { + rmSync(root, { recursive: true, force: true }); + } } });