fix(codex): unblock Computer Use after plugin install (#126699)

* fix(codex): release config fence before readiness probe

* chore(codex): upgrade managed app-server to 0.148.0
This commit is contained in:
Peter Steinberger
2026-08-20 08:26:41 -07:00
committed by GitHub
parent eb84b56766
commit 02c08bba71
35 changed files with 319 additions and 123 deletions
+1 -1
View File
@@ -258,7 +258,7 @@ reconciliation so OpenClaw does not override that selection.
## Remote marketplaces
Remote marketplace support was introduced in Codex 0.146.1 and remains
available in OpenClaw's pinned Codex 0.147.0. OpenClaw passes the opaque remote
available in OpenClaw's pinned Codex 0.148.0. OpenClaw passes the opaque remote
plugin ID returned by Codex to `plugin/read` and `plugin/install`; a
human-readable plugin name is not a valid substitute.
+13 -17
View File
@@ -172,7 +172,7 @@ flags, and plugin allow/deny references into this block. Explicit canonical
## App-server transport
For ordinary harness turns, OpenClaw starts the managed Codex binary shipped
with the official plugin (currently `@openai/codex` `0.147.0`):
with the official plugin (currently `@openai/codex` `0.148.0`):
```bash
codex app-server --listen stdio://
@@ -317,8 +317,8 @@ If the normal app-server runtime would be `danger-full-access`, enabling
permission profile instead. Codex-managed network enforcement is sandboxed
networking, so a full-access profile would not protect outbound traffic.
The plugin ships Codex app-server `0.147.0` and accepts external versions at or
above that minimum. Older, malformed, and unversioned handshakes are rejected.
The plugin ships Codex app-server `0.148.0` and accepts external versions at or
above `0.147.0`. Older, malformed, and unversioned handshakes are rejected.
Build metadata does not affect SemVer precedence. The same minimum applies to
explicit custom executables, remote app-servers, and macOS desktop binaries;
admission is not readiness proof.
@@ -446,7 +446,7 @@ The stable default is fail-closed: active OpenClaw sandboxing disables native
Codex execution surfaces that would otherwise run from the Codex app-server
host. Use `appServer.experimental.sandboxExecServer: true` only when you want
to try Codex's remote environment support with OpenClaw's sandbox backend.
This preview path uses the pinned Codex `0.147.0` app-server.
This preview path uses the pinned Codex `0.148.0` app-server.
```json5
{
@@ -746,21 +746,17 @@ response remains authoritative even if it contains no visible models; HTTP
`401` and `403` return an empty catalog rather than exposing fallback models.
<Note>
The current bundled harness is `@openai/codex` `0.147.0`. A live `model/list`
probe against the official `0.147.0` app-server returned these public picker
The current bundled harness is `@openai/codex` `0.148.0`. A live `model/list`
probe against the official `0.148.0` app-server returned these public picker
rows:
| Model id | Input modalities | Reasoning efforts |
| --------------- | ---------------- | ------------------------------- |
| `gpt-5.5` | text, image | low, medium, high, xhigh |
| `gpt-5.6` | text, image | low, medium, high, xhigh, ultra |
| `gpt-5.6-luna` | text, image | low, medium, high, xhigh, ultra |
| `gpt-5.6-terra` | text, image | low, medium, high, xhigh, ultra |
| `gpt-5.6-sol` | text, image | low, medium, high, xhigh, ultra |
| `gpt-5.4` | text, image | low, medium, high, xhigh |
| `gpt-5.4-mini` | text, image | low, medium, high, xhigh |
| `gpt-5.3-codex` | text, image | low, medium, high, xhigh |
| `gpt-5.2` | text, image | low, medium, high, xhigh |
| Model id | Input modalities | Reasoning efforts |
| --------------- | ---------------- | ------------------------------------ |
| `gpt-5.6-sol` | text, image | low, medium, high, xhigh, max, ultra |
| `gpt-5.6-terra` | text, image | low, medium, high, xhigh, max, ultra |
| `gpt-5.6-luna` | text, image | low, medium, high, xhigh, max |
| `gpt-5.5` | text, image | low, medium, high, xhigh |
| `gpt-5.2` | text, image | low, medium, high, xhigh |
Available model IDs, input modalities, and reasoning efforts remain
account-scoped. Run `/codex models` after starting or upgrading the gateway to
+6 -6
View File
@@ -89,12 +89,12 @@ channel is the communication surface.
- The official `@openclaw/codex` plugin installed. Include `codex` in
`plugins.allow` if your config uses an allowlist.
- Codex app-server `0.147.0` or newer. The plugin still ships and manages the
exact `@openai/codex` `0.147.0` artifact, so a `codex` command on `PATH` does
not affect normal startup. Explicit custom, remote, and macOS desktop-owned
app-servers must report valid SemVer at or above that managed baseline.
Newer versions initialize with a warning; acceptance permits an attempt and
is not readiness or capability proof.
- Codex app-server `0.147.0` or newer. The plugin ships and manages the exact
`@openai/codex` `0.148.0` artifact, so a `codex` command on `PATH` does not
affect normal startup. Explicit custom, remote, and macOS desktop-owned
app-servers must report valid SemVer at or above the supported minimum.
Versions newer than the managed artifact initialize with a warning;
acceptance permits an attempt and is not readiness or capability proof.
- Node.js on the remote Codex app-server host when `remoteWorkspaceRoot` is set
and cross-machine workspace attachments must be transferred.
- Codex auth through `openclaw models auth login --provider openai`, an
+2 -2
View File
@@ -23,8 +23,8 @@ working.
- `plugins.entries.codex.enabled` is `true`.
- `plugins.entries.codex.config.codexPlugins.enabled` is `true`.
- Codex app-server reports version `0.147.0` or newer. The official plugin
still ships `@openai/codex` `0.147.0`; accepted external versions remain
subject to normal startup and capability validation.
ships `@openai/codex` `0.148.0`; accepted external versions remain subject to
normal startup and capability validation.
- The target Codex app-server can see the expected marketplace, plugin, and
app inventory.
- Migration supports only `openai-curated` plugins that it observed as
+1 -1
View File
@@ -18,7 +18,7 @@ import type { CodexSessionCatalogControlFactory } from "./src/session-catalog-ty
// New runtime identity uses the `openai` provider.
const DEFAULT_CODEX_HARNESS_PROVIDER_IDS = new Set(["codex", "openai"]);
const SHARED_CODEX_APP_SERVER_CLIENT_DISPOSER = Symbol.for("openclaw.codexAppServerClientDisposer");
// Audited against @openai/codex 0.147.0 (rust-v0.147.0). These exact denies
// Audited against @openai/codex 0.148.0 (rust-v0.148.0). These exact denies
// either have no Codex-native equivalent or are enforced by the harness. Keep
// the list positive and conservative: an omitted tool isolates the native surface.
const CODEX_TOOL_POLICY_SAFE_DENY_NAMES = [
@@ -48,7 +48,7 @@ function threadStartResult() {
status: { type: "idle" },
path: null,
cwd: "/tmp/openclaw-agent",
cliVersion: "0.147.0",
cliVersion: "0.148.0",
source: "unknown",
agentNickname: null,
agentRole: null,
+1 -1
View File
@@ -8,7 +8,7 @@
},
"type": "module",
"dependencies": {
"@openai/codex": "0.147.0",
"@openai/codex": "0.148.0",
"semver": "7.8.5",
"smol-toml": "1.7.1",
"typebox": "1.3.6",
@@ -15,7 +15,10 @@ import { afterEach, describe, expect, it, vi } from "vitest";
import { resolveCodexAppServerRuntimeOptions } from "./config.js";
import type { CodexModelListResponse } from "./protocol.js";
import { runCodexAppServerAttempt } from "./run-attempt.js";
import { createCodexTestBindingStore } from "./session-binding.test-helpers.js";
import {
createCodexTestBindingStore,
sessionBindingIdentity,
} from "./session-binding.test-helpers.js";
import { createIsolatedCodexAppServerClient } from "./shared-client.js";
const LIVE =
@@ -132,14 +135,18 @@ describeLive("Codex app-server approval requester real-binary bridge", () => {
params.hostCapabilities = host.capabilities;
closeHost = host.close;
const bindingStore = createCodexTestBindingStore();
const result = await runCodexAppServerAttempt(params, {
bindingStore: createCodexTestBindingStore(),
bindingStore,
pluginConfig: { appServer: { homeScope: "user" } },
nativeHookRelay: { enabled: true, events: ["pre_tool_use"] },
clientFactory: async () => client,
});
expect(result.terminal.kind, JSON.stringify(result.terminal)).toBe("ok");
const binding = await bindingStore.read(sessionBindingIdentity(params));
expect(binding).toMatchObject({ cwd: workspace, model: modelId });
expect(binding?.threadId).toEqual(expect.any(String));
expect(await fs.readFile(target, "utf8")).toBe("REAL_BINARY_OWNER_OK\n");
expect(
serverRequestMethods.filter((method) => method.endsWith("/requestApproval")),
@@ -184,7 +184,7 @@ async function captureExpectedRuntimeArtifact(
before,
startOptions: appServer.start,
spawnIdentity,
runtimeIdentity: { serverVersion: "0.147.0", userAgent: "openclaw/0.147.0 (macOS; test)" },
runtimeIdentity: { serverVersion: "0.148.0", userAgent: "openclaw/0.148.0 (macOS; test)" },
});
}
@@ -194,7 +194,7 @@ async function answerInitialize(harness: ClientHarness): Promise<void> {
timeout: HARNESS_REQUEST_TIMEOUT_MS,
});
const initialize = JSON.parse(harness.writes[0] ?? "{}") as { id?: number };
harness.send({ id: initialize.id, result: { userAgent: "openclaw/0.147.0 (macOS; test)" } });
harness.send({ id: initialize.id, result: { userAgent: "openclaw/0.148.0 (macOS; test)" } });
}
async function waitForRequest(
+22 -16
View File
@@ -331,7 +331,7 @@ describe("CodexAppServerClient", () => {
const { harness, initializing, outbound } = startInitialize();
harness.send({
id: outbound.id,
result: { userAgent: "openclaw/0.147.0 (macOS; test)" },
result: { userAgent: `openclaw/${CODEX_APP_SERVER_VERSION} (macOS; test)` },
});
await expect(initializing).resolves.toBeUndefined();
@@ -436,28 +436,34 @@ describe("CodexAppServerClient", () => {
expect(harness.writes).toHaveLength(1);
});
it.each(["0.148.0-alpha.9", "0.148.0-alpha.15", "0.148.0-alpha.23", "0.148.0", "1.0.0"])(
"accepts a newer app-server version %s for normal startup validation",
async (newerVersion) => {
const warn = vi.spyOn(embeddedAgentLog, "warn").mockImplementation(() => undefined);
const { harness, initializing, outbound } = startInitialize();
harness.send({
id: outbound.id,
result: { userAgent: `openclaw/${newerVersion} (macOS; test)` },
});
it.each([
["0.148.0-alpha.9", 0],
["0.148.0-alpha.15", 0],
["0.148.0-alpha.23", 0],
["0.148.0", 0],
["1.0.0", 1],
])("accepts app-server version %s for normal startup validation", async (version, warnings) => {
const warn = vi.spyOn(embeddedAgentLog, "warn").mockImplementation(() => undefined);
const { harness, initializing, outbound } = startInitialize();
harness.send({
id: outbound.id,
result: { userAgent: `openclaw/${version} (macOS; test)` },
});
await expect(initializing).resolves.toBeUndefined();
expect(harness.client.getServerVersion()).toBe(newerVersion);
expect(JSON.parse(harness.writes[1] ?? "{}")).toEqual({ method: "initialized" });
await expect(initializing).resolves.toBeUndefined();
expect(harness.client.getServerVersion()).toBe(version);
expect(JSON.parse(harness.writes[1] ?? "{}")).toEqual({ method: "initialized" });
expect(warn).toHaveBeenCalledTimes(warnings);
if (warnings > 0) {
expect(warn).toHaveBeenCalledWith(
"codex app-server is newer than OpenClaw's managed runtime; continuing with normal startup validation",
{
detectedVersion: newerVersion,
detectedVersion: version,
validatedVersion: CODEX_APP_SERVER_VERSION,
},
);
},
);
}
});
it.each(["0.147.00", "0.148.0-alpha..9", "0.148.0-alpha.09"])(
"blocks malformed app-server version %s during initialize",
@@ -33,7 +33,7 @@ export function threadStartResult(threadId = "thread-1", cwd = "/tmp/openclaw-co
status: { type: "idle" },
path: null,
cwd,
cliVersion: "0.147.0",
cliVersion: CODEX_APP_SERVER_VERSION,
source: "unknown",
agentNickname: null,
agentRole: null,
@@ -156,6 +156,74 @@ describe("Codex Computer Use setup", () => {
expect(sharedClientMocks.releaseLeasedSharedCodexAppServerClient).toHaveBeenCalledWith(client);
});
it("releases the install mutation fence before the guarded readiness thread", async () => {
const agentDir = "/tmp/openclaw-computer-use-guarded-install-agent";
const pluginConfig = {
computerUse: { marketplaceName: "desktop-tools", liveTestTimeoutMs: 150 },
};
const startOptions = resolveCodexAppServerRuntimeOptions({
pluginConfig,
managedCommandOrder: "desktop-first",
}).start;
const fenceKey = resolveCodexNativeConfigFenceKey({ startOptions, agentDir });
expect(fenceKey).toBeTypeOf("string");
const harness = createClientHarness();
harness.client.setThreadSessionRequestGuard((options) =>
acquireCodexNativeConfigFence(fenceKey as string, options),
);
sharedClientMocks.getLeasedSharedCodexAppServerClient.mockResolvedValueOnce(harness.client);
const fixture = createComputerUseRequest({ installed: false });
let cursor = 0;
const readFrame = async (method: string) => {
await vi.waitFor(() => expect(harness.writes.length).toBeGreaterThan(cursor), {
timeout: 1_000,
});
const frame = JSON.parse(harness.writes[cursor++] ?? "{}") as {
id: number;
method: string;
params?: unknown;
};
expect(frame.method).toBe(method);
return frame;
};
const answerFrame = async (frame: { id: number; method: string; params?: unknown }) => {
const result = await fixture(frame.method, frame.params);
harness.send({ id: frame.id, result: result ?? null });
};
const answer = async (method: string) => answerFrame(await readFrame(method));
const install = installCodexComputerUse({ pluginConfig, agentDir, timeoutMs: 2_000 });
void install.catch(() => undefined);
await answer("experimentalFeature/enablement/set");
await answer("plugin/list");
await answer("plugin/read");
const mutation = await readFrame("plugin/install");
await expect(
acquireCodexNativeConfigFence(fenceKey as string, {
timeoutMs: 10,
timeoutMessage: "mutation fence held",
}),
).rejects.toThrow("mutation fence held");
await answerFrame(mutation);
await answer("config/mcpServer/reload");
await answer("plugin/read");
await answer("mcpServerStatus/list");
await answer("thread/start");
await answer("mcpServer/tool/call");
await answer("thread/unsubscribe");
await answer("thread/archive");
await expect(install).resolves.toMatchObject({
ready: true,
liveTest: { status: "passed", attempts: 1 },
});
expect(sharedClientMocks.releaseLeasedSharedCodexAppServerClient).toHaveBeenCalledWith(
harness.client,
);
harness.client.close();
});
it.each(["abort", "timeout"] as const)(
"holds the install fence through process exit after a post-write %s",
async (mode) => {
@@ -153,6 +153,7 @@ type CodexComputerUseInspectionParams = {
defaultBundledMarketplacePath?: string;
defaultBundledMarketplacePathCandidates?: readonly string[];
repairComputerUseMcpChildren?: () => Promise<CodexComputerUseRepairStatus>;
releaseNativeConfigFence?: () => void;
};
type MarketplaceRef =
@@ -331,6 +332,7 @@ async function inspectCodexComputerUse(
try {
return await inspectCodexComputerUseWithoutFence({
...params,
releaseNativeConfigFence: release,
...(client
? {
client,
@@ -411,6 +413,7 @@ async function inspectCodexComputerUseWithoutFence(
plugin: pluginInspection.plugin,
installPlugin: params.installPlugin,
repairComputerUseMcpChildren,
releaseNativeConfigFence: params.releaseNativeConfigFence,
});
}
@@ -470,6 +473,7 @@ async function readComputerUseTools(params: {
plugin: CodexPluginDetail;
installPlugin: boolean;
repairComputerUseMcpChildren?: () => Promise<CodexComputerUseRepairStatus>;
releaseNativeConfigFence?: () => void;
}): Promise<CodexComputerUseStatus> {
let server = await readMcpServerStatus(params.request, params.config.mcpServerName);
let tools = Object.keys(server?.tools ?? {}).toSorted();
@@ -504,6 +508,8 @@ async function readComputerUseTools(params: {
reason: "ready",
message: "Computer Use is ready.",
});
// The readiness thread reacquires this fence before loading native config.
params.releaseNativeConfigFence?.();
const { liveTest, repair } = await runCodexComputerUseLiveTest({
request: params.request,
config: params.config,
@@ -205,6 +205,7 @@ describe("CodexAppServerEventProjector terminal errors", () => {
{ codexErrorInfo: "serverOverloaded", expected: true },
{ codexErrorInfo: "usageLimitExceeded", expected: false },
{ codexErrorInfo: "unauthorized", expected: false },
{ codexErrorInfo: "misalignmentPolicyViolation", expected: false },
{ codexErrorInfo: "other", expected: false },
])(
"projects $codexErrorInfo terminal error recovery eligibility as $expected",
+12 -5
View File
@@ -149,7 +149,7 @@ describe("listCodexAppServerModels", () => {
const initialize = JSON.parse(harness.writes[0] ?? "{}") as { id?: number };
harness.send({
id: initialize.id,
result: { userAgent: "openclaw/0.147.0 (macOS; test)" },
result: { userAgent: "openclaw/0.148.0 (macOS; test)" },
});
await vi.waitFor(() => expect(harness.writes.length).toBeGreaterThanOrEqual(3));
const list = JSON.parse(harness.writes[2] ?? "{}") as { id?: number; method?: string };
@@ -170,7 +170,7 @@ describe("listCodexAppServerModels", () => {
const initialize = JSON.parse(harness.writes[0] ?? "{}") as { id?: number };
harness.send({
id: initialize.id,
result: { userAgent: "openclaw/0.147.0 (macOS; test)" },
result: { userAgent: "openclaw/0.148.0 (macOS; test)" },
});
await vi.waitFor(() => expect(harness.writes.length).toBeGreaterThanOrEqual(3));
const list = JSON.parse(harness.writes[2] ?? "{}") as { id?: number; method?: string };
@@ -184,7 +184,13 @@ describe("listCodexAppServerModels", () => {
id: "gpt-5.4",
model: "gpt-5.4",
upgrade: null,
upgradeInfo: null,
upgradeInfo: {
model: "gpt-5.6",
upgradeCopy: "Try GPT-5.6",
modelLink: null,
migrationMarkdown: null,
retirementAt: 1_800_000_000,
},
availabilityNux: null,
displayName: "gpt-5.4",
description: "GPT-5.4",
@@ -196,6 +202,7 @@ describe("listCodexAppServerModels", () => {
],
defaultReasoningEffort: "medium",
supportsPersonality: false,
multiAgentVersion: "v2",
additionalSpeedTiers: [],
isDefault: true,
},
@@ -232,7 +239,7 @@ describe("listCodexAppServerModels", () => {
const initialize = JSON.parse(harness.writes[0] ?? "{}") as { id?: number };
harness.send({
id: initialize.id,
result: { userAgent: "openclaw/0.147.0 (macOS; test)" },
result: { userAgent: "openclaw/0.148.0 (macOS; test)" },
});
await vi.waitFor(() => expect(harness.writes.length).toBeGreaterThanOrEqual(3));
const firstList = JSON.parse(harness.writes[2] ?? "{}") as {
@@ -312,7 +319,7 @@ describe("listCodexAppServerModels", () => {
const initialize = JSON.parse(harness.writes[0] ?? "{}") as { id?: number };
harness.send({
id: initialize.id,
result: { userAgent: "openclaw/0.147.0 (macOS; test)" },
result: { userAgent: "openclaw/0.148.0 (macOS; test)" },
});
await vi.waitFor(() => expect(harness.writes.length).toBeGreaterThanOrEqual(3));
const firstList = JSON.parse(harness.writes[2] ?? "{}") as { id?: number };
@@ -33,7 +33,7 @@ describe("Codex plugin metadata cache", () => {
expect(request).toHaveBeenCalledTimes(1);
});
it("coalesces installed plugins through the exact Codex 0.146 endpoint", async () => {
it("coalesces installed plugins through the canonical endpoint", async () => {
const cache = new CodexPluginMetadataCache();
let release: ((response: v2.PluginInstalledResponse) => void) | undefined;
const request = vi.fn(
@@ -235,6 +235,7 @@ export type CodexConfigBatchWriteParams = {
};
type CodexConfigLayerSource =
| { type: "packagedDefaults"; file: string }
| { type: "mdm"; domain: string; key: string }
| { type: "system"; file: string }
| { type: "enterpriseManaged"; id: string; name: string }
@@ -299,6 +299,7 @@
"usageLimitExceeded",
"serverOverloaded",
"cyberPolicy",
"misalignmentPolicyViolation",
"internalServerError",
"unauthorized",
"badRequest",
@@ -623,6 +624,37 @@
],
"type": "string"
},
"ImageGenerationFailure": {
"oneOf": [
{
"properties": {
"limitId": {
"type": "string"
},
"resetsAt": {
"format": "int64",
"type": [
"integer",
"null"
]
},
"type": {
"enum": [
"usageLimitExceeded"
],
"title": "UsageLimitExceededImageGenerationFailureType",
"type": "string"
}
},
"required": [
"limitId",
"type"
],
"title": "UsageLimitExceededImageGenerationFailure",
"type": "object"
}
]
},
"InputModality": {
"description": "Canonical user-input modality tags advertised by a model.",
"oneOf": [
@@ -853,6 +885,17 @@
"null"
]
},
"multiAgentVersion": {
"anyOf": [
{
"$ref": "#/definitions/MultiAgentVersion"
},
{
"type": "null"
}
],
"description": "Multi-agent runtime declared by this model, when available."
},
"serviceTiers": {
"default": [],
"items": {
@@ -946,6 +989,14 @@
"null"
]
},
"retirementAt": {
"description": "Informational Unix timestamp for this upgrade's scheduled retirement, if known.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"upgradeCopy": {
"type": [
"string",
@@ -983,6 +1034,15 @@
}
]
},
"MultiAgentVersion": {
"description": "Multi-agent runtime supported by a model.",
"enum": [
"disabled",
"v1",
"v2"
],
"type": "string"
},
"NetworkAccess": {
"enum": [
"restricted",
@@ -2236,6 +2296,17 @@
},
{
"properties": {
"failure": {
"anyOf": [
{
"$ref": "#/definitions/ImageGenerationFailure"
},
{
"type": "null"
}
],
"default": null
},
"id": {
"type": "string"
},
@@ -2358,6 +2429,18 @@
"ThreadSection": {
"description": "An independently persisted, user-visible thread section.",
"properties": {
"appearance": {
"anyOf": [
{
"$ref": "#/definitions/ThreadSectionAppearance"
},
{
"type": "null"
}
],
"default": null,
"description": "Optional appearance synchronized across clients."
},
"id": {
"description": "Opaque UUIDv7 identity that remains stable when the section is renamed.",
"type": "string"
@@ -2373,6 +2456,24 @@
],
"type": "object"
},
"ThreadSectionAppearance": {
"description": "Extensible visual presentation for a custom thread section.",
"properties": {
"color": {
"type": [
"string",
"null"
]
},
"icon": {
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"ThreadSource": {
"type": "string"
},
@@ -49,7 +49,7 @@
},
"itemsBackwardsCursor": {
"default": null,
"description": "Opaque head cursor for hydrating paginated items backwards.\n\nPass this as `cursor` to `thread/items/list` with `sortDirection: \"desc\"`. The first page includes the cursor's head item.",
"description": "Opaque cursor for hydrating paginated items backwards.\n\nPass this as `cursor` to `thread/items/list` with `sortDirection: \"desc\"`. The first page includes the item identified by the cursor.",
"type": [
"string",
"null"
@@ -107,7 +107,7 @@
},
"turnsBackwardsCursor": {
"default": null,
"description": "Opaque head cursor for hydrating paginated turns backwards.\n\nPass this as `cursor` to `thread/turns/list` with `sortDirection: \"desc\"`. The first page includes the cursor's head turn.",
"description": "Opaque cursor for hydrating paginated turns backwards.\n\nPass this as `cursor` to `thread/turns/list` with `sortDirection: \"desc\"`. The first page includes the turn identified by the cursor.",
"type": [
"string",
"null"
@@ -78,7 +78,7 @@ function threadStartResult(threadId = "thread-1", serviceTier: string | null = n
status: { type: "idle" },
path: null,
cwd: tempDir,
cliVersion: "0.147.0",
cliVersion: "0.148.0",
source: "unknown",
agentNickname: null,
agentRole: null,
@@ -508,7 +508,7 @@ describe("shared Codex app-server client", () => {
expect(pluginLocal.process.stdin.destroyed).toBe(true);
});
it("keeps a newer desktop app-server instead of falling back by version", async () => {
it("keeps a supported desktop prerelease instead of falling back by version", async () => {
const desktop = createClientHarness();
const startSpy = vi.spyOn(CodexAppServerClient, "start").mockReturnValueOnce(desktop.client);
const startOptions = configureManagedDesktopFallback();
@@ -525,13 +525,7 @@ describe("shared Codex app-server client", () => {
managedFallbackCommandPaths: ["/cache/openclaw/codex"],
});
expect(desktop.process.stdin.destroyed).toBe(false);
expect(mocks.embeddedAgentLog.warn).toHaveBeenCalledWith(
"codex app-server is newer than OpenClaw's managed runtime; continuing with normal startup validation",
{
detectedVersion: "0.148.0-alpha.23",
validatedVersion: CODEX_APP_SERVER_VERSION,
},
);
expect(mocks.embeddedAgentLog.warn).not.toHaveBeenCalled();
await clearSharedCodexAppServerClientAndWait({ exitTimeoutMs: 25, forceKillDelayMs: 5 });
expect(desktop.process.stdin.destroyed).toBe(true);
@@ -283,7 +283,7 @@ function threadResult(threadId: string) {
status: { type: "idle" },
path: null,
cwd: "/tmp/workspace",
cliVersion: "0.147.0",
cliVersion: "0.148.0",
source: "unknown",
agentNickname: null,
agentRole: null,
@@ -1285,7 +1285,14 @@ describe("Codex app-server thread lifecycle bindings", () => {
const request = vi.fn(async (method: string, _requestParams?: unknown) => {
if (method === "config/read") {
return {
layers: [],
layers: [
{
name: {
type: "packagedDefaults",
file: "/managed/codex/defaults.toml",
},
},
],
config: {
mcp_servers: {
"arbitrary.server": { command: "ignored" },
@@ -77,7 +77,7 @@ export function threadStartResult(threadId = "thread-1"): Record<string, unknown
status: { type: "idle" },
path: null,
cwd: "/tmp",
cliVersion: "0.147.0",
cliVersion: "0.148.0",
source: "unknown",
agentNickname: null,
agentRole: null,
@@ -882,7 +882,7 @@ function threadStartResult(threadId = "thread-1") {
status: { type: "idle" },
path: null,
cwd: tempDir,
cliVersion: "0.147.0",
cliVersion: "0.148.0",
source: "unknown",
agentNickname: null,
agentRole: null,
@@ -70,7 +70,7 @@ const CODEX_DELEGATION_DISABLED_THREAD_CONFIG: JsonObject = {
"features.multi_agent_v2": false,
};
// Exact Codex 0.147 registry features that can expose a model-visible tool or
// Exact Codex 0.148 registry features that can expose a model-visible tool or
// host capability. One list owns both the thread deny patch and requirement pin rejection.
const CODEX_RING_ZERO_RESTRICTED_FEATURES = new Set([
"apps",
@@ -141,6 +141,7 @@ const CODEX_RING_ZERO_RESTRICTED_FEATURE_ALIASES = new Map<string, string>([
]);
const CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES = new Set([
"packagedDefaults",
"mdm",
"system",
"enterpriseManaged",
@@ -55,7 +55,10 @@ describe("Codex app-server websocket transport", () => {
const message = JSON.parse(rawDataToText(data)) as { id?: number; method?: string };
if (message.method === "initialize") {
socket.send(
JSON.stringify({ id: message.id, result: { userAgent: "openclaw/0.147.0" } }),
JSON.stringify({
id: message.id,
result: { userAgent: `openclaw/${CODEX_APP_SERVER_VERSION}` },
}),
);
return;
}
@@ -82,7 +82,7 @@ function forkResponse(threadId = "thread-forked") {
thread: {
id: threadId,
sessionId: "session-forked",
cliVersion: "0.147.0",
cliVersion: "0.148.0",
createdAt: 1715299200,
updatedAt: 1715299200,
cwd: "/tmp",
+1 -1
View File
@@ -2,7 +2,7 @@
* Version and package pins for the managed Codex app-server runtime.
*/
/** Exact Codex app-server version shipped by the OpenClaw Codex bridge. */
export const CODEX_APP_SERVER_VERSION = "0.147.0";
export const CODEX_APP_SERVER_VERSION = "0.148.0";
/** Inclusive runtime compatibility floor for external app-server binaries. */
export const MIN_SUPPORTED_CODEX_APP_SERVER_VERSION = "0.147.0";
/** npm package name for the managed Codex app-server binary. */
@@ -368,7 +368,7 @@ function conversationThreadStartResult(threadId: string) {
status: { type: "idle" },
path: null,
cwd: tempDir,
cliVersion: "0.147.0",
cliVersion: "0.148.0",
source: "unknown",
agentNickname: null,
agentRole: null,
@@ -48,7 +48,7 @@ function threadStartResult() {
status: { type: "idle" },
path: null,
cwd: "/tmp/openclaw-agent",
cliVersion: "0.147.0",
cliVersion: "0.148.0",
source: "unknown",
agentNickname: null,
agentRole: null,
+1 -1
View File
@@ -91,7 +91,7 @@ function classifyOpenAiFailoverCode(code: string | undefined) {
const OPENAI_MODELS_ENDPOINT = "https://api.openai.com/v1/models";
// Keep synchronized with extensions/codex's exact @openai/codex dependency;
// the provider contract test fails when that managed-runtime pin changes.
const OPENAI_CODEX_CLIENT_VERSION = "0.147.0";
const OPENAI_CODEX_CLIENT_VERSION = "0.148.0";
const OPENAI_CODEX_MODELS_ENDPOINT = `${OPENAI_CODEX_RESPONSES_BASE_URL}/models?client_version=${OPENAI_CODEX_CLIENT_VERSION}`;
const OPENAI_MODELS_CACHE_TTL_MS = 60_000;
const OPENAI_CODEX_MODELS_CACHE_TTL_MS = 60_000;
+31 -31
View File
@@ -5,7 +5,7 @@ settings:
excludeLinksFromLockfile: false
overrides:
'@agentclientprotocol/codex-acp@1.1.7>@openai/codex': 0.147.0
'@agentclientprotocol/codex-acp@1.1.7>@openai/codex': 0.148.0
'@anthropic-ai/sdk': 0.115.0
'@opentelemetry/core': 2.10.0
'@opentelemetry/propagator-jaeger': 2.10.0
@@ -639,8 +639,8 @@ importers:
extensions/codex:
dependencies:
'@openai/codex':
specifier: 0.147.0
version: 0.147.0
specifier: 0.148.0
version: 0.148.0
semver:
specifier: 7.8.5
version: 7.8.5
@@ -3948,43 +3948,43 @@ packages:
resolution: {integrity: sha512-3zcN5Q3yEmeyxXBzqB6fXPQFzYa2ROsGFSr69W0ArXIAGJqxl/aFECOVPD2kbkYPm0U/EHxFKgclK3UA9WQg5A==}
engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0}
'@openai/codex@0.147.0':
resolution: {integrity: sha512-EQLEXecAG2ptxI7UpBMo2TR/ga5596/c/OsYF/0LoUDh5JANZ7IoGqlzBEWbuEVQ76JePIbtTW/ihCkp1a7Z3w==}
'@openai/codex@0.148.0':
resolution: {integrity: sha512-bh5kH9+BMrFaHGmLeoSansPdfRksvr4UXzjQInns/KRO7r8VJ+6AAW+SqUsE8XcG3+OW/mI4EEy8Gpo9UDXGvQ==}
engines: {node: '>=16'}
hasBin: true
'@openai/codex@0.147.0-darwin-arm64':
resolution: {integrity: sha512-BEUVkiOW7kLcRyrMLfAr/h9wF8sRVJyZDy6OHtVn6QGDXiv3BvAZVTY1Pu9xF7KdIdkYXbp4uayN0aDQQaAUJw==}
'@openai/codex@0.148.0-darwin-arm64':
resolution: {integrity: sha512-xgBPFiF1fHUlRS7HE6wGB56LjBJh16kGD7b4TTbwdVBZNB4QDkTok+vdkAGrfpVkfKcwGNhPSKDgCw+KMZOVug==}
engines: {node: '>=16'}
cpu: [arm64]
os: [darwin]
'@openai/codex@0.147.0-darwin-x64':
resolution: {integrity: sha512-Tb8McE5SvJIH0Vs5R6sq7u+quiC931yan2KOOl6km1OdZ82+Wi7eF5XrSFPs5CF7xCgoIK4Vs+byMbT5hN+ZUw==}
'@openai/codex@0.148.0-darwin-x64':
resolution: {integrity: sha512-qepQolhJutfOp+e9i7L3xsi8aoWeCUiiRq274WMWqRj50rKTrXxsuAgkAwDbqEfT3G5VynhYZuQvDsW37JgdNQ==}
engines: {node: '>=16'}
cpu: [x64]
os: [darwin]
'@openai/codex@0.147.0-linux-arm64':
resolution: {integrity: sha512-SLC1JXw2TYfr/c3HhrJubyyLelq7vTOLWVmiThFA+z0+WgzCPmaseJ/kzDD3Gge/TO7fCnnj7UcPmC0d2c8XAg==}
'@openai/codex@0.148.0-linux-arm64':
resolution: {integrity: sha512-51DCd+izzk6n4mMh4w2utWj3lTLhSTnCOEJQfRh0LS9nBDkcYZcK3iSKOST6fByRIlLSXuLO33LlYYA1VPot6A==}
engines: {node: '>=16'}
cpu: [arm64]
os: [linux]
'@openai/codex@0.147.0-linux-x64':
resolution: {integrity: sha512-0W9MBxPpWW0cSkNqrTDN2jR7rzzT7oNMhQY5446lT2Lw5cz5yhDTck4Va9rjkQEm+HlFzP/dmEMSZbXfJsINmw==}
'@openai/codex@0.148.0-linux-x64':
resolution: {integrity: sha512-uDT9s7AfMr9xLuJX3ZLVWHgHkUpCnZ33CZjZEdVQhrYCIErkDHsCW5TG290nNjaKngK0WxGt5uCcxeUHv9MWWA==}
engines: {node: '>=16'}
cpu: [x64]
os: [linux]
'@openai/codex@0.147.0-win32-arm64':
resolution: {integrity: sha512-e2ZstJ8zT8Rm1nvR7CUVO+Gr3cTChE41+VfOzGhynzDXEoW0wfbjUQbc2bWbh1arG94LMm4y3dqBtUIbSrfeGA==}
'@openai/codex@0.148.0-win32-arm64':
resolution: {integrity: sha512-a8iOwLzs8UdnlWDHjgK3W/YSBBsUImG8X5XLBjengp3XGJRruhiIsQtUDUOYimCmotKPM4aX7Ub6zjl/KPxMQQ==}
engines: {node: '>=16'}
cpu: [arm64]
os: [win32]
'@openai/codex@0.147.0-win32-x64':
resolution: {integrity: sha512-oT7Ss5fAPf2fiWE9QNURqZcQGAAawSVxmIUdgPzckq4KFZAM+pRz9JbM4Rr498CjtbNgTOjWvDJ+DXvIBSfOPA==}
'@openai/codex@0.148.0-win32-x64':
resolution: {integrity: sha512-/Jg8eYw0BqTGNUpnrzzWlK2kbu29NWg7t6pnUDEfxqpTUf+mK8r3okXQn60Zjbk9InYZ4d8SwSjrtOa+i5hSPw==}
engines: {node: '>=16'}
cpu: [x64]
os: [win32]
@@ -9105,7 +9105,7 @@ snapshots:
'@agentclientprotocol/codex-acp@1.1.7':
dependencies:
'@agentclientprotocol/sdk': 1.3.0(zod@4.4.3)
'@openai/codex': 0.147.0
'@openai/codex': 0.148.0
diff: 9.0.0
open: 11.0.0
vscode-jsonrpc: 9.0.1
@@ -10912,31 +10912,31 @@ snapshots:
'@npmcli/redact@5.0.0': {}
'@openai/codex@0.147.0':
'@openai/codex@0.148.0':
optionalDependencies:
'@openai/codex-darwin-arm64': '@openai/codex@0.147.0-darwin-arm64'
'@openai/codex-darwin-x64': '@openai/codex@0.147.0-darwin-x64'
'@openai/codex-linux-arm64': '@openai/codex@0.147.0-linux-arm64'
'@openai/codex-linux-x64': '@openai/codex@0.147.0-linux-x64'
'@openai/codex-win32-arm64': '@openai/codex@0.147.0-win32-arm64'
'@openai/codex-win32-x64': '@openai/codex@0.147.0-win32-x64'
'@openai/codex-darwin-arm64': '@openai/codex@0.148.0-darwin-arm64'
'@openai/codex-darwin-x64': '@openai/codex@0.148.0-darwin-x64'
'@openai/codex-linux-arm64': '@openai/codex@0.148.0-linux-arm64'
'@openai/codex-linux-x64': '@openai/codex@0.148.0-linux-x64'
'@openai/codex-win32-arm64': '@openai/codex@0.148.0-win32-arm64'
'@openai/codex-win32-x64': '@openai/codex@0.148.0-win32-x64'
'@openai/codex@0.147.0-darwin-arm64':
'@openai/codex@0.148.0-darwin-arm64':
optional: true
'@openai/codex@0.147.0-darwin-x64':
'@openai/codex@0.148.0-darwin-x64':
optional: true
'@openai/codex@0.147.0-linux-arm64':
'@openai/codex@0.148.0-linux-arm64':
optional: true
'@openai/codex@0.147.0-linux-x64':
'@openai/codex@0.148.0-linux-x64':
optional: true
'@openai/codex@0.147.0-win32-arm64':
'@openai/codex@0.148.0-win32-arm64':
optional: true
'@openai/codex@0.147.0-win32-x64':
'@openai/codex@0.148.0-win32-x64':
optional: true
'@openclaw/crabline@0.1.11':
+1 -1
View File
@@ -127,7 +127,7 @@ verifyDepsBeforeRun: false
blockExoticSubdeps: true
overrides:
"@agentclientprotocol/codex-acp@1.1.7>@openai/codex": 0.147.0
"@agentclientprotocol/codex-acp@1.1.7>@openai/codex": 0.148.0
"@anthropic-ai/sdk": 0.115.0
"@opentelemetry/core": 2.10.0
"@opentelemetry/propagator-jaeger": 2.10.0
+11 -13
View File
@@ -456,21 +456,19 @@ const openClawThreadStartResponse: Omit<CodexThreadStartResponse, "thread"> =
export {};
`;
await fs.writeFile(probePath, probe);
const probeConfigPath = path.join(sourceRoot, "openclaw-protocol-compatibility.tsconfig.json");
await fs.writeFile(
probeConfigPath,
JSON.stringify({
extends: path.resolve("tsconfig.json"),
compilerOptions: { rootDir: process.cwd() },
files: [probePath],
include: [],
}),
);
const result = spawnSync(
process.execPath,
[
"scripts/run-tsgo.mjs",
"--ignoreConfig",
"--noEmit",
"--allowImportingTsExtensions",
"--strict",
"--skipLibCheck",
"--module",
"nodenext",
"--moduleResolution",
"nodenext",
probePath,
],
["scripts/run-tsgo.mjs", "--project", probeConfigPath],
{ cwd: process.cwd(), encoding: "utf8" },
);
if (result.error) {