Commit Graph

6501 Commits

  • feat(app-server): filter threads by parent (#26662)
    ## Why
    
    Clients that display or coordinate spawned subagents need an
    authoritative snapshot of a thread's immediate spawned children when
    they connect to app-server or recover after missing live events.
    `thread/list` cannot query by parent, so clients must otherwise scan
    unrelated threads or reconstruct relationships from rollout history and
    transient events.
    
    The direct spawn relationship already exists in persisted
    `thread_spawn_edges` state. Review and Guardian threads do not
    participate in that lifecycle and are intentionally outside this
    filter's scope.
    
    ## What changed
    
    This adds an experimental `parentThreadId` filter to `thread/list`.
    Parent-filtered requests return direct spawned children from persisted
    state while preserving the existing response shape, explicit filters,
    sorting, and timestamp-only cursor behavior. The lookup does not read
    rollout transcripts or recursively return descendants.
    
    Supersedes #25112 with the narrower `thread/list` filter approach.
    
    ## How it works
    
    1. An experimental client passes a valid thread ID as `parentThreadId`.
    2. App-server routes the list through the existing thread-store and
    state-database boundaries.
    3. SQLite selects threads whose IDs have a direct persisted spawn edge
    from that parent.
    4. Omitted provider and source filters include all values; explicit
    filters keep ordinary `thread/list` semantics.
    5. Grandchildren, Review threads, and Guardian threads are excluded.
    
    ## Verification
    
    State (144 tests), rollout (69 tests), and focused app-server
    thread-list (31 tests) suites passed. Scoped Clippy checks and
    repository formatting also passed. Coverage includes direct spawned
    children, omitted grandchildren, pagination, malformed IDs, mixed source
    kinds, explicit filters, and operation without rollout files.
  • [codex] exec-server honors remote environment cwd and shell (#28122)
    ## Why
    
    Next slice needed to make progress on the `remote_env_windows` test is
    to support passing a Windows cwd for the remote environment and using
    that environment's native shell. This lets the test run a real Windows
    process instead of only recording an early path or shell mismatch.
    
    ## What
    
    - change `TurnEnvironmentSelection.cwd` from `AbsolutePathBuf` to
    `PathUri`
    - convert local cwd values to URIs when constructing selections
    - preserve a remote primary cwd instead of replacing it with the local
    legacy fallback
    - prefer the selected environment's discovered shell for unified exec,
    falling back to the session shell when unavailable
    - convert back to a host-native absolute path at current native-only
    consumer boundaries
    - reject or deny unsupported foreign cwd values at the existing
    request-permissions boundary, with TODOs for its future migration
    - extend the hermetic Wine test to execute Windows PowerShell in
    `C:\windows` and verify successful process completion
    - record the current app-server rejection against the same Wine-backed
    remote Windows fixture when its cwd is supplied as a native Windows path
  • path-uri: render native paths across platforms (#27819)
    ## Why
    
    We're moving to `PathUri` in more places to support cross-OS
    app-server/exec-server, but we don't want to expose the URI encoding to
    users of app-server's public APIs yet.
    
    We'll need to translate at the app-server API boundary between
    client-visible "regular" paths that are appropriate for the OS of the
    environment for which the paths make sense, which means using the
    environment's path personality to do the conversion.
    
    `PathUri` doesn't yet attempt to encode environment ID, so for now we'll
    sniff the most likely path convention for a given path.
    
    ## What
    
    - Add `PathConvention` and `NativePathString` with host-independent
    POSIX, Windows drive, and UNC rendering.
    - Cover cross-host rendering, encoding, Unicode, invalid components.
  • build: run buildifier from just fmt (#28125)
    ## Intent
    
    Keep Bazel and Starlark files consistently formatted without requiring
    contributors to install or version buildifier themselves.
    
    ## Implementation
    
    - Add a SHA-256-pinned, cross-platform DotSlash manifest for buildifier
    v8.5.1.
    - Run buildifier from the shared `just fmt` and `just fmt-check` driver,
    with Windows-safe explicit DotSlash invocation.
    - Provision DotSlash in formatting CI and contributor devcontainers, and
    document the source-build prerequisite.
    - Apply the initial mechanical buildifier formatting baseline.
  • [codex] Pin bundled SQLite to fixed WAL-reset version (#27992)
    ## Summary
    
    Prevent dependency refreshes from silently downgrading Codex's bundled
    SQLite to a release affected by the WAL-reset corruption bug.
    
    SQLx 0.9 accepts a broad `libsqlite3-sys` range. An unrelated lock
    refresh therefore moved Codex from `libsqlite3-sys 0.37.0` back to
    `0.35.0`, changing the bundled SQLite runtime from 3.51.3 to 3.50.2.
    SQLite documents the affected versions and fix in [The WAL Reset
    Bug](https://www.sqlite.org/wal.html#the_wal_reset_bug) and the [SQLite
    3.51.3 changelog](https://www.sqlite.org/changes.html#version_3_51_3).
  • [codex] Dedupe plugin MCPs by app declaration name (#27607)
    ## Context
    
    This is the next step in the plugin auth-routing stack. The earlier PRs
    make `PluginsManager` auth-aware and move the broad App/MCP surface
    decision into that layer. This PR narrows the ChatGPT/SIWC behavior so
    we only hide a plugin MCP server when it conflicts with an App
    declaration of the same name.
    
    In product terms: if a plugin exposes both an App route and MCP route
    for `foo`, ChatGPT/SIWC sessions should use the App route for `foo`. If
    the same plugin also exposes a separate MCP server like `foo2`, that MCP
    server should remain available.
    
    ```json
    // .app.json
    {
      "apps": {
        "foo": {
          "id": "connector_abc"
        }
      }
    }
    ```
    
    ```json
    // .mcp.json
    {
      "mcpServers": {
        "foo": {
          "url": "https://mcp.foo.com/mcp"
        },
        "foo2": {
          "url": "https://mcp.foo2.com/mcp"
        }
      }
    }
    ```
    
    ## Stack
    
    - PR1: #27652 seed plugin manager auth at construction.
    - PR2: #27459 route plugin surfaces by auth mode.
    - PR3: #27607 dedupe plugin MCP servers by App declaration name.
    - PR4: #27602 preserve plugin Apps in connector listings.
    - PR5: #27461 skip install-time plugin MCP OAuth for matching App
    routes.
    
    ## Summary
    
    - Preserve App declaration names in loaded plugin metadata.
    - Keep public effective App outputs as deduped connector IDs for
    existing callers.
    - For ChatGPT/SIWC, suppress only plugin MCP servers whose names match
    declared App names.
    
    ## Validation
    
    ```bash
    cargo fmt --all
    cargo test -p codex-core-plugins plugin_auth_projection
    cargo test -p codex-core-plugins effective_apps
    cargo test -p codex-core-plugins read_plugin_for_config_installed_git_source_reads_from_cache_without_cloning
    cargo test -p codex-core explicit_plugin_mentions_use_apps_for_chatgpt_dual_surface_plugins
    cargo test -p codex-core explicit_plugin_mentions_keep_non_conflicting_mcp_for_chatgpt_auth
    cargo test -p codex-app-server --test all plugin_install_filters_disallowed_apps_needing_auth
    git diff --check
    ```
    
    ---------
    
    Co-authored-by: Xin Lin <xl@openai.com>
  • [codex] Carry exec-server cwd as PathUri (#28032)
    ## Why
    
    This is the second-to-last place in the exec-server protocol that needs
    to migrate to URIs to support cross-OS operation.
    
    ## What
    
    - Change `ExecParams.cwd` to `PathUri`.
    - Keep the cwd URI-shaped through core and rmcp producers, converting it
    to `AbsolutePathBuf` only in `LocalProcess::start_process`.
    - Reject non-native cwd URIs before launch and update the affected
    protocol documentation and call sites.
  • [codex] Send turn state through compact requests (#28002)
    ## Context
    
    Inline compaction is part of the active logical turn. Compact requests
    and the sampling requests around them should use the same turn state,
    including when compaction is the first request to establish it.
    
    ## Change
    
    Pass the turn-scoped `OnceLock` directly to inline v1 compaction so
    `/responses/compact` includes an established value in the existing HTTP
    header. Capture `x-codex-turn-state` from the compact response into that
    same lock, allowing pre-turn compact to establish the value that
    subsequent sampling reuses.
    
    V2 compact already uses the normal Responses HTTP/WebSocket path and
    continues to share the same `OnceLock` without separate plumbing. The
    first returned value wins for the logical turn.
    
    ## Test plan
    
    Integration coverage verifies that:
    
    - pre-turn v1 compact can establish state for the first sampling request
    - inline v1 compact receives established state over HTTP
    - inline v2 compact reuses established state over HTTP
    - inline v2 compact reuses established state over WebSocket
    
    CI validates the full change.
  • [codex] Send request-scoped turn state over WebSocket (#27996)
    ## Context
    
    Turn state is scoped to one logical turn, but the WebSocket path
    currently exchanges it through upgrade headers, which are scoped to the
    physical connection. A connection may be reused across turns, so its
    handshake cannot represent the turn lifecycle reliably.
    
    ## Change
    
    Exchange turn state on each WebSocket response request instead:
    
    - send an established value in `response.create.client_metadata`
    - read the returned value from the existing `response.metadata` event
    - retain the first value in the turn-scoped `ModelClientSession`
    `OnceLock`
    - start the next logical turn without state, even when it reuses the
    same WebSocket connection
    
    This gives WebSocket requests the same first-value-wins contract as the
    existing HTTP path.
    
    ## Test plan
    
    Integration coverage verifies that:
    
    - WebSocket replays returned state on same-turn follow-ups
    - later response metadata does not replace the first value
    - state resets at the logical turn boundary without requiring a
    reconnect
    
    CI validates the full change.
    
    ## Stack
    
    This is 1/2. #28002 builds on this request-scoped transport to carry
    established state through compact requests.
  • [codex] Add hermetic Wine exec-server test (#27937)
    ## Why
    
    We want to make it possible for an app-server orchestrator on one OS to
    control an exec-server on another host running a different OS. In
    practice this kinda already works if you get lucky and the two hosts
    have the same path format, but we mangle quite a lot of operations if
    either end is Windows.
    
    This test starts exercising that interaction, although right now the
    initial bootstrap fails. Future changes will expand the test's
    assertions to match improved support.
    
    ## What
    
    Stacked on #27964. This adds a small Windows exec-server fixture and a
    Linux protocol smoke test using the reusable Wine harness, covering
    Windows environment discovery, non-TTY `cmd.exe` execution, output, exit
    status, and working directory.
    
    Once we've got the full codex binary cross-building under Bazel we could
    consider moving to the real binary instead of the stripped down
    exec-server-only binary used here.
  • feat(app-server): enforce managed remote control disable (#27961)
    ## Why
    
    Managed deployments need a reliable deny gate for remote control.
    Persisted enablement and explicit startup requests currently remain able
    to start the transport, while the removed `features.remote_control` key
    is intentionally only a compatibility no-op.
    
    This adds a dedicated requirement that administrators can use to force
    remote control off without deleting the user's persisted preference.
    Removing the requirement and restarting restores the prior choice.
    
    ## What Changed
    
    - Added top-level `allow_remote_control` requirements parsing, sourced
    layer precedence, debug output, and `configRequirements/read` exposure
    as `allowRemoteControl`.
    - Added a typed transport policy captured from the startup requirements
    snapshot. Managed disable forces the initial state to disabled and
    prevents enrollment, refresh, connection, and persisted-preference
    mutation.
    - Rejected every `remoteControl/*` RPC before parameter deserialization
    with JSON-RPC `-32600` and `remote control is disabled by managed
    requirements`.
    - Preserved the existing disabled status notification and the previous
    behavior when the requirement is `true` or omitted.
    - Regenerated app-server protocol schemas and documented the new
    requirement.
    
    ## Verification
    
    - Confirmed all remote-control RPCs, including a malformed request,
    return the managed-policy error while the initial status notification
    remains `disabled`.
    - Confirmed explicit ephemeral startup and persisted enablement make no
    backend connection and leave the SQLite preference unchanged.
    - Confirmed `allow_remote_control = true` does not enable or block
    remote control and `configRequirements/read` returns
    `allowRemoteControl: false` for the deny policy.
    
    Related issue: N/A (managed-policy hardening).
  • [codex] Gate plugin MCP servers by auth route (#27459)
    ## Context
    
    Some plugins expose both Apps and MCP servers. This PR moves auth-aware
    surface projection into `core-plugins::PluginsManager`, so callers get a
    consistent effective plugin view. Later PRs narrow the conflict rule and
    update listing/install paths.
    
    The high level goal of this PR is to set up the plumbing to
    conditionally filter App/MCP in the plugin manager layer. We start by
    removing MCP servers when using SIWC/Codex-backend auth, and removing
    Apps when using API-key-style auth.
    
    This PR is now stacked on #27652, which contains only the constructor
    plumbing for seeding `PluginsManager` with the current auth mode.
    
    ## Stack
    
    - PR1: #27652 seed plugin manager auth at construction.
    - PR2: #27459 route plugin surfaces by auth mode.
    - PR3: #27607 dedupe plugin MCP servers by App declaration name.
    - PR4: #27602 preserve plugin Apps in connector listings.
    - PR5: #27461 skip install-time plugin MCP OAuth for matching App
    routes.
    
    ## Summary
    
    - API-key/non-ChatGPT routes hide plugin Apps and keep plugin MCPs.
    - ChatGPT/SIWC with Apps enabled keeps plugin Apps and suppresses MCPs
    for dual-surface plugins.
    - MCP-only plugins stay available for ChatGPT/SIWC sessions.
    - Cached plugin load outcomes are re-projected when auth mode changes.
    
    ## Validation
    
    ```bash
    cargo test -p codex-core-plugins plugin_auth_projection
    cargo test -p codex-core list_tool_suggest_discoverable_plugins
    git diff --check
    ```
  • [codex] Add auth mode to plugin manager constructor (#27652)
    ## Context
    
    Plugins can expose more than one way for Codex to use them: App
    connectors for ChatGPT/SIWC-backed sessions and MCP servers for API key
    login sessions. The broader goal is to make `PluginsManager` the place
    that understands which plugin surfaces should be visible for the current
    auth route, so callers do not each have to make that decision
    themselves.
    
    This PR is the small setup step for that work. It lets the plugin
    manager be created with the current `AuthMode`, which gives the followup
    auth routing PRs the information they need without relying on setter
    injection.
    
    ## Stack
    
    - PR1: #27652 seed plugin manager auth at construction.
    - PR2: #27459 route plugin surfaces by auth mode.
    - PR3: #27607 dedupe plugin MCP servers by App declaration name.
    - PR4: #27602 preserve plugin Apps in connector listings.
    - PR5: #27461 skip install-time plugin MCP OAuth for matching App
    routes.
    
    ## Summary
    
    - Let `PluginsManager::new_with_restriction_product` accept an initial
    `AuthMode`.
    - Keep `PluginsManager::new` behavior unchanged for ordinary callers.
    
    ## Validation
    
    ```bash
    cargo test -p codex-core-plugins plugins_manager_tracks_auth_mode
    cargo test -p codex-core list_tool_suggest_discoverable_plugins
    git diff --check
    ```
    
    ---------
    
    Co-authored-by: Xin Lin <xl@openai.com>
  • [codex] Limit app-based plugin suggestions to remote catalogs (#27988)
    ## Summary
    
    - Keep local plugin suggestions bounded to fallback and explicitly
    configured plugins.
    - Preserve app-overlap recommendations for remote plugins using cached
    catalog metadata.
    - Remove the WSL-specific local discovery exception and move
    manager-owned discovery tests into `codex-core-plugins`.
    
    ## Why
    
    Local curated marketplaces were allowlisted before plugin detail
    loading, so every uninstalled candidate could be deep-read before its
    app IDs were checked. That caused per-turn reads of candidate plugin
    manifests, skills, app configs, hooks, and MCP configs, which is
    especially expensive on slow disks.
    
    Remote discovery does not need those local candidate reads because app
    IDs are already available in the cached remote catalog. Installed local
    plugins are still loaded when needed to determine the user's installed
    app IDs.
    
    ## Validation
    
    - `just fmt`
    - `just test -p codex-core-plugins discoverable::tests` (13 passed)
    - `just test -p codex-core plugins::discoverable::tests` (4 passed)
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    - `git diff --check`
  • feat(tui): reland token activity command (#27925)
    ## Why
    
    [#25345](https://github.com/openai/codex/pull/25345) was approved,
    green, and squash-merged into its stacked base branch,
    `fcoury/tokenmaxxing-api`. Four minutes later, that base branch was
    force-pushed back to an API-only rebased head while preparing
    [#25344](https://github.com/openai/codex/pull/25344) for `main`. As a
    result, the squash commit from #25345 was orphaned and the TUI command
    never reached `main` or a release.
    
    This PR relands the orphaned TUI change from
    [`411410b8`](https://github.com/openai/codex/commit/411410b85c2d8eb050d441f17396c5c4048d866f)
    on current `main`.
    
    ## What changed
    
    - Add `/usage`, `/usage daily`, `/usage weekly`, and `/usage cumulative`
    for account token activity.
    - Fetch account usage asynchronously through the existing
    `account/usage/read` app-server RPC.
    - Render daily, weekly, and cumulative activity with theme-aware
    terminal palettes and bounded transient cards.
    - Preserve transcript ordering while assistant streams, history
    consolidations, active cells, and hooks complete.
    - Hide `/usage` from completion when backend auth is unavailable while
    keeping typed-command guidance.
    - Carry current-main behavior forward for cwd-aware Markdown parsing,
    Windows Terminal color detection, and personal access token auth.
    - Clear pending usage cards on thread rollback and delay completed cards
    until live hook output is committed.
    - Add focused regression and snapshot coverage for loading, auth errors,
    invalid views, rollback, hook ordering, layout, and charts.
    
    ## Prior review
    
    The original implementation was approved by Eric Traut in #25345 after
    testing multiple themes and light/dark terminals. This PR preserves that
    reviewed implementation while adapting it to current `main` and adding
    regression coverage for newer rollback and hook lifecycle behavior.
    
    ## Validation
    
    - `just test -p codex-tui token_activity palette renderable
    usage_command` — 37 passed.
    - Focused rollback, hook-ordering, and error snapshot tests — 4 passed.
    - `just fix -p codex-tui` — passed.
    - `UV_CACHE_DIR=/private/tmp/codex-uv-cache just fmt` — passed.
    - `cargo insta pending-snapshots` — no pending snapshots.
    - `just test -p codex-tui` — 2,870 passed; two unrelated guardian
    feature-flag tests failed because their expected `OverrideTurnContext`
    event was absent:
    -
    `update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`
    -
    `update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
    - `just argument-comment-lint` could not complete because the local
    Bazel LLVM `compiler-rt` repository is missing `include/sanitizer/*.h`.
    The touched Rust diff was manually inspected and no missing
    opaque-literal argument comments were found.
  • [3 of 3] Support images in TUI goals (#27510)
    ## Stack
    
    1. [1 of 3] Support long raw TUI goal objectives - #27508
    2. [2 of 3] Support long pasted text in TUI goals - #27509
    3. **[3 of 3] Support images in TUI goals** - this PR
    
    ## Why
    
    The first two PRs make goal definitions resilient to long text, but
    `/goal` still dropped image inputs from the composer. That meant a user
    could attach images while defining a goal and the resulting goal
    continuation would not have any useful reference to those images.
    
    Goal state still persists only objective text, so image inputs need to
    become paths or URLs that the agent can read later.
    
    ## What Changed
    
    - Extends TUI `GoalDraft` with local image attachments and remote image
    URLs.
    - Copies local goal images through the app-server filesystem layer into
    the managed goal attachment directory, then rewrites active image
    placeholders to file references.
    - Appends unplaced local images and remote image URLs to the objective
    as referenced image files or URLs.
    - Preserves goal image metadata through live `/goal` submission and
    queued `/goal` dispatch.
    
    ## Verification
    
    - Added goal materialization coverage for local image files and remote
    image URLs.
    - Added/updated TUI slash-command coverage showing `/goal` drafts
    include attached images instead of dropping them.
    
    ## Manual Testing
    
    - Attached an image by bracketed-pasting its local path into a live
    `/goal` composer. The `[Image #1]` placeholder became a server-host
    `image-1.png` reference, copied bytes matched exactly, and no attachment
    was written under the TUI's local home.
    - Deleted an image placeholder before submitting a small goal and
    verified no image was copied.
    - Attached PNG and JPEG files to the same goal. Placeholder order was
    preserved as `image-1.png` and `image-2.jpg`, and both remote copies
    matched their source bytes.
    - Tried extensionless, malformed-extension, and
    extension/content-mismatched paths; the composer rejected them as image
    attachments before goal dispatch rather than creating misleading managed
    image files.
    - Combined a local image, a large pasted block, and enough raw text to
    exceed 4,000 characters. The remote attachment directory contained the
    image, paste sidecar, and `goal-objective.md`; all embedded references
    used server-host paths and both payloads matched their sources.
    - Submitted an image replacement while a goal was active, verified no
    image was copied before confirmation, then canceled and confirmed the
    attachment count was unchanged.
  • [codex] add latency tracing spans (#27710)
    ## Why
    
    We have some large gaps in our thread start, resume, and pre-sampling
    traces that make it hard to tell where latency is coming from.
    
    ## What Changed
    
    - Added coarse spans around thread start/resume, turn context
    construction, rollout reconstruction, skill/plugin loading, and tool
    preparation.
    - Added a breakdown of discoverable-tool preparation across connector
    loading, plugin discovery, and local plugin details.
    
    ## Testing
    
    - `cargo check -p codex-app-server -p codex-core -p codex-core-skills -p
    codex-core-plugins`
    - Built the app-server locally and exercised thread start, first turn,
    follow-up turn, server restart, thread resume, and a resumed turn.
  • [codex] make PathUri::from_abs_path infallible (#27976)
    ## Why
    
    `PathUri::from_abs_path` can fail for absolute paths that do not have a
    normal `file:` URI representation, forcing filesystem call sites to
    handle a conversion error even though the original path can be preserved
    losslessly.
    
    ## What
    
    Make `from_abs_path` infallible and migrate its callers. Unrepresentable
    paths use `file:///%00/bad/path/<base64>`, encoding Unix bytes or
    Windows UTF-16LE; `to_abs_path` validates and decodes that fallback. The
    leading encoded null reserves a namespace that cannot collide with a
    real Unix or Windows path, and fallback URIs remain opaque to lexical
    path operations.
    
    ## Validation
    
    Added path-URI coverage for Unix null and non-UTF-8 paths, Windows
    device/verbatim and non-Unicode paths, serialization, malformed
    fallbacks, opaque lexical operations, invalid native payloads, and
    literal `/bad/path` collision resistance.
  • [codex] Let generic test turns inherit their environment (#27972)
    ## Why
    
    The paired thread-environment migration changed several generic test
    turn helpers from supplying a fallback cwd to explicitly selecting the
    local environment. That changes their meaning under
    `build_with_remote_env()`: remote-only fixtures cannot resolve the
    forced local selection, so the tests fail before exercising apply-patch,
    RMCP, unified-exec, or view-image behavior.
    
    Generic helpers should inherit the environment selected by their
    fixture. Tests that intentionally exercise local routing continue to
    select the local environment explicitly.
    
    ## What changed
    
    - Remove forced `local_selections(...)` overrides from the generic
    apply-patch, RMCP, unified-exec, and view-image turn helpers.
    - Remove the imports made unused by those deletions.
    
    ## Testing
    
    - Not run locally; this is a test-fixture-only change and the `full-ci`
    branch will exercise the affected remote shards.
  • Promote TUI unified mentions in composer to default mentions feature (#27499)
    ## Summary
    
    This PR promotes Mentions 2.0 (unified TUI mention popup) to stable and
    enables it by default.
    
    - Keep `mentions_v2` as a temporary rollback path to the legacy split
    popups (`--disable mentions_v2`).
    - Add feature-default and snapshot coverage for the default experience.
    
    ## Prior work
    
    - [#19068 — Unified mentions in
    TUI](https://github.com/openai/codex/pull/19068)
    - [#22375 — Use plugin/list to get plugins for
    mentions](https://github.com/openai/codex/pull/22375)
    - [#23363 — Unified mentions tweaks and rendering
    polish](https://github.com/openai/codex/pull/23363)
    
    ## Test plan
    
    - Launch Codex without any feature overrides.
    - Type `@` in the TUI composer.
    - Confirm the unified mentions menu opens and displays filesystem,
    plugin, and skill results.
  • Add Guardian catalog diagnostics metadata (#27109)
    ## Why
    
    We need request-level evidence for Guardian cases where
    `codex-auto-review` is missing from the client-side model catalog and
    the review falls back to the parent model.
    
    ## What changed
    
    - Add `guardian_catalog_contains_auto_review` to Guardian Responses API
    client metadata.
    - Add `guardian_model_provider_id` to Guardian Responses API client
    metadata.
    - Keep review-session metadata optional so callers without metadata
    preserve the existing `None` path.
    - Add tests for override, normal preferred-model, and
    missing-auto-review-catalog behavior.
    
    ## Validation
    
    - `just test -p codex-core
    guardian_review_records_missing_auto_review_model_in_request_metadata`
    - `just test -p codex-core
    guardian_review_uses_model_catalog_override_when_preferred_review_model_exists`
    - `just test -p codex-core
    guardian_review_uses_preferred_review_model_without_model_catalog_override`
    - `git diff --check origin/main`
  • [2 of 3] Support long pasted text in TUI goals (#27509)
    ## Stack
    
    1. [1 of 3] Support long raw TUI goal objectives - #27508
    2. **[2 of 3] Support long pasted text in TUI goals** - this PR
    3. [3 of 3] Support images in TUI goals - #27510
    
    ## Why
    
    Large text pasted into the TUI composer is represented as a paste
    placeholder plus pending paste metadata. For `/goal`, preserving only
    the visible placeholder is not enough: the agent would see a short
    placeholder string instead of the actual pasted text, and the long-text
    support from the first PR would never see the payload.
    
    The TUI also needs to avoid writing stale sidecar files when a user
    pastes a large block and then deletes its placeholder before submitting
    the goal.
    
    ## What Changed
    
    - Introduces a TUI `GoalDraft` for goal submissions so `/goal`, `/goal
    edit`, and queued goal commands can carry objective text plus text
    elements and pending paste payloads.
    - Materializes active pasted-text placeholders to `pasted-text-N.txt`
    files through the app-server filesystem path introduced in #27508.
    - Rewrites active paste placeholders in the persisted objective to file
    references, while leaving literal placeholder-looking text alone.
    - Filters out deleted paste placeholders so otherwise-small goals do not
    require `$CODEX_HOME` or remote filesystem writes.
    - Preserves pending paste metadata when a `/goal` command is queued
    before a thread exists.
    
    ## Verification
    
    - Added goal materialization tests for active paste placeholders,
    deleted paste placeholders, and whitespace-only paste payloads.
    - Added/updated TUI slash-command tests for large pasted text, queued
    `/goal` commands before thread start, and queued oversized goal
    behavior.
    
    ## Manual Testing
    
    - Used real terminal bracketed-paste sequences through a remote TUI
    session. A 1,228-byte multiline paste became `pasted-text-1.txt`; its
    first/last lines and byte count matched exactly, and the persisted
    objective referenced the server-host path.
    - Pasted a large block, deleted its placeholder, and submitted a small
    replacement objective. No new directory or sidecar file was created.
    - Added two same-length large pastes to one goal. The composer
    disambiguated their visible placeholders, and materialization preserved
    order and contents in `pasted-text-1.txt` and `pasted-text-2.txt`.
    - Submitted a whitespace-only large paste and verified the goal was
    rejected as empty without writing a file.
    - Submitted a pasted-text replacement while another goal was active,
    verified no file was written before confirmation, then canceled and
    confirmed the original goal remained unchanged.
    - Combined a large paste with enough raw text to exceed 4,000 characters
    after placeholder rewriting. The paste sidecar and `goal-objective.md`
    were created in the same remote attachment directory, and `/goal edit`
    restored the rewritten objective with its sidecar reference.
  • [codex] add roles to realtime append text (#27936)
    ## Summary
    
    Add an explicit `user` or `developer` role to
    `thread/realtime/appendText` and propagate it through the realtime input
    queue into `conversation.item.create`. Older JSON clients that omit the
    field continue to default to `user`.
    
    This lets app-provided context such as memory retain developer authority
    without bypassing app-server through a renderer-owned data channel. The
    app-server schemas, API documentation, and focused protocol and
    websocket coverage are updated with the new contract.
    
    The Codex Apps consumer is tracked in
    [openai/openai#1025261](https://github.com/openai/openai/pull/1025261).
  • feat: use encrypted local secrets for MCP OAuth (#27541)
    ## Summary
    
    - store MCP OAuth credentials in the configured auth credential backend
    - support encrypted-local OAuth storage, including legacy keyring
    migration
    - propagate the credential backend through MCP refresh, session, CLI,
    and app-server paths
    
    ## Stack
    
    1. #27504 — config and feature flag
    2. #27535 — auth-specific secret namespaces
    3. #27539 — encrypted CLI auth storage
    4. this PR — encrypted MCP OAuth storage
    
    This is a parallel review stack; the original #17931 remains unchanged.
    
    ## Tests
    
    - `just test -p codex-rmcp-client` (the transport round-trip test passed
    after building the required `codex` binary and retrying)
    - `just test -p codex-mcp`
    - `just test -p codex-app-server
    refresh_config_uses_latest_auth_keyring_backend`
    - `just test -p codex-core
    refresh_mcp_servers_is_deferred_until_next_turn`
    - `just test -p codex-cli mcp`
    - `just fix -p codex-rmcp-client -p codex-mcp -p codex-core -p codex-cli
    -p codex-app-server -p codex-protocol`
    - `just bazel-lock-check`
  • Warn for structured feature toggles (#27076)
    ## Summary
    Startup warnings for under-development features only recognized bare
    boolean toggles like `features.foo = true`. An upcoming feature will use
    table-format config, so `features.foo = { enabled = true, ... }` needs
    to count as an explicit opt-in too.
    
    This updates the warning predicate to recognize structured tables with
    `enabled = true`, while leaving tables without that field unwarned.
    
    ## Testing
    - `just fmt`
    - `just test -p codex-features
    unstable_warning_event_mentions_enabled_structured_under_development_feature`
  • feat: use encrypted local secrets for CLI auth (#27539)
    ## Why
    
    Windows Credential Manager limits generic credential blobs to 2,560
    bytes. Large serialized ChatGPT auth payloads can exceed that limit, so
    keyring-mode CLI auth needs a backend that keeps only the encryption key
    in the OS keyring and stores the payload in Codex's encrypted
    local-secrets file.
    
    This is the third PR in the encrypted-auth stack:
    
    1. #27504 — feature and config selection
    2. #27535 — auth-specific local-secrets namespaces
    3. This PR — CLI auth implementation and activation
    4. MCP OAuth implementation and activation
    
    ## What Changed
    
    - Added encrypted CLI-auth storage using the `CliAuth` secrets
    namespace.
    - Preserved direct keyring storage for platforms/configurations where it
    remains selected.
    - Selected the backend consistently for login, logout, refresh,
    device-code login, auth loading, and login restrictions.
    - Threaded resolved bootstrap/full config through CLI, exec, TUI,
    app-server account handling, cloud config, and cloud tasks.
    - Removed stale `auth.json` fallback data after successful encrypted
    saves and removed encrypted, direct-keyring, and fallback data during
    logout.
    - Added storage and integration coverage for both direct and encrypted
    keyring modes.
    
    MCP OAuth persistence is intentionally left to the next PR.
    
    ## Validation
    
    - `just test -p codex-login` — 131 passed
    - `just test -p codex-cli` — 280 passed
    - `just test -p codex-app-server v2::account` — 25 passed
    - `just test -p codex-cloud-config service` — 21 passed, 7 skipped
    - `just fix -p codex-login`
    - `just fix -p codex-cli`
    - `just fmt`
  • Remove TUI realtime voice support (#27801)
    ## Why
    
    Removes the realtime audio support from TUI.
    
    ## What Changed
    
    - Removed the TUI `/realtime` and realtime `/settings` command paths.
    - Deleted TUI voice capture/playback, WebRTC session handling,
    audio-device selection UI, and recording-meter code.
    - Removed TUI realtime tests and snapshots that covered the deleted
    surfaces.
    - Dropped the TUI-only `cpal` and `codex-realtime-webrtc` dependencies
    and refreshed the Rust/Bazel locks.
  • Support plaintext agent messages (#27830)
    ## Why
    
    Multi-agent v2 `send_message` deliveries already reach the receiving
    model as typed `agent_message` items with encrypted content.
    Child-completion notifications are generated by Codex itself, so their
    content is plaintext and previously fell back to a serialized JSON
    envelope inside an assistant message.
    
    With plaintext `input_text` supported for `agent_message`, both delivery
    paths can use the same model-visible type while preserving explicit
    author and recipient metadata.
    
    ## What changed
    
    - add plaintext `input_text` support to `AgentMessageInputContent` and
    regenerate the affected app-server schemas
    - preserve `InterAgentCommunication` as structured mailbox input instead
    of converting it to assistant text
    - record delivered communications as typed `agent_message` history items
    - persist a dedicated rollout item so local delivery metadata such as
    `trigger_turn` remains available without leaking into the Responses
    request
    - reconstruct typed agent messages on resume and preserve fork-turn
    truncation behavior
    - remove request-time assistant-content parsing
    - preserve plaintext and encrypted inter-agent deliveries in stage-one
    memory inputs
    - normalize and link plaintext and encrypted agent messages in rollout
    traces without treating inbound messages as child results
    - cover the real MultiAgent V2 child-completion path end to end with
    deterministic mailbox synchronization
    
    ## Verification
    
    - `just test -p codex-core
    plaintext_multi_agent_v2_completion_sends_agent_message`
    - `just test -p codex-core input_queue_drains_mailbox_in_delivery_order
    record_initial_history_reconstructs_typed_inter_agent_message
    fork_turn_positions_use_inter_agent_delivery_metadata`
    - `just test -p codex-memories-write
    serializes_inter_agent_communications_for_memory`
    - `just test -p codex-rollout-trace
    agent_messages_preserve_routing_and_content
    sub_agent_started_activity_creates_spawn_edge`
    - `just test -p codex-rollout-trace
    agent_result_edge_falls_back_to_child_thread_without_result_message`
    - `just test -p codex-protocol -p codex-rollout -p
    codex-app-server-protocol`
  • fix(plugins) rm plugin descriptions (#23254)
    ## Summary
    Removes Plugin descriptions from the dev message, since descriptions of
    skills and MCPs cover the capabilities offered by the plugin.
    
    ## Testing
    - [x] Updates unit tests
  • [codex] Align implicit skill reads with parser (#27926)
    ## Summary
    - reuse the shared shell read parser for implicit skill doc invocation
    detection
    - add regression coverage for `nl -ba .../SKILL.md`
    
    ## Why
    Desktop could render `Read User Context skill` for reads recognized by
    the shared command parser, while implicit `skill_invocation` analytics
    used a separate reader allowlist and missed cases such as `nl`.
    
    ## Validation
    - `HOME=/private/tmp/codex-core-skills-home-pr
    PATH=/Users/alexsong/.cache/cargo-home/bin:$PATH
    CARGO_HOME=/Users/alexsong/.cache/cargo-home just test -p
    codex-core-skills`
    - `git diff --cached --check`
    - `just fmt` attempted; Rust formatting completed, but the Python
    formatters could not download uncached Ruff wheels because
    `files.pythonhosted.org` is blocked in this sandbox.
    - `bazel mod deps --lockfile_mode=update/error
    --repo_env=ASPECT_TOOLS_TELEMETRY= --repo_env=DO_NOT_TRACK=1` evaluated
    the module graph and produced no `MODULE.bazel.lock` diff, but Bazel
    crashed on sandboxed `sysctl` during exit.
  • feat: add auth-specific encrypted secret namespaces (#27535)
    ## Why
    
    CLI auth and MCP OAuth credentials should use separate encrypted files
    while sharing the existing local-secrets implementation and
    OS-keyring-backed encryption key mechanism.
    
    This is the second PR in the encrypted-auth stack:
    
    1. #27504 — feature and config selection
    2. This PR — auth-specific local-secrets namespaces
    3. CLI auth implementation and activation
    4. MCP OAuth implementation and activation
    
    ## What Changed
    
    - Added `LocalSecretsNamespace` variants for shared secrets, CLI auth,
    and MCP OAuth.
    - Selected `local.age`, `cli_auth.age`, or `mcp_oauth.age` from the
    namespace.
    - Made atomic temporary filenames derive from the selected secrets
    filename.
    - Added namespaced `SecretsManager` construction and coverage proving
    the auth namespaces write separate encrypted files.
    - Made the default keyring store clonable for downstream namespaced auth
    backends.
    
    This PR does not activate either auth backend or change existing
    credential behavior.
    
    ## Validation
    
    - `just test -p codex-secrets` — 7 passed
    - `just test -p codex-keyring-store` — package has no test binaries
    - `just fmt`
  • [login] revoke existing auth before starting login (#27674)
    ## Why
    
    `codex login` previously persisted newly issued OAuth credentials and
    only then attempted to revoke the superseded refresh token. The old
    credential must be revoked before a replacement browser or device-code
    flow starts, and successful login must not perform any post-login
    revocation attempt.
    
    ## What changed
    
    - Revoke and clear existing stored auth before browser or device-code
    CLI login begins.
    - Remove superseded-token detection and revocation from the shared token
    persistence path; successful login now only saves the new credentials.
    - Read the raw configured auth store during CLI cleanup so
    environment-provided auth cannot mask the stored refresh token.
    - Preserve `auto` storage fallback semantics when keyring deletion fails
    by clearing the fallback auth file.
    - Add a process-level CLI regression test that requires the revoke
    request to precede every device-login request and occur exactly once.
    
    If replacement login is canceled or fails, the previous local
    credentials have already been cleared. Remote revocation remains best
    effort, matching explicit logout behavior.
    
    ## Validation
    
    ### Process-level before/after reproduction
    
    I compiled the real `codex` CLI from the pre-fix parent (`14df0e8833`)
    and from the PR implementation (`25c002f23b`; the login behavior is
    unchanged at the current head), then ran the same device-code flow
    against a local HTTP mock OAuth authority.
    
    Each run:
    
    1. Used a fresh temporary `CODEX_HOME` configured with
    `cli_auth_credentials_store = "file"`.
    2. Seeded that temporary home with managed ChatGPT auth containing
    `old-access` and `old-refresh` tokens.
    3. Pointed `CODEX_REVOKE_TOKEN_URL_OVERRIDE` at the mock `/oauth/revoke`
    endpoint.
    4. Ran the compiled CLI as:
    
       ```shell
       CODEX_HOME=<temporary-home> \
         CODEX_REVOKE_TOKEN_URL_OVERRIDE=<mock-issuer>/oauth/revoke \
    <compiled-codex> login --device-auth --experimental_issuer <mock-issuer>
       ```
    
    5. Recorded every request received by the mock authority. The mock
    marked `new-access` valid when `/oauth/token` issued it and invalidated
    it if `/oauth/revoke` arrived afterward, reproducing the observed
    session-invalidating failure mode. After login exited, the harness also
    verified the persisted refresh token and probed a protected endpoint
    with `new-access`.
    
    | Build | Observed request order | CLI/persistence result | `new-access`
    probe |
    | --- | --- | --- | --- |
    | Pre-fix | `usercode → device token → OAuth token →
    revoke(old-refresh)` | Exit `0`; `new-refresh` persisted | `401` |
    | PR | `revoke(old-refresh) → usercode → device token → OAuth token` |
    Exit `0`; `new-refresh` persisted | `200` |
    
    The PR run therefore issued exactly one revocation request, before any
    request that initiated the replacement login, and issued no revocation
    after token exchange.
    
    ### Regression coverage
    
    
    `codex-rs/cli/tests/login.rs::device_login_revokes_existing_auth_before_requesting_new_tokens`
    runs the real first-party `codex` binary against a `wiremock` OAuth
    server with an isolated temporary `CODEX_HOME`. It asserts:
    
    - the exact request sequence is `/oauth/revoke`,
    `/api/accounts/deviceauth/usercode`, `/api/accounts/deviceauth/token`,
    then `/oauth/token`;
    - there is exactly one revoke request and its body contains
    `old-refresh` with the `refresh_token` hint;
    - the completed login persists `new-refresh`.
    
    Local validation:
    
    - `just test -p codex-login` — 130 passed
    - `just test -p codex-cli` — 280 passed, including the new process-level
    regression test
    - `just bazel-lock-check`
  • feat: add secret auth storage configuration (#27504)
    ## Why
    
    Windows Credential Manager limits generic credential blobs to 2,560
    bytes. The encrypted local secrets backend avoids storing large
    serialized auth payloads directly in the OS keyring, but selecting that
    backend needs an independently reviewable feature/config layer before
    the auth and secrets implementation is wired in.
    
    ## What Changed
    
    - Added the stable `secret_auth_storage` feature, enabled by default on
    Windows and disabled by default elsewhere.
    - Added `AuthKeyringBackendKind` and config resolution for full and
    bootstrap config loading.
    - Applied managed feature requirements when resolving the bootstrap auth
    backend.
    - Updated the generated config schema and added focused tests.
    
    This is the base PR for #17931. The auth, secrets, MCP, CLI, TUI, and
    app-server implementation remains in that follow-up PR.
    
    ## Validation
    
    - `just test -p codex-features`
    - `just test -p codex-config`
    - `just test -p codex-core
    resolve_bootstrap_auth_keyring_backend_kind_uses_secret_auth_storage_feature`
    - `just write-config-schema`
    - `just fix -p codex-core`
    
    The full `just test -p codex-core` run compiled successfully and ran
    2,690 tests; 2,589 passed, one was flaky, and 101 environment-sensitive
    tests failed because this shell injects a `pyenv` rehash warning into
    command output or because sandboxed subprocesses timed out.
  • [codex] Add size to internal filesystem metadata (#27927)
    ## Why
    
    `ExecutorFileSystem::get_metadata` reports file kind and timestamps but
    not size. Internal callers that need to enforce a size limit therefore
    have to read the complete file first, which is especially wasteful for
    remote filesystems.
    
    This adds the missing internal metadata so consumers can reject
    oversized files before transferring or buffering them. The field is
    named `size`, matching VS Code's `FileStat.size` filesystem convention.
    
    ## What changed
    
    - add `size: u64` to internal `FileMetadata`
    - populate it from the underlying filesystem metadata
    - carry it through sandbox-helper and remote exec-server responses
    - cover files, directories, symlink targets, and sandboxed reads across
    local and remote filesystem implementations
    
    The new field is intentionally not exposed through the app-server API.
    
    ## Testing
    
    - `just test -p codex-exec-server get_metadata`
    - `just test -p codex-exec-server
    file_system_sandboxed_metadata_and_read_allow_readable_root`
    - `just test -p codex-core-plugins`
    - `just test -p codex-skills-extension`
  • Handle standalone image generation failures as terminal items (#27920)
    ## Why
    
    Standalone image generation emitted a started item but no terminal item
    when the backend failed. Clients could leave the operation unresolved or
    render it as successful.
    
    ## What changed
    
    - Emit a terminal image-generation item with `status: "failed"` when
    generation or editing fails.
    - Skip image persistence for failed terminal items.
    - Render failed image generation distinctly in TUI history.
    - Preserve the status when handling live and replayed terminal items.
    
    ## Looks for TUI, App-Side change needed 
    
    <img width="867" height="89" alt="image"
    src="https://github.com/user-attachments/assets/9e32342f-a982-411e-8498-456639fc468a"
    />
    
    ## Validation
    
    - `just test -p codex-image-generation-extension`
    - App-server image-generation tests
    - Core stream-event tests
    - TUI image-generation lifecycle and snapshot tests
    - Scoped Clippy and formatting
  • [codex] unify apply patch parsing (#27913)
    ## Why
    
    `apply_patch` maintained separate batch and streaming parsers for the
    same patch grammar. That duplicated the parsing rules and allowed final
    execution to disagree with the live streamed preview.
    
    ## What changed
    
    - Make `StreamingPatchParser` the single owner of hunk and environment
    ID parsing.
    - Keep heredoc and outer patch-boundary normalization in the existing
    `parse_patch` wrapper, preserving its public API.
    - Reject non-whitespace content after `*** End Patch` and preserve
    separator handling after `*** End of File`.
    - Reject duplicate environment ID preambles explicitly.
    - Remove the duplicate batch hunk parser and its implementation-specific
    tests.
    
    The change removes 201 net lines while retaining focused coverage for
    the unified parser's boundary behavior.
    
    ## Validation
    
    - `just test -p codex-apply-patch`
    - Compared a 24-hour corpus of 2,788,059 observed `apply_patch` payloads
    against the previous batch parser. All 2,779,502 accepted payloads
    produced identical hunks, canonical patch text, and environment IDs; the
    remaining 8,557 payloads were rejected by both parsers, with zero
    acceptance or payload mismatches.
  • [codex] expose remote plugin share URL (#27890)
    ## Summary
    
    - expose the remote plugin detail endpoint's `share_url` as nullable
    `PluginDetail.shareUrl`
    - preserve existing `PluginSummary.shareContext` behavior for local and
    workspace sharing flows
    - regenerate the app-server TypeScript and JSON schema fixtures
    
    ## Why
    
    The remote plugin detail response already includes a canonical
    `share_url`, but that value was not surfaced by `plugin/read` for global
    plugins. Global plugins intentionally have no `shareContext`, so using
    that model for the URL would change the semantics consumed by the
    existing share modal.
    
    ## User impact
    
    Codex clients can use `PluginDetail.shareUrl` for a remote plugin's
    copy-link action, including when the plugin is disabled by an
    administrator, without changing existing share-modal or ownership
    behavior.
    
    ## Validation
    
    - `cargo test -p codex-app-server
    plugin_read_includes_share_url_for_admin_disabled_remote_plugin`
    - `cargo test -p codex-app-server-protocol
    typescript_schema_fixtures_match_generated`
    - `cargo test -p codex-app-server-protocol
    json_schema_fixtures_match_generated`
    - `cargo fmt --all`
  • sandboxing: migrate cwd inputs to PathUri (#27816)
    ## Why
    
    Sandbox cwd values can cross app-server and exec-server host boundaries.
    They should retain URI semantics until the receiving host validates them
    instead of being interpreted early as native paths.
    
    ## What
    
    - Carry `PathUri` through filesystem sandbox contexts, sandbox commands,
    and transform inputs.
    - Convert command and policy cwd once in `SandboxManager::transform`,
    then keep launch requests native.
    - Preserve sandbox cwd over remote filesystem transport and reject
    non-native URIs without fallback.
    - Cache paired native/URI turn-environment cwd values during migration,
    with immutable access to keep them synchronized.
    - Extend existing protocol, forwarding, transform, and core runtime
    tests.
  • chore: prompt MAv2 (#27919)
    Prompt update of MAv2
  • realtime: add AVAS architecture override (#27720)
    ## Summary
    
    Adds a `RealtimeConversationArchitecture` option for realtime
    conversation startup, with `realtimeapi` as the default and `avas` as an
    opt-in architecture.
    
    The AVAS path is limited to realtime v1 conversational WebRTC starts,
    and WebRTC call creation appends `intent=quicksilver&architecture=avas`
    to `/v1/realtime/calls`. The existing sideband websocket still joins by
    `call_id`.
    
    This also exposes the per-session architecture override through
    app-server v2 `thread/realtime/start` params and updates the config
    schema for `[realtime].architecture`.
    
    ## Validation
    
    - `just fmt`
    - `just write-config-schema`
    - `just test -p codex-api sends_avas_session_call_query_params`
    - `just test -p codex-core -E
    'test(~conversation_webrtc_start_uses_avas_architecture_query)'`
    - `just test -p codex-core -E 'test(realtime_loads_from_config_toml)'`
    - `just test -p codex-app-server-protocol -E
    'test(~serialize_thread_realtime_start) |
    test(generated_ts_optional_nullable_fields_only_in_params)'`
    - `just test -p codex-app-server -E
    'test(realtime_webrtc_start_emits_sdp_notification)'`
  • tui: Allow extra o's in /goal command (#27814)
    ## Why
    
    The TUI rejected playful `/goal` spellings such as `/goooooooooooal`,
    even though Codex Apps accepts them for the World Cup promotion. This
    keeps the TUI behavior consistent without changing how the canonical
    command is presented.
    
    ## How it works
    
    Built-in command lookup recognizes lowercase `go+al` as the existing
    `goal` command after normal exact-name parsing fails. The command
    catalog remains unchanged, so autocomplete continues to advertise
    `/goal` normally.
    
    ## Verification
    
    Added lookup-level and end-to-end TUI coverage for the flexible
    spelling. The focused tests, scoped Clippy checks, and formatting pass.
    The full `codex-tui` suite passed 2,833 of 2,835 tests; the two failing
    guardian feature-flag tests reproduce unchanged on fresh `origin/main`.
  • Persist update dismissal without cache (#27783)
    ## Summary
    
    Choosing “Don’t remind me” can silently fail when `version.json`
    disappears before dismissal because `dismiss_version` returns success
    without writing anything. The same update can then reappear on the next
    launch.
    
    Initialize a minimal `VersionInfo` from the selected version when the
    cache cannot be read, then persist the dismissal through the existing
    write path.
    
    Fixes #27147
  • [ez][codex-rs] Support approvals reviewer in app defaults (#27075)
    [from codex]
    
    ## Summary
    
    - add `approvals_reviewer` support to `[apps._default]`
    - resolve connected-app reviewers in per-app, app-default, then global
    order
    - expose the setting through the v2 config API and regenerate schema
    fixtures
    
    ## Context
    
    PR #25167 added `apps.<connector_id>.approvals_reviewer`, but the shared
    app defaults table could not specify the reviewer. This extends the same
    behavior to `[apps._default]` while preserving per-app overrides.
    
    Managed `allowed_approvals_reviewers` requirements still constrain both
    default and per-app values. A disallowed app value falls back to the
    global reviewer, and non-app MCP servers continue using the global
    reviewer.
    
    ## Testing
    
    - `just write-config-schema`
    - `just write-app-server-schema`
    - `just fmt`
    - `just test -p codex-config`
    - `just test -p codex-core app_approvals_reviewer`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server config_read_includes_apps`
  • Reject transcript backtrack in side conversations (#27791)
    ## Why
    
    Fixes #27735.
    
    Side conversations are ephemeral forks, and thread rollback currently
    requires persisted thread history. The normal backtrack path already
    rejected editing previous prompts in side conversations, but
    transcript-mode backtrack could still call the rollback path and surface
    the core `thread/rollback` failure as a TUI error.
    
    ## What changed
    
    - Moved the existing side-conversation edit rejection message into
    `app_backtrack.rs` so backtrack rollback code can reuse it.
    - Added a side-conversation guard in `apply_backtrack_rollback` so
    transcript-mode confirmation is rejected before submitting
    `thread/rollback`.
    
    ## Verification
    
    - `just test -p codex-tui
    app::tests::side_backtrack_rejection_reports_unavailable_message_snapshot`
  • fix: serialize auth environment tests (#27879)
    ## Summary
    - serialize the remaining login tests that mutate or read the
    process-global auth environment
    - include Bedrock auth-manager tests in the existing `codex_auth_env`
    serial group
    
    ## Root cause
    The login unit-test binary runs tests concurrently. One test removed
    `CODEX_ACCESS_TOKEN` without joining the existing serial group, while
    several Bedrock tests constructed `AuthManager` and read that same
    process-global environment outside the group. An interleaving could
    restore a stale personal access token while another test was loading
    file-backed auth, causing the observed mismatched `/whoami` request
    count and related auth-state flakes.
    
    ## Verification
    - `just fmt`
    - `git diff --check`
    - `bazel test //codex-rs/login:login-unit-tests --nocache_test_results
    --runs_per_test=20 --test_output=errors` (20/20 passed)
    - `just test -p codex-login` (135/135 passed)
  • [codex] restore source-specific import copy (#27703)
    ## Summary
    
    - restore source-specific wording across the `/import` picker, lifecycle
    messages, diagnostics, and help text
    - update the matching Unix and Windows snapshots
    - leave import behavior unchanged
    
    ## Why
    
    The import path currently supports one source, so the UI should identify
    that source directly instead of presenting the flow as
    provider-agnostic.
    
    ## Validation
    
    - `just test -p codex-tui external_agent_config_migration` (12 passed)
    - `just fix -p codex-tui`
    - `just fmt`
  • Extract shared plugin MCP config parsing (#27863)
    ## Why
    
    We want a thread-selected plugin to eventually expose stdio MCP servers
    that run on the executor owning that plugin.
    
    The existing plugin MCP parser lived inside `core-plugins` and was
    coupled to the host filesystem loader. Reusing it from an executor
    provider would either duplicate MCP normalization or make the plugin
    package layer own MCP runtime semantics. This PR creates the shared
    MCP-owned boundary first.
    
    In simple terms:
    
    ```text
    plugin .mcp.json
            |
            v
    shared parser in codex-mcp
            |
            +-- Declared placement: preserve current local-plugin behavior
            |
            +-- Environment placement: produce config bound to one executor
    ```
    
    This builds on the authority-bound plugin descriptors from #27692. It
    intentionally does not discover, register, or launch executor MCP
    servers yet.
    
    ## What changed
    
    - Moved plugin MCP file parsing and normalization from `core-plugins`
    into `codex-mcp`.
    - Kept support for both existing file shapes: a top-level server map and
    an object containing `mcpServers`.
    - Kept per-server failure isolation: one invalid server does not discard
    valid siblings, while malformed top-level JSON still fails the whole
    file.
    - Updated the existing local plugin loader to use `Declared` placement,
    preserving its current transport, OAuth, relative `cwd`, and error
    behavior.
    - Added `Environment` placement for the next stacked PR:
    - the selected environment ID overrides anything declared by the plugin;
      - missing stdio `cwd` defaults to the plugin root;
    - relative `cwd` is resolved beneath the plugin root and cannot traverse
    outside it;
    - bare or source-less environment-variable references resolve on a
    non-local executor;
    - explicit orchestrator environment-variable forwarding is rejected for
    executor-owned plugins.
    
    ## User impact
    
    None in this PR. Existing local plugin MCP loading follows the same
    behavior through the shared parser. The executor placement mode is not
    connected to thread startup until the follow-up registration PR.
    
    ## Assumptions
    
    - A selected capability root's environment is authoritative. A plugin
    cannot redirect its stdio process to the orchestrator or another
    executor.
    - Relative working directories belong under the plugin package root.
    Explicit absolute working directories remain valid within the owning
    environment.
    - For a non-local executor, unqualified environment-variable names refer
    to that executor. Reading an orchestrator variable requires an explicit
    contract and is rejected for now.
    - Parsing only produces normalized `McpServerConfig` values. Process
    startup remains owned by the existing MCP runtime and connection
    manager.
    
    ## Follow-ups
    
    1. Add the executor MCP provider and catalog registration: read the
    selected plugin's MCP config through the same executor filesystem,
    support stdio only, freeze the result per active thread, apply managed
    policy, and resolve name collisions as discovered plugin < selected
    plugin < explicit config.
    2. Install that provider in app-server and add an end-to-end test
    proving `thread/start.selectedCapabilityRoots` launches and calls the
    MCP tool on the selected executor, preserves the frozen registration
    across refresh, and does not expose it to an unselected thread.
    3. After the initial executor-stdio vertical, define
    resume/fork/environment-replacement semantics, executor HTTP placement,
    warning delivery, common MCP tool-context bounds, and move remaining MCP
    source composition above core.
    
    ## Verification
    
    - `cargo check -p codex-mcp -p codex-core-plugins --tests`
    - `just bazel-lock-check`
    - Added focused parser coverage for legacy local normalization, executor
    authority, working-directory handling, and environment-variable
    sourcing.
  • Add executor-owned plugin resolution (#27692)
    ## Why
    
    CCA can select a capability root that lives in an executor environment,
    but
    Codex only had a host-filesystem plugin loader. Before selected executor
    plugins can contribute MCP servers, we need a small package boundary
    that can
    answer:
    
    > Does this selected root contain a plugin, and if so, what does its
    manifest
    > declare?
    
    The answer must come from the selected environment's filesystem. A
    failed
    executor lookup must never fall back to the orchestrator filesystem.
    
    ## What this changes
    
    This PR introduces:
    
    ```rust
    PluginProvider::resolve(root)
        -> Result<Option<ResolvedPlugin>, Error>
    ```
    
    `ExecutorPluginProvider` resolves one `SelectedCapabilityRoot` through
    its
    exact `environment_id`. It checks the recognized manifest locations,
    reads the
    manifest through that environment's `ExecutorFileSystem`, and returns an
    inert
    `ResolvedPlugin` containing:
    
    - the opaque selected-root ID;
    - the environment-bound plugin root;
    - the authority-bound manifest resource;
    - parsed metadata and authority-bound component locators.
    
    Descriptor construction rejects manifest or component paths outside the
    selected package root, so consumers cannot accidentally lose the package
    boundary when they receive a resolved plugin.
    
    If the root has no plugin manifest, resolution returns `None`, allowing
    the
    caller to treat it as a standalone capability such as a skill.
    
    ```text
    selected root: repo -> env-1:/workspace/repo
                             |
                             | env-1 filesystem only
                             v
                 .codex-plugin/plugin.json
                             |
                             v
            ResolvedPlugin { authority, root, manifest }
    ```
    
    The existing host loader and the new executor provider now share the
    same
    manifest parser. Existing `codex-core-plugins::manifest` type paths
    remain
    available through re-exports, so host behavior and callers are
    unchanged.
    
    ## Scope
    
    This is intentionally a non-user-visible package-resolution PR. It does
    not:
    
    - parse or register plugin MCP server configurations;
    - activate skills, connectors, hooks, or MCP servers;
    - change app-server wiring;
    - introduce host fallback, caching, or lifecycle behavior.
    
    #27670 has merged, and this PR is now based directly on `main`. Together
    with
    the resolved MCP catalog from #27634, it establishes the inputs needed
    for the
    executor stdio MCP vertical without changing the existing MCP runtime.
    
    ## Follow-up
    
    The next PR will consume `ResolvedPlugin`, read its declared/default MCP
    config
    through the same executor filesystem, bind supported stdio servers to
    that
    environment, and feed those registrations into the resolved MCP catalog.
    An
    app-server E2E will prove that selecting an executor plugin exposes and
    invokes
    its tool on the owning executor.
    
    Resume/fork semantics, dynamic environment replacement, and non-stdio
    placement remain separate lifecycle decisions.
    
    ## Validation
    
    - `just fmt`
    - `cargo check --tests -p codex-plugin -p codex-core-plugins`
    - `just bazel-lock-check`
    - `git diff --check`
    
    Test targets were compiled but not executed locally; CI will run the
    test and
    Clippy suites.
  • [code-mode] Reject remote image URLs from output helpers (#27732)
    ## Summary
    
    - reject HTTP(S) image URLs from the shared code-mode output-image
    normalization path
    - return a concise model-visible tool error so the model can recover on
    its next turn
    - apply the targeted rejection to both `image()` and `generatedImage()`
    - leave other non-empty image URL values to existing downstream handling
    
    The returned error is:
    
    > Tool call failed: remote image URLs are not supported in tool outputs.
    Pass a base64 data URI instead
    
    ## Why
    
    Responses Lite cannot lower a remote image URL emitted from a structured
    tool output. Rejecting HTTP(S) values in the Codex harness preserves the
    tool-call metadata and gives the model a recoverable next turn instead
    of invalidating the sample.
    
    ## Test coverage
    
    The regression is covered primarily by a `test_codex()` agent
    integration test that simulates the Responses API exchange and asserts
    the failed model-visible exec output. A supplemental runtime test covers
    both `http://` and `https://` inputs across both image output helpers.
    
    ## Test plan
    
    - `cd codex-rs && just test -p codex-code-mode`
    - `cd codex-rs && just test -p codex-code-mode-protocol`
    - `cd codex-rs && just test -p codex-core
    code_mode_image_helper_rejects_remote_url`
    - `cd codex-rs && just fmt`
    - `git diff --check origin/main...HEAD`
    
    Related context: https://github.com/openai/openai/pull/1022346
  • Make MCP server contributions thread-scoped (#27670)
    ## Why
    
    `selectedCapabilityRoots` belongs to one thread, but MCP contributors
    previously received only the global Codex config. That left no clean way
    for a selected executor capability to contribute MCP servers to its own
    thread.
    
    ## What this PR does
    
    - Gives MCP contributors a small context containing the config and, for
    a running thread, its frozen host-seeded inputs.
    - Uses the same thread inputs during startup, status queries, refreshes,
    and skill dependency checks.
    - Keeps threadless MCP operations and the existing hosted Apps behavior
    unchanged.
    - Adds coverage showing that two threads resolve independent
    registrations and that later lifecycle mutations do not change the
    frozen MCP inputs.
    
    This PR does not discover plugin manifests, add MCP servers, or launch
    anything new. It only establishes the thread-scoped registration
    boundary.
    
    ## Follow-ups
    
    - Resolve selected executor plugin roots through their owning
    environment filesystem.
    - Convert their stdio MCP declarations into environment-bound
    registrations and add an executor MCP end-to-end test.
    
    ## Verification
    
    - `just fmt`
    - `cargo check --tests -p codex-protocol -p codex-extension-api -p
    codex-mcp-extension -p codex-core -p codex-app-server`
    
    Tests and Clippy were not run.