From f12e9c41fa15bd667a57adf2ab256319b1ba70b0 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 15 May 2026 05:03:28 +0100 Subject: [PATCH] fix(codex): inject app-server client factories Co-authored-by: Benjamin Badejo --- CHANGELOG.md | 1 + .../auth-profile-runtime-contract.test.ts | 74 ++++++++----- .../codex/src/app-server/client-factory.ts | 13 --- .../codex/src/app-server/compact.test.ts | 43 ++++++-- extensions/codex/src/app-server/compact.ts | 13 +-- .../run-attempt.context-engine.test.ts | 31 +++++- .../codex/src/app-server/run-attempt.test.ts | 102 +++++++++++------- .../codex/src/app-server/run-attempt.ts | 16 +-- 8 files changed, 181 insertions(+), 112 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94b8ab0db82..4c03c2c9528 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Codex app-server: inject native client factories per run and compaction attempt instead of using module-scope test state, avoiding temporal-dead-zone reads during cyclic startup. (#81148) Thanks @bdjben. - Plugin skills: replace generated Windows plugin-skill directories before publishing the current skill link, avoiding repeated `EINVAL` warnings from stale non-symlink entries. Fixes #81432. (#81446) Thanks @hclsys and @vincentkoc. - Channels/config: treat channel entries with only `enabled: true` as configured state so plugin-backed channels can auto-enable from an explicit on switch. Fixes #81323. (#81331) Thanks @EvanYao826 and @vincentkoc. - CLI/update: add an update finalization path for externally swapped core runtimes, running update-time doctor repair and plugin convergence from post-doctor config and install-record state before reporting completion. Thanks @shakkernerd. diff --git a/extensions/codex/src/app-server/auth-profile-runtime-contract.test.ts b/extensions/codex/src/app-server/auth-profile-runtime-contract.test.ts index fcc78dac550..cf0f22968d4 100644 --- a/extensions/codex/src/app-server/auth-profile-runtime-contract.test.ts +++ b/extensions/codex/src/app-server/auth-profile-runtime-contract.test.ts @@ -7,10 +7,36 @@ import { } from "openclaw/plugin-sdk/agent-harness"; import { AUTH_PROFILE_RUNTIME_CONTRACT } from "openclaw/plugin-sdk/agent-runtime-test-contracts"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { runCodexAppServerAttempt, __testing } from "./run-attempt.js"; +import type { CodexAppServerClientFactory } from "./client-factory.js"; +import { runCodexAppServerAttempt as runCodexAppServerAttemptImpl } from "./run-attempt.js"; import { readCodexAppServerBinding, writeCodexAppServerBinding } from "./session-binding.js"; import { createCodexTestModel } from "./test-support.js"; +let codexAppServerClientFactoryForTest: CodexAppServerClientFactory | undefined; + +type RunCodexAppServerAttemptOptions = NonNullable< + Parameters[1] +>; + +function setCodexAppServerClientFactoryForTest(factory: CodexAppServerClientFactory): void { + codexAppServerClientFactoryForTest = factory; +} + +function resetCodexAppServerClientFactoryForTest(): void { + codexAppServerClientFactoryForTest = undefined; +} + +function runCodexAppServerAttempt( + params: EmbeddedRunAttemptParams, + options: RunCodexAppServerAttemptOptions = {}, +) { + const clientFactory = options.clientFactory ?? codexAppServerClientFactoryForTest; + return runCodexAppServerAttemptImpl( + params, + clientFactory ? { ...options, clientFactory } : options, + ); +} + function createParams(sessionFile: string, workspaceDir: string): EmbeddedRunAttemptParams { return { prompt: AUTH_PROFILE_RUNTIME_CONTRACT.workspacePrompt, @@ -85,29 +111,27 @@ function createCodexAuthProfileHarness(params: { startMethod: "thread/start" | " const seenAgentDirs: Array = []; const requests: Array<{ method: string; params: unknown }> = []; let notify: (notification: unknown) => Promise = async () => undefined; - __testing.setCodexAppServerClientFactoryForTests( - async (_startOptions, authProfileId, agentDir) => { - seenAuthProfileIds.push(authProfileId); - seenAgentDirs.push(agentDir); - return { - request: vi.fn(async (method: string, requestParams?: unknown) => { - requests.push({ method, params: requestParams }); - if (method === params.startMethod) { - return threadStartResult(); - } - if (method === "turn/start") { - return turnStartResult(); - } - throw new Error(`unexpected method: ${method}`); - }), - addNotificationHandler: (handler: (notification: unknown) => Promise) => { - notify = handler; - return () => undefined; - }, - addRequestHandler: () => () => undefined, - } as never; - }, - ); + setCodexAppServerClientFactoryForTest(async (_startOptions, authProfileId, agentDir) => { + seenAuthProfileIds.push(authProfileId); + seenAgentDirs.push(agentDir); + return { + request: vi.fn(async (method: string, requestParams?: unknown) => { + requests.push({ method, params: requestParams }); + if (method === params.startMethod) { + return threadStartResult(); + } + if (method === "turn/start") { + return turnStartResult(); + } + throw new Error(`unexpected method: ${method}`); + }), + addNotificationHandler: (handler: (notification: unknown) => Promise) => { + notify = handler; + return () => undefined; + }, + addRequestHandler: () => () => undefined, + } as never; + }); return { seenAuthProfileIds, seenAgentDirs, @@ -138,7 +162,7 @@ describe("Auth profile runtime contract - Codex app-server adapter", () => { afterEach(async () => { abortAgentHarnessRun(AUTH_PROFILE_RUNTIME_CONTRACT.sessionId); - __testing.resetCodexAppServerClientFactoryForTests(); + resetCodexAppServerClientFactoryForTest(); await fs.rm(tmpDir, { recursive: true, force: true }); }); diff --git a/extensions/codex/src/app-server/client-factory.ts b/extensions/codex/src/app-server/client-factory.ts index 7398ed75454..e3885c47bb7 100644 --- a/extensions/codex/src/app-server/client-factory.ts +++ b/extensions/codex/src/app-server/client-factory.ts @@ -22,16 +22,3 @@ export const defaultCodexAppServerClientFactory: CodexAppServerClientFactory = ( import("./shared-client.js").then(({ getSharedCodexAppServerClient }) => getSharedCodexAppServerClient({ startOptions, authProfileId, agentDir, config }), ); - -export function createCodexAppServerClientFactoryTestHooks( - setFactory: (factory: CodexAppServerClientFactory) => void, -) { - return { - setCodexAppServerClientFactoryForTests(factory: CodexAppServerClientFactory): void { - setFactory(factory); - }, - resetCodexAppServerClientFactoryForTests(): void { - setFactory(defaultCodexAppServerClientFactory); - }, - } as const; -} diff --git a/extensions/codex/src/app-server/compact.test.ts b/extensions/codex/src/app-server/compact.test.ts index d9c65f5a7dd..396513f5bcd 100644 --- a/extensions/codex/src/app-server/compact.test.ts +++ b/extensions/codex/src/app-server/compact.test.ts @@ -3,12 +3,35 @@ import os from "node:os"; import path from "node:path"; import type { HarnessContextEngine as ContextEngine } from "openclaw/plugin-sdk/agent-harness-runtime"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import type { CodexAppServerClientFactory } from "./client-factory.js"; import type { CodexAppServerClient } from "./client.js"; -import { maybeCompactCodexAppServerSession, __testing } from "./compact.js"; +import { maybeCompactCodexAppServerSession as maybeCompactCodexAppServerSessionImpl } from "./compact.js"; import type { CodexServerNotification } from "./protocol.js"; import { writeCodexAppServerBinding } from "./session-binding.js"; let tempDir: string; +let codexAppServerClientFactoryForTest: CodexAppServerClientFactory | undefined; + +type MaybeCompactOptions = NonNullable[1]>; + +function setCodexAppServerClientFactoryForTest(factory: CodexAppServerClientFactory): void { + codexAppServerClientFactoryForTest = factory; +} + +function resetCodexAppServerClientFactoryForTest(): void { + codexAppServerClientFactoryForTest = undefined; +} + +function maybeCompactCodexAppServerSession( + params: Parameters[0], + options: MaybeCompactOptions = {}, +) { + const clientFactory = options.clientFactory ?? codexAppServerClientFactoryForTest; + return maybeCompactCodexAppServerSessionImpl( + params, + clientFactory ? { ...options, clientFactory } : options, + ); +} async function writeTestBinding(options: { authProfileId?: string } = {}): Promise { const sessionFile = path.join(tempDir, "session.jsonl"); @@ -49,13 +72,13 @@ describe("maybeCompactCodexAppServerSession", () => { }); afterEach(async () => { - __testing.resetCodexAppServerClientFactoryForTests(); + resetCodexAppServerClientFactoryForTest(); await fs.rm(tempDir, { recursive: true, force: true }); }); it("waits for native app-server compaction before reporting success", async () => { const fake = createFakeCodexClient(); - __testing.setCodexAppServerClientFactoryForTests(async () => fake.client); + setCodexAppServerClientFactoryForTest(async () => fake.client); const sessionFile = await writeTestBinding(); const pendingResult = startCompaction(sessionFile, { currentTokenCount: 123 }); @@ -88,7 +111,7 @@ describe("maybeCompactCodexAppServerSession", () => { it("accepts native context-compaction item completion as success", async () => { const fake = createFakeCodexClient(); - __testing.setCodexAppServerClientFactoryForTests(async () => fake.client); + setCodexAppServerClientFactoryForTest(async () => fake.client); const sessionFile = await writeTestBinding(); const pendingResult = startCompaction(sessionFile); @@ -115,7 +138,7 @@ describe("maybeCompactCodexAppServerSession", () => { it("reuses the bound auth profile for native compaction", async () => { const fake = createFakeCodexClient(); let seenAuthProfileId: string | undefined; - __testing.setCodexAppServerClientFactoryForTests(async (_startOptions, authProfileId) => { + setCodexAppServerClientFactoryForTest(async (_startOptions, authProfileId) => { seenAuthProfileId = authProfileId; return fake.client; }); @@ -137,7 +160,7 @@ describe("maybeCompactCodexAppServerSession", () => { it("fails closed when the persisted binding auth profile disagrees with the runtime request", async () => { const fake = createFakeCodexClient(); const factory = vi.fn(async () => fake.client); - __testing.setCodexAppServerClientFactoryForTests(factory); + setCodexAppServerClientFactoryForTest(factory); const sessionFile = path.join(tempDir, "session.jsonl"); await writeCodexAppServerBinding(sessionFile, { threadId: "thread-1", @@ -163,7 +186,7 @@ describe("maybeCompactCodexAppServerSession", () => { it("prefers owning context-engine compaction and records native status separately", async () => { const fake = createFakeCodexClient(); - __testing.setCodexAppServerClientFactoryForTests(async () => fake.client); + setCodexAppServerClientFactoryForTest(async () => fake.client); const sessionFile = await writeTestBinding(); const compact = vi.fn(async (_params: unknown) => ({ ok: true, @@ -260,7 +283,7 @@ describe("maybeCompactCodexAppServerSession", () => { it("still runs native compaction when context-engine maintenance fails", async () => { const fake = createFakeCodexClient(); - __testing.setCodexAppServerClientFactoryForTests(async () => fake.client); + setCodexAppServerClientFactoryForTest(async () => fake.client); const sessionFile = await writeTestBinding(); const contextEngine: ContextEngine = { info: { id: "lossless-claw", name: "Lossless Claw", ownsCompaction: true }, @@ -307,7 +330,7 @@ describe("maybeCompactCodexAppServerSession", () => { it("records native compaction status when primary compaction has no result payload", async () => { const fake = createFakeCodexClient(); - __testing.setCodexAppServerClientFactoryForTests(async () => fake.client); + setCodexAppServerClientFactoryForTest(async () => fake.client); const sessionFile = await writeTestBinding(); const contextEngine: ContextEngine = { info: { id: "lossless-claw", name: "Lossless Claw", ownsCompaction: true }, @@ -350,7 +373,7 @@ describe("maybeCompactCodexAppServerSession", () => { it("reports context-engine compaction errors without skipping native compaction", async () => { const fake = createFakeCodexClient(); - __testing.setCodexAppServerClientFactoryForTests(async () => fake.client); + setCodexAppServerClientFactoryForTest(async () => fake.client); const sessionFile = await writeTestBinding(); const contextEngine: ContextEngine = { info: { id: "lossless-claw", name: "Lossless Claw", ownsCompaction: true }, diff --git a/extensions/codex/src/app-server/compact.ts b/extensions/codex/src/app-server/compact.ts index 45a5cc90767..f2cd53162e6 100644 --- a/extensions/codex/src/app-server/compact.ts +++ b/extensions/codex/src/app-server/compact.ts @@ -7,8 +7,8 @@ import { type EmbeddedPiCompactResult, } from "openclaw/plugin-sdk/agent-harness-runtime"; import { - createCodexAppServerClientFactoryTestHooks, defaultCodexAppServerClientFactory, + type CodexAppServerClientFactory, } from "./client-factory.js"; import type { CodexAppServerClient, CodexServerNotificationHandler } from "./client.js"; import { resolveCodexAppServerRuntimeOptions } from "./config.js"; @@ -30,11 +30,9 @@ type ContextEngineCompactResult = Awaited< const DEFAULT_CODEX_COMPACTION_WAIT_TIMEOUT_MS = 5 * 60 * 1000; -let clientFactory = defaultCodexAppServerClientFactory; - export async function maybeCompactCodexAppServerSession( params: CompactEmbeddedPiSessionParams, - options: { pluginConfig?: unknown } = {}, + options: { pluginConfig?: unknown; clientFactory?: CodexAppServerClientFactory } = {}, ): Promise { const activeContextEngine = isActiveHarnessContextEngine(params.contextEngine) ? params.contextEngine @@ -107,7 +105,7 @@ export async function maybeCompactCodexAppServerSession( async function compactCodexNativeThread( params: CompactEmbeddedPiSessionParams, - options: { pluginConfig?: unknown } = {}, + options: { pluginConfig?: unknown; clientFactory?: CodexAppServerClientFactory } = {}, ): Promise { const appServer = resolveCodexAppServerRuntimeOptions({ pluginConfig: options.pluginConfig }); const binding = await readCodexAppServerBinding(params.sessionFile, { config: params.config }); @@ -123,6 +121,7 @@ async function compactCodexNativeThread( return { ok: false, compacted: false, reason: "auth profile mismatch for session binding" }; } + const clientFactory = options.clientFactory ?? defaultCodexAppServerClientFactory; const client = await clientFactory( appServer.start, requestedAuthProfileId ?? binding.authProfileId, @@ -370,7 +369,3 @@ function formatCompactionError(error: unknown): string { } return String(error); } - -export const __testing = createCodexAppServerClientFactoryTestHooks((factory) => { - clientFactory = factory; -}); diff --git a/extensions/codex/src/app-server/run-attempt.context-engine.test.ts b/extensions/codex/src/app-server/run-attempt.context-engine.test.ts index 13f3cbd1bed..a17904dd6d6 100644 --- a/extensions/codex/src/app-server/run-attempt.context-engine.test.ts +++ b/extensions/codex/src/app-server/run-attempt.context-engine.test.ts @@ -9,12 +9,37 @@ import { type HarnessContextEngine as ContextEngine, } from "openclaw/plugin-sdk/agent-harness-runtime"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import type { CodexAppServerClientFactory } from "./client-factory.js"; import type { CodexServerNotification } from "./protocol.js"; -import { runCodexAppServerAttempt, __testing } from "./run-attempt.js"; +import { runCodexAppServerAttempt as runCodexAppServerAttemptImpl } from "./run-attempt.js"; import { readCodexAppServerBinding, writeCodexAppServerBinding } from "./session-binding.js"; import { createCodexTestModel } from "./test-support.js"; let tempDir: string; +let codexAppServerClientFactoryForTest: CodexAppServerClientFactory | undefined; + +type RunCodexAppServerAttemptOptions = NonNullable< + Parameters[1] +>; + +function setCodexAppServerClientFactoryForTest(factory: CodexAppServerClientFactory): void { + codexAppServerClientFactoryForTest = factory; +} + +function resetCodexAppServerClientFactoryForTest(): void { + codexAppServerClientFactoryForTest = undefined; +} + +function runCodexAppServerAttempt( + params: EmbeddedRunAttemptParams, + options: RunCodexAppServerAttemptOptions = {}, +) { + const clientFactory = options.clientFactory ?? codexAppServerClientFactoryForTest; + return runCodexAppServerAttemptImpl( + params, + clientFactory ? { ...options, clientFactory } : options, + ); +} function createParams(sessionFile: string, workspaceDir: string): EmbeddedRunAttemptParams { return { @@ -133,7 +158,7 @@ function createStartedThreadHarness( return {}; }); - __testing.setCodexAppServerClientFactoryForTests( + setCodexAppServerClientFactoryForTest( async () => ({ request, @@ -258,7 +283,7 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => { }); afterEach(async () => { - __testing.resetCodexAppServerClientFactoryForTests(); + resetCodexAppServerClientFactoryForTest(); vi.restoreAllMocks(); await fs.rm(tempDir, { recursive: true, force: true }); }); diff --git a/extensions/codex/src/app-server/run-attempt.test.ts b/extensions/codex/src/app-server/run-attempt.test.ts index 28e35b2b672..c75cb1c92b3 100644 --- a/extensions/codex/src/app-server/run-attempt.test.ts +++ b/extensions/codex/src/app-server/run-attempt.test.ts @@ -27,6 +27,7 @@ function queueActiveRunMessageForTest( import { CODEX_GPT5_BEHAVIOR_CONTRACT } from "../../prompt-overlay.js"; import { defaultCodexAppInventoryCache } from "./app-inventory-cache.js"; import { resolveCodexAppServerEnvApiKeyCacheKey } from "./auth-bridge.js"; +import type { CodexAppServerClientFactory } from "./client-factory.js"; import { readCodexPluginConfig, resolveCodexAppServerRuntimeOptions } from "./config.js"; import { CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE, @@ -39,7 +40,10 @@ import { } from "./plugin-app-cache-key.js"; import type { CodexServerNotification } from "./protocol.js"; import { rememberCodexRateLimits, resetCodexRateLimitCacheForTests } from "./rate-limit-cache.js"; -import { runCodexAppServerAttempt, __testing } from "./run-attempt.js"; +import { + runCodexAppServerAttempt as runCodexAppServerAttemptImpl, + __testing, +} from "./run-attempt.js"; import { readCodexAppServerBinding, writeCodexAppServerBinding } from "./session-binding.js"; import { createCodexTestModel } from "./test-support.js"; import { @@ -50,6 +54,30 @@ import { } from "./thread-lifecycle.js"; let tempDir: string; +let codexAppServerClientFactoryForTest: CodexAppServerClientFactory | undefined; + +type RunCodexAppServerAttemptOptions = NonNullable< + Parameters[1] +>; + +function setCodexAppServerClientFactoryForTest(factory: CodexAppServerClientFactory): void { + codexAppServerClientFactoryForTest = factory; +} + +function resetCodexAppServerClientFactoryForTest(): void { + codexAppServerClientFactoryForTest = undefined; +} + +function runCodexAppServerAttempt( + params: EmbeddedRunAttemptParams, + options: RunCodexAppServerAttemptOptions = {}, +) { + const clientFactory = options.clientFactory ?? codexAppServerClientFactoryForTest; + return runCodexAppServerAttemptImpl( + params, + clientFactory ? { ...options, clientFactory } : options, + ); +} function createParams(sessionFile: string, workspaceDir: string): EmbeddedRunAttemptParams { return { @@ -212,22 +240,20 @@ function createAppServerHarness( return requestImpl(method, params); }); - __testing.setCodexAppServerClientFactoryForTests( - async (_startOptions, authProfileId, agentDir) => { - options.onStart?.(authProfileId, agentDir); - return { - request, - addNotificationHandler: (handler: typeof notify) => { - notify = handler; - return () => undefined; - }, - addRequestHandler: (handler: AppServerRequestHandler) => { - handleServerRequest = handler; - return () => undefined; - }, - } as never; - }, - ); + setCodexAppServerClientFactoryForTest(async (_startOptions, authProfileId, agentDir) => { + options.onStart?.(authProfileId, agentDir); + return { + request, + addNotificationHandler: (handler: typeof notify) => { + notify = handler; + return () => undefined; + }, + addRequestHandler: (handler: AppServerRequestHandler) => { + handleServerRequest = handler; + return () => undefined; + }, + } as never; + }); const waitForServerRequestHandler = async () => { await vi.waitFor(() => expect(handleServerRequest).toBeTypeOf("function"), { @@ -555,7 +581,7 @@ describe("runCodexAppServerAttempt", () => { }); afterEach(async () => { - __testing.resetCodexAppServerClientFactoryForTests(); + resetCodexAppServerClientFactoryForTest(); __testing.resetOpenClawCodingToolsFactoryForTests(); resetCodexRateLimitCacheForTests(); nativeHookRelayTesting.clearNativeHookRelaysForTests(); @@ -1333,7 +1359,7 @@ describe("runCodexAppServerAttempt", () => { } return {}; }); - __testing.setCodexAppServerClientFactoryForTests( + setCodexAppServerClientFactoryForTest( async () => ({ request, @@ -1417,7 +1443,7 @@ describe("runCodexAppServerAttempt", () => { } return {}; }); - __testing.setCodexAppServerClientFactoryForTests( + setCodexAppServerClientFactoryForTest( async () => ({ request, @@ -1464,7 +1490,7 @@ describe("runCodexAppServerAttempt", () => { } return {}; }); - __testing.setCodexAppServerClientFactoryForTests( + setCodexAppServerClientFactoryForTest( async () => ({ request, @@ -1542,7 +1568,7 @@ describe("runCodexAppServerAttempt", () => { } return {}; }); - __testing.setCodexAppServerClientFactoryForTests( + setCodexAppServerClientFactoryForTest( async () => ({ request, @@ -1633,7 +1659,7 @@ describe("runCodexAppServerAttempt", () => { } return {}; }); - __testing.setCodexAppServerClientFactoryForTests( + setCodexAppServerClientFactoryForTest( async () => ({ request, @@ -1726,7 +1752,7 @@ describe("runCodexAppServerAttempt", () => { } return {}; }); - __testing.setCodexAppServerClientFactoryForTests( + setCodexAppServerClientFactoryForTest( async () => ({ request, @@ -1904,7 +1930,7 @@ describe("runCodexAppServerAttempt", () => { } return {}; }); - __testing.setCodexAppServerClientFactoryForTests( + setCodexAppServerClientFactoryForTest( async () => ({ request, @@ -1979,7 +2005,7 @@ describe("runCodexAppServerAttempt", () => { } return {}; }); - __testing.setCodexAppServerClientFactoryForTests( + setCodexAppServerClientFactoryForTest( async () => ({ request, @@ -2062,7 +2088,7 @@ describe("runCodexAppServerAttempt", () => { } return {}; }); - __testing.setCodexAppServerClientFactoryForTests( + setCodexAppServerClientFactoryForTest( async () => ({ request, @@ -2153,7 +2179,7 @@ describe("runCodexAppServerAttempt", () => { } return {}; }); - __testing.setCodexAppServerClientFactoryForTests( + setCodexAppServerClientFactoryForTest( async () => ({ request, @@ -2233,7 +2259,7 @@ describe("runCodexAppServerAttempt", () => { } return {}; }); - __testing.setCodexAppServerClientFactoryForTests( + setCodexAppServerClientFactoryForTest( async () => ({ request, @@ -2297,7 +2323,7 @@ describe("runCodexAppServerAttempt", () => { } return {}; }); - __testing.setCodexAppServerClientFactoryForTests( + setCodexAppServerClientFactoryForTest( async () => ({ request, @@ -3590,7 +3616,7 @@ describe("runCodexAppServerAttempt", () => { } return {}; }); - __testing.setCodexAppServerClientFactoryForTests( + setCodexAppServerClientFactoryForTest( async () => ({ request, @@ -3968,7 +3994,7 @@ describe("runCodexAppServerAttempt", () => { } return {}; }); - __testing.setCodexAppServerClientFactoryForTests( + setCodexAppServerClientFactoryForTest( async () => ({ request, @@ -4027,7 +4053,7 @@ describe("runCodexAppServerAttempt", () => { } return {}; }); - __testing.setCodexAppServerClientFactoryForTests( + setCodexAppServerClientFactoryForTest( async () => ({ request, @@ -4208,7 +4234,7 @@ describe("runCodexAppServerAttempt", () => { } return {}; }); - __testing.setCodexAppServerClientFactoryForTests( + setCodexAppServerClientFactoryForTest( async () => ({ request, @@ -4579,7 +4605,7 @@ describe("runCodexAppServerAttempt", () => { }); it("times out app-server startup before thread setup can hang forever", async () => { - __testing.setCodexAppServerClientFactoryForTests(() => new Promise(() => undefined)); + setCodexAppServerClientFactoryForTest(() => new Promise(() => undefined)); const params = createParams( path.join(tempDir, "session.jsonl"), path.join(tempDir, "workspace"), @@ -4636,7 +4662,7 @@ describe("runCodexAppServerAttempt", () => { return {}; }, ); - __testing.setCodexAppServerClientFactoryForTests( + setCodexAppServerClientFactoryForTest( async () => ({ request, @@ -5009,7 +5035,7 @@ describe("runCodexAppServerAttempt", () => { const requests: string[][] = []; let starts = 0; let notify: (notification: CodexServerNotification) => Promise = async () => undefined; - __testing.setCodexAppServerClientFactoryForTests(async () => { + setCodexAppServerClientFactoryForTest(async () => { const startIndex = starts++; const methods: string[] = []; requests.push(methods); @@ -5058,7 +5084,7 @@ describe("runCodexAppServerAttempt", () => { const requests: string[][] = []; let starts = 0; let notify: (notification: CodexServerNotification) => Promise = async () => undefined; - __testing.setCodexAppServerClientFactoryForTests(async () => { + setCodexAppServerClientFactoryForTest(async () => { const startIndex = starts++; const methods: string[] = []; requests.push(methods); diff --git a/extensions/codex/src/app-server/run-attempt.ts b/extensions/codex/src/app-server/run-attempt.ts index e43f91073a2..ae77ae610ec 100644 --- a/extensions/codex/src/app-server/run-attempt.ts +++ b/extensions/codex/src/app-server/run-attempt.ts @@ -1,4 +1,3 @@ -import { AsyncLocalStorage } from "node:async_hooks"; import { createHash } from "node:crypto"; import fs from "node:fs/promises"; import path from "node:path"; @@ -182,14 +181,8 @@ type CodexSystemPromptReport = NonNullable(); -const clientFactory = defaultCodexAppServerClientFactory; let openClawCodingToolsFactoryForTests: OpenClawCodingToolsFactory | undefined; -function resolveCodexAppServerClientFactory(): CodexAppServerClientFactory { - return testClientFactoryStorage.getStore() ?? clientFactory; -} - function emitCodexAppServerEvent( params: EmbeddedRunAttemptParams, event: Parameters>[0], @@ -443,10 +436,11 @@ export async function runCodexAppServerAttempt( turnCompletionIdleTimeoutMs?: number; turnAssistantCompletionIdleTimeoutMs?: number; turnTerminalIdleTimeoutMs?: number; + clientFactory?: CodexAppServerClientFactory; } = {}, ): Promise { const attemptStartedAt = Date.now(); - const attemptClientFactory = resolveCodexAppServerClientFactory(); + const attemptClientFactory = options.clientFactory ?? defaultCodexAppServerClientFactory; const pluginConfig = readCodexPluginConfig(options.pluginConfig); const configuredAppServer = resolveCodexAppServerRuntimeOptions({ pluginConfig }); const resolvedWorkspace = resolveUserPath(params.workspaceDir); @@ -3264,10 +3258,4 @@ export const __testing = { resetOpenClawCodingToolsFactoryForTests(): void { openClawCodingToolsFactoryForTests = undefined; }, - setCodexAppServerClientFactoryForTests(factory: CodexAppServerClientFactory): void { - testClientFactoryStorage.enterWith(factory); - }, - resetCodexAppServerClientFactoryForTests(): void { - testClientFactoryStorage.enterWith(undefined); - }, } as const;