fix(codex): inject app-server client factories

Co-authored-by: Benjamin Badejo <ben@benbadejo.com>
This commit is contained in:
Peter Steinberger
2026-05-15 05:03:28 +01:00
parent 0db0979365
commit f12e9c41fa
8 changed files with 181 additions and 112 deletions

View File

@@ -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.

View File

@@ -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<typeof runCodexAppServerAttemptImpl>[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<string | undefined> = [];
const requests: Array<{ method: string; params: unknown }> = [];
let notify: (notification: unknown) => Promise<void> = 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<void>) => {
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<void>) => {
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 });
});

View File

@@ -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;
}

View File

@@ -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<Parameters<typeof maybeCompactCodexAppServerSessionImpl>[1]>;
function setCodexAppServerClientFactoryForTest(factory: CodexAppServerClientFactory): void {
codexAppServerClientFactoryForTest = factory;
}
function resetCodexAppServerClientFactoryForTest(): void {
codexAppServerClientFactoryForTest = undefined;
}
function maybeCompactCodexAppServerSession(
params: Parameters<typeof maybeCompactCodexAppServerSessionImpl>[0],
options: MaybeCompactOptions = {},
) {
const clientFactory = options.clientFactory ?? codexAppServerClientFactoryForTest;
return maybeCompactCodexAppServerSessionImpl(
params,
clientFactory ? { ...options, clientFactory } : options,
);
}
async function writeTestBinding(options: { authProfileId?: string } = {}): Promise<string> {
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 },

View File

@@ -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<EmbeddedPiCompactResult | undefined> {
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<EmbeddedPiCompactResult | undefined> {
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;
});

View File

@@ -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<typeof runCodexAppServerAttemptImpl>[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 });
});

View File

@@ -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<typeof runCodexAppServerAttemptImpl>[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<never>(() => undefined));
setCodexAppServerClientFactoryForTest(() => new Promise<never>(() => 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<void> = 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<void> = async () => undefined;
__testing.setCodexAppServerClientFactoryForTests(async () => {
setCodexAppServerClientFactoryForTest(async () => {
const startIndex = starts++;
const methods: string[] = [];
requests.push(methods);

View File

@@ -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<EmbeddedRunAttemptResult["systemPromp
type CodexToolReportEntry = CodexSystemPromptReport["tools"]["entries"][number];
type CodexWorkspaceBootstrapContext = CodexBootstrapContext & { instructions?: string };
const testClientFactoryStorage = new AsyncLocalStorage<CodexAppServerClientFactory | undefined>();
const clientFactory = defaultCodexAppServerClientFactory;
let openClawCodingToolsFactoryForTests: OpenClawCodingToolsFactory | undefined;
function resolveCodexAppServerClientFactory(): CodexAppServerClientFactory {
return testClientFactoryStorage.getStore() ?? clientFactory;
}
function emitCodexAppServerEvent(
params: EmbeddedRunAttemptParams,
event: Parameters<NonNullable<EmbeddedRunAttemptParams["onAgentEvent"]>>[0],
@@ -443,10 +436,11 @@ export async function runCodexAppServerAttempt(
turnCompletionIdleTimeoutMs?: number;
turnAssistantCompletionIdleTimeoutMs?: number;
turnTerminalIdleTimeoutMs?: number;
clientFactory?: CodexAppServerClientFactory;
} = {},
): Promise<EmbeddedRunAttemptResult> {
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;