From a0f35574d047e2a9f2bf3198e7a86d1bbae6770e Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 14 May 2026 10:07:18 +0100 Subject: [PATCH] Remove codex-cli backend and migrate to Codex runtime Remove the bundled codex-cli backend, migrate legacy codex-cli refs and runtime pins to the Codex app-server runtime, and update live/backend workflow coverage for the supported CLI lanes. --- .../openclaw-live-and-e2e-checks-reusable.yml | 23 +- CHANGELOG.md | 1 + docs/cli/crestodian.md | 3 +- docs/concepts/agent-runtimes.md | 6 + docs/concepts/model-providers.md | 4 +- docs/gateway/cli-backends.md | 54 ++-- docs/gateway/config-agents.md | 4 +- docs/help/testing-live.md | 17 +- docs/plugins/manifest.md | 2 +- docs/plugins/sdk-overview.md | 4 +- docs/providers/openai.md | 5 +- docs/reference/test.md | 2 +- extensions/openai/cli-backend.ts | 70 ----- extensions/openai/index.ts | 2 - extensions/openai/openclaw.plugin.json | 1 - extensions/openai/register.runtime.ts | 1 - extensions/openai/setup-api.ts | 2 - extensions/openai/test-api.ts | 1 - package.json | 5 - scripts/lib/docker-e2e-scenarios.mjs | 26 +- scripts/print-cli-backend-live-metadata.ts | 20 +- scripts/test-live-cli-backend-docker.sh | 85 +----- src/agents/model-runtime-aliases.ts | 37 ++- src/auto-reply/reply/commands-models.test.ts | 6 +- src/cli/plugins-cli-test-helpers.ts | 1 + .../doctor-legacy-config.migrations.test.ts | 155 +++++++++- .../shared/legacy-config-core-normalizers.ts | 269 +++++++++++++++--- src/crestodian/assistant-backends.ts | 14 +- src/crestodian/assistant-prompts.ts | 2 +- src/crestodian/assistant.test.ts | 34 +-- src/crestodian/operations.ts | 4 +- .../gateway-cli-backend.live-helpers.test.ts | 17 +- .../gateway-cli-backend.live-helpers.ts | 5 + src/plugins/channel-plugin-ids.test.ts | 2 +- src/plugins/plugin-lookup-table.test.ts | 4 +- src/plugins/providers.test.ts | 4 +- test/scripts/docker-build-helper.test.ts | 4 +- .../package-acceptance-workflow.test.ts | 12 +- 38 files changed, 531 insertions(+), 377 deletions(-) delete mode 100644 extensions/openai/cli-backend.ts diff --git a/.github/workflows/openclaw-live-and-e2e-checks-reusable.yml b/.github/workflows/openclaw-live-and-e2e-checks-reusable.yml index 4fe39508fe7..8167f75357e 100644 --- a/.github/workflows/openclaw-live-and-e2e-checks-reusable.yml +++ b/.github/workflows/openclaw-live-and-e2e-checks-reusable.yml @@ -2154,27 +2154,11 @@ jobs: fi case "${{ matrix.suite_id }}" in live-cli-backend-docker) - echo "OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/gpt-5.4" >> "$GITHUB_ENV" - # Keep the release-blocking CI lane on Codex API-key auth. The - # staged auth-file path remains supported for local maintainer - # reruns, but it can hang on stale subscription/session state in - # an otherwise healthy release run. + echo "OPENCLAW_LIVE_CLI_BACKEND_MODEL=claude-cli/claude-sonnet-4-6" >> "$GITHUB_ENV" echo "OPENCLAW_LIVE_CLI_BACKEND_AUTH=api-key" >> "$GITHUB_ENV" - # Replace the staged config.toml with a minimal CI-safe config so - # the repo stays trusted for MCP/tool use without inheriting - # maintainer-local provider/profile overrides that do not exist - # inside CI. - # Codex's workspace-write sandbox relies on user namespaces that - # this Docker lane does not provide, so run Codex unsandboxed - # inside the already-isolated container to keep MCP cron/tool - # execution representative instead of failing on nested sandbox - # setup. - echo 'OPENCLAW_LIVE_CLI_BACKEND_ARGS=["exec","--json","--color","never","--sandbox","danger-full-access","-c","service_tier=\"fast\"","--skip-git-repo-check"]' >> "$GITHUB_ENV" - echo 'OPENCLAW_LIVE_CLI_BACKEND_RESUME_ARGS=["exec","resume","{sessionId}","-c","sandbox_mode=\"danger-full-access\"","-c","service_tier=\"fast\"","--skip-git-repo-check"]' >> "$GITHUB_ENV" echo "OPENCLAW_LIVE_CLI_BACKEND_DEBUG=1" >> "$GITHUB_ENV" echo "OPENCLAW_CLI_BACKEND_LOG_OUTPUT=1" >> "$GITHUB_ENV" echo "OPENCLAW_TEST_CONSOLE=1" >> "$GITHUB_ENV" - echo "OPENCLAW_LIVE_CLI_BACKEND_USE_CI_SAFE_CODEX_CONFIG=1" >> "$GITHUB_ENV" ;; live-codex-harness-docker) # Keep CI on the API-key path for now. The staged Codex auth secret @@ -2395,14 +2379,11 @@ jobs: fi case "${{ matrix.suite_id }}" in live-cli-backend-docker) - echo "OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/gpt-5.4" >> "$GITHUB_ENV" + echo "OPENCLAW_LIVE_CLI_BACKEND_MODEL=claude-cli/claude-sonnet-4-6" >> "$GITHUB_ENV" echo "OPENCLAW_LIVE_CLI_BACKEND_AUTH=api-key" >> "$GITHUB_ENV" - echo 'OPENCLAW_LIVE_CLI_BACKEND_ARGS=["exec","--json","--color","never","--sandbox","danger-full-access","-c","service_tier=\"fast\"","--skip-git-repo-check"]' >> "$GITHUB_ENV" - echo 'OPENCLAW_LIVE_CLI_BACKEND_RESUME_ARGS=["exec","resume","{sessionId}","-c","sandbox_mode=\"danger-full-access\"","-c","service_tier=\"fast\"","--skip-git-repo-check"]' >> "$GITHUB_ENV" echo "OPENCLAW_LIVE_CLI_BACKEND_DEBUG=1" >> "$GITHUB_ENV" echo "OPENCLAW_CLI_BACKEND_LOG_OUTPUT=1" >> "$GITHUB_ENV" echo "OPENCLAW_TEST_CONSOLE=1" >> "$GITHUB_ENV" - echo "OPENCLAW_LIVE_CLI_BACKEND_USE_CI_SAFE_CODEX_CONFIG=1" >> "$GITHUB_ENV" ;; live-codex-harness-docker) echo "OPENCLAW_LIVE_CODEX_HARNESS_AUTH=api-key" >> "$GITHUB_ENV" diff --git a/CHANGELOG.md b/CHANGELOG.md index 72eb03b3dd8..539364436d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai - Maintainer tooling: fail CI when pull requests add package patch files or pnpm patched dependencies, preserving the upstream-and-bump dependency workflow. - Amazon Bedrock: externalize the Bedrock and Bedrock Mantle provider packages so core installs no longer pull AWS SDK dependencies unless those providers are installed. - Plugins: externalize Slack, OpenShell sandbox, and Anthropic Vertex so their runtime dependency cones install only when those plugins are installed. +- Codex migration: remove the bundled `codex-cli` backend and repair legacy `codex-cli/*` model refs to the Codex app-server route on `openai/*`. - Control UI/WebChat: add a persisted auto-scroll mode selector so users can keep the current near-bottom behavior, always follow streaming output, or turn automatic streaming scroll off and use the New messages button manually. Fixes #7648 and #81287. Thanks @BunsDev. - ACP: add `acp.fallbacks` so ACP turns can try configured backup runtime backends when the primary backend is unavailable before any output is emitted. (#69542) Thanks @kaseonedge. - Gateway/startup: add owner-level startup trace attribution for auth, plugin loading, lookup counts, and plugin sidecar services. (#81738) Thanks @samzong. diff --git a/docs/cli/crestodian.md b/docs/cli/crestodian.md index b43203c9343..52774770ef2 100644 --- a/docs/cli/crestodian.md +++ b/docs/cli/crestodian.md @@ -155,7 +155,7 @@ order and tells you what it chose: - `OPENAI_API_KEY` -> `openai/gpt-5.5` - `ANTHROPIC_API_KEY` -> `anthropic/claude-opus-4-7` - Claude Code CLI -> `claude-cli/claude-opus-4-7` -- Codex CLI -> `codex-cli/gpt-5.5` +- Codex -> `openai/gpt-5.5` through the Codex app-server harness If none are available, setup still writes the default workspace and leaves the model unset. Install or log into Codex/Claude Code, or expose @@ -171,7 +171,6 @@ back to local runtimes already present on the machine: - Claude Code CLI: `claude-cli/claude-opus-4-7` - Codex app-server harness: `openai/gpt-5.5` -- Codex CLI: `codex-cli/gpt-5.5` The model-assisted planner cannot mutate config directly. It must translate the request into one of Crestodian's typed commands, then the normal approval and diff --git a/docs/concepts/agent-runtimes.md b/docs/concepts/agent-runtimes.md index 56a112eb57e..7e860576e11 100644 --- a/docs/concepts/agent-runtimes.md +++ b/docs/concepts/agent-runtimes.md @@ -97,6 +97,8 @@ This is the agent-facing decision tree: `openai/` with `openclaw doctor --fix`; doctor keeps the Codex auth route by adding provider/model-scoped `agentRuntime.id: "codex"` where the old model ref implied it. + Legacy **`codex-cli/*` model refs** repair to the same `openai/` Codex + app-server route; OpenClaw no longer keeps a bundled Codex CLI backend. 5. If the user explicitly says **ACP**, **acpx**, or **Codex ACP adapter**, use ACP with `runtime: "acp"` and `agentId: "codex"`. 6. If the request is for **Claude Code, Gemini CLI, OpenCode, Cursor, Droid, or @@ -180,6 +182,10 @@ Legacy refs such as `claude-cli/claude-opus-4-7` remain supported for compatibility, but new config should keep the provider/model canonical and put the execution backend in provider/model runtime policy. +Legacy `codex-cli/*` refs are different: doctor migrates them to `openai/*` so +they run through the Codex app-server harness instead of preserving a Codex CLI +backend. + `auto` mode is intentionally conservative for most providers. OpenAI agent models are the exception: unset runtime and `auto` both resolve to the Codex harness. Explicit PI runtime config remains an opt-in compatibility route for diff --git a/docs/concepts/model-providers.md b/docs/concepts/model-providers.md index 7e574f2befc..52e792dd8b8 100644 --- a/docs/concepts/model-providers.md +++ b/docs/concepts/model-providers.md @@ -41,9 +41,9 @@ Reference for **LLM/model providers** (not chat channels like WhatsApp/Telegram) - CLI runtimes use the same split: choose canonical model refs such as `anthropic/claude-*`, `google/gemini-*`, or `openai/gpt-*`, then set provider/model runtime policy to `claude-cli`, `google-gemini-cli`, or `codex-cli` when you want a local CLI backend. + CLI runtimes use the same split: choose canonical model refs such as `anthropic/claude-*` or `google/gemini-*`, then set provider/model runtime policy to `claude-cli` or `google-gemini-cli` when you want a local CLI backend. - Legacy `claude-cli/*`, `google-gemini-cli/*`, and `codex-cli/*` refs migrate back to canonical provider refs with the runtime recorded separately. + Legacy `claude-cli/*` and `google-gemini-cli/*` refs migrate back to canonical provider refs with the runtime recorded separately. Legacy `codex-cli/*` refs migrate to `openai/*` and use the Codex app-server route; OpenClaw no longer keeps a bundled Codex CLI backend. diff --git a/docs/gateway/cli-backends.md b/docs/gateway/cli-backends.md index a927755f190..22f361cfab0 100644 --- a/docs/gateway/cli-backends.md +++ b/docs/gateway/cli-backends.md @@ -2,7 +2,7 @@ summary: "CLI backends: local AI CLI fallback with optional MCP tool bridge" read_when: - You want a reliable fallback when API providers fail - - You are running Codex CLI or other local AI CLIs and want to reuse them + - You are running local AI CLIs and want to reuse them - You want to understand the MCP loopback bridge for CLI backend tool access title: "CLI backends" --- @@ -31,11 +31,11 @@ thread/conversation binding, and persistent external coding sessions, use ## Beginner-friendly quick start -You can use Codex CLI **without any config** (the bundled OpenAI plugin +You can use Claude Code CLI **without any config** (the bundled Anthropic plugin registers a default backend): ```bash -openclaw agent --message "hi" --model codex-cli/gpt-5.5 +openclaw agent --message "hi" --model claude-cli/claude-sonnet-4-6 ``` If your gateway runs under launchd/systemd and PATH is minimal, add just the @@ -46,8 +46,8 @@ command path: agents: { defaults: { cliBackends: { - "codex-cli": { - command: "/opt/homebrew/bin/codex", + "claude-cli": { + command: "/opt/homebrew/bin/claude", }, }, }, @@ -72,11 +72,11 @@ Add a CLI backend to your fallback list so it only runs when primary models fail defaults: { model: { primary: "anthropic/claude-opus-4-6", - fallbacks: ["codex-cli/gpt-5.5"], + fallbacks: ["claude-cli/claude-sonnet-4-6"], }, models: { "anthropic/claude-opus-4-6": { alias: "Opus" }, - "codex-cli/gpt-5.5": {}, + "claude-cli/claude-sonnet-4-6": {}, }, }, }, @@ -97,7 +97,7 @@ All CLI backends live under: agents.defaults.cliBackends ``` -Each entry is keyed by a **provider id** (e.g. `codex-cli`, `my-cli`). +Each entry is keyed by a **provider id** (e.g. `claude-cli`, `my-cli`). The provider id becomes the left side of your model ref: ``` @@ -111,9 +111,6 @@ The provider id becomes the left side of your model ref: agents: { defaults: { cliBackends: { - "codex-cli": { - command: "/opt/homebrew/bin/codex", - }, "my-cli": { command: "my-cli", args: ["--json"], @@ -149,7 +146,7 @@ The provider id becomes the left side of your model ref: ## How it works -1. **Selects a backend** based on the provider prefix (`codex-cli/...`). +1. **Selects a backend** based on the provider prefix (`claude-cli/...`). 2. **Builds a system prompt** using the same OpenClaw prompt + workspace context. 3. **Executes the CLI** with a session id (if supported) so history stays consistent. The bundled `claude-cli` backend keeps a Claude stdio process alive per @@ -164,12 +161,6 @@ told us OpenClaw-style Claude CLI usage is allowed again, so OpenClaw treats a new policy. -The bundled OpenAI `codex-cli` backend passes OpenClaw's system prompt through -Codex's `model_instructions_file` config override (`-c -model_instructions_file="..."`). Codex does not expose a Claude-style -`--append-system-prompt` flag, so OpenClaw writes the assembled prompt to a -temporary file for each fresh Codex CLI session. - The bundled Anthropic `claude-cli` backend receives the OpenClaw skills snapshot two ways: the compact OpenClaw skills catalog in the appended system prompt, and a temporary Claude Code plugin passed with `--plugin-dir`. The plugin contains @@ -292,7 +283,7 @@ load local files from plain paths. - `output: "json"` (default) tries to parse JSON and extract text + session id. - For Gemini CLI JSON output, OpenClaw reads reply text from `response` and usage from `stats` when `usage` is missing or empty. -- `output: "jsonl"` parses JSONL streams (for example Codex CLI `--json`) and extracts the final agent message plus session +- `output: "jsonl"` parses JSONL streams and extracts the final agent message plus session identifiers when present. - `output: "text"` treats stdout as the final response. @@ -304,16 +295,19 @@ Input modes: ## Defaults (plugin-owned) -The bundled OpenAI plugin also registers a default for `codex-cli`: +Bundled CLI backend defaults live with their owning plugin. For example, +Anthropic owns `claude-cli` and Google owns `google-gemini-cli`. OpenAI Codex +agent runs use the Codex app-server harness through `openai/*`; OpenClaw no +longer registers a bundled `codex-cli` backend. -- `command: "codex"` -- `args: ["exec","--json","--color","never","--sandbox","workspace-write","--skip-git-repo-check"]` -- `resumeArgs: ["exec","resume","{sessionId}","-c","sandbox_mode=\"workspace-write\"","--skip-git-repo-check"]` +The bundled Anthropic plugin registers a default for `claude-cli`: + +- `command: "claude"` +- `args: ["-p","--output-format","stream-json","--include-partial-messages","--verbose", ...]` - `output: "jsonl"` -- `resumeOutput: "text"` +- `input: "stdin"` - `modelArg: "--model"` -- `imageArg: "--image"` -- `sessionMode: "existing"` +- `sessionMode: "always"` The bundled Google plugin also registers a default for `google-gemini-cli`: @@ -383,9 +377,6 @@ opt into a generated MCP config overlay with `bundleMcp: true`. Current bundled behavior: - `claude-cli`: generated strict MCP config file -- `codex-cli`: inline config overrides for `mcp_servers`; the generated - OpenClaw loopback server is marked with Codex's per-server tool approval mode - so MCP calls cannot stall on local approval prompts - `google-gemini-cli`: generated Gemini system settings file When bundle MCP is enabled, OpenClaw: @@ -414,16 +405,13 @@ children and Streamable HTTP/SSE streams do not outlive the run. - **Streaming is backend-specific.** Some backends stream JSONL; others buffer until exit. - **Structured outputs** depend on the CLI's JSON format. -- **Codex CLI sessions** resume via text output (no JSONL), which is less - structured than the initial `--json` run. OpenClaw sessions still work - normally. ## Troubleshooting - **CLI not found**: set `command` to a full path. - **Wrong model name**: use `modelAliases` to map `provider/model` → CLI model. - **No session continuity**: ensure `sessionArg` is set and `sessionMode` is not - `none` (Codex CLI currently cannot resume with JSON output). + `none`. - **Images ignored**: set `imageArg` (and verify CLI supports file paths). ## Related diff --git a/docs/gateway/config-agents.md b/docs/gateway/config-agents.md index 0c310ab5379..9f81c5705f0 100644 --- a/docs/gateway/config-agents.md +++ b/docs/gateway/config-agents.md @@ -461,8 +461,8 @@ Optional CLI backends for text-only fallback runs (no tool calls). Useful as a b agents: { defaults: { cliBackends: { - "codex-cli": { - command: "/opt/homebrew/bin/codex", + "claude-cli": { + command: "/opt/homebrew/bin/claude", }, "my-cli": { command: "my-cli", diff --git a/docs/help/testing-live.md b/docs/help/testing-live.md index 4d4f750d349..f12f2237dc4 100644 --- a/docs/help/testing-live.md +++ b/docs/help/testing-live.md @@ -133,7 +133,7 @@ openclaw models list --json -## Live: CLI backend smoke (Claude, Codex, Gemini, or other local CLIs) +## Live: CLI backend smoke (Claude, Gemini, or other local CLIs) - Test: `src/gateway/gateway-cli-backend.live.test.ts` - Goal: validate the Gateway + agent pipeline using a local CLI backend, without touching your default config. @@ -145,9 +145,9 @@ openclaw models list --json - Default provider/model: `claude-cli/claude-sonnet-4-6` - Command/args/image behavior come from the owning CLI backend plugin metadata. - Overrides (optional): - - `OPENCLAW_LIVE_CLI_BACKEND_MODEL="codex-cli/gpt-5.5"` - - `OPENCLAW_LIVE_CLI_BACKEND_COMMAND="/full/path/to/codex"` - - `OPENCLAW_LIVE_CLI_BACKEND_ARGS='["exec","--json","--color","never","--sandbox","read-only","--skip-git-repo-check"]'` + - `OPENCLAW_LIVE_CLI_BACKEND_MODEL="claude-cli/claude-sonnet-4-6"` + - `OPENCLAW_LIVE_CLI_BACKEND_COMMAND="/full/path/to/claude"` + - `OPENCLAW_LIVE_CLI_BACKEND_ARGS='["-p","--output-format","json"]'` - `OPENCLAW_LIVE_CLI_BACKEND_IMAGE_PROBE=1` to send a real image attachment (paths are injected into the prompt). Docker recipes default this off unless explicitly requested. - `OPENCLAW_LIVE_CLI_BACKEND_IMAGE_ARG="--image"` to pass image file paths as CLI args instead of prompt injection. - `OPENCLAW_LIVE_CLI_BACKEND_IMAGE_MODE="repeat"` (or `"list"`) to control how image args are passed when `IMAGE_ARG` is set. @@ -158,8 +158,8 @@ openclaw models list --json Example: ```bash -OPENCLAW_LIVE_CLI_BACKEND=1 \ - OPENCLAW_LIVE_CLI_BACKEND_MODEL="codex-cli/gpt-5.5" \ + OPENCLAW_LIVE_CLI_BACKEND=1 \ + OPENCLAW_LIVE_CLI_BACKEND_MODEL="claude-cli/claude-sonnet-4-6" \ pnpm test:live src/gateway/gateway-cli-backend.live.test.ts ``` @@ -186,7 +186,6 @@ Single-provider Docker recipes: ```bash pnpm test:docker:live-cli-backend:claude pnpm test:docker:live-cli-backend:claude-subscription -pnpm test:docker:live-cli-backend:codex pnpm test:docker:live-cli-backend:gemini ``` @@ -194,9 +193,9 @@ Notes: - The Docker runner lives at `scripts/test-live-cli-backend-docker.sh`. - It runs the live CLI-backend smoke inside the repo Docker image as the non-root `node` user. -- It resolves CLI smoke metadata from the owning extension, then installs the matching Linux CLI package (`@anthropic-ai/claude-code`, `@openai/codex`, or `@google/gemini-cli`) into a cached writable prefix at `OPENCLAW_DOCKER_CLI_TOOLS_DIR` (default: `~/.cache/openclaw/docker-cli-tools`). +- It resolves CLI smoke metadata from the owning extension, then installs the matching Linux CLI package (`@anthropic-ai/claude-code` or `@google/gemini-cli`) into a cached writable prefix at `OPENCLAW_DOCKER_CLI_TOOLS_DIR` (default: `~/.cache/openclaw/docker-cli-tools`). - `pnpm test:docker:live-cli-backend:claude-subscription` requires portable Claude Code subscription OAuth through either `~/.claude/.credentials.json` with `claudeAiOauth.subscriptionType` or `CLAUDE_CODE_OAUTH_TOKEN` from `claude setup-token`. It first proves direct `claude -p` in Docker, then runs two Gateway CLI-backend turns without preserving Anthropic API-key env vars. This subscription lane disables the Claude MCP/tool and image probes by default because Claude currently routes third-party app usage through extra-usage billing instead of normal subscription plan limits. -- The live CLI-backend smoke now exercises the same end-to-end flow for Claude, Codex, and Gemini: text turn, image classification turn, then MCP `cron` tool call verified through the gateway CLI. +- The live CLI-backend smoke now exercises the same end-to-end flow for Claude and Gemini: text turn, image classification turn, then MCP `cron` tool call verified through the gateway CLI. - Claude's default smoke also patches the session from Sonnet to Opus and verifies the resumed session still remembers an earlier note. ## Live: APNs HTTP/2 proxy reachability diff --git a/docs/plugins/manifest.md b/docs/plugins/manifest.md index f07b3d15563..a606fa5ad5b 100644 --- a/docs/plugins/manifest.md +++ b/docs/plugins/manifest.md @@ -388,7 +388,7 @@ Prefer the narrowest metadata that already describes ownership. Use when those fields express the relationship. Use `activation` for extra planner hints that cannot be represented by those ownership fields. Use top-level `cliBackends` for CLI runtime aliases such as `claude-cli`, -`codex-cli`, or `google-gemini-cli`; `activation.onAgentHarnesses` is only for +`my-cli`, or `google-gemini-cli`; `activation.onAgentHarnesses` is only for embedded agent harness ids that do not already have an ownership field. This block is metadata only. It does not register runtime behavior, and it does diff --git a/docs/plugins/sdk-overview.md b/docs/plugins/sdk-overview.md index 4d315d95056..1189ade3d96 100644 --- a/docs/plugins/sdk-overview.md +++ b/docs/plugins/sdk-overview.md @@ -320,9 +320,9 @@ descriptor-backed placeholders for parse-time lazy loading. ### CLI backend registration `api.registerCliBackend(...)` lets a plugin own the default config for a local -AI CLI backend such as `codex-cli`. +AI CLI backend such as `claude-cli` or `my-cli`. -- The backend `id` becomes the provider prefix in model refs like `codex-cli/gpt-5`. +- The backend `id` becomes the provider prefix in model refs like `my-cli/gpt-5`. - The backend `config` uses the same shape as `agents.defaults.cliBackends.`. - User config still wins. OpenClaw merges `agents.defaults.cliBackends.` over the plugin default before running the CLI. diff --git a/docs/providers/openai.md b/docs/providers/openai.md index 3ac8df79497..81a8843c0bd 100644 --- a/docs/providers/openai.md +++ b/docs/providers/openai.md @@ -75,7 +75,7 @@ PI runtime config remains available as an opt-in compatibility route. When PI is explicitly selected with an `openai-codex` auth profile, OpenClaw keeps the public model ref as `openai/*` and routes PI internally through the legacy Codex-auth transport. Run `openclaw doctor --fix` to repair stale -`openai-codex/*` model refs or old PI session pins that do not come from +`openai-codex/*`, `codex-cli/*`, or old PI session pins that do not come from explicit runtime config. @@ -85,7 +85,7 @@ explicit runtime config. | ------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------ | | Chat / Responses | `openai/` model provider | Yes | | Codex subscription models | `openai/` with `openai-codex` OAuth | Yes | -| Legacy Codex model refs | `openai-codex/` | Repaired by doctor to `openai/` | +| Legacy Codex model refs | `openai-codex/` or `codex-cli/` | Repaired by doctor to `openai/` | | Codex app-server harness | `openai/` with omitted runtime or provider/model `agentRuntime.id: codex` | Yes | | Server-side web search | Native OpenAI Responses tool | Yes, when web search is enabled and no provider pinned | | Images | `image_generate` | Yes | @@ -245,6 +245,7 @@ Choose your preferred auth method and follow the setup steps. | `openai/gpt-5.5` | omitted / provider/model `agentRuntime.id: "codex"` | Native Codex app-server harness | Codex sign-in or ordered `openai` auth profile | | `openai/gpt-5.5` | provider/model `agentRuntime.id: "pi"` | PI embedded runtime with internal Codex-auth transport | Selected `openai-codex` profile | | `openai-codex/gpt-5.5` | repaired by doctor | Legacy route rewritten to `openai/gpt-5.5` | Existing `openai-codex` profile | + | `codex-cli/gpt-5.5` | repaired by doctor | Legacy CLI route rewritten to `openai/gpt-5.5` | Codex app-server auth | Do not configure older `openai-codex/gpt-5.1*`, `openai-codex/gpt-5.2*`, or diff --git a/docs/reference/test.md b/docs/reference/test.md index 017e2d959ac..6996aefa3aa 100644 --- a/docs/reference/test.md +++ b/docs/reference/test.md @@ -43,7 +43,7 @@ title: "Tests" - `pnpm test:docker:all`: Builds the shared live-test image, packs OpenClaw once as an npm tarball, builds/reuses a bare Node/Git runner image plus a functional image that installs that tarball into `/app`, then runs Docker smoke lanes with `OPENCLAW_SKIP_DOCKER_BUILD=1` through a weighted scheduler. The bare image (`OPENCLAW_DOCKER_E2E_BARE_IMAGE`) is used for installer/update/plugin-dependency lanes; those lanes mount the prebuilt tarball instead of using copied repo sources. The functional image (`OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE`) is used for normal built-app functionality lanes. `scripts/package-openclaw-for-docker.mjs` is the single local/CI package packer and validates the tarball plus `dist/postinstall-inventory.json` before Docker consumes it. Docker lane definitions live in `scripts/lib/docker-e2e-scenarios.mjs`; planner logic lives in `scripts/lib/docker-e2e-plan.mjs`; `scripts/test-docker-all.mjs` executes the selected plan. `node scripts/test-docker-all.mjs --plan-json` emits the scheduler-owned CI plan for selected lanes, image kinds, package/live-image needs, state scenarios, and credential checks without building or running Docker. `OPENCLAW_DOCKER_ALL_PARALLELISM=` controls process slots and defaults to 10; `OPENCLAW_DOCKER_ALL_TAIL_PARALLELISM=` controls the provider-sensitive tail pool and defaults to 10. Heavy lane caps default to `OPENCLAW_DOCKER_ALL_LIVE_LIMIT=9`, `OPENCLAW_DOCKER_ALL_NPM_LIMIT=10`, and `OPENCLAW_DOCKER_ALL_SERVICE_LIMIT=7`; provider caps default to one heavy lane per provider via `OPENCLAW_DOCKER_ALL_LIVE_CLAUDE_LIMIT=4`, `OPENCLAW_DOCKER_ALL_LIVE_CODEX_LIMIT=4`, and `OPENCLAW_DOCKER_ALL_LIVE_GEMINI_LIMIT=4`. Use `OPENCLAW_DOCKER_ALL_WEIGHT_LIMIT` or `OPENCLAW_DOCKER_ALL_DOCKER_LIMIT` for larger hosts. If one lane exceeds the effective weight or resource cap on a low-parallelism host, it can still start from an empty pool and will run alone until it releases capacity. Lane starts are staggered by 2 seconds by default to avoid local Docker daemon create storms; override with `OPENCLAW_DOCKER_ALL_START_STAGGER_MS=`. The runner preflights Docker by default, cleans stale OpenClaw E2E containers, emits active-lane status every 30 seconds, shares provider CLI tool caches between compatible lanes, retries transient live-provider failures once by default (`OPENCLAW_DOCKER_ALL_LIVE_RETRIES=`), and stores lane timings in `.artifacts/docker-tests/lane-timings.json` for longest-first ordering on later runs. Use `OPENCLAW_DOCKER_ALL_DRY_RUN=1` to print the lane manifest without running Docker, `OPENCLAW_DOCKER_ALL_STATUS_INTERVAL_MS=` to tune status output, or `OPENCLAW_DOCKER_ALL_TIMINGS=0` to disable timing reuse. Use `OPENCLAW_DOCKER_ALL_LIVE_MODE=skip` for deterministic/local lanes only or `OPENCLAW_DOCKER_ALL_LIVE_MODE=only` for live-provider lanes only; package aliases are `pnpm test:docker:local:all` and `pnpm test:docker:live:all`. Live-only mode merges main and tail live lanes into one longest-first pool so provider buckets can pack Claude, Codex, and Gemini work together. The runner stops scheduling new pooled lanes after the first failure unless `OPENCLAW_DOCKER_ALL_FAIL_FAST=0` is set, and each lane has a 120-minute fallback timeout overrideable with `OPENCLAW_DOCKER_ALL_LANE_TIMEOUT_MS`; selected live/tail lanes use tighter per-lane caps. CLI backend Docker setup commands have their own timeout via `OPENCLAW_LIVE_CLI_BACKEND_SETUP_TIMEOUT_SECONDS` (default 180). Per-lane logs, `summary.json`, `failures.json`, and phase timings are written under `.artifacts/docker-tests//`; use `pnpm test:docker:timings ` to inspect slow lanes and `pnpm test:docker:rerun ` to print cheap targeted rerun commands. - `pnpm test:docker:browser-cdp-snapshot`: Builds a Chromium-backed source E2E container, starts raw CDP plus an isolated Gateway, runs `browser doctor --deep`, and verifies CDP role snapshots include link URLs, cursor-promoted clickables, iframe refs, and frame metadata. - `pnpm test:docker:skill-install`: Installs the packed OpenClaw tarball in a bare Docker runner, disables `skills.install.allowUploadedArchives`, resolves a current skill slug from live ClawHub search, installs it through `openclaw skills install`, and verifies `SKILL.md`, `.clawhub/origin.json`, `.clawhub/lock.json`, and `skills info --json`. -- CLI backend live Docker probes can be run as focused lanes, for example `pnpm test:docker:live-cli-backend:codex`, `pnpm test:docker:live-cli-backend:codex:resume`, or `pnpm test:docker:live-cli-backend:codex:mcp`. Claude and Gemini have matching `:resume` and `:mcp` aliases. +- CLI backend live Docker probes can be run as focused lanes, for example `pnpm test:docker:live-cli-backend:claude`, `pnpm test:docker:live-cli-backend:claude:resume`, or `pnpm test:docker:live-cli-backend:claude:mcp`. Gemini has matching `:resume` and `:mcp` aliases. - `pnpm test:docker:openwebui`: Starts Dockerized OpenClaw + Open WebUI, signs in through Open WebUI, checks `/api/models`, then runs a real proxied chat through `/api/chat/completions`. Requires a usable live model key, pulls an external Open WebUI image, and is not expected to be CI-stable like the normal unit/e2e suites. - `pnpm test:docker:mcp-channels`: Starts a seeded Gateway container and a second client container that spawns `openclaw mcp serve`, then verifies routed conversation discovery, transcript reads, attachment metadata, live event queue behavior, outbound send routing, and Claude-style channel + permission notifications over the real stdio bridge. The Claude notification assertion reads the raw stdio MCP frames directly so the smoke reflects what the bridge actually emits. - `pnpm test:docker:upgrade-survivor`: Installs the packed OpenClaw tarball over a dirty old-user fixture, runs package update plus non-interactive doctor without live provider or channel keys, then starts a loopback Gateway and checks that agents, channel config, plugin allowlists, workspace/session files, stale legacy plugin dependency state, startup, and RPC status survive. diff --git a/extensions/openai/cli-backend.ts b/extensions/openai/cli-backend.ts deleted file mode 100644 index b14c050c2a8..00000000000 --- a/extensions/openai/cli-backend.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { CliBackendPlugin } from "openclaw/plugin-sdk/cli-backend"; -import { - CLI_FRESH_WATCHDOG_DEFAULTS, - CLI_RESUME_WATCHDOG_DEFAULTS, -} from "openclaw/plugin-sdk/cli-backend"; - -const CODEX_CLI_DEFAULT_MODEL_REF = "codex-cli/gpt-5.5"; -// Keep this in sync with MANAGED_CODEX_APP_SERVER_PACKAGE_VERSION in the Codex plugin. -const CODEX_CLI_NPM_PACKAGE = "@openai/codex@0.130.0"; - -export function buildOpenAICodexCliBackend(): CliBackendPlugin { - return { - id: "codex-cli", - liveTest: { - defaultModelRef: CODEX_CLI_DEFAULT_MODEL_REF, - defaultImageProbe: true, - defaultMcpProbe: true, - docker: { - npmPackage: CODEX_CLI_NPM_PACKAGE, - binaryName: "codex", - }, - }, - bundleMcp: true, - bundleMcpMode: "codex-config-overrides", - nativeToolMode: "always-on", - config: { - command: "codex", - args: [ - "exec", - "--json", - "--color", - "never", - "--sandbox", - "workspace-write", - "-c", - 'service_tier="fast"', - "--skip-git-repo-check", - ], - resumeArgs: [ - "exec", - "resume", - "{sessionId}", - "-c", - 'sandbox_mode="workspace-write"', - "-c", - 'service_tier="fast"', - "--skip-git-repo-check", - ], - output: "jsonl", - resumeOutput: "text", - input: "arg", - modelArg: "--model", - sessionIdFields: ["thread_id"], - sessionMode: "existing", - systemPromptFileConfigArg: "-c", - systemPromptFileConfigKey: "model_instructions_file", - systemPromptWhen: "first", - imageArg: "--image", - imageMode: "repeat", - imagePathScope: "workspace", - reliability: { - watchdog: { - fresh: { ...CLI_FRESH_WATCHDOG_DEFAULTS }, - resume: { ...CLI_RESUME_WATCHDOG_DEFAULTS }, - }, - }, - serialize: true, - }, - }; -} diff --git a/extensions/openai/index.ts b/extensions/openai/index.ts index 1865428be5c..e3e8d8ab243 100644 --- a/extensions/openai/index.ts +++ b/extensions/openai/index.ts @@ -1,7 +1,6 @@ import { resolvePluginConfigObject } from "openclaw/plugin-sdk/plugin-config-runtime"; import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; import { buildProviderToolCompatFamilyHooks } from "openclaw/plugin-sdk/provider-tools"; -import { buildOpenAICodexCliBackend } from "./cli-backend.js"; import { buildOpenAIImageGenerationProvider } from "./image-generation-provider.js"; import { openaiCodexMediaUnderstandingProvider, @@ -45,7 +44,6 @@ export default definePluginEntry({ }); }, }); - api.registerCliBackend(buildOpenAICodexCliBackend()); api.registerProvider(buildProviderWithPromptContribution(buildOpenAIProvider())); api.registerProvider(buildProviderWithPromptContribution(buildOpenAICodexProviderPlugin())); api.registerMemoryEmbeddingProvider(openAiMemoryEmbeddingProviderAdapter); diff --git a/extensions/openai/openclaw.plugin.json b/extensions/openai/openclaw.plugin.json index 159450f38f1..1ed34b5211d 100644 --- a/extensions/openai/openclaw.plugin.json +++ b/extensions/openai/openclaw.plugin.json @@ -756,7 +756,6 @@ } ] }, - "cliBackends": ["codex-cli"], "providerAuthEnvVars": { "openai": ["OPENAI_API_KEY"] }, diff --git a/extensions/openai/register.runtime.ts b/extensions/openai/register.runtime.ts index 672514e4b68..97c00010d02 100644 --- a/extensions/openai/register.runtime.ts +++ b/extensions/openai/register.runtime.ts @@ -1,4 +1,3 @@ -export { buildOpenAICodexCliBackend } from "./cli-backend.js"; export { buildOpenAIImageGenerationProvider } from "./image-generation-provider.js"; export { openaiCodexMediaUnderstandingProvider, diff --git a/extensions/openai/setup-api.ts b/extensions/openai/setup-api.ts index f9d3624951c..10bbd7328db 100644 --- a/extensions/openai/setup-api.ts +++ b/extensions/openai/setup-api.ts @@ -17,7 +17,6 @@ import { OPENAI_CODEX_LOGIN_LABEL, OPENAI_CODEX_WIZARD_GROUP, } from "./auth-choice-copy.js"; -import { buildOpenAICodexCliBackend } from "./cli-backend.js"; async function runOpenAIProviderAuthMethod( methodId: string, @@ -159,6 +158,5 @@ export default definePluginEntry({ register(api) { api.registerProvider(buildOpenAISetupProvider()); api.registerProvider(buildOpenAICodexSetupProvider()); - api.registerCliBackend(buildOpenAICodexCliBackend()); }, }); diff --git a/extensions/openai/test-api.ts b/extensions/openai/test-api.ts index 081b833ef58..0f20b19cb4c 100644 --- a/extensions/openai/test-api.ts +++ b/extensions/openai/test-api.ts @@ -1,4 +1,3 @@ -export { buildOpenAICodexCliBackend } from "./cli-backend.js"; export { buildOpenAIImageGenerationProvider } from "./image-generation-provider.js"; export { openaiCodexMediaUnderstandingProvider, diff --git a/package.json b/package.json index 063a201c8ce..1610d1be61e 100644 --- a/package.json +++ b/package.json @@ -1608,9 +1608,6 @@ "test:docker:live-cli-backend:claude-subscription": "OPENCLAW_LIVE_CLI_BACKEND_AUTH=subscription OPENCLAW_LIVE_CLI_BACKEND_MODEL=claude-cli/claude-sonnet-4-6 OPENCLAW_LIVE_CLI_BACKEND_DISABLE_MCP_CONFIG=1 OPENCLAW_LIVE_CLI_BACKEND_MODEL_SWITCH_PROBE=0 OPENCLAW_LIVE_CLI_BACKEND_RESUME_PROBE=1 OPENCLAW_LIVE_CLI_BACKEND_IMAGE_PROBE=0 OPENCLAW_LIVE_CLI_BACKEND_MCP_PROBE=0 bash scripts/test-live-cli-backend-docker.sh", "test:docker:live-cli-backend:claude:mcp": "OPENCLAW_LIVE_CLI_BACKEND_MODEL=claude-cli/claude-sonnet-4-6 OPENCLAW_LIVE_CLI_BACKEND_MCP_PROBE=1 bash scripts/test-live-cli-backend-docker.sh", "test:docker:live-cli-backend:claude:resume": "OPENCLAW_LIVE_CLI_BACKEND_MODEL=claude-cli/claude-sonnet-4-6 OPENCLAW_LIVE_CLI_BACKEND_RESUME_PROBE=1 bash scripts/test-live-cli-backend-docker.sh", - "test:docker:live-cli-backend:codex": "OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/gpt-5.4 bash scripts/test-live-cli-backend-docker.sh", - "test:docker:live-cli-backend:codex:mcp": "OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/gpt-5.4 OPENCLAW_LIVE_CLI_BACKEND_MCP_PROBE=1 bash scripts/test-live-cli-backend-docker.sh", - "test:docker:live-cli-backend:codex:resume": "OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/gpt-5.4 OPENCLAW_LIVE_CLI_BACKEND_RESUME_PROBE=1 bash scripts/test-live-cli-backend-docker.sh", "test:docker:live-cli-backend:gemini": "OPENCLAW_LIVE_CLI_BACKEND_MODEL=google-gemini-cli/gemini-3-flash-preview bash scripts/test-live-cli-backend-docker.sh", "test:docker:live-cli-backend:gemini:mcp": "OPENCLAW_LIVE_CLI_BACKEND_MODEL=google-gemini-cli/gemini-3-flash-preview OPENCLAW_LIVE_CLI_BACKEND_MCP_PROBE=1 bash scripts/test-live-cli-backend-docker.sh", "test:docker:live-cli-backend:gemini:resume": "OPENCLAW_LIVE_CLI_BACKEND_MODEL=google-gemini-cli/gemini-3-flash-preview OPENCLAW_LIVE_CLI_BACKEND_RESUME_PROBE=1 bash scripts/test-live-cli-backend-docker.sh", @@ -1621,11 +1618,9 @@ "test:docker:live-subagent-announce": "bash scripts/test-live-subagent-announce-docker.sh", "test:docker:live-gateway": "bash scripts/test-live-gateway-models-docker.sh", "test:docker:live-gateway:claude": "OPENCLAW_LIVE_GATEWAY_PROVIDERS=claude-cli OPENCLAW_LIVE_GATEWAY_MODELS=claude-cli/claude-sonnet-4-6 bash scripts/test-live-gateway-models-docker.sh", - "test:docker:live-gateway:codex": "OPENCLAW_LIVE_GATEWAY_PROVIDERS=codex-cli OPENCLAW_LIVE_GATEWAY_MODELS=codex-cli/gpt-5.5 bash scripts/test-live-gateway-models-docker.sh", "test:docker:live-gateway:gemini": "OPENCLAW_LIVE_GATEWAY_PROVIDERS=google-gemini-cli OPENCLAW_LIVE_GATEWAY_MODELS=google-gemini-cli/gemini-3.1-pro-preview bash scripts/test-live-gateway-models-docker.sh", "test:docker:live-models": "bash scripts/test-live-models-docker.sh", "test:docker:live-models:claude": "OPENCLAW_LIVE_PROVIDERS=claude-cli OPENCLAW_LIVE_MODELS=claude-cli/claude-sonnet-4-6 bash scripts/test-live-models-docker.sh", - "test:docker:live-models:codex": "OPENCLAW_LIVE_PROVIDERS=codex-cli OPENCLAW_LIVE_MODELS=codex-cli/gpt-5.5 bash scripts/test-live-models-docker.sh", "test:docker:live-models:gemini": "OPENCLAW_LIVE_PROVIDERS=google-gemini-cli OPENCLAW_LIVE_MODELS=google-gemini-cli/gemini-3.1-pro-preview bash scripts/test-live-models-docker.sh", "test:docker:live:all": "OPENCLAW_DOCKER_ALL_LIVE_MODE=only node scripts/test-docker-all.mjs", "test:docker:local:all": "OPENCLAW_DOCKER_ALL_LIVE_MODE=skip node scripts/test-docker-all.mjs", diff --git a/scripts/lib/docker-e2e-scenarios.mjs b/scripts/lib/docker-e2e-scenarios.mjs index b8be8ede54b..39be8fe4ab1 100644 --- a/scripts/lib/docker-e2e-scenarios.mjs +++ b/scripts/lib/docker-e2e-scenarios.mjs @@ -161,7 +161,7 @@ function liveOpenAiChatToolsLane() { export const mainLanes = [ liveLane("live-models", liveDockerScriptCommand("test-live-models-docker.sh"), { - providers: ["claude-cli", "codex-cli", "google-gemini-cli"], + providers: ["claude-cli", "google-gemini-cli"], timeoutMs: LIVE_PROFILE_TIMEOUT_MS, weight: 4, }), @@ -169,11 +169,11 @@ export const mainLanes = [ "live-gateway", liveDockerScriptCommand( "test-live-gateway-models-docker.sh", - "OPENCLAW_IMAGE=openclaw:local-live-gateway OPENCLAW_DOCKER_BUILD_EXTENSIONS=matrix OPENCLAW_LIVE_GATEWAY_PROVIDERS=claude-cli,codex-cli,google-gemini-cli", + "OPENCLAW_IMAGE=openclaw:local-live-gateway OPENCLAW_DOCKER_BUILD_EXTENSIONS=matrix OPENCLAW_LIVE_GATEWAY_PROVIDERS=claude-cli,google-gemini-cli", { skipBuild: false }, ), { - providers: ["claude-cli", "codex-cli", "google-gemini-cli"], + providers: ["claude-cli", "google-gemini-cli"], timeoutMs: LIVE_PROFILE_TIMEOUT_MS, weight: 4, }, @@ -431,7 +431,7 @@ export const tailLanes = [ ), liveLane("live-codex-harness", liveDockerScriptCommand("test-live-codex-harness-docker.sh"), { cacheKey: "codex-harness", - provider: "codex-cli", + provider: "openai", resources: ["npm"], timeoutMs: LIVE_ACP_TIMEOUT_MS, weight: 3, @@ -455,7 +455,7 @@ export const tailLanes = [ ), { cacheKey: "codex-harness", - provider: "codex-cli", + provider: "openai", resources: ["npm"], timeoutMs: LIVE_ACP_TIMEOUT_MS, weight: 3, @@ -475,20 +475,6 @@ export const tailLanes = [ }, ), livePluginToolLane(), - liveLane( - "live-cli-backend-codex", - liveDockerScriptCommand( - "test-live-cli-backend-docker.sh", - "OPENCLAW_LIVE_CLI_BACKEND_AUTH=api-key OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/gpt-5.4", - ), - { - cacheKey: "cli-backend-codex", - provider: "codex-cli", - resources: ["npm"], - timeoutMs: LIVE_CLI_TIMEOUT_MS, - weight: 3, - }, - ), liveLane( "live-acp-bind-claude", liveDockerScriptCommand("test-live-acp-bind-docker.sh", "OPENCLAW_LIVE_ACP_BIND_AGENT=claude"), @@ -505,7 +491,7 @@ export const tailLanes = [ liveDockerScriptCommand("test-live-acp-bind-docker.sh", "OPENCLAW_LIVE_ACP_BIND_AGENT=codex"), { cacheKey: "acp-bind-codex", - provider: "codex-cli", + provider: "openai", resources: ["npm"], timeoutMs: LIVE_ACP_TIMEOUT_MS, weight: 3, diff --git a/scripts/print-cli-backend-live-metadata.ts b/scripts/print-cli-backend-live-metadata.ts index 02a7dca3d73..54d17fad1df 100644 --- a/scripts/print-cli-backend-live-metadata.ts +++ b/scripts/print-cli-backend-live-metadata.ts @@ -7,16 +7,28 @@ if (!provider) { process.exit(1); } +if (provider === "codex-cli") { + process.stdout.write( + JSON.stringify( + { + provider, + unsupported: true, + reason: + "codex-cli is no longer a bundled CLI backend. Use openai/* with the Codex app-server runtime instead.", + }, + null, + 2, + ), + ); + process.exit(0); +} + async function loadFallbackBackend(id: string) { switch (id) { case "claude-cli": { const mod = await import("../extensions/anthropic/cli-backend.ts"); return mod.buildAnthropicCliBackend(); } - case "codex-cli": { - const mod = await import("../extensions/openai/cli-backend.ts"); - return mod.buildOpenAICodexCliBackend(); - } case "google-gemini-cli": { const mod = await import("../extensions/google/cli-backend.ts"); return mod.buildGoogleGeminiCliBackend(); diff --git a/scripts/test-live-cli-backend-docker.sh b/scripts/test-live-cli-backend-docker.sh index f5a55a9ee51..b1a70995e7e 100644 --- a/scripts/test-live-cli-backend-docker.sh +++ b/scripts/test-live-cli-backend-docker.sh @@ -33,15 +33,6 @@ DOCKER_TRUSTED_HARNESS_MOUNT=(-v "$TRUSTED_HARNESS_DIR":"$DOCKER_TRUSTED_HARNESS if [[ -z "$CLI_PROVIDER" || "$CLI_PROVIDER" == "$CLI_MODEL" ]]; then CLI_PROVIDER="$DEFAULT_PROVIDER" fi -CLI_USE_CI_SAFE_CODEX_CONFIG="${OPENCLAW_LIVE_CLI_BACKEND_USE_CI_SAFE_CODEX_CONFIG:-}" -if [[ -z "$CLI_USE_CI_SAFE_CODEX_CONFIG" ]]; then - if [[ "$CLI_PROVIDER" == "codex-cli" ]]; then - CLI_USE_CI_SAFE_CODEX_CONFIG="1" - else - CLI_USE_CI_SAFE_CODEX_CONFIG="0" - fi -fi - if [[ -f "$PROFILE_FILE" && -r "$PROFILE_FILE" ]]; then set -a # shellcheck disable=SC1090 @@ -63,11 +54,9 @@ if [[ "$CLI_AUTH_MODE" == "subscription" && "$CLI_PROVIDER" != "claude-cli" ]]; exit 1 fi -if [[ "$CLI_AUTH_MODE" == "api-key" && "$CLI_PROVIDER" == "codex-cli" ]]; then - if [[ -z "${OPENAI_API_KEY:-}" ]]; then - echo "ERROR: OPENCLAW_LIVE_CLI_BACKEND_AUTH=api-key for codex-cli requires OPENAI_API_KEY." >&2 - exit 1 - fi +if [[ "$CLI_PROVIDER" == "codex-cli" ]]; then + echo "ERROR: codex-cli is no longer a bundled CLI backend. Use openai/* with the Codex app-server runtime instead." >&2 + exit 1 fi CLI_METADATA_JSON="$(node --import tsx "$ROOT_DIR/scripts/print-cli-backend-live-metadata.ts" "$CLI_PROVIDER")" @@ -187,9 +176,7 @@ fi AUTH_DIRS=() AUTH_FILES=() -if [[ "$CLI_AUTH_MODE" == "api-key" && "$CLI_PROVIDER" == "codex-cli" ]]; then - AUTH_FILES+=(".codex/config.toml") -elif [[ -n "${OPENCLAW_DOCKER_AUTH_DIRS:-}" ]]; then +if [[ -n "${OPENCLAW_DOCKER_AUTH_DIRS:-}" ]]; then while IFS= read -r auth_dir; do [[ -n "$auth_dir" ]] || continue AUTH_DIRS+=("$auth_dir") @@ -285,10 +272,6 @@ provider="${OPENCLAW_DOCKER_CLI_BACKEND_PROVIDER:-claude-cli}" default_command="${OPENCLAW_DOCKER_CLI_BACKEND_COMMAND_DEFAULT:-}" docker_package="${OPENCLAW_DOCKER_CLI_BACKEND_NPM_PACKAGE:-}" binary_name="${OPENCLAW_DOCKER_CLI_BACKEND_BINARY_NAME:-}" -if [ "$provider" = "codex-cli" ] && [ "${OPENCLAW_LIVE_CLI_BACKEND_AUTH:-auto}" != "api-key" ]; then - unset OPENAI_API_KEY - unset OPENAI_BASE_URL -fi if [ -z "$binary_name" ] && [ -n "$default_command" ]; then binary_name="$(basename "$default_command")" fi @@ -310,13 +293,6 @@ if [ -n "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND:-}" ] && [ ! -x "${OPENCLAW_LIVE_CL elif [ -n "$docker_package" ] && package_has_explicit_version "$docker_package"; then run_setup_command npm install -g "$docker_package" fi -if [ "$provider" = "codex-cli" ] && [ "${OPENCLAW_LIVE_CLI_BACKEND_AUTH:-auto}" = "api-key" ]; then - codex_login_command="${OPENCLAW_LIVE_CLI_BACKEND_COMMAND:-$NPM_CONFIG_PREFIX/bin/codex}" - if [ ! -x "$codex_login_command" ] && [ -x "$NPM_CONFIG_PREFIX/bin/codex" ]; then - codex_login_command="$NPM_CONFIG_PREFIX/bin/codex" - fi - printf '%s\n' "$OPENAI_API_KEY" | "$codex_login_command" login --with-api-key >/dev/null -fi if [ -n "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND:-}" ] && [ -x "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND}" ]; then echo "==> CLI backend binary: ${OPENCLAW_LIVE_CLI_BACKEND_COMMAND}" "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND}" -V || "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND}" --version || true @@ -403,42 +379,6 @@ openclaw_live_link_runtime_tree "$tmp_dir" openclaw_live_stage_state_dir "$tmp_dir/.openclaw-state" openclaw_live_prepare_staged_config cd "$tmp_dir" -if [ "${OPENCLAW_LIVE_CLI_BACKEND_USE_CI_SAFE_CODEX_CONFIG:-0}" = "1" ]; then - node --import tsx "$trusted_scripts_dir/prepare-codex-ci-config.ts" "$HOME/.codex/config.toml" "$tmp_dir" -fi -if [ "$provider" = "codex-cli" ] && [ "${OPENCLAW_LIVE_CLI_BACKEND_AUTH:-auto}" = "api-key" ]; then - codex_probe_model="${OPENCLAW_LIVE_CLI_BACKEND_MODEL#*/}" - codex_probe_token="OPENCLAW-CODEX-DIRECT-PROBE" - codex_probe_stdout="$tmp_dir/codex-direct-probe.stdout" - codex_probe_stderr="$tmp_dir/codex-direct-probe.stderr" - if ! timeout --foreground --kill-after=10s 180s \ - "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND:-codex}" \ - exec \ - --json \ - --color \ - never \ - --sandbox \ - danger-full-access \ - -c \ - 'service_tier="fast"' \ - --skip-git-repo-check \ - --model \ - "$codex_probe_model" \ - "Reply exactly: $codex_probe_token" \ - >"$codex_probe_stdout" 2>"$codex_probe_stderr" &2 - sed -n '1,120p' "$codex_probe_stdout" >&2 || true - sed -n '1,120p' "$codex_probe_stderr" >&2 || true - exit 1 - fi - if ! grep -q "$codex_probe_token" "$codex_probe_stdout"; then - echo "ERROR: direct Codex CLI probe did not return expected token." >&2 - sed -n '1,120p' "$codex_probe_stdout" >&2 || true - sed -n '1,120p' "$codex_probe_stderr" >&2 || true - exit 1 - fi - echo "==> Direct Codex CLI probe ok" -fi node scripts/test-live.mjs -- src/gateway/gateway-cli-backend.live.test.ts EOF @@ -450,9 +390,6 @@ echo "==> Provider: $CLI_PROVIDER" echo "==> Auth mode: $CLI_AUTH_MODE" echo "==> Setup timeout: ${CLI_SETUP_TIMEOUT_SECONDS}s" echo "==> Profile file: $PROFILE_STATUS" -if [[ "$CLI_PROVIDER" == "codex-cli" ]]; then - echo "==> CI-safe Codex config: $CLI_USE_CI_SAFE_CODEX_CONFIG" -fi if [[ "$CLI_PROVIDER" == "claude-cli" && "$CLI_AUTH_MODE" == "subscription" ]]; then echo "==> Claude subscription: $CLAUDE_SUBSCRIPTION_TYPE" echo "==> Claude subscription source: $CLAUDE_SUBSCRIPTION_AUTH_SOURCE" @@ -462,18 +399,7 @@ echo "==> External auth files: ${AUTH_FILES_CSV:-none}" DOCKER_AUTH_ENV=( -e OPENCLAW_LIVE_CLI_BACKEND_AUTH="$CLI_AUTH_MODE" ) -if [[ "$CLI_PROVIDER" == "codex-cli" && "$CLI_AUTH_MODE" == "api-key" ]]; then - docker_env_dir="$(mktemp -d "${RUNNER_TEMP:-/tmp}/openclaw-cli-backend-env.XXXXXX")" - TEMP_DIRS+=("$docker_env_dir") - docker_env_file="$docker_env_dir/openai.env" - { - printf 'OPENAI_API_KEY=%s\n' "${OPENAI_API_KEY}" - if [[ -n "${OPENAI_BASE_URL:-}" ]]; then - printf 'OPENAI_BASE_URL=%s\n' "${OPENAI_BASE_URL}" - fi - } >"$docker_env_file" - DOCKER_EXTRA_ENV_FILES+=(--env-file "$docker_env_file") -elif [[ "$CLI_PROVIDER" == "claude-cli" && "$CLI_AUTH_MODE" == "subscription" ]]; then +if [[ "$CLI_PROVIDER" == "claude-cli" && "$CLI_AUTH_MODE" == "subscription" ]]; then DOCKER_AUTH_ENV+=( -e CLAUDE_CODE_OAUTH_TOKEN="${CLAUDE_CODE_OAUTH_TOKEN:-}" -e OPENCLAW_LIVE_CLI_BACKEND_PRESERVE_ENV="$OPENCLAW_LIVE_CLI_BACKEND_PRESERVE_ENV" @@ -501,7 +427,6 @@ DOCKER_RUN_ARGS=(docker run --rm -t \ -e OPENCLAW_DOCKER_AUTH_FILES_RESOLVED="$AUTH_FILES_CSV" \ -e OPENCLAW_LIVE_DOCKER_SCRIPTS_DIR="${DOCKER_TRUSTED_HARNESS_CONTAINER_DIR}/scripts" \ -e OPENCLAW_LIVE_DOCKER_SOURCE_STAGE_MODE="${OPENCLAW_LIVE_DOCKER_SOURCE_STAGE_MODE:-copy}" \ - -e OPENCLAW_LIVE_CLI_BACKEND_USE_CI_SAFE_CODEX_CONFIG="$CLI_USE_CI_SAFE_CODEX_CONFIG" \ -e OPENCLAW_LIVE_CLI_BACKEND_SETUP_TIMEOUT_SECONDS="$CLI_SETUP_TIMEOUT_SECONDS" \ -e OPENCLAW_DOCKER_CLI_BACKEND_PROVIDER="$CLI_PROVIDER" \ -e OPENCLAW_DOCKER_CLI_BACKEND_COMMAND_DEFAULT="$CLI_DEFAULT_COMMAND" \ diff --git a/src/agents/model-runtime-aliases.ts b/src/agents/model-runtime-aliases.ts index 5a1e3f3b04e..3505031e5d3 100644 --- a/src/agents/model-runtime-aliases.ts +++ b/src/agents/model-runtime-aliases.ts @@ -8,24 +8,53 @@ type LegacyRuntimeModelProviderAlias = { legacyProvider: string; /** Canonical provider id that should own model selection. */ provider: string; - /** Runtime/backend id that preserves the old execution behavior. */ + /** Runtime/backend id selected for the migrated ref. */ runtime: string; /** True when the runtime is a CLI backend rather than an embedded harness. */ cli: boolean; + /** True when doctor must write a runtime policy even if the target runtime is the default. */ + requiresRuntimePolicy: boolean; }; const LEGACY_RUNTIME_MODEL_PROVIDER_ALIASES = [ - { legacyProvider: "codex", provider: "openai", runtime: "codex", cli: false }, - { legacyProvider: "codex-cli", provider: "openai", runtime: "codex-cli", cli: true }, - { legacyProvider: "claude-cli", provider: "anthropic", runtime: "claude-cli", cli: true }, + { + legacyProvider: "codex", + provider: "openai", + runtime: "codex", + cli: false, + requiresRuntimePolicy: false, + }, + { + legacyProvider: "codex-cli", + provider: "openai", + runtime: "codex", + cli: false, + requiresRuntimePolicy: true, + }, + { + legacyProvider: "claude-cli", + provider: "anthropic", + runtime: "claude-cli", + cli: true, + requiresRuntimePolicy: true, + }, { legacyProvider: "google-gemini-cli", provider: "google", runtime: "google-gemini-cli", cli: true, + requiresRuntimePolicy: true, }, ] as const satisfies readonly LegacyRuntimeModelProviderAlias[]; +export function legacyRuntimeModelAliasRequiresRuntimePolicy(provider: string): boolean { + return ( + LEGACY_RUNTIME_MODEL_PROVIDER_ALIASES.find( + (entry) => normalizeProviderId(entry.legacyProvider) === normalizeProviderId(provider), + )?.requiresRuntimePolicy === true + ); +} + const LEGACY_ALIAS_BY_PROVIDER = new Map( LEGACY_RUNTIME_MODEL_PROVIDER_ALIASES.map((entry) => [ normalizeProviderId(entry.legacyProvider), diff --git a/src/auto-reply/reply/commands-models.test.ts b/src/auto-reply/reply/commands-models.test.ts index cdb7f9d0440..671f3f027e8 100644 --- a/src/auto-reply/reply/commands-models.test.ts +++ b/src/auto-reply/reply/commands-models.test.ts @@ -241,10 +241,9 @@ describe("handleModelsCommand", () => { expect(result?.reply?.text).not.toContain("- anthropic"); }); - it("hides bare backwards-compat aliases but surfaces CLI runtime providers in /models lists", async () => { + it("hides bare backwards-compat aliases but surfaces supported CLI runtime providers in /models lists", async () => { modelCatalogMocks.loadModelCatalog.mockResolvedValueOnce([ { provider: "codex", id: "gpt-5.5", name: "GPT-5.5" }, - { provider: "codex-cli", id: "gpt-5.5", name: "GPT-5.5" }, { provider: "claude-cli", id: "claude-opus-4-7", name: "Claude Opus" }, { provider: "google-gemini-cli", id: "gemini-3.1-pro-preview", name: "Gemini Pro" }, { provider: "anthropic", id: "claude-opus-4-7", name: "Claude Opus" }, @@ -256,7 +255,6 @@ describe("handleModelsCommand", () => { "google", "openai", "claude-cli", - "codex-cli", "google-gemini-cli", ]); @@ -271,9 +269,9 @@ describe("handleModelsCommand", () => { expect(result?.reply?.text).toContain("- google (1)"); expect(result?.reply?.text).toContain("- openai (1)"); expect(result?.reply?.text).toContain("- claude-cli (1)"); - expect(result?.reply?.text).toContain("- codex-cli (1)"); expect(result?.reply?.text).toContain("- google-gemini-cli (1)"); expect(result?.reply?.text).not.toMatch(/^- codex \(/m); + expect(result?.reply?.text).not.toMatch(/^- codex-cli \(/m); }); it("sources CLI runtime provider model lists from the catalog, not user agents.defaults.models", async () => { diff --git a/src/cli/plugins-cli-test-helpers.ts b/src/cli/plugins-cli-test-helpers.ts index f7226cf3237..35e673c406f 100644 --- a/src/cli/plugins-cli-test-helpers.ts +++ b/src/cli/plugins-cli-test-helpers.ts @@ -204,6 +204,7 @@ vi.mock("../config/config.js", () => ({ })); vi.mock("../config/paths.js", () => ({ + resolveIsNixMode: () => false, resolveStateDir: () => resolveStateDir(), })); diff --git a/src/commands/doctor-legacy-config.migrations.test.ts b/src/commands/doctor-legacy-config.migrations.test.ts index bea30d5fba1..9942b8697ee 100644 --- a/src/commands/doctor-legacy-config.migrations.test.ts +++ b/src/commands/doctor-legacy-config.migrations.test.ts @@ -629,7 +629,7 @@ describe("normalizeCompatibilityConfigValues", () => { }); }); - it("migrates legacy Codex CLI primary refs to OpenAI refs plus model runtime", () => { + it("migrates legacy Codex CLI primary refs to the Codex app-server route", () => { const res = normalizeCompatibilityConfigValues({ agents: { defaults: { @@ -652,16 +652,159 @@ describe("normalizeCompatibilityConfigValues", () => { expect(res.config.agents?.defaults?.agentRuntime).toBeUndefined(); expect(res.config.agents?.defaults?.models).toEqual({ "codex-cli/gpt-5.5": { alias: "Codex CLI" }, - "openai/gpt-5.5": { - alias: "OpenAI GPT", - agentRuntime: { id: "codex-cli" }, + "openai/gpt-5.5": { alias: "OpenAI GPT", agentRuntime: { id: "codex" } }, + "openai/gpt-5.4-mini": { agentRuntime: { id: "codex" } }, + }); + }); + + it("migrates legacy Codex CLI fallback refs when the primary is already canonical", () => { + const res = normalizeCompatibilityConfigValues({ + agents: { + defaults: { + model: { + primary: "openai/gpt-5.5", + fallbacks: ["codex-cli/gpt-5.4"], + }, + models: { + "codex-cli/gpt-5.4": { alias: "Legacy CLI fallback" }, + }, + }, }, - "openai/gpt-5.4-mini": { - agentRuntime: { id: "codex-cli" }, + } as unknown as OpenClawConfig); + + expect(res.config.agents?.defaults?.model).toEqual({ + primary: "openai/gpt-5.5", + fallbacks: ["openai/gpt-5.4"], + }); + expect(res.config.agents?.defaults?.models).toEqual({ + "codex-cli/gpt-5.4": { alias: "Legacy CLI fallback" }, + "openai/gpt-5.4": { + alias: "Legacy CLI fallback", + agentRuntime: { id: "codex" }, }, }); }); + it("migrates standalone legacy Codex CLI allowlist keys", () => { + const res = normalizeCompatibilityConfigValues({ + agents: { + defaults: { + models: { + "codex-cli/gpt-5.4": { alias: "Legacy CLI fallback" }, + }, + }, + }, + } as unknown as OpenClawConfig); + + expect(res.config.agents?.defaults?.models).toEqual({ + "codex-cli/gpt-5.4": { alias: "Legacy CLI fallback" }, + "openai/gpt-5.4": { + alias: "Legacy CLI fallback", + agentRuntime: { id: "codex" }, + }, + }); + }); + + it("pins migrated Codex CLI refs to Codex when OpenAI uses a custom base URL", () => { + const res = normalizeCompatibilityConfigValues({ + agents: { + defaults: { + model: "codex-cli/gpt-5.5", + }, + }, + models: { + providers: { + openai: { + baseUrl: "https://proxy.example/v1", + }, + }, + }, + } as unknown as OpenClawConfig); + + expect(res.config.agents?.defaults?.model).toBe("openai/gpt-5.5"); + expect(res.config.agents?.defaults?.models?.["openai/gpt-5.5"]?.agentRuntime).toEqual({ + id: "codex", + }); + }); + + it("migrates existing Codex CLI runtime pins to the Codex app-server runtime", () => { + const res = normalizeCompatibilityConfigValues({ + agents: { + defaults: { + models: { + "openai/gpt-5.5": { + agentRuntime: { id: "codex-cli", mode: "strict" }, + }, + }, + }, + list: [ + { + id: "reviewer", + models: { + "openai/gpt-5.4-mini": { + agentRuntime: { id: "codex-cli" }, + }, + }, + }, + ], + }, + models: { + providers: { + openai: { + agentRuntime: { id: "codex-cli" }, + models: [ + { + id: "gpt-5.5", + agentRuntime: { id: "codex-cli" }, + }, + ], + }, + }, + }, + } as unknown as OpenClawConfig); + + expect(res.config.agents?.defaults?.models?.["openai/gpt-5.5"]?.agentRuntime).toEqual({ + id: "codex", + mode: "strict", + }); + expect(res.config.agents?.list?.[0]?.models?.["openai/gpt-5.4-mini"]?.agentRuntime).toEqual({ + id: "codex", + }); + expect(res.config.models?.providers?.openai?.agentRuntime).toEqual({ id: "codex" }); + expect(res.config.models?.providers?.openai?.models?.[0]?.agentRuntime).toEqual({ + id: "codex", + }); + expect(res.changes).toContain( + "Moved agents.defaults.models.openai/gpt-5.5 agentRuntime.id from codex-cli to codex.", + ); + expect(res.changes).toContain( + "Moved agents.list.reviewer.models.openai/gpt-5.4-mini agentRuntime.id from codex-cli to codex.", + ); + expect(res.changes).toContain( + "Moved models.providers.openai agentRuntime.id from codex-cli to codex.", + ); + expect(res.changes).toContain( + "Moved models.providers.openai.models.gpt-5.5 agentRuntime.id from codex-cli to codex.", + ); + }); + + it("migrates provider-scoped Codex CLI runtime pins without agents config", () => { + const res = normalizeCompatibilityConfigValues({ + models: { + providers: { + openai: { + agentRuntime: { id: "codex-cli" }, + }, + }, + }, + } as unknown as OpenClawConfig); + + expect(res.config.models?.providers?.openai?.agentRuntime).toEqual({ id: "codex" }); + expect(res.changes).toContain( + "Moved models.providers.openai agentRuntime.id from codex-cli to codex.", + ); + }); + it("migrates legacy Gemini CLI primary refs to Google refs plus model runtime", () => { const res = normalizeCompatibilityConfigValues({ agents: { diff --git a/src/commands/doctor/shared/legacy-config-core-normalizers.ts b/src/commands/doctor/shared/legacy-config-core-normalizers.ts index 21a70b690f8..7144eb5c107 100644 --- a/src/commands/doctor/shared/legacy-config-core-normalizers.ts +++ b/src/commands/doctor/shared/legacy-config-core-normalizers.ts @@ -1,4 +1,7 @@ -import { migrateLegacyRuntimeModelRef } from "../../../agents/model-runtime-aliases.js"; +import { + legacyRuntimeModelAliasRequiresRuntimePolicy, + migrateLegacyRuntimeModelRef, +} from "../../../agents/model-runtime-aliases.js"; import { normalizeProviderId } from "../../../agents/provider-id.js"; import { resolveSingleAccountKeysToMove } from "../../../channels/plugins/setup-promotion-helpers.js"; import { resolveNormalizedProviderModelMaxTokens } from "../../../config/defaults.js"; @@ -229,6 +232,18 @@ type ModelProviderEntry = Partial< >; type ModelsConfigPatch = Partial>; type ModelDefinitionEntry = NonNullable[number]; +type SelectedRuntimeRef = { + ref: string; + runtime: string; + requiresRuntimePolicy: boolean; +}; + +const LEGACY_CODEX_CLI_RUNTIME_ID = "codex-cli"; +const CODEX_APP_SERVER_RUNTIME_ID = "codex"; + +function migratedRuntimeRequiresPolicy(legacyProvider: string): boolean { + return legacyRuntimeModelAliasRequiresRuntimePolicy(legacyProvider); +} function mergeModelEntry(legacyEntry: unknown, currentEntry: unknown): unknown { if (!isRecord(legacyEntry) || !isRecord(currentEntry)) { @@ -237,11 +252,28 @@ function mergeModelEntry(legacyEntry: unknown, currentEntry: unknown): unknown { return { ...legacyEntry, ...currentEntry }; } +function normalizeLegacyCodexCliAgentRuntimePolicy(raw: unknown): { + value?: unknown; + changed: boolean; +} { + if (!isRecord(raw)) { + return { value: raw, changed: false }; + } + if (normalizeOptionalLowercaseString(raw.id) !== LEGACY_CODEX_CLI_RUNTIME_ID) { + return { value: raw, changed: false }; + } + return { + value: { ...raw, id: CODEX_APP_SERVER_RUNTIME_ID }, + changed: true, + }; +} + function normalizeLegacyRuntimeAgentModelConfig(raw: unknown): { value?: unknown; changed: boolean; selectedRuntime?: string; - selectedRefs: string[]; + selectedRuntimeRequiresPolicy: boolean; + selectedRefs: SelectedRuntimeRef[]; } { if (typeof raw === "string") { const migrated = migrateLegacyRuntimeModelRef(raw); @@ -250,39 +282,72 @@ function normalizeLegacyRuntimeAgentModelConfig(raw: unknown): { value: migrated.ref, changed: true, selectedRuntime: migrated.runtime, - selectedRefs: [migrated.ref], + selectedRuntimeRequiresPolicy: migratedRuntimeRequiresPolicy(migrated.legacyProvider), + selectedRefs: [ + { + ref: migrated.ref, + runtime: migrated.runtime, + requiresRuntimePolicy: migratedRuntimeRequiresPolicy(migrated.legacyProvider), + }, + ], } - : { value: raw, changed: false, selectedRefs: [] }; + : { value: raw, changed: false, selectedRuntimeRequiresPolicy: false, selectedRefs: [] }; } if (!isRecord(raw)) { - return { value: raw, changed: false, selectedRefs: [] }; + return { value: raw, changed: false, selectedRuntimeRequiresPolicy: false, selectedRefs: [] }; } const migratedPrimary = typeof raw.primary === "string" ? migrateLegacyRuntimeModelRef(raw.primary) : null; - if (!migratedPrimary) { - return { value: raw, changed: false, selectedRefs: [] }; + let changed = false; + const next: Record = { ...raw }; + const selectedRefs: SelectedRuntimeRef[] = []; + let selectedRuntime = migratedPrimary?.runtime; + let selectedRuntimeRequiresPolicy = + migratedPrimary !== null && migratedRuntimeRequiresPolicy(migratedPrimary.legacyProvider); + if (migratedPrimary) { + next.primary = migratedPrimary.ref; + selectedRefs.push({ + ref: migratedPrimary.ref, + runtime: migratedPrimary.runtime, + requiresRuntimePolicy: migratedRuntimeRequiresPolicy(migratedPrimary.legacyProvider), + }); + changed = true; } - - const next: Record = { ...raw, primary: migratedPrimary.ref }; - const selectedRefs = [migratedPrimary.ref]; if (Array.isArray(raw.fallbacks)) { next.fallbacks = raw.fallbacks.map((fallback) => { if (typeof fallback !== "string") { return fallback; } const migratedFallback = migrateLegacyRuntimeModelRef(fallback); - if (migratedFallback?.runtime === migratedPrimary.runtime) { - selectedRefs.push(migratedFallback.ref); + if ( + migratedFallback && + (migratedFallback.runtime === selectedRuntime || + migratedFallback.legacyProvider === LEGACY_CODEX_CLI_RUNTIME_ID) + ) { + selectedRuntime ??= migratedFallback.runtime; + selectedRuntimeRequiresPolicy ||= migratedRuntimeRequiresPolicy( + migratedFallback.legacyProvider, + ); + selectedRefs.push({ + ref: migratedFallback.ref, + runtime: migratedFallback.runtime, + requiresRuntimePolicy: migratedRuntimeRequiresPolicy(migratedFallback.legacyProvider), + }); + changed = true; return migratedFallback.ref; } return fallback; }); } + if (!changed) { + return { value: raw, changed: false, selectedRuntimeRequiresPolicy: false, selectedRefs: [] }; + } return { value: next, changed: true, - selectedRuntime: migratedPrimary.runtime, + selectedRuntime, + selectedRuntimeRequiresPolicy, selectedRefs, }; } @@ -309,56 +374,77 @@ function mergeModelEntryWithRuntimePolicy( legacyEntry: unknown, currentEntry: unknown, runtime: string | undefined, + requiresRuntimePolicy = runtimeNeedsExplicitModelPolicy(runtime), ): unknown { const merged = mergeModelEntry(legacyEntry, currentEntry); - return runtimeNeedsExplicitModelPolicy(runtime) - ? modelEntryWithRuntimePolicy(merged, runtime) - : merged; + return runtime && requiresRuntimePolicy ? modelEntryWithRuntimePolicy(merged, runtime) : merged; } function normalizeLegacyRuntimeAllowlistModels( rawModels: unknown, selectedRuntime: string | undefined, + selectedRuntimeRequiresPolicy: boolean, ): { value?: unknown; changed: boolean; } { - if (!selectedRuntime || !isRecord(rawModels)) { + if (!isRecord(rawModels)) { return { value: rawModels, changed: false }; } let changed = false; const next: Record = {}; - const legacyEntries: Array<[string, unknown]> = []; + const legacyEntries: Array<{ + migratedKey: string; + entry: unknown; + runtime: string; + requiresRuntimePolicy: boolean; + }> = []; for (const [rawKey, entry] of Object.entries(rawModels)) { const migrated = migrateLegacyRuntimeModelRef(rawKey); - if (migrated?.runtime === selectedRuntime) { + if ( + migrated && + (migrated.runtime === selectedRuntime || + migrated.legacyProvider === LEGACY_CODEX_CLI_RUNTIME_ID) + ) { changed = true; next[rawKey] = mergeModelEntry(entry, next[rawKey]); - legacyEntries.push([migrated.ref, entry]); + legacyEntries.push({ + migratedKey: migrated.ref, + entry, + runtime: migrated.runtime, + requiresRuntimePolicy: migratedRuntimeRequiresPolicy(migrated.legacyProvider), + }); continue; } next[rawKey] = mergeModelEntry(entry, next[rawKey]); } - for (const [migratedKey, entry] of legacyEntries) { - next[migratedKey] = mergeModelEntryWithRuntimePolicy(entry, next[migratedKey], selectedRuntime); + for (const { migratedKey, entry, runtime, requiresRuntimePolicy } of legacyEntries) { + next[migratedKey] = mergeModelEntryWithRuntimePolicy( + entry, + next[migratedKey], + runtime, + requiresRuntimePolicy || (runtime === selectedRuntime && selectedRuntimeRequiresPolicy), + ); } return { value: next, changed }; } function ensureSelectedModelRuntimePolicies( rawModels: unknown, - selectedRefs: readonly string[], - selectedRuntime: string | undefined, + selectedRefs: readonly SelectedRuntimeRef[], ): { value?: unknown; changed: boolean } { - if (!runtimeNeedsExplicitModelPolicy(selectedRuntime) || selectedRefs.length === 0) { + if (selectedRefs.length === 0) { return { value: rawModels, changed: false }; } const next: Record = isRecord(rawModels) ? { ...rawModels } : {}; let changed = false; - for (const ref of selectedRefs) { + for (const { ref, runtime, requiresRuntimePolicy } of selectedRefs) { + if (!requiresRuntimePolicy) { + continue; + } const current = next[ref]; - const updated = modelEntryWithRuntimePolicy(current, selectedRuntime); + const updated = modelEntryWithRuntimePolicy(current, runtime); if (JSON.stringify(updated) !== JSON.stringify(current ?? {})) { next[ref] = updated; changed = true; @@ -367,6 +453,33 @@ function ensureSelectedModelRuntimePolicies( return { value: next, changed }; } +function normalizeLegacyCodexCliRuntimePinsInModels( + rawModels: unknown, + path: string, + changes: string[], +): { value?: unknown; changed: boolean } { + if (!isRecord(rawModels)) { + return { value: rawModels, changed: false }; + } + let changed = false; + const next: Record = { ...rawModels }; + for (const [modelRef, rawEntry] of Object.entries(rawModels)) { + if (!isRecord(rawEntry)) { + continue; + } + const runtime = normalizeLegacyCodexCliAgentRuntimePolicy(rawEntry.agentRuntime); + if (!runtime.changed) { + continue; + } + next[modelRef] = { ...rawEntry, agentRuntime: runtime.value }; + changed = true; + changes.push( + `Moved ${path}.${sanitizeForLog(modelRef)} agentRuntime.id from codex-cli to codex.`, + ); + } + return { value: next, changed }; +} + function normalizeLegacyRuntimeAgentContainer( raw: Record, path: string, @@ -387,7 +500,11 @@ function normalizeLegacyRuntimeAgentContainer( ); } - const models = normalizeLegacyRuntimeAllowlistModels(raw.models, model.selectedRuntime); + const models = normalizeLegacyRuntimeAllowlistModels( + raw.models, + model.selectedRuntime, + model.selectedRuntimeRequiresPolicy, + ); if (models.changed) { next.models = models.value; changed = true; @@ -395,11 +512,7 @@ function normalizeLegacyRuntimeAgentContainer( } if (model.selectedRuntime) { - const modelRuntimes = ensureSelectedModelRuntimePolicies( - next.models, - model.selectedRefs, - model.selectedRuntime, - ); + const modelRuntimes = ensureSelectedModelRuntimePolicies(next.models, model.selectedRefs); if (modelRuntimes.changed) { next.models = modelRuntimes.value; changed = true; @@ -407,16 +520,95 @@ function normalizeLegacyRuntimeAgentContainer( } } + const codexCliRuntimePins = normalizeLegacyCodexCliRuntimePinsInModels( + next.models, + `${path}.models`, + changes, + ); + if (codexCliRuntimePins.changed) { + next.models = codexCliRuntimePins.value; + changed = true; + } + return { value: next, changed }; } +function normalizeLegacyCodexCliProviderRuntimePins( + cfg: OpenClawConfig, + changes: string[], +): { config: OpenClawConfig; changed: boolean } { + const rawModels = cfg.models; + if (!isRecord(rawModels) || !isRecord(rawModels.providers)) { + return { config: cfg, changed: false }; + } + + let changed = false; + const nextProviders: Record = { ...rawModels.providers }; + for (const [providerId, rawProvider] of Object.entries(rawModels.providers)) { + if (!isRecord(rawProvider)) { + continue; + } + let providerChanged = false; + const nextProvider: Record = { ...rawProvider }; + const providerRuntime = normalizeLegacyCodexCliAgentRuntimePolicy(rawProvider.agentRuntime); + if (providerRuntime.changed) { + nextProvider.agentRuntime = providerRuntime.value; + providerChanged = true; + changes.push( + `Moved models.providers.${sanitizeForLog(providerId)} agentRuntime.id from codex-cli to codex.`, + ); + } + + if (Array.isArray(rawProvider.models)) { + const nextProviderModels = rawProvider.models.map((entry, index) => { + if (!isRecord(entry)) { + return entry; + } + const runtime = normalizeLegacyCodexCliAgentRuntimePolicy(entry.agentRuntime); + if (!runtime.changed) { + return entry; + } + providerChanged = true; + const modelId = normalizeOptionalString(entry.id) ?? `[${index}]`; + changes.push( + `Moved models.providers.${sanitizeForLog(providerId)}.models.${sanitizeForLog(modelId)} agentRuntime.id from codex-cli to codex.`, + ); + return Object.assign({}, entry, { agentRuntime: runtime.value }); + }); + if (providerChanged) { + nextProvider.models = nextProviderModels; + } + } + + if (providerChanged) { + nextProviders[providerId] = nextProvider; + changed = true; + } + } + + return changed + ? { + config: { + ...cfg, + models: { + ...rawModels, + providers: nextProviders as NonNullable["providers"], + }, + }, + changed: true, + } + : { config: cfg, changed: false }; +} + export function normalizeLegacyRuntimeModelRefs( cfg: OpenClawConfig, changes: string[], ): OpenClawConfig { - const rawAgents = cfg.agents; + const providerPinned = normalizeLegacyCodexCliProviderRuntimePins(cfg, changes); + const cfgWithProviders = providerPinned.config; + const rawAgents = cfgWithProviders.agents; if (!isRecord(rawAgents)) { - return cfg; + return cfgWithProviders; } let changed = false; @@ -452,12 +644,13 @@ export function normalizeLegacyRuntimeModelRefs( } } - return changed + const nextCfg = changed ? { - ...cfg, + ...cfgWithProviders, agents: nextAgents as OpenClawConfig["agents"], } - : cfg; + : cfgWithProviders; + return nextCfg; } export function normalizeLegacyOpenAICodexModelsAddMetadata( diff --git a/src/crestodian/assistant-backends.ts b/src/crestodian/assistant-backends.ts index c7da48e8a5c..f559aad9940 100644 --- a/src/crestodian/assistant-backends.ts +++ b/src/crestodian/assistant-backends.ts @@ -5,7 +5,7 @@ const CRESTODIAN_CLAUDE_CLI_MODEL = "claude-opus-4-7"; const CRESTODIAN_CODEX_MODEL = "gpt-5.5"; type CrestodianLocalPlannerBackend = { - kind: "claude-cli" | "codex-app-server" | "codex-cli"; + kind: "claude-cli" | "codex-app-server"; label: string; runner: "cli" | "embedded"; provider: string; @@ -32,16 +32,6 @@ const CODEX_APP_SERVER_BACKEND: CrestodianLocalPlannerBackend = { buildConfig: buildCodexAppServerPlannerConfig, }; -const CODEX_CLI_BACKEND: CrestodianLocalPlannerBackend = { - kind: "codex-cli", - label: `codex-cli/${CRESTODIAN_CODEX_MODEL}`, - runner: "cli", - provider: "codex-cli", - model: CRESTODIAN_CODEX_MODEL, - buildConfig: (workspaceDir) => - buildCliPlannerConfig(workspaceDir, `codex-cli/${CRESTODIAN_CODEX_MODEL}`), -}; - export function selectCrestodianLocalPlannerBackends( overview: CrestodianOverview, ): CrestodianLocalPlannerBackend[] { @@ -50,7 +40,7 @@ export function selectCrestodianLocalPlannerBackends( backends.push(CLAUDE_CLI_BACKEND); } if (overview.tools.codex.found) { - backends.push(CODEX_APP_SERVER_BACKEND, CODEX_CLI_BACKEND); + backends.push(CODEX_APP_SERVER_BACKEND); } return backends; } diff --git a/src/crestodian/assistant-prompts.ts b/src/crestodian/assistant-prompts.ts index 86fbdc211ff..e14799c0831 100644 --- a/src/crestodian/assistant-prompts.ts +++ b/src/crestodian/assistant-prompts.ts @@ -67,7 +67,7 @@ export function buildCrestodianAssistantUserPrompt(params: { `Default model: ${params.overview.defaultModel ?? "not configured"}`, `Config valid: ${params.overview.config.valid}`, `Gateway reachable: ${params.overview.gateway.reachable}`, - `Codex CLI: ${params.overview.tools.codex.found ? "found" : "not found"}`, + `Codex binary: ${params.overview.tools.codex.found ? "found" : "not found"}`, `Claude Code CLI: ${params.overview.tools.claude.found ? "found" : "not found"}`, `OpenAI API key: ${params.overview.tools.apiKeys.openai ? "found" : "not found"}`, `Anthropic API key: ${params.overview.tools.apiKeys.anthropic ? "found" : "not found"}`, diff --git a/src/crestodian/assistant.test.ts b/src/crestodian/assistant.test.ts index 64256cd053b..7faef881d5c 100644 --- a/src/crestodian/assistant.test.ts +++ b/src/crestodian/assistant.test.ts @@ -165,9 +165,9 @@ describe("Crestodian assistant", () => { codex: { command: "codex", found: true }, }), ).map((backend) => backend.kind), - ).toEqual(["claude-cli", "codex-app-server", "codex-cli"]); + ).toEqual(["claude-cli", "codex-app-server"]); - const [codexAppServer, codexCli] = selectCrestodianLocalPlannerBackends( + const [codexAppServer] = selectCrestodianLocalPlannerBackends( overview({ codex: { command: "codex", found: true }, }), @@ -182,13 +182,6 @@ describe("Crestodian assistant", () => { expect(codexAppServerDefaults.workspace).toBe("/tmp/workspace"); expect(codexAppServerModel.primary).toBe("openai/gpt-5.5"); expect(codexAppServerCodexEntry.enabled).toBe(true); - - const codexCliConfig = requireRecord(codexCli?.buildConfig("/tmp/workspace")); - const codexCliAgents = requireRecord(codexCliConfig.agents); - const codexCliDefaults = requireRecord(codexCliAgents.defaults); - const codexCliModel = requireRecord(codexCliDefaults.model); - expect(codexCliDefaults.workspace).toBe("/tmp/workspace"); - expect(codexCliModel.primary).toBe("codex-cli/gpt-5.5"); }); it("falls back to Codex app-server when Claude CLI planning fails", async () => { @@ -242,14 +235,8 @@ describe("Crestodian assistant", () => { expect(embeddedCodexEntry.enabled).toBe(true); }); - it("uses Codex CLI if the app-server planner is not usable", async () => { - const runCliAgent = vi.fn(async (params: RunCliAgentParams): Promise => { - if (params.provider === "codex-cli") { - return { - payloads: [{ text: '{"reply":"CLI fallback.","command":"models"}' }], - meta: { durationMs: 0 }, - }; - } + it("does not fall back to Codex CLI if the app-server planner is not usable", async () => { + const runCliAgent = vi.fn(async (): Promise => { throw new Error("unexpected cli provider"); }); const runEmbeddedPiAgent = vi.fn(async () => { @@ -268,18 +255,9 @@ describe("Crestodian assistant", () => { removeTempDir: async () => {}, }, }); - if (result === null) { - throw new Error("Expected planner result"); - } - expect(result.command).toBe("models"); - expect(result.reply).toBe("CLI fallback."); - expect(result.modelLabel).toBe("codex-cli/gpt-5.5"); + expect(result).toBeNull(); expect(runEmbeddedPiAgent).toHaveBeenCalledTimes(1); - expect(runCliAgent).toHaveBeenCalledTimes(1); - const firstCliCall = firstMockArg(runCliAgent); - expect(firstCliCall.provider).toBe("codex-cli"); - expect(firstCliCall.model).toBe("gpt-5.5"); - expect(firstCliCall.cleanupCliLiveSessionOnRunEnd).toBe(true); + expect(runCliAgent).not.toHaveBeenCalled(); }); }); diff --git a/src/crestodian/operations.ts b/src/crestodian/operations.ts index 5ac54c0b9b2..d20ffb3fb33 100644 --- a/src/crestodian/operations.ts +++ b/src/crestodian/operations.ts @@ -112,7 +112,7 @@ const PLUGIN_UNINSTALL_RE = const OPENAI_API_DEFAULT_MODEL_REF = `${DEFAULT_PROVIDER}/${DEFAULT_MODEL}`; const ANTHROPIC_API_DEFAULT_MODEL_REF = "anthropic/claude-opus-4-7"; const CLAUDE_CLI_DEFAULT_MODEL_REF = "claude-cli/claude-opus-4-7"; -const CODEX_CLI_DEFAULT_MODEL_REF = "codex-cli/gpt-5.5"; +const CODEX_APP_SERVER_DEFAULT_MODEL_REF = "openai/gpt-5.5"; export function parseCrestodianOperation(input: string): CrestodianOperation { const trimmed = input.trim(); @@ -385,7 +385,7 @@ function chooseSetupModel( return { model: CLAUDE_CLI_DEFAULT_MODEL_REF, source: "Claude Code CLI" }; } if (overview.tools.codex.found) { - return { model: CODEX_CLI_DEFAULT_MODEL_REF, source: "Codex CLI" }; + return { model: CODEX_APP_SERVER_DEFAULT_MODEL_REF, source: "Codex app-server" }; } return { source: "none" }; } diff --git a/src/gateway/gateway-cli-backend.live-helpers.test.ts b/src/gateway/gateway-cli-backend.live-helpers.test.ts index a5d3c2c46bf..43f2dbcd215 100644 --- a/src/gateway/gateway-cli-backend.live-helpers.test.ts +++ b/src/gateway/gateway-cli-backend.live-helpers.test.ts @@ -114,22 +114,21 @@ describe("gateway cli backend live helpers", () => { expect(shouldRunCliModelSwitchProbe("codex-cli", "codex-cli/gpt-5.5")).toBe(false); }); - it("configures legacy CLI model refs as canonical provider models plus CLI runtime", async () => { + it("rejects removed Codex CLI refs for live CLI backend selection", async () => { const { resolveCliBackendLiveModelSelection } = await import("./gateway-cli-backend.live-helpers.js"); - expect( + expect(() => resolveCliBackendLiveModelSelection({ rawModel: "codex-cli/gpt-5.4", defaultProvider: "claude-cli", }), - ).toEqual({ - providerId: "codex-cli", - cliModelKey: "codex-cli/gpt-5.4", - configModelKey: "openai/gpt-5.4", - configModelSwitchTarget: undefined, - agentRuntime: { id: "codex-cli" }, - }); + ).toThrow(/codex-cli\/\.\.\. is no longer supported/u); + }); + + it("configures legacy CLI model refs as canonical provider models plus CLI runtime", async () => { + const { resolveCliBackendLiveModelSelection } = + await import("./gateway-cli-backend.live-helpers.js"); expect( resolveCliBackendLiveModelSelection({ diff --git a/src/gateway/gateway-cli-backend.live-helpers.ts b/src/gateway/gateway-cli-backend.live-helpers.ts index 49e2b87c17f..6976babedba 100644 --- a/src/gateway/gateway-cli-backend.live-helpers.ts +++ b/src/gateway/gateway-cli-backend.live-helpers.ts @@ -73,6 +73,11 @@ export function resolveCliBackendLiveModelSelection(params: { } const migrated = migrateLegacyRuntimeModelRef(params.rawModel); + if (migrated?.legacyProvider === "codex-cli") { + throw new Error( + "OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/... is no longer supported. Use a supported CLI backend such as claude-cli or google-gemini-cli.", + ); + } if (migrated?.cli) { return { providerId: migrated.runtime, diff --git a/src/plugins/channel-plugin-ids.test.ts b/src/plugins/channel-plugin-ids.test.ts index fc03fc7cc1e..5fb67f63286 100644 --- a/src/plugins/channel-plugin-ids.test.ts +++ b/src/plugins/channel-plugin-ids.test.ts @@ -164,7 +164,7 @@ function createManifestRegistryFixture(): PluginManifestRegistry { origin: "bundled", enabledByDefault: true, providers: ["openai", "openai-codex"], - cliBackends: ["codex-cli"], + cliBackends: [], contracts: { imageGenerationProviders: ["openai"], videoGenerationProviders: ["openai"], diff --git a/src/plugins/plugin-lookup-table.test.ts b/src/plugins/plugin-lookup-table.test.ts index 5987765f23c..dd54da7d5c6 100644 --- a/src/plugins/plugin-lookup-table.test.ts +++ b/src/plugins/plugin-lookup-table.test.ts @@ -196,7 +196,7 @@ describe("loadPluginLookUpTable", () => { }, }, }, - cliBackends: ["codex-cli"], + cliBackends: [], setup: { providers: [{ id: "openai" }], }, @@ -248,7 +248,7 @@ describe("loadPluginLookUpTable", () => { expect(table.owners.providers.get("openai")).toEqual(["openai"]); expect(table.owners.modelCatalogProviders.get("openai")).toEqual(["openai"]); expect(table.owners.modelCatalogProviders.get("azure-openai-responses")).toEqual(["openai"]); - expect(table.owners.cliBackends.get("codex-cli")).toEqual(["openai"]); + expect(table.owners.cliBackends.get("codex-cli")).toBeUndefined(); expect(table.owners.setupProviders.get("openai")).toEqual(["openai"]); expect(table.owners.commandAliases.get("telegram-send")).toEqual(["telegram"]); expect(table.owners.contracts.get("tools")).toEqual(["telegram"]); diff --git a/src/plugins/providers.test.ts b/src/plugins/providers.test.ts index 754a074e2bb..fdf5e822fcc 100644 --- a/src/plugins/providers.test.ts +++ b/src/plugins/providers.test.ts @@ -86,7 +86,6 @@ function setOwningProviderManifestPlugins() { createManifestProviderPlugin({ id: "openai", providerIds: ["openai", "openai-codex"], - cliBackends: ["codex-cli"], modelSupport: { modelPrefixes: ["gpt-", "o1", "o3", "o4"], }, @@ -111,7 +110,6 @@ function setOwningProviderManifestPluginsWithWorkspace() { createManifestProviderPlugin({ id: "openai", providerIds: ["openai", "openai-codex"], - cliBackends: ["codex-cli"], modelSupport: { modelPrefixes: ["gpt-", "o1", "o3", "o4"], }, @@ -516,7 +514,7 @@ describe("resolvePluginProviders", () => { setOwningProviderManifestPlugins(); expectOwningPluginIds("claude-cli", ["anthropic"]); - expectOwningPluginIds("codex-cli", ["openai"]); + expectOwningPluginIds("codex-cli"); }); it("reflects provider ownership manifest changes on the next lookup", () => { diff --git a/test/scripts/docker-build-helper.test.ts b/test/scripts/docker-build-helper.test.ts index 30e9172f008..f3dc78acc18 100644 --- a/test/scripts/docker-build-helper.test.ts +++ b/test/scripts/docker-build-helper.test.ts @@ -94,8 +94,8 @@ describe("docker build helper", () => { expect(liveCliBackend).toContain( 'OPENCLAW_LIVE_DOCKER_REPO_ROOT="$ROOT_DIR" "$TRUSTED_HARNESS_DIR/scripts/test-live-build-docker.sh"', ); - expect(liveCliBackend).toContain("direct Codex CLI probe failed before OpenClaw gateway smoke"); - expect(liveCliBackend).toContain("==> Direct Codex CLI probe ok"); + expect(liveCliBackend).toContain("codex-cli is no longer a bundled CLI backend"); + expect(liveCliBackend).not.toContain("==> Direct Codex CLI probe ok"); expect(liveCliBackend).not.toContain( 'echo "==> Reuse live-test image: $LIVE_IMAGE_NAME (OPENCLAW_SKIP_DOCKER_BUILD=1)"', ); diff --git a/test/scripts/package-acceptance-workflow.test.ts b/test/scripts/package-acceptance-workflow.test.ts index 9cfe6e024b3..61230207d98 100644 --- a/test/scripts/package-acceptance-workflow.test.ts +++ b/test/scripts/package-acceptance-workflow.test.ts @@ -115,7 +115,9 @@ describe("package acceptance workflow", () => { expect(workflow).toContain("update-channel-switch skill-install update-corrupt-plugin"); expect(workflow).toContain("update-corrupt-plugin upgrade-survivor"); expect(workflow).toContain("published-upgrade-survivor"); - expect(workflow).toContain("published-upgrade-survivor root-managed-vps-upgrade update-restart-auth"); + expect(workflow).toContain( + "published-upgrade-survivor root-managed-vps-upgrade update-restart-auth", + ); expect(workflow).toContain("plugins-offline plugin-update"); expect(workflow).toContain("include_release_path_suites=true"); expect(workflow).not.toContain("telegram_mode requires source=npm"); @@ -382,10 +384,12 @@ describe("package artifact reuse", () => { expect(workflow).toContain("OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go,openrouter"); expect(workflow).toContain("OPENCLAW_LIVE_GATEWAY_PROVIDERS=xai,zai"); expect(workflow).toContain("inputs.live_suite_filter == 'live-gateway-advisory-docker'"); - expect(workflow).toContain("OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/gpt-5.4"); + expect(workflow).toContain("OPENCLAW_LIVE_CLI_BACKEND_MODEL=claude-cli/claude-sonnet-4-6"); expect(workflow).toContain("OPENCLAW_LIVE_CLI_BACKEND_AUTH=api-key"); - expect(workflow).toContain("OPENCLAW_LIVE_CLI_BACKEND_USE_CI_SAFE_CODEX_CONFIG=1"); - expect((workflow.match(/service_tier=\\"fast\\"/g) ?? []).length).toBeGreaterThanOrEqual(2); + expect(workflow).not.toContain("OPENCLAW_LIVE_CLI_BACKEND_USE_CI_SAFE_CODEX_CONFIG=1"); + expect(workflow).not.toContain('service_tier=\\"fast\\"'); + expect(workflow).not.toContain("OPENCLAW_LIVE_CLI_BACKEND_ARGS="); + expect(workflow).not.toContain("OPENCLAW_LIVE_CLI_BACKEND_RESUME_ARGS="); expect(workflow).not.toContain( 'OPENCLAW_LIVE_CLI_BACKEND_ARGS=["exec","--json","--color","never","--sandbox","danger-full-access","--skip-git-repo-check"]', );