mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 11:25:50 -06:00
fix(codex): preserve desktop install and cache identity
This commit is contained in:
@@ -76,6 +76,7 @@ import {
|
||||
clearSharedCodexAppServerClientIfCurrentAndUnclaimed,
|
||||
createIsolatedCodexAppServerClient,
|
||||
isCodexAppServerStartSelectionChangedError,
|
||||
readCodexAppServerClientDesktopGenerationFingerprint,
|
||||
releaseLeasedSharedCodexAppServerClient,
|
||||
retireSharedCodexAppServerClientIfCurrent,
|
||||
type CodexAppServerClientOptions,
|
||||
@@ -334,6 +335,8 @@ export async function startCodexAttemptThread(params: {
|
||||
envApiKeyFingerprint: params.startupEnvApiKeyCacheKey,
|
||||
appServerVersion: activeStartupClient.getServerVersion(),
|
||||
runtimeIdentity: startupRuntimeIdentity,
|
||||
desktopGenerationFingerprint:
|
||||
readCodexAppServerClientDesktopGenerationFingerprint(activeStartupClient),
|
||||
});
|
||||
const appServerRuntimeFingerprint = buildCodexAppServerRuntimeFingerprint({
|
||||
appServer: params.appServer,
|
||||
|
||||
@@ -992,49 +992,52 @@ describe("Codex Computer Use setup", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("provisions the managed wrapper and service for an explicit isolated-home install", async () => {
|
||||
const root = tempDirs.make("openclaw-codex-explicit-install-");
|
||||
const agentDir = path.join(root, "agent");
|
||||
const codexHome = path.join(agentDir, "codex-home");
|
||||
const managedMarketplacePath = path.join(
|
||||
codexHome,
|
||||
".tmp",
|
||||
"bundled-marketplaces",
|
||||
"openai-bundled",
|
||||
);
|
||||
fs.mkdirSync(managedMarketplacePath, { recursive: true });
|
||||
const harness = createClientHarness();
|
||||
vi.spyOn(harness.client, "getRuntimeIdentity").mockReturnValue({
|
||||
serverVersion: "0.148.0",
|
||||
codexHome,
|
||||
});
|
||||
sharedClientMocks.readCodexAppServerClientProcessIdentity.mockReturnValue({
|
||||
clientId: "client-explicit-install",
|
||||
command: "/Applications/ChatGPT.app/Contents/Resources/codex",
|
||||
nativeCommand: "/Applications/ChatGPT.app/Contents/Resources/codex",
|
||||
argsFingerprint: "args",
|
||||
});
|
||||
const request = createBundledMarketplaceComputerUseRequest(managedMarketplacePath);
|
||||
it.each(["config", "env"] as const)(
|
||||
"provisions the managed wrapper and service for a %s-selected desktop install",
|
||||
async (commandSource) => {
|
||||
const root = tempDirs.make("openclaw-codex-explicit-install-");
|
||||
const agentDir = path.join(root, "agent");
|
||||
const codexHome = path.join(agentDir, "codex-home");
|
||||
const managedMarketplacePath = path.join(
|
||||
codexHome,
|
||||
".tmp",
|
||||
"bundled-marketplaces",
|
||||
"openai-bundled",
|
||||
);
|
||||
fs.mkdirSync(managedMarketplacePath, { recursive: true });
|
||||
const harness = createClientHarness();
|
||||
vi.spyOn(harness.client, "getRuntimeIdentity").mockReturnValue({
|
||||
serverVersion: "0.148.0",
|
||||
codexHome,
|
||||
});
|
||||
sharedClientMocks.readCodexAppServerClientProcessIdentity.mockReturnValue({
|
||||
clientId: "client-explicit-install",
|
||||
command: "/Applications/ChatGPT.app/Contents/Resources/codex",
|
||||
commandSource,
|
||||
argsFingerprint: "args",
|
||||
});
|
||||
const request = createBundledMarketplaceComputerUseRequest(managedMarketplacePath);
|
||||
|
||||
const status = await installCodexComputerUse({
|
||||
agentDir,
|
||||
client: harness.client,
|
||||
request,
|
||||
pluginConfig: { computerUse: { enabled: true, autoInstall: false } },
|
||||
});
|
||||
const status = await installCodexComputerUse({
|
||||
agentDir,
|
||||
client: harness.client,
|
||||
request,
|
||||
pluginConfig: { computerUse: { enabled: true, autoInstall: false } },
|
||||
});
|
||||
|
||||
expect(status.ready).toBe(true);
|
||||
expect(managedProvisioningMocks.ensureCodexManagedBundledMarketplace).toHaveBeenCalledWith({
|
||||
codexHome,
|
||||
ownershipRoot: agentDir,
|
||||
appServerCommand: "/Applications/ChatGPT.app/Contents/Resources/codex",
|
||||
});
|
||||
expect(managedProvisioningMocks.ensureCodexComputerUseServiceApp).toHaveBeenCalledWith({
|
||||
codexHome,
|
||||
ownershipRoot: agentDir,
|
||||
appServerCommand: "/Applications/ChatGPT.app/Contents/Resources/codex",
|
||||
});
|
||||
});
|
||||
expect(status.ready).toBe(true);
|
||||
expect(managedProvisioningMocks.ensureCodexManagedBundledMarketplace).toHaveBeenCalledWith({
|
||||
codexHome,
|
||||
ownershipRoot: agentDir,
|
||||
appServerCommand: "/Applications/ChatGPT.app/Contents/Resources/codex",
|
||||
});
|
||||
expect(managedProvisioningMocks.ensureCodexComputerUseServiceApp).toHaveBeenCalledWith({
|
||||
codexHome,
|
||||
ownershipRoot: agentDir,
|
||||
appServerCommand: "/Applications/ChatGPT.app/Contents/Resources/codex",
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("allows auto-install from a configured local marketplace path", async () => {
|
||||
const request = createComputerUseRequest({ installed: false });
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
type ResolvedCodexComputerUseConfig,
|
||||
} from "./config.js";
|
||||
import { resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath } from "./desktop-app-paths.js";
|
||||
import { isManagedCodexDesktopCommand } from "./managed-binary.js";
|
||||
import { acquireCodexNativeConfigFence } from "./native-config-fence.js";
|
||||
import type {
|
||||
CodexListMcpServerStatusResponse,
|
||||
@@ -453,7 +454,12 @@ async function prepareExplicitManagedComputerUseInstall(
|
||||
return;
|
||||
}
|
||||
const codexHome = params.client.getRuntimeIdentity()?.codexHome;
|
||||
const command = readCodexAppServerClientProcessIdentity(params.client)?.nativeCommand;
|
||||
const processIdentity = readCodexAppServerClientProcessIdentity(params.client);
|
||||
const command =
|
||||
processIdentity?.nativeCommand ??
|
||||
(processIdentity && isManagedCodexDesktopCommand(processIdentity.command, "darwin")
|
||||
? processIdentity.command
|
||||
: undefined);
|
||||
if (!codexHome || !command) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ export function resolveManagedCodexNativeCommand(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** Returns whether a resolved managed command is owned by the macOS desktop app. */
|
||||
/** Returns whether a command is one of the standard macOS desktop app executables. */
|
||||
export function isManagedCodexDesktopCommand(
|
||||
command: string,
|
||||
platform: NodeJS.Platform = process.platform,
|
||||
|
||||
@@ -50,6 +50,35 @@ describe("resolveCodexPluginAppCacheEndpoint", () => {
|
||||
expect(second).not.toContain("secret-token");
|
||||
});
|
||||
|
||||
it("separates plugin inventory across managed desktop generations", () => {
|
||||
const base = {
|
||||
appServer: {
|
||||
start: {
|
||||
transport: "stdio" as const,
|
||||
command: "/Applications/ChatGPT.app/Contents/Resources/codex",
|
||||
args: ["app-server"],
|
||||
headers: {},
|
||||
},
|
||||
},
|
||||
agentDir: "/tmp/openclaw-agent",
|
||||
runtimeIdentity: {
|
||||
serverVersion: "0.20.0",
|
||||
codexHome: "/tmp/openclaw-agent/codex-home",
|
||||
},
|
||||
};
|
||||
|
||||
const generationX = buildCodexPluginAppCacheKey({
|
||||
...base,
|
||||
desktopGenerationFingerprint: "desktop-x",
|
||||
});
|
||||
const generationY = buildCodexPluginAppCacheKey({
|
||||
...base,
|
||||
desktopGenerationFingerprint: "desktop-y",
|
||||
});
|
||||
|
||||
expect(generationX).not.toEqual(generationY);
|
||||
});
|
||||
|
||||
it("fingerprints the remote app-server runtime used by thread bindings", () => {
|
||||
const first = buildCodexAppServerRuntimeFingerprint({
|
||||
appServer: {
|
||||
|
||||
@@ -54,6 +54,7 @@ type CodexPluginAppCacheKeyParams = Omit<
|
||||
appServer: Pick<CodexAppServerRuntimeOptions, "start">;
|
||||
agentDir?: string;
|
||||
runtimeIdentity?: CodexAppServerRuntimeIdentity;
|
||||
desktopGenerationFingerprint?: string;
|
||||
};
|
||||
|
||||
/** Builds the full app inventory cache key for Codex plugin/app discovery. */
|
||||
@@ -68,7 +69,12 @@ export function buildCodexPluginAppCacheKey(params: CodexPluginAppCacheKeyParams
|
||||
accountId: params.accountId,
|
||||
envApiKeyFingerprint: params.envApiKeyFingerprint,
|
||||
appServerVersion: params.appServerVersion ?? params.runtimeIdentity?.serverVersion,
|
||||
runtimeIdentity: params.runtimeIdentity,
|
||||
runtimeIdentity: params.desktopGenerationFingerprint
|
||||
? {
|
||||
...params.runtimeIdentity,
|
||||
desktopGeneration: params.desktopGenerationFingerprint,
|
||||
}
|
||||
: params.runtimeIdentity,
|
||||
},
|
||||
OPENCLAW_VERSION,
|
||||
CODEX_PLUGIN_VERSION,
|
||||
|
||||
@@ -165,6 +165,13 @@ export function readCodexAppServerClientProcessIdentity(
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns the lifecycle generation that owns a managed desktop client. */
|
||||
export function readCodexAppServerClientDesktopGenerationFingerprint(
|
||||
client: CodexAppServerClient,
|
||||
): string | undefined {
|
||||
return getCodexAppServerClientStartMetadata().get(client)?.desktopGeneration?.fingerprint;
|
||||
}
|
||||
|
||||
/** Resolves non-secret spawn identity before startup; argv is represented only by its hash. */
|
||||
export function resolveCodexAppServerSpawnIdentity(
|
||||
startOptions: CodexAppServerStartOptions,
|
||||
|
||||
Reference in New Issue
Block a user