fix(codex): track package-first computer use generations

This commit is contained in:
joshavant
2026-08-22 02:27:24 -05:00
parent a9b905f805
commit 7d3c4e76e1
2 changed files with 74 additions and 6 deletions
@@ -18,7 +18,10 @@ const mocks = vi.hoisted(() => ({
},
bridgeCodexAppServerStartOptions: vi.fn(async ({ startOptions }) => startOptions),
reconcileCodexComputerUseStartArtifacts: vi.fn(
async (_params?: { startOptions: { command: string } }) => undefined,
async (_params?: {
startOptions: { command: string };
desktopGeneration?: { epoch: number; fingerprint: string };
}) => undefined,
),
applyCodexAppServerAuthProfile: vi.fn(
async (_params?: {
@@ -2308,6 +2311,60 @@ describe("shared Codex app-server client", () => {
expect(releaseLeasedSharedCodexAppServerClient(clientY)).toBe(true);
});
it("tracks a package-first client whose Computer Use artifacts come from the desktop", async () => {
const generationX = { epoch: 1, fingerprint: "desktop-x" };
const generationY = { epoch: 2, fingerprint: "desktop-y" };
mocks.desktopGeneration = generationX;
mocks.resolveManagedCodexAppServerStartOptions.mockImplementation(async (startOptions) => ({
...startOptions,
command: "/cache/openclaw/codex",
commandSource: "resolved-managed" as const,
managedFallbackCommandPaths: ["/Applications/Codex.app/Contents/Resources/codex"],
}));
const packageX = createClientHarness();
const packageY = createClientHarness();
const startSpy = vi
.spyOn(CodexAppServerClient, "start")
.mockReturnValueOnce(packageX.client)
.mockReturnValueOnce(packageY.client);
const options = {
config: {},
pluginConfig: { computerUse: { enabled: true, autoInstall: true } },
agentDir: "/tmp/openclaw-agent",
startOptions: {
transport: "stdio" as const,
homeScope: "agent" as const,
command: "codex",
commandSource: "managed" as const,
managedCommandOrder: "package-first" as const,
args: ["app-server"],
headers: {},
},
};
const firstAcquire = getLeasedSharedCodexAppServerClient(options);
await sendInitializeResult(packageX, "openclaw/0.148.0 (macOS; test)");
const clientX = await firstAcquire;
mocks.desktopGeneration = generationY;
retireSharedCodexAppServerClientsBeforeDesktopGeneration(generationY);
const replacementAcquire = getLeasedSharedCodexAppServerClient(options);
await sendInitializeResult(packageY, "openclaw/0.148.0 (macOS; test)");
const clientY = await replacementAcquire;
expect(clientY).not.toBe(clientX);
expect(startSpy).toHaveBeenCalledTimes(2);
expect(
mocks.reconcileCodexComputerUseStartArtifacts.mock.calls.map(
([params]) => params?.desktopGeneration,
),
).toEqual([generationX, generationY]);
expect(packageX.process.stdin.destroyed).toBe(false);
expect(releaseLeasedSharedCodexAppServerClient(clientX)).toBe(true);
expect(packageX.process.stdin.destroyed).toBe(true);
expect(releaseLeasedSharedCodexAppServerClient(clientY)).toBe(true);
});
it("binds a package-first acquisition when its actual fallback is a desktop app", async () => {
const generationX = { epoch: 1, fingerprint: "desktop-x" };
const generationY = { epoch: 2, fingerprint: "desktop-y" };
@@ -29,6 +29,7 @@ import { ensureCodexAppServerClientRuntime } from "./client-runtime.js";
import { CodexAppServerClient, isUnsupportedCodexAppServerVersionError } from "./client.js";
import {
codexAppServerStartOptionsKey,
resolveCodexComputerUseConfig,
resolveCodexAppServerRuntimeOptions,
resolveCodexAppServerStartOptionsForAgent,
resolveCodexAppServerUserHomeDir,
@@ -335,7 +336,10 @@ async function resolveCodexAppServerClientStartContext(
const agentDir = options?.agentDir ?? resolveDefaultAgentDir(options?.config ?? {});
const requestedStartOptions =
options?.startOptions ?? resolveCodexAppServerRuntimeOptions().start;
const desktopGeneration = shouldTrackDesktopGeneration(requestedStartOptions)
const desktopGeneration = shouldTrackDesktopGeneration(
requestedStartOptions,
options?.pluginConfig,
)
? await waitForCodexDesktopGeneration()
: undefined;
const preparedAuth = options?.preparedAuth;
@@ -447,11 +451,18 @@ async function resolveCodexAppServerClientStartContext(
};
}
function shouldTrackDesktopGeneration(startOptions: CodexAppServerStartOptions): boolean {
function shouldTrackDesktopGeneration(
startOptions: CodexAppServerStartOptions,
pluginConfig: unknown,
): boolean {
if (startOptions.transport !== "stdio" || startOptions.commandSource !== "managed") {
return false;
}
// An explicitly package-first process can still publish and load Computer Use
// artifacts from a desktop app, so it belongs to that desktop generation too.
return (
startOptions.transport === "stdio" &&
startOptions.commandSource === "managed" &&
(startOptions.managedCommandOrder ?? "package-first") === "desktop-first"
(startOptions.managedCommandOrder ?? "package-first") === "desktop-first" ||
resolveCodexComputerUseConfig({ pluginConfig }).enabled
);
}