diff --git a/docs/plugins/codex-computer-use.md b/docs/plugins/codex-computer-use.md index 211d462cff07..b13e9d5ea863 100644 --- a/docs/plugins/codex-computer-use.md +++ b/docs/plugins/codex-computer-use.md @@ -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. diff --git a/docs/plugins/codex-harness-reference.md b/docs/plugins/codex-harness-reference.md index c20905daffc5..2680a2e13451 100644 --- a/docs/plugins/codex-harness-reference.md +++ b/docs/plugins/codex-harness-reference.md @@ -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. -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 diff --git a/docs/plugins/codex-harness.md b/docs/plugins/codex-harness.md index 5c12c8c5884b..b4f720ea8f30 100644 --- a/docs/plugins/codex-harness.md +++ b/docs/plugins/codex-harness.md @@ -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 diff --git a/docs/plugins/codex-native-plugins.md b/docs/plugins/codex-native-plugins.md index 38b74250d669..5921f00c015d 100644 --- a/docs/plugins/codex-native-plugins.md +++ b/docs/plugins/codex-native-plugins.md @@ -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 diff --git a/extensions/codex/harness.ts b/extensions/codex/harness.ts index ffe797bda259..b1ccb94fe5bc 100644 --- a/extensions/codex/harness.ts +++ b/extensions/codex/harness.ts @@ -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 = [ diff --git a/extensions/codex/media-understanding-provider.test.ts b/extensions/codex/media-understanding-provider.test.ts index 87c56a9f47b5..51ad670a86b8 100644 --- a/extensions/codex/media-understanding-provider.test.ts +++ b/extensions/codex/media-understanding-provider.test.ts @@ -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, diff --git a/extensions/codex/package.json b/extensions/codex/package.json index d681f50bc278..459a77b4f59d 100644 --- a/extensions/codex/package.json +++ b/extensions/codex/package.json @@ -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", diff --git a/extensions/codex/src/app-server/approval-requester.real-binary.live.test.ts b/extensions/codex/src/app-server/approval-requester.real-binary.live.test.ts index 45c580f26fed..41563c38fe90 100644 --- a/extensions/codex/src/app-server/approval-requester.real-binary.live.test.ts +++ b/extensions/codex/src/app-server/approval-requester.real-binary.live.test.ts @@ -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")), diff --git a/extensions/codex/src/app-server/attempt-startup.test.ts b/extensions/codex/src/app-server/attempt-startup.test.ts index c0735a4b33b1..67291a0fde61 100644 --- a/extensions/codex/src/app-server/attempt-startup.test.ts +++ b/extensions/codex/src/app-server/attempt-startup.test.ts @@ -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 { 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( diff --git a/extensions/codex/src/app-server/client.test.ts b/extensions/codex/src/app-server/client.test.ts index 65a1d5b26087..0cb7d1f60f83 100644 --- a/extensions/codex/src/app-server/client.test.ts +++ b/extensions/codex/src/app-server/client.test.ts @@ -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", diff --git a/extensions/codex/src/app-server/codex-app-server.test-fixtures.ts b/extensions/codex/src/app-server/codex-app-server.test-fixtures.ts index 0488110a28e5..2ba67802b93f 100644 --- a/extensions/codex/src/app-server/codex-app-server.test-fixtures.ts +++ b/extensions/codex/src/app-server/codex-app-server.test-fixtures.ts @@ -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, diff --git a/extensions/codex/src/app-server/computer-use.test.ts b/extensions/codex/src/app-server/computer-use.test.ts index 3ce2c1bf33ef..bf0d77ba1dcc 100644 --- a/extensions/codex/src/app-server/computer-use.test.ts +++ b/extensions/codex/src/app-server/computer-use.test.ts @@ -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) => { diff --git a/extensions/codex/src/app-server/computer-use.ts b/extensions/codex/src/app-server/computer-use.ts index d3a258bf569a..b16a3582aa9b 100644 --- a/extensions/codex/src/app-server/computer-use.ts +++ b/extensions/codex/src/app-server/computer-use.ts @@ -153,6 +153,7 @@ type CodexComputerUseInspectionParams = { defaultBundledMarketplacePath?: string; defaultBundledMarketplacePathCandidates?: readonly string[]; repairComputerUseMcpChildren?: () => Promise; + 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; + releaseNativeConfigFence?: () => void; }): Promise { 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, diff --git a/extensions/codex/src/app-server/event-projector.terminal-errors.test.ts b/extensions/codex/src/app-server/event-projector.terminal-errors.test.ts index 04f8eca328ad..5b19653798e6 100644 --- a/extensions/codex/src/app-server/event-projector.terminal-errors.test.ts +++ b/extensions/codex/src/app-server/event-projector.terminal-errors.test.ts @@ -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", diff --git a/extensions/codex/src/app-server/models.test.ts b/extensions/codex/src/app-server/models.test.ts index 20c0eb989ffd..33b98aeada8b 100644 --- a/extensions/codex/src/app-server/models.test.ts +++ b/extensions/codex/src/app-server/models.test.ts @@ -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 }; diff --git a/extensions/codex/src/app-server/plugin-metadata-cache.test.ts b/extensions/codex/src/app-server/plugin-metadata-cache.test.ts index 44208d757cda..a5123ecc6625 100644 --- a/extensions/codex/src/app-server/plugin-metadata-cache.test.ts +++ b/extensions/codex/src/app-server/plugin-metadata-cache.test.ts @@ -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( diff --git a/extensions/codex/src/app-server/protocol-control-plane.ts b/extensions/codex/src/app-server/protocol-control-plane.ts index 7fa771d0ac10..2b6c04f27d87 100644 --- a/extensions/codex/src/app-server/protocol-control-plane.ts +++ b/extensions/codex/src/app-server/protocol-control-plane.ts @@ -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 } diff --git a/extensions/codex/src/app-server/protocol-generated/json/v2/CodexAppServerProtocolDefinitions.json b/extensions/codex/src/app-server/protocol-generated/json/v2/CodexAppServerProtocolDefinitions.json index 952e57abdb9a..e4694e2a39a6 100644 --- a/extensions/codex/src/app-server/protocol-generated/json/v2/CodexAppServerProtocolDefinitions.json +++ b/extensions/codex/src/app-server/protocol-generated/json/v2/CodexAppServerProtocolDefinitions.json @@ -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" }, diff --git a/extensions/codex/src/app-server/protocol-generated/json/v2/ThreadResumeResponse.json b/extensions/codex/src/app-server/protocol-generated/json/v2/ThreadResumeResponse.json index 2718262c0f8b..06a0f6348d1b 100644 --- a/extensions/codex/src/app-server/protocol-generated/json/v2/ThreadResumeResponse.json +++ b/extensions/codex/src/app-server/protocol-generated/json/v2/ThreadResumeResponse.json @@ -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" diff --git a/extensions/codex/src/app-server/schema-normalization-runtime-contract.test.ts b/extensions/codex/src/app-server/schema-normalization-runtime-contract.test.ts index 5d331d17c91c..e1d5cb8a74be 100644 --- a/extensions/codex/src/app-server/schema-normalization-runtime-contract.test.ts +++ b/extensions/codex/src/app-server/schema-normalization-runtime-contract.test.ts @@ -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, diff --git a/extensions/codex/src/app-server/shared-client.test.ts b/extensions/codex/src/app-server/shared-client.test.ts index deeb55cc7ee9..da581cb93bc2 100644 --- a/extensions/codex/src/app-server/shared-client.test.ts +++ b/extensions/codex/src/app-server/shared-client.test.ts @@ -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); diff --git a/extensions/codex/src/app-server/side-question.test.ts b/extensions/codex/src/app-server/side-question.test.ts index 1bd1a4370fc8..a7a7c3bbcf21 100644 --- a/extensions/codex/src/app-server/side-question.test.ts +++ b/extensions/codex/src/app-server/side-question.test.ts @@ -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, diff --git a/extensions/codex/src/app-server/thread-lifecycle.binding.test.ts b/extensions/codex/src/app-server/thread-lifecycle.binding.test.ts index 307ac864de25..abe6838dcbb2 100644 --- a/extensions/codex/src/app-server/thread-lifecycle.binding.test.ts +++ b/extensions/codex/src/app-server/thread-lifecycle.binding.test.ts @@ -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" }, diff --git a/extensions/codex/src/app-server/thread-lifecycle.test-fixtures.ts b/extensions/codex/src/app-server/thread-lifecycle.test-fixtures.ts index 8cbf51844691..e19c9452e025 100644 --- a/extensions/codex/src/app-server/thread-lifecycle.test-fixtures.ts +++ b/extensions/codex/src/app-server/thread-lifecycle.test-fixtures.ts @@ -77,7 +77,7 @@ export function threadStartResult(threadId = "thread-1"): Record([ ]); const CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES = new Set([ + "packagedDefaults", "mdm", "system", "enterpriseManaged", diff --git a/extensions/codex/src/app-server/transport-websocket.test.ts b/extensions/codex/src/app-server/transport-websocket.test.ts index ec644ad9bdd7..eaa19c8e8a75 100644 --- a/extensions/codex/src/app-server/transport-websocket.test.ts +++ b/extensions/codex/src/app-server/transport-websocket.test.ts @@ -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; } diff --git a/extensions/codex/src/app-server/upstream-session-fork.test.ts b/extensions/codex/src/app-server/upstream-session-fork.test.ts index 99a22bbe6e93..9008f36289f7 100644 --- a/extensions/codex/src/app-server/upstream-session-fork.test.ts +++ b/extensions/codex/src/app-server/upstream-session-fork.test.ts @@ -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", diff --git a/extensions/codex/src/app-server/version.ts b/extensions/codex/src/app-server/version.ts index e386707c8d3e..732f8d2e3266 100644 --- a/extensions/codex/src/app-server/version.ts +++ b/extensions/codex/src/app-server/version.ts @@ -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. */ diff --git a/extensions/codex/src/conversation-binding.test.ts b/extensions/codex/src/conversation-binding.test.ts index 9db3489b2d5b..9a98f3d81129 100644 --- a/extensions/codex/src/conversation-binding.test.ts +++ b/extensions/codex/src/conversation-binding.test.ts @@ -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, diff --git a/extensions/codex/src/web-search-provider.test.ts b/extensions/codex/src/web-search-provider.test.ts index 94372e933296..97da7d8cb997 100644 --- a/extensions/codex/src/web-search-provider.test.ts +++ b/extensions/codex/src/web-search-provider.test.ts @@ -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, diff --git a/extensions/openai/openai-provider.ts b/extensions/openai/openai-provider.ts index 53a607deb3d0..ed809e4c9be4 100644 --- a/extensions/openai/openai-provider.ts +++ b/extensions/openai/openai-provider.ts @@ -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; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f10d00165cea..22d4fb15b271 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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': diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 2d65c3ed84fd..5ffb3a0e5927 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -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 diff --git a/scripts/check-codex-app-server-protocol.ts b/scripts/check-codex-app-server-protocol.ts index 66cd10bcd241..1cf99f527d62 100644 --- a/scripts/check-codex-app-server-protocol.ts +++ b/scripts/check-codex-app-server-protocol.ts @@ -456,21 +456,19 @@ const openClawThreadStartResponse: Omit = 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) {