[plugins] Add dark-mode logo metadata#28945
Conversation
|
All contributors have signed the CLA ✍️ ✅ |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 25013584fa
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
I have read the CLA Document and I hereby sign the CLA |
2501358 to
369f989
Compare
369f989 to
352d83c
Compare
|
Superseded by #29488. The implementation commits are unchanged; the replacement uses an upstream branch so trusted CI can use the repository-provided BuildBuddy configuration. The replacement macOS and Linux argument-comment-lint jobs both used OpenAI BuildBuddy RBE and passed. Closing this fork PR to avoid duplicate checks. |
Adds additive dark-mode plugin logo metadata across manifests, remote catalogs, and the app-server protocol while keeping uninstalled Git listings free of synthetic local paths. Supersedes #28945. This replacement uses an upstream branch so trusted CI can use the repository-provided remote Bazel configuration. ## Current state Plugin interfaces expose only the default logo asset. Clients therefore cannot select a dedicated dark-mode logo even when a plugin provides one. ## What this PR changes - Adds nullable `logoDark` and `logoUrlDark` fields to `PluginInterface`. - Resolves local `interface.logoDark` assets and maps remote `logo_url_dark` values. - Removes path-backed interface assets, including `logoDark`, from uninstalled Git fallback listings until the plugin has a real local root. - Updates the bundled plugin validator and manifest reference. - Regenerates the app-server JSON schemas and TypeScript types. Local manifests expose `interface.logoDark` as a package-relative asset path. Remote catalog responses expose `logo_url_dark`. These values map into separate app-server fields so clients can preserve local-path and remote-URL handling. ## Risk The fields are additive and nullable, so existing clients retain their current logo behavior. The main risks are an incomplete mapping path or exposing a synthetic local path for an uninstalled Git plugin. Local-manifest, remote-catalog, fallback-listing, protocol serialization, and app-server integration tests cover those paths. Spiciness: 2/5 ## Testing - `just write-app-server-schema` - `just fmt` - Regression test first failed with `logo_dark` resolved to `/assets/logo-dark.png`, then passed after the fallback-listing fix. - `just test -p codex-core-plugins` (267 tests passed) - `just test -p codex-app-server 'suite::v2::plugin'` (114 tests passed) - `just test -p codex-app-server-protocol -p codex-core-plugins -p codex-plugin -p codex-skills` (517 tests passed before the follow-up) - `just test -p codex-tui plugin` (47 tests passed) - Validated a local plugin manifest containing `interface.logoDark` with the bundled validator. ## Manual verification Create a local plugin with both `interface.logo` and `interface.logoDark`, then call `plugin/list` or `plugin/read`. Confirm the response contains separate `logo` and `logoDark` paths. For a remote catalog entry, confirm `logoUrlDark` is populated from `logo_url_dark`. For an uninstalled Git marketplace entry, confirm path-backed interface assets remain absent until installation. Issue: N/A - coordinated maintainer change.
#271) * Apply sandbox intent inside remote exec servers (#29113) ## Why PR #29108 lets the orchestrator send sandbox intent with `process/start` without wrapping the command for its own operating system. This PR completes that boundary by making the executor interpret and enforce the intent using its own filesystem paths and sandbox implementation. For example, a macOS TUI targeting a Linux devbox sends `/bin/bash -lc pwd`. The Linux executor turns that into its own `codex-linux-sandbox ... /bin/bash -lc pwd` launch. ## What changes - Keep `process/start` unchanged when no sandbox intent is present. - Convert sandbox `PathUri` values into native paths on the executor. - Bind symbolic `:workspace_roots` permissions to the executor's native sandbox cwd. - Select the sandbox implementation on the executor and wrap the original command immediately before spawning it. - Reject sandbox-required execution before spawning when the executor cannot enforce the intent. - Pass exec-server runtime paths into process creation so Linux can locate `codex-linux-sandbox`. The boundary is therefore: ```text orchestrator executor original argv + sandbox intent -> select and enforce local sandbox ``` This PR intentionally treats a denied remote command as an ordinary command failure. Draft follow-up #29424 carries a semantic `sandboxDenied` result back to unified exec for the existing approval and retry flow. ## Platform scope Linux and macOS use their existing direct-spawn sandbox transforms. Windows sandboxed remote process launch is intentionally unsupported in this PR. The current Windows direct-spawn wrapper does not correctly preserve arbitrary argv, TTY behavior, or pass the full child environment out of band. The executor rejects the request instead of running it incorrectly or unsandboxed. ## Known follow-ups - The transported permission profile can still contain orchestrator-materialized helper or explicit paths. A `TODO(jif)` marks where the executor boundary should receive pre-host-materialization permission intent. - The sandbox wrapper currently replaces a requested custom inner `arg0`. A `TODO(jif)` marks where this must be preserved or rejected explicitly. - Draft PR #29424 contains the deferred sandbox-denial classification and approval/retry behavior. ## Rollout assumption This executor-sandbox stack is unreleased and its client and executor are expected to move together. This PR does not add mixed-version negotiation with older exec servers. * Add workspace messages app-server API (#29001) ## Summary - Add backend-client types and fetch support for active workspace messages. - Add the app-server v2 `account/workspaceMessages/read` method, generated schemas, and README documentation. - Delegate workspace-message eligibility to the Codex backend feature gate; map a backend 404 to `featureEnabled: false`. ## Testing - `just write-app-server-schema` - `just test -p codex-backend-client` - `just test -p codex-app-server-protocol` - `just test -p codex-app-server workspace_messages` - `just fix -p codex-backend-client -p codex-app-server-protocol -p codex-app-server` - `just fmt` ## Stack - Base PR for #28232, which adds the TUI status-line integration. * Stop logging every Responses WebSocket event (#29432) ## Why Every successful Responses WebSocket event currently produces three local log records: the full payload at TRACE, an OpenTelemetry log event, and an OpenTelemetry trace event. On busy threads these records fill the 1,000-row log partition in seconds and cause continuous SQLite insert-and-prune churn. Related to https://openai.slack.com/archives/C095U48JNL9/p1782128972644209 ## What changed - Stop logging each successful Responses WebSocket payload at TRACE. - Stop emitting `codex.websocket_event` as OpenTelemetry log and trace events. - Keep WebSocket event counters, duration metrics, response timing metrics, parsing, and error handling. * core: refresh environment context before sampling (#29073) ## Why Nonblocking environment snapshots allow a turn to reach the model while a remote environment is still starting. The initial context can describe that environment as still loading, but nothing currently refreshes the model-visible environment context when startup finishes during the same turn. This adds the first request-scoped reconciliation slice on top of #28683. It is gated by `DeferredExecutor` and intentionally updates only model-visible environment context; tools and other environment-derived state will migrate separately. ## What - Add a minimal `StepContext` containing the environment snapshot captured before each sampling request. - Render attached environments with their resolved shell and starting environments with `still loading`. - Track the latest environment state recorded in model history and append a bounded update only when it changes. - Seed that baseline from full initial context so ready-at-start environments are not duplicated. - Clear the in-memory baseline when history is rewritten so replacement history can be refreshed safely. ## Testing - `just test -p codex-core deferred_executor` - `just test -p codex-core environment_context_baseline_deduplicates_until_history_is_replaced` The integration coverage verifies that a pending environment reaches the first request, the ready state reaches the next request, later requests do not duplicate it, and ready-at-start environments remain single-injected. <details> <summary>Live verification</summary> - Connected to a real remote executor with startup deliberately delayed and forced three sampling requests in one turn. - Inspected the raw model inputs: request 1 showed the remote environment as `still loading`, request 2 appended its ready shell and cwd, and request 3 contained no duplicate ready update. - With the feature disabled, startup waited for the delayed executor and the first request contained only the ready environment. - With a synchronously ready environment and the feature enabled, the first request contained one environment context with no duplicate. - Executed `pwd` and read a marker file through the remote process runner; the command exited successfully and returned the remote cwd and marker contents. </details> * fix(core): restore thread_source in x-codex-turn-metadata (#29455) ## Description Restore `thread_source` in `x-codex-turn-metadata`. Inadvertently removed `thread_source` from `x-codex-turn-metadata` in https://github.com/openai/codex/pull/27122 - didn't realize it was a top-level thread app-server API field, not passed in `responsesapi_client_metadata`. This also reserves the key so `responsesapi_client_metadata` cannot override it. * Filter noisy targets from persistent logs (#29457) ## Why The local SQLite log sink currently enables TRACE for every target. This persists high-volume dependency logs bridged through `target=log` and duplicates OpenTelemetry mirror events in `codex_otel.log_only` and `codex_otel.trace_safe`. These records rapidly consume the per-partition log budget and cause unnecessary SQLite insert-and-prune churn. ## What changed - Keep TRACE persistence for other targets. - Exclude bridged `target=log` events from the SQLite sink. - Exclude the two `codex_otel` mirror targets from the SQLite sink. - Share the same filter between app-server and TUI. Remote OpenTelemetry export and metrics are unchanged. * remove flag for image preparation (#29429) ## What - make Fjord's centralized response-item image preparation unconditional for new and resumed history - have local user images and `view_image` outputs always defer decoding and resizing to that path - retain `resize_all_images` as an ignored, removed compatibility key for released clients - delete the flag-off producer paths and obsolete policy-specific tests ## Why Centralized preparation is now the intended image path. Keeping the runtime feature checks also kept two image-processing implementations alive and allowed client config to select the legacy behavior. This is a clean replacement for #28975, rebuilt from the latest `main`. ## How `prepare_response_items` now runs whenever items enter history and whenever persisted history is reconstructed. Producers emit deferred image data, so malformed images become the existing model-visible placeholder instead of failing the session at the producer. ## Test plan - `just fmt` - `just fix -p codex-core -p codex-features` - `just test -p codex-features` — 52 passed - focused affected `codex-core` set — 20 passed - `just test -p codex-core handle_accepts_explicit_high_detail` — 1 passed - full `just test -p codex-core` attempt — 2,723 passed; 88 unrelated environment failures from read-only `~/.codex` SQLite state and unavailable integration helper binaries * ci: restore custom Windows runner with hermetic LLVM 0.7.9 (#29143) The custom Windows argument-comment-lint job was temporarily moved to `windows-2022` in #28940 after hermetic LLVM source extraction failed on the newer runner. This takes the upstream extraction fix so the job can return to the intended custom runner. This upgrades `llvm` to `0.7.9` and `rules_cc` to `0.2.18`, refreshes the module lock, rebases the remaining Windows and custom libc++ patches, drops the obsolete symlink-extraction workaround, and restores the `windows-x64` runner configuration. Validation: - Verified all LLVM patches apply cleanly against the `0.7.9` source. - Built `@llvm-project//compiler-rt:clang_rt.builtins.static`. * [codex] Centralize Plugin Analytics Metadata (#27102) This PR moves construction of `PluginTelemetryMetadata` from loader and model helpers into `PluginsManager`, which already owns installed plugin state and will eventually perform remote identity enrichment. The metadata type remains in `codex-plugin`, and serialized analytics events remain unchanged. ## Before ```mermaid flowchart LR subgraph Events["Analytics event paths"] direction TB Lifecycle["Local install / uninstall"] Config["Enable / disable"] Remote["Remote install"] Used["Plugin used"] end subgraph Construction["Metadata construction"] direction TB Loader["Loader telemetry helpers"] Summary["PluginCapabilitySummary::telemetry_metadata"] Override["Caller adds remote_plugin_id"] end Metadata["PluginTelemetryMetadata"] Lifecycle --> Loader Config --> Loader Remote --> Loader Loader -->|"local events"| Metadata Loader -->|"remote install"| Override Override --> Metadata Used --> Summary Summary --> Metadata ``` Telemetry metadata was constructed through loader helpers, a capability-summary method, and a remote-install call-site override. ## After ```mermaid flowchart LR subgraph Events["Analytics event paths"] direction TB Lifecycle["Local install / uninstall"] Config["Enable / disable"] Remote["Remote install"] Used["Plugin used"] end Manager["PluginsManager — single construction owner"] Metadata["PluginTelemetryMetadata"] Lifecycle --> Manager Config --> Manager Remote -->|"authoritative remote ID"| Manager Used -->|"capability summary"| Manager Manager --> Metadata ``` Every analytics path delegates metadata construction to `PluginsManager`. Remote install still supplies its authoritative backend ID explicitly. ## What Changes - Make loader code return a focused plugin capability summary instead of constructing analytics metadata. - Centralize immutable plugin telemetry metadata construction in `PluginsManager`. - Route local install/uninstall, remote install, enable/disable, and plugin-used emitters through the manager. - Preserve the current serialized analytics contract exactly. Normal metadata still has no remote override. Remote install continues to provide its authoritative backend ID explicitly, so the existing serializer continues reporting that ID through `plugin_id`. Snapshot-based enrichment is intentionally deferred to the final PR. ## Testing - `just test -p codex-core-plugins` (238 tests passed) - `just test -p codex-plugin` (3 tests passed) - Scoped Clippy/compile checks passed for `codex-plugin`, `codex-core-plugins`, `codex-app-server`, and `codex-core`. ## Split Overview ```text main ├── #27093 Debug analytics capture (merged) ├── #27099 Non-mutating plugin smoke (merged) ├── #27100 Remote install/uninstall smoke (merged) └── #27102 Plugin telemetry metadata refactor ← you are here └── #27669 Persist remote plugin identity After #27102 and #27669 merge: └── Final PR: add explicit local and remote IDs to plugin analytics ``` Review order and dependencies: 1. [#27093 Add debug-only analytics event capture](https://github.com/openai/codex/pull/27093) (merged) 2. [#27099 Add a plugin analytics smoke workflow](https://github.com/openai/codex/pull/27099) (merged) 3. [#27100 Add a remote plugin analytics mutation smoke workflow](https://github.com/openai/codex/pull/27100) (merged) 4. This metadata refactor, independent and based on `main` 5. [#27669 Persist remote plugin identity](https://github.com/openai/codex/pull/27669), stacked on this PR 6. Final remote-ID behavior PR, created after the prerequisites merge The original [#26281](https://github.com/openai/codex/pull/26281) remains open as the aggregate reference until the final replacement PR is published. * TUI Plugin Sharing 3 - render remote plugin catalog sections (#26703) ## Summary [#26701](https://github.com/openai/codex/pull/26701) added remote plugin identity support, [#26702](https://github.com/openai/codex/pull/26702) added remote-section fetching and state, and [#28768](https://github.com/openai/codex/pull/28768) extracted the catalog rendering module. This PR builds the product-facing `/plugins` catalog on that foundation so remote records appear as OpenAI Curated, Workspace, and Shared with me sections rather than backend marketplace implementation details. Plugin details remain read-only for sharing metadata. This PR does not add share-authoring actions or change the app-server protocol. ## Changes - Renders OpenAI Curated, Workspace, and Shared with me sections with loading, empty, and error states. - Preserves section selection and stable tab ordering as remote sections transition between fallback and populated states. - Shows OpenAI Curated loading only when the explicit vertical fallback request was issued. - Centralizes remote marketplace identity matching around the existing marketplace constants. - Uses product labels for remote marketplaces and identifies the personal marketplace as Local by its path. - Shows read-only source, authentication, version, and sharing metadata in plugin detail views. - Applies narrow display deduplication for local and remote records sharing a remote plugin ID: - installed records take precedence; - local mapped sources are preferred for details only when their installed state matches the selected record. - Returns from detail and confirmation views through the current plugin cache so newly loaded remote sections are not overwritten by an older captured response. - Keeps admin-disabled plugins view-only and labels default-installed plugins as Available by default. ## Tests New tests: - `plugins_popup_admin_disabled_available_plugin_has_view_only_hint` - `plugins_popup_remote_section_fallback_states_snapshot` - `plugins_popup_installed_remote_row_keeps_remote_detail_when_local_share_is_uninstalled` Updated existing plugin catalog tests and snapshots for product labels, detail metadata, personal-marketplace labeling, and stable tab ordering. Verification: - `cargo clippy -p codex-tui --all-targets -- -D warnings` ## Follow-ups - Local/remote duplicate normalization should eventually move into app-server. This PR intentionally keeps the compatibility behavior narrow and display-only. - PR5 will sanitize sensitive components before displaying Git source URLs. * Report remote sandbox denials semantically (#29424) ## Why #29113 moved remote sandbox setup and enforcement to the exec server. That gives the executor ownership of the platform-specific work: a Linux executor chooses and runs a Linux sandbox even when the Codex orchestrator is running on macOS or Windows. It also means the orchestrator no longer knows which concrete sandbox the executor selected. When that sandbox blocks a remote command, the orchestrator currently sees only a failed process and can treat the denial as an ordinary command failure. The existing sandbox approval and retry path is then skipped. This PR lets the executor report one portable fact: > This command probably failed because the executor sandbox blocked it. The executor keeps its concrete sandbox type private. The protocol sends only the semantic result. ## Example Suppose a local macOS Codex session asks a Linux devbox to write outside the allowed workspace. Before this PR: ```text Linux sandbox blocks the write -> remote process exits with "Permission denied" -> local orchestrator sees an ordinary command failure -> the normal sandbox approval and retry path can be skipped ``` With this PR: ```text Linux sandbox blocks the write -> executor reports sandboxDenied: true -> unified exec returns UnifiedExecError::SandboxDenied -> the existing approval prompt is shown -> an approved retry runs through the existing unsandboxed retry path ``` ## What changes ### The executor remembers its selected sandbox The prepared remote process now retains the executor-selected `SandboxType`. This value never crosses the executor boundary. Commands started without a sandbox retain `SandboxType::None` and are never reported as sandbox denials. ### The executor uses the existing denial heuristic The existing local denial heuristic moves from `codex-core` into the shared `codex-sandboxing` crate. When a sandboxed remote process exits, the executor: 1. waits the same short output grace period used by local unified exec; 2. reads the output currently available in the existing retained output buffer; 3. runs the existing heuristic using the exit code and common denial messages; 4. stores the yes/no result before publishing the process exit. This deliberately matches the old local unified-exec behavior. It does not add a new streaming classifier, another output buffer, or stronger output-retention guarantees. ### The protocol reports a portable boolean `process/read` gains `sandboxDenied`: ```json { "exited": true, "exitCode": 1, "closed": false, "sandboxDenied": true } ``` The field defaults to `false` when an older executor omits it. The response does not expose the executor sandbox implementation or executor-native paths. ### Unified exec uses the existing error path The exec-server client carries `sandboxDenied` into the unified process state. If it is true, unified exec returns the existing `SandboxDenied` error instead of trying to classify remote output using an orchestrator-side sandbox type. Remote process exit remains visible as soon as the process exits. This PR does not wait for stdout or stderr to close and does not change the existing process lifecycle. ## Scope This PR is intentionally limited to matching the existing local unified-exec behavior for the initial command execution path. It does not add: - incremental denial tracking across the full output stream; - new denial handling for commands completed later through `write_stdin`; - new guarantees for preserving the semantic flag during the narrow reconnect-recovery race. Those can be considered separately if the same behavior is added for local execution. ## Test coverage One remote end-to-end integration test covers the complete intended flow: ```text remote read-only sandbox -> denied write -> executor reports the denial -> Codex requests approval -> user approves -> retry succeeds on the remote executor ``` Existing lifecycle coverage continues to verify that remote process exit is reported before late output streams close. * core: rename metadata -> internal_chat_message_metadata_passthrough (#28968) ## Description This PR cuts Codex over from generic `ResponseItem.metadata` (introduced here: https://github.com/openai/codex/pull/28355) to `ResponseItem.internal_chat_message_metadata_passthrough`, which is the blessed path and has strongly-typed keys. For now we have to drop this MAv2 usage of `metadata`: https://github.com/openai/codex/pull/28561 until we figure out where that should live. * [sdk/python] Stop advertising HTTP image URLs (#29464) ## Summary - use generated image data URLs in the Python SDK examples and notebook - document HTTP and HTTPS image URLs as deprecated and recommend `LocalImageInput` - replace the remote-URL integration test with data-URL coverage `ImageInput` remains available for data URLs. The SDK does not duplicate app-server URL validation. ## Testing - `uv run --frozen --no-sync ruff check --output-format=full .` - `uv run --frozen --no-sync ruff format --check .` - full Python SDK test suite with an isolated writable `CODEX_SQLITE_HOME` (119 passed, 38 skipped) * [codex] Fix usage-limit reset copy and state (#28793) ## Why The reset flow introduced in #28154 still describes earned reset credits as "rate-limit resets" and uses generic reset-scope copy. It can also retain a stale available-credit count after redemption or an account change, leaving the reset action enabled after the last credit is used. This follow-up updates terminology only within that reset feature. Existing rate-limit wording elsewhere in the CLI and TUI is unchanged. ## What changed - Rename reset-specific `/usage` menu items, startup hints, and reset dialogs to "usage limit reset." - Describe monthly resets for Free, Go, and accounts that report a monthly usage window; otherwise describe the current 5-hour and weekly limits. - Recheck a cached zero balance when `/usage` is reopened, and refresh the balance after redemption so the final reset immediately disables the action. - Correlate async refresh results before updating snapshots and clear account-derived reset state, warnings, prompts, and status surfaces when the account changes. ## Validation - `just test -p codex-tui chatwidget::tests::usage` — 29 passed. - `just test -p codex-tui chatwidget::tests::status_command_tests` — 7 passed. - Account-boundary prompt and plan-mode prompt regression tests passed. - `cargo insta pending-snapshots` from `codex-rs/tui` — no pending snapshots.\ <img width="814" height="318" alt="image" src="https://github.com/user-attachments/assets/2a460e96-458b-4805-8d9f-c759382d21a4" /> view for monthly <img width="905" height="243" alt="image" src="https://github.com/user-attachments/assets/179f88e3-08fb-4af5-8dc6-ce6a944ed681" /> * [codex] Start the guardian child session when parent session is started (#27982) ## Why The first auto-review currently creates its Guardian child session on demand, adding avoidable latency before the review can begin. Creating the ordinary Guardian child during parent-session initialization lets that child use the existing session startup WebSocket prewarm before the first escalation. This does not introduce a Guardian-specific prewarm mechanism. ## What changed - initialize the existing Guardian review-session manager owned by `Session` when a thread starts with auto-review enabled and an approval policy that routes to Guardian - use the standard Guardian child-session construction and the existing session startup WebSocket prewarm - preserve the existing reuse-key invalidation and lazy creation fallback when startup initialization fails or the effective review configuration changes - add an integration test that verifies normal root-session startup emits a Guardian `generate=false` prewarm request ## Benchmark I compared release builds against main. Each prompt first ran a non-escalated `sleep 3`, then requested an escalated marker command. | binary | count | avg Guardian duration | median Guardian duration | avg Guardian TTFT | |---|---:|---:|---:|---:| | origin-main | 10 | 4008.7 ms | 3949.5 ms | 3746.5 ms | | session-fix | 10 | 2865.0 ms | 2594.0 ms | 2492.7 ms | Guardian duration fell by 28.5% and Guardian TTFT fell by 33.5%. These measurements cover Guardian review latency; they do not measure parent thread-start latency. * core: remove unused permissions cwd plumbing (#29468) ## Why `compile_scoped_filesystem_pattern()` accepted a `_policy_cwd` parameter even though scoped glob compilation no longer uses the policy working directory. Keeping that unused argument forced the surrounding permissions compilation path to keep forwarding `policy_cwd` through call sites that did not need it, making the API look more dependent on cwd resolution than it is. ## What changed Removed the unused cwd parameter from `compile_scoped_filesystem_pattern()` and the callers that only forwarded it: `compile_filesystem_permission()`, `compile_permission_profile()`, and `compile_permission_profile_selection()`. Workspace root resolution still keeps `policy_cwd`, because that path still resolves relative roots against the active policy cwd. Relevant code: [`codex-rs/core/src/config/permissions.rs`](https://github.com/openai/codex/blob/b8b9816102e064dae4488ec130cf560f63c1ab78/codex-rs/core/src/config/permissions.rs#L346). ## Verification - `just test -p codex-core config::permissions` - `just test -p codex-core` was also run after building `test_stdio_server`; it passed the touched permissions coverage but still reported unrelated existing failures in `cli_stream` and shell snapshot tests. * PAC 2 - Add shared auth system proxy contract (#26707) ## Summary Stacked on #26706. Adds the shared auth/system-proxy contract that later platform resolver PRs plug into. This PR moves Codex-owned auth and startup HTTP clients through a common route-aware boundary, but does not yet add Windows or macOS system proxy resolution. The default path remains unchanged when `respect_system_proxy` is absent or disabled. ## Implementation - Adds `codex-client/src/outbound_proxy.rs` with the shared route-selection model: - `OutboundProxyConfig`; - `ClientRouteClass`; - `RouteFailureClass`; - `build_reqwest_client_for_route`. - Preserves the existing reqwest/default-client behavior when no route config is supplied. - Uses the fixed MVP routing policy when route config is supplied: platform system/PAC/WPAD discovery, then explicit env proxy variables, then direct connection. - Keeps platform-specific system discovery behind the shared client boundary. This PR provides the contract and fallback behavior; later resolver PRs plug in Windows and macOS discovery. - Adds `login::AuthRouteConfig` so auth call sites depend on a small policy type instead of platform resolver details. - Maps the resolved `Config.respect_system_proxy` boolean into `AuthRouteConfig` for auth-owned clients. - Wires the route config through browser login, device-code login, access-token login, login status, logout/revoke, token refresh, API-key exchange, app-server account login, TUI/app startup, cloud-config bootstrap, cloud tasks, plugin auth, and exec startup config loading. ## End-user behavior - No behavior changes by default. - When `respect_system_proxy = true`, auth-owned clients opt into the shared route-aware client path. - On platforms without a resolver implementation in this PR, system discovery is unavailable and the route-aware path falls back to explicit env proxy handling, then direct connection. - Custom CA handling remains separate from proxy route selection and still runs through the shared client builder. - No proxy URLs, PAC contents, or resolved platform details are exposed through the public config surface introduced here. ## Tests Adds or updates coverage for: - preserving default auth-client fallback behavior when no route config is provided; - injected environment-proxy fallback without mutating process environment; - existing login-server E2E flows using explicit `auth_route_config: None` to guard unchanged default behavior; - updated auth manager, login, logout, cloud-config, startup, and plugin-auth call sites passing route config explicitly. * Allow ChatGPT accounts without email (#28991) # Summary Codex required every ChatGPT account to have an email address. A service-account personal access token can return valid account metadata without one, so PAT login failed while decoding the metadata response. This change makes email optional in the account metadata type that owns it and preserves that absence through authentication, provider account state, the app-server API, generated clients, and TUI bootstrap. Existing accounts with email addresses keep the same behavior. ## Behavior-changing call sites | Call site | Behavior after this change | | --- | --- | | `login/src/auth/personal_access_token.rs` | PAT metadata accepts a missing or null email and retains `None`. | | `agent-identity/src/lib.rs` | Agent Identity JWT claims accept an omitted email. | | `login/src/auth/storage.rs` and `login/src/auth/agent_identity.rs` | Stored and managed Agent Identity records carry `Option<String>`. Deserialization maps the legacy empty-string sentinel to `None`. | | `login/src/auth/manager.rs` | `get_account_email` returns the stored option, and managed identity bootstrap no longer converts `None` to an empty string. | | `model-provider/src/provider.rs` and `protocol/src/account.rs` | A ChatGPT provider account requires a plan type but may carry no email. | | `app-server-protocol/src/protocol/v2/account.rs` | `account/read` keeps the `email` field on the wire and returns `null` when the account has no email. Generated TypeScript and JSON schemas describe a required, nullable field. | | `sdk/python/src/openai_codex/generated/v2_all.py` | The generated Python `ChatgptAccount` model accepts `None` for email. | | `tui/src/app_server_session.rs` | Email-less ChatGPT accounts bootstrap normally, keep external feedback routing, omit account-email telemetry, and display the plan in account status. | ## Design decisions - Missing email remains `None` at every layer. The code never uses an empty string as a substitute. - The app-server response includes `"email": null` instead of omitting the field. Clients retain a stable response shape. - Plan type remains required for provider account state. This change relaxes only the email assumption. ## Testing Tests: affected test targets compile, scoped Clippy and formatting pass, a focused TUI snapshot covers plan-only account status, real before/after PAT login smoke covers metadata without email, app-server smoke covers `account/read` with `email: null`, and a regression smoke covers an existing email-bearing PAT. Unit tests run in CI. ## Evidence Visual smoke evidence will be attached here. * [codex] configure rollout budget reminder thresholds (#29423) ## Summary Instead of: reminder_interval_tokens = 65_536 allow users to configure explicit remaining-token reminder thresholds: reminder_at_remaining_tokens = [65_536, 32_768, 16_384, 8_192, 4_096, 2_048, 1_024, 512] ## Validation - CARGO_INCREMENTAL=0 just test -p codex-core rollout_budget: 9 passed - just fix -p codex-core - just fmt * permission profiles: expose availability to clients (#26678) ## Why `permissionProfile/list` currently advertises every built-in and configured profile even when effective enterprise requirements prevent selecting it. That forces each client to reconstruct policy from lower-level requirement fields, which is easy to miss and difficult to keep consistent. The catalog should remain complete so clients can explain that an option was disabled by an administrator, while also reporting whether each profile is selectable. ## What - Add an `allowed` field to each permission profile summary. - Build a shared catalog from the effective config and current requirements, including `allowed_sandbox_modes`, `allowed_permissions`, and filesystem restrictions. - Use the shared catalog in app-server and the TUI so disallowed profiles remain visible but cannot be selected. - Use the canonical `:danger-full-access` profile ID in the TUI. - Update the app-server schemas, API documentation, behavioral tests, and TUI snapshots. ## Scope This PR targets `main` directly and is independent of #24852. It preserves the current behavior where built-in profiles are constrained by sandbox-mode requirements and `allowed_permissions` applies to configured profiles. ## Testing - `just test -p codex-core permission_profile_catalog_marks_profiles_disallowed_by_requirements` - `just test -p codex-app-server permission_profile_list` - `just test -p codex-app-server-protocol` - `just test -p codex-tui profile_permissions` - `just fix -p codex-core` - `just fix -p codex-app-server-protocol` - `just fix -p codex-app-server` - `just fix -p codex-tui` - `just fmt` --------- Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Joey Trasatti <joey.trasatti@openai.com> * [codex] handle request_user_input in app-server test client (#29476) ## Why `codex-app-server-test-client` previously treated `item/tool/requestUserInput` as an unsupported server request and terminated the connection. That made it impossible to use the client for end-to-end testing of interactive turns: an operator could observe the request, but could not answer it and confirm that the same turn resumed. ## What changed - Handle `ToolRequestUserInput` server requests in the test client's central request dispatcher. - Render numbered terminal choices, accept exact option labels, support free-form `Other` and text-only questions, and collect multiple answers. - Send a protocol-native `ToolRequestUserInputResponse` and continue streaming the active turn. - Fail clearly when interactive input is requested without a terminal. - Document the interactive behavior and add focused tests for option selection, free-form answers, multiple questions, and invalid-selection retries. ## Testing - `just test -p codex-app-server-test-client` - `just bazel-lock-check` - Manually exercised the app-server flow, selected `TUI`, observed `serverRequest/resolved`, and verified that the same turn completed with the selected answer. * fix(config): address permission profile review follow-ups (#29479) ## Summary - rename `Config::permission_profile_allowed` to `is_permission_profile_allowed` - use `BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS` in the TUI and its assertion - follow up on the late review comments from #26678 The previous `:danger-no-sandbox` value was an invalid built-in profile ID. #26678 corrected it to `:danger-full-access`; this PR centralizes the value to prevent future drift. ## Testing - Not run per request; `cargo fmt` only Co-authored-by: Codex <noreply@openai.com> * Honor startup custom CA bundles with managed MITM (#29014) ## Why When Codex starts with a custom CA override such as `SSL_CERT_FILE=/path/to/corp-ca.pem codex`, `rustls-native-certs` treats that override as a replacement for the platform trust store. The managed proxy then rewrites child CA variables to its generated bundle, so the custom root or the ordinary platform roots can be lost. The proxy's upstream TLS connector must trust the same roots or private and corporate upstream certificates still fail after interception. ## What - load platform-native roots without consulting inherited CA override variables - append certificates from the existing curated startup CA file variables and `SSL_CERT_DIR` - share those platform and startup roots with the MITM upstream rustls connector - exclude the Codex managed MITM CA from upstream trust - normalize OpenSSL `TRUSTED CERTIFICATE` blocks while dropping trailing trust metadata - skip an inherited current Codex-managed bundle so nested launches do not duplicate it - append the Codex managed MITM CA to the child-facing bundle - copy certificate material only, so a private key or unrelated text colocated in a startup file is never exposed through the public bundle This is intentionally limited to CA paths present when Codex starts. It does not parse inline shell assignments or add per-command bundle materialization. This changes only `codex-network-proxy` and dependency metadata; it does not touch `codex-core` or sandbox orchestration. ## Validation - `just test -p codex-network-proxy` - includes an end-to-end upstream TLS test using a server trusted only by the startup custom CA - `just fix -p codex-network-proxy` - `just bazel-lock-check` * chore: advance tungstenite fork pins (#29480) ## Why `openai-oss-forks/tokio-tungstenite` now includes the updated `tungstenite` fork revision from [openai-oss-forks/tokio-tungstenite#3](https://github.com/openai-oss-forks/tokio-tungstenite/pull/3). Codex should consume the merged fork commit and resolve its direct and transitive `tungstenite` dependencies to the same revision instead of retaining the older pins. ## What Changed - Advanced the `tokio-tungstenite` git pin to `0e5b2d73aa18dd9f0a50ee9ff199d5aef7594186`. - Advanced the `tungstenite` fork pin to `4fffad30fe373adbdcffab9545e9e9bf4f2fc19f` and adjusted the patch source so the transitive dependency resolves to that revision. - Updated `Cargo.lock` and `MODULE.bazel.lock` to match the dependency graph. * [codex-core-plugins] Remote Plugin ID Persisted to File (#27669) ## This PR Remote plugin analytics cannot rely only on the in-memory installed-plugin snapshot because that snapshot is refreshed asynchronously after startup. This PR persists the authoritative backend identity alongside each cached remote plugin bundle so later consumers can resolve it without a network request. ### Behavior - Store Codex-owned remote installation metadata in an atomic `.codex-remote-plugin-install.json` sidecar under the plugin cache root. - Use a versioned, snake_case schema: ```json { "schema_version": 1, "remote_plugin_id": "plugins~Plugin_..." } ``` - Write the metadata during remote bundle installation. - Backfill it when bundle sync finds an already-current cached bundle. - Clear it when a generic/local install replaces the cache. - Let existing uninstall and stale-cache removal delete it with the plugin cache root. - Reject unsupported schema versions rather than silently misreading future formats. This PR does not change analytics serialization or event behavior. ### Review surface The implementation is limited to four `codex-core-plugins` files: - `store.rs`: owns the versioned sidecar read/write/remove lifecycle. - `remote_bundle.rs`: persists the backend ID after a remote bundle install. - `remote/remote_installed_plugin_sync.rs`: backfills metadata for an already-current cached bundle. - Tests cover the storage lifecycle and both remote write paths. ## Testing / Validation ### Automated - `just test -p codex-core-plugins` (268 tests passed) - `just fix -p codex-core-plugins` passes with one pre-existing `large_enum_variant` warning in `manifest.rs`. - Coverage verifies the exact filename and JSON schema, identity replacement, local reinstall clearing, uninstall cleanup, remote bundle installation, unsupported schema rejection, and installed-plugin sync backfill. ### Live manual validation Validated the production app-server RPC path with an isolated temporary `CODEX_HOME` and the PR-built Codex binary. The app-server communicated over stdio and did not bind a port. Test plugin: `plugins~Plugin_b80dd84519148191a409cde181c9b3d6` (`build-macos-apps@openai-curated-remote`). 1. Confirmed `plugin/read` initially reported the plugin uninstalled. 2. Installed it through `plugin/install` and confirmed version `0.1.4` was cached. 3. Verified `$CODEX_HOME/plugins/cache/openai-curated-remote/build-macos-apps/.codex-remote-plugin-install.json` was created beside the `0.1.4/` bundle directory with mode `0600` and the expected contents: ```json { "schema_version": 1, "remote_plugin_id": "plugins~Plugin_b80dd84519148191a409cde181c9b3d6" } ``` 4. Deleted only the sidecar, restarted the app-server, and confirmed installed-plugin startup sync recreated it with the same contents. 5. Uninstalled through `plugin/uninstall`, confirmed `plugin/read` returned `installed: false`, and verified the local plugin cache root was removed. 6. Restored the account's original uninstalled state and removed the isolated home and copied credentials. ## Split Overview ```text main ├── #27093 Debug analytics capture merged │ └── #27099 Non-mutating plugin smoke merged │ └── #27100 Remote install/uninstall smoke merged └── #27102 Plugin telemetry metadata refactor merged └── #27669 Persist remote plugin identity ← this PR Next: └── Final PR: add explicit local and remote IDs to plugin analytics ``` This PR is based directly on `main`; prerequisite [#27102](https://github.com/openai/codex/pull/27102) has merged. The original combined [#26281](https://github.com/openai/codex/pull/26281) remains the aggregate reference until the final replacement PR is published. * PAC 3 - Add Windows system proxy resolver (#26708) ## Summary Stacked on #26707. Adds the Windows implementation of the shared system-proxy contract. This allows Codex-owned auth clients to use the route Windows selects for each auth URL, including explicit PAC configuration, WPAD auto-detection, static proxies, and bypass rules. The `respect_system_proxy` feature is disabled by default, so existing client behavior remains unchanged unless explicitly enabled. ## Implementation - Adds Windows-only `codex-client` dependencies: - `windows-sys` with `Win32_Foundation` and `Win32_Networking_WinHttp`; - `sha2` for redacted cache keys. - Dispatches system-proxy resolution to `outbound_proxy/windows.rs` on Windows. - Reads the current-user WinHTTP/IE proxy configuration via `WinHttpGetIEProxyConfigForCurrentUser`. - Resolves explicit PAC URLs first, then OS-enabled WPAD auto-detection, then static proxy and bypass settings. - Uses `WinHttpGetProxyForUrl` for PAC/WPAD and maps results into the shared `SystemProxyDecision::{Direct, Proxy, Unavailable}` contract. - Parses `DIRECT`, `PROXY`, `HTTPS`, and keyed static proxy entries. - Treats unsupported schemes such as SOCKS as unavailable so the shared resolver can apply its environment-proxy fallback. - Handles Windows bypass entries, including `<local>` and host, suffix, wildcard, and port matching. - Releases WinHTTP-owned strings with `GlobalFree` and closes sessions with `WinHttpCloseHandle`. - Hashes URL-specific cache keys with SHA-256 so PAC decisions remain URL-specific without retaining raw request URLs or query strings. ## End-user behavior - Disabled/default: existing client behavior is unchanged. - Enabled with `[features.respect_system_proxy]`: - Windows auth clients honor explicit PAC configuration, OS-enabled WPAD, static proxies, and bypass rules; - valid OS/PAC `DIRECT` decisions use a direct connection; - unavailable system resolution falls back to explicit environment proxy variables, then `DIRECT`, through the shared contract from #26707. - Unsupported proxy schemes are not silently translated into a different route. - Custom CA handling remains separate from proxy selection. ## Tests Adds coverage for: - PAC-style proxy tokens such as `PROXY proxy.internal:8080` and `HTTPS proxy.internal:8443`; - static WinHTTP proxy entries keyed by target scheme; - `DIRECT` and unsupported proxy-token behavior; - Windows bypass matching, including `<local>`, wildcard, suffix, and port-qualified entries; - preserving URL-specific PAC cache decisions without retaining the raw URL on Windows. * Register full CDP requirements feature (#28769) register cdp requirements feature flag * [codex] fetch featured IDs for remote plugins (#29485) ## Summary - fetch featured plugin IDs when the loaded catalog includes `openai-curated-remote` - extend the existing remote marketplace regression test to cover the featured IDs response ## Why When the remote plugin catalog was enabled, app-server loaded `openai-curated-remote` but skipped `/plugins/featured` because the request processor only fetched featured IDs for the local `openai-curated` marketplace. As a result, the desktop app could not render the backend-curated remote featured set. This keeps the existing local behavior and also returns the curated ranking for remote plugins. ## Test plan - `just fmt` - `git diff --check` - `just test -p codex-app-server plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled` * Upgrade bundled OpenSSL to 3.6.3 (#29487) ## Summary - upgrade the bundled OpenSSL source from 3.5.5 to 3.6.3 - update the Bazel `openssl-sys` build dependency to use the upgraded source crate - refresh the Bazel module lockfile ## Why OpenSSL 3.5.5 is within the affected ranges for security issues fixed in later releases. The Rust `openssl-src` wrapper does not currently publish OpenSSL 3.5.7, so this moves the vendored Linux musl build to the available patched 3.6.3 release. * [codex] Update esbuild to 0.28.1 (#29489) ## Why The TypeScript workspace resolved `esbuild` 0.25.10 transitively through the SDK toolchain. `esbuild` 0.28.1 adds integrity verification to the Deno binary download path addressed by [GHSA-gv7w-rqvm-qjhr](https://github.com/evanw/esbuild/security/advisories/GHSA-gv7w-rqvm-qjhr), preventing an attacker-controlled npm registry from supplying an executable without a content check. ## What changed - Add a root workspace resolution for `esbuild` 0.28.1. - Regenerate `pnpm-lock.yaml` so `tsup`, `bundle-require`, and `ts-jest` all resolve the patched version. ## Validation - Frozen pnpm install, including the SDK's `tsup` build - `pnpm --filter @openai/codex-sdk exec jest tests/exec.test.ts --runInBand` - Confirmed the installed dependency graph contains only `esbuild` 0.28.1 * [plugins] Add dark-mode logo metadata (#29488) Adds additive dark-mode plugin logo metadata across manifests, remote catalogs, and the app-server protocol while keeping uninstalled Git listings free of synthetic local paths. Supersedes #28945. This replacement uses an upstream branch so trusted CI can use the repository-provided remote Bazel configuration. ## Current state Plugin interfaces expose only the default logo asset. Clients therefore cannot select a dedicated dark-mode logo even when a plugin provides one. ## What this PR changes - Adds nullable `logoDark` and `logoUrlDark` fields to `PluginInterface`. - Resolves local `interface.logoDark` assets and maps remote `logo_url_dark` values. - Removes path-backed interface assets, including `logoDark`, from uninstalled Git fallback listings until the plugin has a real local root. - Updates the bundled plugin validator and manifest reference. - Regenerates the app-server JSON schemas and TypeScript types. Local manifests expose `interface.logoDark` as a package-relative asset path. Remote catalog responses expose `logo_url_dark`. These values map into separate app-server fields so clients can preserve local-path and remote-URL handling. ## Risk The fields are additive and nullable, so existing clients retain their current logo behavior. The main risks are an incomplete mapping path or exposing a synthetic local path for an uninstalled Git plugin. Local-manifest, remote-catalog, fallback-listing, protocol serialization, and app-server integration tests cover those paths. Spiciness: 2/5 ## Testing - `just write-app-server-schema` - `just fmt` - Regression test first failed with `logo_dark` resolved to `/assets/logo-dark.png`, then passed after the fallback-listing fix. - `just test -p codex-core-plugins` (267 tests passed) - `just test -p codex-app-server 'suite::v2::plugin'` (114 tests passed) - `just test -p codex-app-server-protocol -p codex-core-plugins -p codex-plugin -p codex-skills` (517 tests passed before the follow-up) - `just test -p codex-tui plugin` (47 tests passed) - Validated a local plugin manifest containing `interface.logoDark` with the bundled validator. ## Manual verification Create a local plugin with both `interface.logo` and `interface.logoDark`, then call `plugin/list` or `plugin/read`. Confirm the response contains separate `logo` and `logoDark` paths. For a remote catalog entry, confirm `logoUrlDark` is populated from `logo_url_dark`. For an uninstalled Git marketplace entry, confirm path-backed interface assets remain absent until installation. Issue: N/A - coordinated maintainer change. * [codex] migrate environment context to model world state (#29249) ## Why Environment context is model-visible state, but it is currently assembled from transient turn values and diffed through environment-specific paths. That makes initial injection, turn-to-turn updates, and changes that happen within a turn use different baselines. This PR introduces the smallest useful model world-state slice: environments only, with one in-memory baseline and one renderer for full state and diffs. ## What changed - Add a typed `WorldState` container whose sections render fragments relative to an optional previous value. Full rendering uses the same diff path with no previous state. - Replace the parallel `EnvironmentContext` representation with an `EnvironmentsState` section keyed by environment ID and rendered in deterministic order. - Preserve the legacy single-environment output while supporting multiple environments, starting environments, unavailable tombstones, and changes to persisted turn-context values. - Store the latest complete `WorldState` on `ContextManager` and use it for both turn-boundary and mid-turn environment diffs. - Build initial and post-compaction context from the same world-state builder, then retain the rendered state as the next baseline. - Seed the in-memory baseline from the latest `TurnContextItem` when resuming an existing rollout; the world state itself is not serialized. - Keep non-world settings updates on their existing path and merge rendered world-state fragments at the session consumer. ## Known limitation A legacy `TurnContextItem` only reconstructs the primary environment as `local`; it cannot faithfully recover a remote-primary environment ID after resume. Live state uses the exact environment IDs once a complete baseline is established. ## Test plan - `just test -p codex-core world_state` - `just test -p codex-core record_context_updates` - `just test -p codex-core deferred_executor_` - `just test -p codex-core build_initial_context` - `just test -p codex-core rollout_reconstruction` - `just test -p codex-core process_compacted_history_reinjects_full_initial_context` * core: wrap token budget window context (#29494) Token-budget initial context carries thread and context-window lineage that the model should treat as one structured context-window block. Wrapping it in `<context_window>` makes that boundary explicit while preserving the existing window id content. Before this change, the window identifiers were injected as an untagged developer text fragment: ```text Thread id <THREAD_ID>. First context window id: <FIRST_WINDOW_ID> Current context window id: <WINDOW_ID> Previous context window id: <PREVIOUS_WINDOW_ID> ``` After this change, the same payload is wrapped as a context-window block: ```text <context_window> Thread id: <THREAD_ID> First context window id: <FIRST_WINDOW_ID> Current context window id: <WINDOW_ID> Previous context window id: <PREVIOUS_WINDOW_ID> </context_window> ``` This adds shared `CONTEXT_WINDOW_*_TAG` protocol constants, updates `TokenBudgetContext` to render with those markers, treats the new wrapper as contextual developer content when mapping history, and refreshes the token-budget request-shape assertions and snapshot. Verification: - `just test -p codex-core token_budget` - `just test -p codex-core recognizes_context_window_as_contextual_developer_content` * [codex] replace remote images with model-visible error text (#29417) ## What This PR will extend the existing centralized image-preparation path to replace HTTP(S) image inputs with a model visible error message. It won't "ruin" and break existing rollouts, but it will deprecate support for the pathway. App server clients should no longer use HTTP image urls if they'd like to upgrade. The HTTP image url pathway is currently resolved in the responsesapi. It is slow and not reccomended. ## Behavior - HTTP(S) image URL: replace with `input_text` - data URL: use the existing decode and resize path - other image URL schemes: leave unchanged This intentionally does not change app-server ingress. That validation remains a follow-up. ## Test plan - `just test -p codex-core -E 'test(/image_preparation|prepares_image_failures_before_history_insertion|prepares_resumed_history_before_installing_it|responses_lite_prepares_images/)'` — 7 passed - `just fix -p codex-core` - `just fmt` * feat(core): store turn_id on ResponseItem metadata (#28360) ## Description This PR is a followup to https://github.com/openai/codex/pull/28355 and starts assigning `internal_chat_message_metadata_passthrough.turn_id` to durable Responses API items created during a turn. The goal is that those items keep the `turn_id` that introduced them when Codex resends stateless HTTP context, reconstructs history for resume/fork paths, or reuses websocket response state. ## What changed - Set `internal_chat_message_metadata_passthrough.turn_id` when missing as response items enter durable history, initial/replacement history, inter-agent communication history, and local compaction summaries. - Preserve existing item turn IDs instead of overwriting them during persistence, resume reconstruction, compaction, forked history, and websocket incremental reuse. - Keep `compaction_trigger` fieldless because it is a request control, not a durable response item. - Update focused history/request assertions and fixtures for stateless requests, websocket incrementals, compaction, thread injection, prompt debug, and related CI coverage. * [codex] Use tool search for MCP tools by default (#29486) ## Why MCP tools were only placed behind `tool_search` when a feature flag was enabled or when there were at least 100 tools. That made the model's tool flow depend on both rollout configuration and the number of installed tools. The searched-tool flow is now the intended behavior. Making it unconditional when the model and provider support it gives every supported setup the same behavior and lets us retire the feature flag safely. ## What changed - Defer all effective MCP tools when `tool_search` and namespaced tools are supported. - Keep exposing MCP tools directly when search cannot be used, so older or unsupported model/provider combinations still work. - Mark `tool_search_always_defer_mcp_tools` as removed and ignore old configured values. - Keep plugin filtering, app-only filtering, file handling, and MCP calls working through the searched-tool flow. ## Why many tests changed Many tests used to act as if the model could see MCP tools in its first request and call them immediately. That is no longer the real flow: the model first receives `tool_search`, searches for a tool, receives the matching MCP tool, and then calls it in the next request. The tests therefore needed an extra search step, and checks for tool names, descriptions, and input fields had to move from the first request to the search result. These are not separate product changes; they make the tests follow what the model will actually see after this change. The plugin tests still check which tools are allowed and where they came from, the file tests still check upload fields and behavior, and the MCP round-trip test still checks a successful call from start to finish. ## Tests - `just test -p codex-features` - Focused `codex-core` tests for MCP exposure and tool planning - `just test -p codex-core explicit_plugin_mentions` - `just test -p codex-core stdio_server_round_trip` - Focused `codex-core` tests for tool search, app-only tools, and MCP file uploads * path-uri: clarify host-native path conversion (#29501) ## Why Downstream refactors are producing confusing code with this functionality having a very generic name. Encoding the specific conversion approach in the method name makes it clearer. ## What Rename `PathUri::from_path` to `PathUri::from_host_native_path` and update its Rust call sites. * fix: world state response item test (#29504) seems to be a merge conflict on main: > pakrym-oai introduced the stale initializer in commit 3b32d861c5, PR #29249. > Context: Owen Lin renamed metadata to internal_chat_message_metadata_passthrough in PR #28968. PR #29249 then landed afterward with the old field name, causing the compile/Clippy failure. * TUI Plugin Sharing 4 - cover remote plugin catalog flows (#26704) Remote plugin catalogs now span workspace, shared, and local sources, so their TUI behavior needs focused regression coverage across loading, navigation, actions, and refreshes. This PR: - Covers product labels and rendered loading/error states for Workspace, Shared with me, Shared with me (link), and Local tabs, including tab persistence across refresh and detail navigation. - Covers remote/local deduplication, Installed-tab remote detail routing, marketplace load-error handling, and disabled install/uninstall navigation. - Adds a full detail snapshot for local shared-plugin metadata plus focused snapshots for marketplace labels and admin-disabled status. - Verifies shared plugins remain eligible for mentions and successful uninstalls trigger a catalog refresh. * [codex] reject remote images at app-server ingress (#29419) ## Stack Stacked on #29417. Review and land that PR first. ## Summary - reject HTTP(S) image URLs in the handlers for `turn/start` and `turn/steer` - validate `thread/inject_items` after its existing JSON-to-`ResponseItem` conversion, so each item is deserialized once - turn invalid dynamic-tool image responses into the existing unsuccessful text fallback; the model receives the validation message as the function output - leave `thread/resume.history` compatible with legacy history; #29417 replaces remote images before model input - continue accepting inline data URLs and `localImage` inputs - keep this policy in app-server; this PR does not add a shared protocol API or change core image preparation ## Test plan - `just test -p codex-app-server -E 'test(/request_handlers_reject_remote_image_urls|dynamic_tool_remote_image_response_becomes_model_visible_error|dynamic_tool_call_round_trip_sends_content_items_to_model|turn_start_tracks_turn_event_analytics|standalone_image_edit_uses_recent_pathless_image/)'` (5 passed) - `just fix -p codex-app-server` - `just fmt` * chore: improve expired Bedrock credential errors (#28992) ## Why Amazon Bedrock returns a `401 Unauthorized` response containing `Signature expired:` when an AWS credential, including a short-lived `AWS_BEARER_TOKEN_BEDROCK`, has expired. Codex currently surfaces that response as a generic `unexpected status` error, which does not explain how to recover. Environment-provided bearer tokens cannot be refreshed automatically, so the error should direct users to refresh their AWS credentials or replace or remove the environment token and restart Codex. This classification belongs to the Amazon Bedrock provider so similar responses from other providers retain their existing behavior. ## What changed - Add a synchronous `ModelProvider::map_api_error` hook that defaults to the existing provider-neutral API error mapping, and route model request, stream, WebSocket, and terminal unauthorized errors through the active provider. - Override the hook for Amazon Bedrock. After preserving the structured status, body, URL, and request metadata, recognize `401` responses containing `Signature expired:` and attach actionable credential guidance. - Keep `codex-protocol` provider-neutral by representing the guidance as an optional `user_message`. Error rendering prefers this message while continuing to append the URL, request ID, Cloudflare ray, and authorization diagnostics. - Add model-provider coverage for expired signatures and negative cases, core coverage for provider dispatch after unauthorized recovery, and a TUI snapshot for the rendered error. ## Testing Tested with a real request with expired bedrock key: <img width="962" height="126" alt="Screenshot 2026-06-22 at 3 56 51 PM" src="https://github.com/user-attachments/assets/7e21cc7c-798e-4662-8467-7f304a2f2b59" /> * Make formatter output quiet on success (#29467) ## Why `just fmt` is quite noisy even on successful runs. ## What Only print output when a formatter fails. - Buffer output from each formatter and print only a failed command and its diagnostics. - Prefix the `justfile` driver invocations with `@` so Just does not echo the command itself. - Retain rustfmt stderr on failure and cover silent-success and failure-reporting behavior. ## Validation - Confirmed `just fmt` and `just fmt-check` both exit successfully with empty stdout and stderr. * PAC 4 - Add macOS system proxy resolver (#26709) ## Summary Stacked on #26708. Adds the macOS implementation of the shared system-proxy contract. This allows Codex-owned auth clients to use the route macOS selects for each auth URL through SystemConfiguration and CFNetwork, including PAC and WPAD results. The `respect_system_proxy` feature is disabled by default, so existing client behavior remains unchanged unless explicitly enabled. ## Implementation - Adds the macOS-only `system-configuration` dependency to `codex-client`. - Dispatches system-proxy resolution to `outbound_proxy/macos.rs` on macOS. - Reads system proxy settings from `SCDynamicStore` and resolves the target URL with `CFNetworkCopyProxiesForURL`. - Executes PAC URLs and inline PAC JavaScript through a bounded run loop with a five-second timeout. - Handles `DIRECT`, HTTP proxies, and CFNetwork HTTPS entries using HTTP CONNECT; unsupported SOCKS entries map to `UnsupportedProxyScheme`. - Builds concrete proxy URLs from host and port entries, including IPv6 host bracketing. - Maps results into the shared `SystemProxyDecision::{Direct, Proxy, Unavailable}` contract. - Hashes URL-specific cache keys so PAC decisions remain distinct without retaining raw request URLs or query strings. ## End-user behavior - Disabled/default: existing client behavior is unchanged. - Enabled with `[features.respect_system_proxy]`: - macOS auth clients honor system proxy configuration, PAC, and WPAD; - valid OS/PAC `DIRECT` decisions use a direct connection; - unavailable system resolution falls back to explicit environment proxy variables, then `DIRECT`, through the shared contract from #26707. - Unsupported proxy schemes are not silently translated into another route. - Custom CA handling remains separate from proxy selection. - Known limitation: only the first supported system/PAC candidate is used. Subsequent proxy or `DIRECT` candidates are not attempted after a connection failure. This matches the current Windows behavior and leaves room for future ordered-fallback support. ## Tests - `just test -p codex-client` — 34 tests passed. - `just clippy -p codex-client` - `just fmt` - `just bazel-lock-check` * chore: warn when Code Mode lacks model metadata (#29490) * mcp: accept foreign absolute cwd for remote stdio (#29493) ## Why Remote stdio MCP servers can run in an environment whose path convention differs from the Codex host. A Windows cwd such as `C:\Users\openai\share` is absolute for the executor but was rejected by a POSIX orchestrator. Built on #29501, now merged, which only clarifies the host-native `PathUri` constructor name. ## What changed - Deserialize MCP cwd values as `LegacyAppPathString` so config does not apply host path rules. - Interpret that spelling as host-native for local launches and convert it to `PathUri` at executor launch. - Skip host filesystem and command resolution checks for remote stdio in `codex doctor`. - Add host-independent config and executor-boundary coverage using the foreign path convention for each test platform. ## Validation - `just test -p codex-utils-path-uri -p codex-config -p codex-mcp -p codex-rmcp-client` (408 passed) - `just test -p codex-cli -p codex-rmcp-client` (372 passed) - `cargo check --workspace --tests` - `just test` (11,311 passed; 43 unrelated environment/timing failures) - `just fix -p codex-cli -p codex-config -p codex-core -p codex-mcp -p codex-mcp-extension -p codex-rmcp-client -p codex-tui` * Propagate safety buffering treatment metadata (#29473) ## Summary - read the request-scoped safety-buffering treatment from HTTP response headers and per-turn WebSocket metadata through one shared header parser - combine that treatment with Responses API safety-buffering signals - propagate `showBufferingUi` and nullable `fasterModel` through the existing `model/safetyBuffering/updated` app-server notification - update the ap…
Adds dark-mode plugin logo metadata to local manifests, remote catalog responses, and the app-server protocol.
What
logoDarkandlogoUrlDarkfields toPluginInterface.interface.logoDarkassets and maps remotelogo_url_darkvalues.Why
Plugin clients need both light and dark logo assets to display a readable brand mark in either theme. The new fields are additive, so clients that do not use them keep the existing logo behavior.
How
Local plugin manifests expose
interface.logoDarkas a package-relative asset path. Remote plugin catalog responses exposelogo_url_dark. Both values map into separate app-server fields so clients can preserve local-path and remote-URL handling.Testing
just test -p codex-app-server-protocoljust test -p codex-core-pluginsjust test -p codex-app-serverjust test -p codex-pluginjust test -p codex-skillscargo build --bin codexinterface.logoDarkwith the bundled validator.just fixcommands andjust fmt.Manual verification
Create a local plugin with both
interface.logoandinterface.logoDark, then callplugin/listorplugin/read. Confirm that the response contains separatelogoandlogoDarkpaths. For a remote catalog entry, confirm thatlogoUrlDarkis populated fromlogo_url_dark.Risk
The app-server change is additive and nullable. The main risk is an incomplete mapping path, covered by local-manifest, remote-catalog, protocol serialization, and app-server integration tests.
Spiciness: 2/5
Issue: N/A — coordinated maintainer change.