mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 11:25:50 -06:00
fix(codex): track package-first computer use generations
This commit is contained in:
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user