Commit Graph

133 Commits

  • feat: add layered --profile-v2 config files (#17141)
    ## Why
    
    `--profile-v2 <name>` gives launchers and runtime entry points a named
    profile config without making each profile duplicate the base user
    config. The base `$CODEX_HOME/config.toml` still loads first, then
    `$CODEX_HOME/<name>.config.toml` layers above it and becomes the active
    writable user config for that session.
    
    That keeps shared defaults, plugin/MCP setup, and managed/user
    constraints in one place while letting a named profile override only the
    pieces that need to differ.
    
    ## What Changed
    
    - Added the shared `--profile-v2 <name>` runtime option with validated
    plain names, now represented by `ProfileV2Name`.
    - Extended config layer state so the base user config and selected
    profile config are both `User` layers; APIs expose the active user layer
    and merged effective user config.
    - Threaded profile selection through runtime entry points: `codex`,
    `codex exec`, `codex review`, `codex resume`, `codex fork`, and `codex
    debug prompt-input`.
    - Made user-facing config writes go to the selected profile file when
    active, including TUI/settings persistence, app-server config writes,
    and MCP/app tool approval persistence.
    - Made plugin, marketplace, MCP, hooks, and config reload paths read
    from the merged user config so base and profile layers both participate.
    - Updated app-server config layer schemas to mark profile-backed user
    layers.
    
    ## Limits
    
    `--profile-v2` is still rejected for config-management subcommands such
    as feature, MCP, and marketplace edits. Those paths remain tied to the
    base `config.toml` until they have explicit profile-selection semantics.
    
    Some adjacent background writes may still update base or global state
    rather than the selected profile:
    
    - marketplace auto-upgrade metadata
    - automatic MCP dependency installs from skills
    - remote plugin sync or uninstall config edits
    - personality migration marker/default writes
    
    ## Verification
    
    Added targeted coverage for profile name validation, layer
    ordering/merging, selected-profile writes, app-server config writes,
    session hot reload, plugin config merging, hooks/config fixture updates,
    and MCP/app approval persistence.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Defer startup NUX impressions until startup succeeds (#22587)
    ## Why
    
    This is a follow-up to #22573. This problem was surfaced in a code
    review comment that I missed before merging the previous PR.
    
    Fresh-session startup could prepare a model-availability NUX before
    `app_server.start_thread(&config)` completed. If thread startup then
    failed, the TUI never rendered the tooltip, but
    `prepare_startup_tooltip_override(...)` had already persisted one of the
    limited impressions.
    
    ## What Changed
    
    - Move startup tooltip preparation inside the fresh-thread startup
    branch, after `start_thread(...)` succeeds.
    - Keep resume/fork paths unchanged.
    - Remove the now-redundant
    `should_prepare_startup_tooltip_override(...)` helper and its gate test.
  • Simplify TUI startup test coverage (#22573)
    ## Why
    
    The TUI startup test surface had drifted into expensive, brittle
    coverage:
    
    - `tui/tests/suite/no_panic_on_startup.rs` was already ignored as flaky
    while still spawning a PTY to exercise malformed exec-policy rules.
    - `tui/tests/suite/model_availability_nux.rs` used a seeded session,
    cursor-query spoofing, and repeated interrupts to verify a narrow
    resume-path invariant.
    - `app/tests.rs` had started accumulating unrelated startup and summary
    coverage in one flat module even after the surrounding app code was
    split into feature modules.
    
    This keeps those behaviors covered while making the tests cheaper to
    understand and less likely to rot. It also preserves the malformed-rules
    regression from #8803 without requiring a terminal orchestration test.
    
    ## What changed
    
    - Replaced the malformed `rules` startup PTY case with a direct
    exec-policy loader regression:
    
    [`rules_path_file_returns_read_dir_error`](https://github.com/openai/codex/blob/21b6b5622f18b8cac0ea41fd083b3106778d9ffc/codex-rs/core/src/exec_policy_tests.rs#L264-L284)
    - Made the existing fresh-session-only startup tooltip behavior explicit
    with
    
    [`should_prepare_startup_tooltip_override`](https://github.com/openai/codex/blob/21b6b5622f18b8cac0ea41fd083b3106778d9ffc/codex-rs/tui/src/app/thread_routing.rs#L1272-L1279),
    then added focused coverage for the resume/fork gate and the persisted
    NUX counter.
    - Split startup and session-summary coverage out of
    `tui/src/app/tests.rs` into dedicated modules so the test layout better
    mirrors the current app architecture.
    - Converted one single-message goal validation snapshot into semantic
    assertions where layout was not the behavior under test.
    - Removed the two PTY-heavy suite files that the narrower tests now
    supersede.
    
    ## Verification
    
    - `cargo test -p codex-core rules_path_file_returns_read_dir_error`
    - `cargo test -p codex-tui startup_`
    - `cargo test -p codex-tui session_summary_`
    - `cargo test -p codex-tui
    goal_slash_command_rejects_oversized_objective`
  • Remove resurrected /collab slash command (#22535)
    ## Summary
    `/collab` was intentionally removed in
    [#12012](https://github.com/openai/codex/pull/12012), but the
    TUI/app-server migration accidentally brought that slash-command path
    back. This restores the earlier product decision so the TUI no longer
    advertises or dispatches `/collab`. This command was redundant because
    it did the same thing as `/plan` but in a less-intuitive way.
    
    ## What Changed
    - Remove `SlashCommand::Collab` from the TUI slash-command surface.
    - Delete the picker and app-event plumbing that only existed to service
    `/collab`.
    - Remove obsolete TUI test coverage for the deleted picker flow.
  • Use plugin/list to get list of plugins for mentions (#22375)
    This switches TUI plugin mentions to use app-server `plugin/list` for
    plugin inventory and metadata instead of `PluginManager`, while keeping
    the same mention-eligibility filters as before.
    
    Same filters as before:
    - Only plugins in the current config / cwd scope.
    - Only installed and enabled plugins.
    - Only plugins that actually expose a capability, meaning at least one
    skill, MCP server, or app connector.
    - Uses `plugin/list` for the mention names/descriptions
  • add --dangerously-bypass-hook-trust CLI flag (#21768)
    # Why
    
    Hook trust happens through the TUI in `/hooks` so it can block
    non-interactive use cases. This flag will allow users that are using
    codex headlessly to bypass hooks when they want to.
    
    # What
    
    This adds one invocation-scoped escape hatch.
    
    - the CLI flag sets a runtime-only `bypass_hook_trust` override; there
    is no durable `config.toml` setting
    - hook discovery still respects normal enablement, so explicitly
    disabled hooks remain disabled
    - we show a `--dangerously-bypass-hook-trust is enabled. Enabled hooks
    may run without review for this invocation.` message on startup so
    accidental use is visible in both interactive and exec flows
    
    This keeps “enabled” and “trusted” as separate concepts in the normal
    path, while giving CI/E2E callers a stable way to opt into the
    exceptional path when they already control the hook set.
  • Add support for UDS in codex --remote (#22414)
    ## Why
    
    Added support for UDS connections in `codex --remote`.
    
    TUI also now connects to local app-server using UDS by default if it is
    running and set to listen to UDS connection.
    
    ## What Changed
    
    - Introduced `RemoteAppServerEndpoint` with `WebSocket` and `UnixSocket`
    variants.
    - Reused the existing JSON-RPC-over-WebSocket protocol over either a TCP
    WebSocket stream or a UDS stream.
    - Updated `codex --remote` to accept `ws://host:port`,
    `wss://host:port`, `unix://`, and `unix://PATH`.
    - Kept `--remote-auth-token-env` restricted to `wss://` and loopback
    `ws://` remotes.
    - Added a fast TUI startup probe for the default daemon socket, falling
    back to the embedded app server when the daemon is absent or
    unresponsive.
    
    ## Verification
    
    - Manually verified that the updated remote flow works.
    - Added coverage for UDS remote round trips, WebSocket auth headers,
    auth-token transport policy, remote address parsing, and missing-daemon
    fallback.
    - Ran focused remote test coverage locally.
  • feat(tui): add ambient terminal pets (#21206)
    ## Why
    
    The Codex App has animated pets, but the TUI had no equivalent ambient
    companion surface. This brings that experience into terminal Codex while
    keeping the main chat flow usable: the pet should feel present, but it
    cannot cover transcript text, composer input, approvals, or picker
    content.
    
    The feature also needs to be terminal-aware. Different terminals support
    different image protocols, tmux can interfere with image rendering, and
    some users will want pets disabled entirely or anchored differently
    depending on their layout.
    
    <table>
    <tr><td>
    <img width="4110" height="2584" alt="CleanShot 2026-05-05 at 12 41
    45@2x"
    src="https://github.com/user-attachments/assets/68a1fcbc-2104-48d6-b834-69c6aaa95cdf"
    />
    <p align="center">macOS - Ghostty, iTerm2 and WezTerm with Custom
    Pet</p>
    </td></tr>
    <tr><td>
    ![Uploading CleanShot 2026-05-10 at 20.28.30.png…]()
    <p align="center">Windows Terminal</p>
    </td></tr>
    <tr><td>
    <img width="3902" height="2752" alt="CleanShot 2026-05-05 at 12 39
    02@2x"
    src="https://github.com/user-attachments/assets/300e2931-6b00-467e-91cb-ab8e28470500"
    />
    <p align="center">Linux - WezTerm and Ghostty</p>
    </td></tr>
    </table>
    
    ## What Changed
    
    - Add a TUI ambient pet renderer in `codex-rs/tui/src/pets/`.
    - Port the app-style pet animation states so the sprite changes with
    task status, waiting-for-input states, review/ready states, and
    failures.
    - Add `/pets` selection UI with a preview pane, loading state, built-in
    pet choices, and a first-row `Disable terminal pets` option.
    - Download built-in pet spritesheets on demand from the same public CDN
    path already used by Android, under
    `https://persistent.oaistatic.com/codex/pets/v1/...`, and cache them
    locally under `~/.codex/cache/tui-pets/`.
    - Keep custom pets local.
    - Add config support for pet selection, disabling pets, and choosing
    whether the pet follows the composer bottom or anchors to the terminal
    bottom.
    - Reserve layout space around the pet so transcript wrapping, live
    responses, and composer input do not render underneath the sprite.
    - Gate image rendering by terminal capability, disable image pets under
    tmux, and support both Kitty Graphics and SIXEL terminals.
    - Add redraw cleanup for terminal image artifacts, including sixel cell
    clearing.
    
    ## Current Scope
    
    - This is an initial TUI version of ambient pets, not full App parity.
    - It focuses on ambient sprite rendering, `/pets` selection, custom
    pets, terminal capability gating, and on-demand CDN-backed built-in
    assets.
    - The ambient text overlay is currently disabled, so the TUI renders the
    pet sprite without extra status text beside it.
    
    ## How to Test
    
    1. Start Codex TUI in a terminal with image support.
    2. Run `/pets`.
    3. Confirm the picker shows built-in pets plus custom pets, and the
    first item is `Disable terminal pets`.
    4. On a fresh `~/.codex/cache/tui-pets/`, move onto a built-in pet and
    confirm the first preview downloads the spritesheet from the shared
    Codex pets CDN and renders successfully.
    5. Move through the pet list and confirm subsequent built-in previews
    use the local cache.
    6. Select a pet, then send and receive messages. Confirm transcript and
    composer text wrap before the pet instead of rendering underneath the
    sprite.
    7. Change the pet anchor setting and confirm the pet can either follow
    the composer bottom or sit at the terminal bottom.
    8. Return to `/pets`, choose `Disable terminal pets`, and confirm the
    sprite disappears cleanly.
    
    Targeted tests:
    - `cargo test -p codex-tui ambient_pet_`
    - `cargo test -p codex-tui
    resize_reflow_wraps_transcript_early_when_pet_is_enabled`
    - `cargo insta pending-snapshots`
  • Fix goal update and add /goal edit command in TUI (#21954)
    ## Why
    
    Users have requested the ability to edit a goal's objective after a goal
    has been created. This PR exposes a new `/goal edit` command in the TUI
    to address this request.
    
    In the process of implementing this, I also noticed an existing bug in
    the goal runtime. When a goal's objective is updated through the
    `thread/goal/set` app server API, the goal runtime didn't emit a new
    steering prompt to tell the agent about the new objective. This PR also
    fixes this hole.
    
    ## What Changed
    
    - Adds `/goal edit` in the TUI, opening an edit box prefilled with the
    current goal objective.
    - Keeps active and paused goals in their current state, resets completed
    goals to active, keeps budget-limited goals budget-limited, and
    preserves the existing token budget.
    - Changes the existing `thread/goal/set` behavior so editing an
    objective preserves goal accounting instead of resetting it. The older
    reset-on-new-objective behavior was left over from before
    `thread/goal/clear`; clients that need to reset accounting can now clear
    the existing goal and create a new one.
    - Reuses the existing goal set API path; this does not add or change
    app-server protocol surface area.
    - Adds a dedicated goal runtime steering prompt when an externally
    persisted goal mutation changes the objective, so active turns receive
    the updated objective.
    
    ## Validation
    
    - Make sure `/goal edit` returns an error if no goal currently exists
    - Make sure `/goal edit` displays an edit box that can be optionally
    canceled with no side effects
    - Make sure that an edited goal results in a steer so the agent starts
    pursuing the new objective
    - Make sure the new objective is reflected in the goal if you use
    `/goal` to display the goal summary
    - Make sure that `/goal edit` doesn't reset the token budget, time/token
    accounting on the updated goal
  • Fix side conversation config inheritance (#22106)
    Addresses #22101
    
    ## Why
    
    Side conversations are ephemeral forks of the active thread, but `/side`
    was building its fork config from the app-level config after refreshing
    it from disk. If the parent thread had runtime settings that differed
    from the current persisted defaults, such as a changed model, reasoning
    effort, permissions, reviewer, or fast-mode selection, the side
    conversation could start with different behavior than its parent.
    
    ## What changed
    
    - Build side fork config from the active parent `ChatWidget` config,
    then overlay the parent thread's effective model, reasoning effort,
    service tier, and fast-mode opt-out state.
    - Forward model reasoning summary, verbosity, personality, web search
    mode, and service-tier overrides through TUI app-server
    start/resume/fork lifecycle params.
    - Add focused tests for parent runtime inheritance, side developer
    guardrail preservation, and lifecycle param forwarding.
  • feat(tui): render responsive Markdown tables in TUI (#22052)
    ## Why
    
    The TUI currently treats Markdown tables as ordinary wrapped text, which
    makes table-heavy responses hard to read and brittle across narrow panes
    and terminal resizes.
    
    This change teaches the TUI to render Markdown tables responsively while
    preserving the raw Markdown source needed to re-render streamed and
    finalized transcript content after width changes. The goal is to keep
    tables legible during streaming, after resize, and once a turn has
    finished, without corrupting scrollback ordering.
    
    ## What Changed
    
    - add table detection and responsive table rendering in the Markdown
    renderer
    - render standard tables with Unicode box-drawing borders when the pane
    is wide enough
    - add a vertical readability fallback for constrained or dense tables so
    narrow panes still show each row clearly
    - keep links and `<br>` content inside table cells instead of leaking
    text outside the table
    - avoid table normalization inside fenced or indented code blocks
    - preserve raw streamed Markdown source and keep the active table as a
    mutable tail until finalization
    - consolidate finalized streamed content into source-backed transcript
    cells so post-resize re-rendering stays correct
    - add snapshot and targeted streaming/resize regression coverage for the
    new table behavior
    
    ## How to Test
    
    1. Start Codex TUI from this branch.
    2. Paste this exact prompt:
    `This is a session to test codex, no need to do any thinking, just end
    different markdown tables, with columns exploring different markdown
    contents, like links, bold italic, code, etc. Make them different sizes,
    some 30+ rows, some not and intertwine them with some paragraphs with
    complex formatting as well.`
    3. Confirm the response includes several Markdown tables mixed with
    richly formatted paragraphs.
    4. Confirm wide-enough tables render with box-drawing borders instead of
    plain wrapped pipe text.
    5. Resize the terminal narrower while the answer is still streaming and
    confirm the in-progress table stays coherent instead of duplicating
    headers or leaving broken scrollback behind.
    6. Resize again after the turn finishes and confirm the finalized
    transcript re-renders cleanly at the new width.
    7. In a narrow pane, verify dense tables fall back to the vertical
    per-row layout instead of producing unreadable wrapped columns.
    8. Also verify pipe-heavy fenced code blocks still render as code, not
    as tables.
    
    Targeted tests:
    - `cargo test -p codex-tui table_readability_fallback --no-fail-fast`
    - `cargo test -p codex-tui markdown_render --no-fail-fast`
    - `cargo test -p codex-tui streaming::controller --no-fail-fast`
    - `cargo test -p codex-tui table_resize_lifecycle --no-fail-fast`
    
    ## Docs
    
    No developer docs update appears necessary.
  • Avoid blocking TUI on agent metadata hydration (#21870)
    ## Why
    
    Fixes #16688.
    
    The TUI currently hydrates collab receiver metadata by awaiting
    `thread/read` before each active-thread notification is rendered. During
    large subagent fan-outs, the embedded app-server can be busy starting
    agents and processing spawn work, so those synchronous metadata reads
    queue behind the fan-out and block the TUI event loop. That makes the UI
    appear frozen even though the underlying agent work can continue.
    
    ## What Changed
    
    - Replaced eager `thread/read` metadata hydration on the active
    notification path with local receiver-thread caching.
    - Kept `ThreadStarted` and picker refreshes as the places that fill in
    agent nickname/role metadata when it is available.
    - Skipped caching receiver threads that are explicitly reported as
    `NotFound`, avoiding live-looking ghost entries for failed stale-agent
    calls.
    - Added TUI tests covering both local receiver caching and `NotFound`
    suppression.
    
    ## Verification
    
    - `cargo test -p codex-tui collab_receiver_notification`
    - `just fix -p codex-tui`
    
    I also ran the full `cargo test -p codex-tui`; the new test passed, but
    the full process later aborted with an unrelated stack overflow in
    `tests::fork_last_filters_latest_session_by_cwd_unless_show_all`.
  • Improve hooks trust flow in TUI (#21755)
    # Why
    Hooks that need trust review were easy to miss, and the existing TUI
    flow made users discover `/hooks` manually before they could decide
    whether to inspect or trust them.
    
    # What
    - add a startup review prompt for new or changed hooks before normal
    composer use
    - add a top-level `t` shortcut in `/hooks` to trust every review-needed
    hook at once
    - make pending-review rows and helper copy use warning styling
    
    ## TUI
    
    ### Startup review interstitial
    
    ```text
    Hooks need review
    2 hooks are new or changed.
    Hooks can run outside the sandbox after you trust them.
    
    › 1. Review hooks
      2. Trust all and continue
      3. Continue without trusting (hooks won't run)
    ```
    
    ### Top-level `/hooks` page when review is needed
    
    ```text
    Hooks
    Lifecycle hooks from config and enabled plugins.
    
    ⚠ 1 hook needs review before it can run.
    
    Event                 Installed   Active   Review   Description
    PreToolUse            1           0        1        Before a tool executes
    ...
    
    Press t to trust all; enter to review hooks; esc to close
    ```
  • [codex] request desktop attestation from app (#20619)
    ## Summary
    
    TL;DR: teaches `codex-rs` / app-server to request a desktop-provided
    attestation token and attach it as `x-oai-attestation` on the scoped
    ChatGPT Codex request paths.
    
    ![DeviceCheck attestation
    interface](https://raw.githubusercontent.com/openai/codex/dev/jm/devicecheck-diagram-assets/pr-assets/devicecheck-attestation-interface.png)
    
    ## Details
    
    This PR teaches the Codex app-server runtime how to request and attach
    an attestation token. It does not generate DeviceCheck tokens directly;
    instead, it relies on the connected desktop app to advertise that it can
    generate attestation and then asks that app for a fresh header value
    when needed.
    
    The flow is:
    
    1. The Codex desktop app connects to app-server.
    2. During `initialize`, the app can advertise that it supports
    `requestAttestation`.
    3. Before app-server calls selected ChatGPT Codex endpoints, it sends
    the internal server request `attestation/generate` to the app.
    4. app-server receives a pre-encoded header value back.
    5. app-server forwards that value as `x-oai-attestation` on the scoped
    outbound requests.
    
    The code in this repo is mostly protocol and runtime plumbing: it adds
    the app-server request/response shape, introduces an attestation
    provider in core, wires that provider into Responses / compaction /
    realtime setup paths, and covers the intended scoping with tests. The
    signed macOS DeviceCheck generation remains owned by the desktop app PR.
    
    ## Related PR
    
    - Codex desktop app implementation:
    https://github.com/openai/openai/pull/878649
    
    ## Validation
    
    <details>
    <summary>Tests run</summary>
    
    ```sh
    cargo test -p codex-app-server-protocol
    cargo test -p codex-core attestation --lib
    cargo test -p codex-app-server --lib attestation
    ```
    
    Also ran:
    
    ```sh
    just fix -p codex-core
    just fix -p codex-app-server
    just fix -p codex-app-server-protocol
    just fmt
    just write-app-server-schema
    ```
    
    </details>
    
    <details>
    <summary>E2E DeviceCheck validation</summary>
    
    First validated the signed desktop app boundary directly: launched a
    packaged signed `Codex.app`, sent `attestation/generate`, decoded the
    returned `v1.` attestation header, and validated the extracted
    DeviceCheck token with `personal/jm/verify_devicecheck_token.py` using
    bundle ID `com.openai.codex`. Apple returned `status_code: 200` and
    `is_ok: true`.
    
    Then ran the fuller app + app-server flow. The packaged `Codex.app`
    launched a current-branch app-server via `CODEX_CLI_PATH`, and a local
    MITM proxy intercepted outbound `chatgpt.com` traffic. The app-server
    requested `attestation/generate` from the real Electron app process, and
    the intercepted `/backend-api/codex/responses` traffic included
    `x-oai-attestation` on both routes:
    
    ```text
    GET  /backend-api/codex/responses  Upgrade: websocket  x-oai-attestation: present
    POST /backend-api/codex/responses  Upgrade: none       x-oai-attestation: present
    ```
    
    The captured header decoded to a DeviceCheck token that also validated
    with Apple for `com.openai.codex` (`status_code: 200`, `is_ok: true`,
    team `2DC432GLL2`).
    
    </details>
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Load configured environments from CODEX_HOME (#20667)
    ## Why
    
    The earlier PRs add stdio transport support and the config-backed
    environment provider, but the feature remains inert until normal Codex
    entrypoints construct `EnvironmentManager` with enough context to
    discover `CODEX_HOME/environments.toml`. This final stack PR activates
    the provider while preserving the legacy `CODEX_EXEC_SERVER_URL`
    fallback when no environments file exists.
    
    **Stack position:** this is PR 5 of 5. It is the product wiring PR that
    activates the configured environment provider added in PR 4.
    
    ## What Changed
    
    - Thread `codex_home` into `EnvironmentManagerArgs`.
    - Change `EnvironmentManager::new(...)` to load the provider from
    `CODEX_HOME`.
    - Preserve legacy behavior by falling back to
    `DefaultEnvironmentProvider::from_env()` when `environments.toml` is
    absent.
    - Make `environments.toml`-backed managers start new threads with all
    configured environments, default first, while keeping the legacy env-var
    path single-default.
    - Update the app-server, TUI, exec, MCP server, connector, prompt-debug,
    and thread-manager-sample callsites to pass `codex_home` and handle
    provider-loading errors.
    
    ## Self-Review Notes
    
    - The multi-environment startup path is intentionally tied to the
    `environments.toml` provider. Using `>1` configured environment as the
    only signal would also expand the legacy `CODEX_EXEC_SERVER_URL`
    provider because it keeps `local` addressable alongside `remote`.
    - The startup environment list is still derived inside
    `EnvironmentManager`; the provider only says whether its snapshot should
    start new threads with all configured environments.
    - The thread-manager sample was updated to pass the current
    `ThreadManager::new(...)` installation id argument so the stack compiles
    under Bazel.
    
    ## Stack
    
    - 1. https://github.com/openai/codex/pull/20663 - Add stdio exec-server
    listener
    - 2. https://github.com/openai/codex/pull/20664 - Add stdio exec-server
    client transport
    - 3. https://github.com/openai/codex/pull/20665 - Make environment
    providers own default selection
    - 4. https://github.com/openai/codex/pull/20666 - Add CODEX_HOME
    environments TOML provider
    - **5. This PR:** https://github.com/openai/codex/pull/20667 - Load
    configured environments from CODEX_HOME
    
    Split from original draft: https://github.com/openai/codex/pull/20508
    
    ## Validation
    
    - `just fmt`
    - `git diff --check`
    - `bazel build --config=remote --strategy=remote
    --remote_download_toplevel
    //codex-rs/thread-manager-sample:codex-thread-manager-sample`
    - `bazel test --config=remote --strategy=remote
    --remote_download_toplevel
    //codex-rs/exec-server:exec-server-unit-tests`
    - `bazel test --config=remote --strategy=remote
    --remote_download_toplevel --test_sharding_strategy=disabled
    --test_arg=default_thread_environment_selections_use_manager_default_id
    //codex-rs/core:core-unit-tests`
    - `bazel test --config=remote --strategy=remote
    --remote_download_toplevel --test_sharding_strategy=disabled
    --test_arg=start_thread_uses_all_default_environments_from_codex_home
    //codex-rs/core:core-unit-tests`
    
    ## Documentation
    
    This activates `CODEX_HOME/environments.toml`; user-facing documentation
    should be added before this stack is treated as a documented public
    workflow.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [codex] Generalize service tier slash commands (#21745)
    ## Why
    
    `/fast` was wired as a one-off slash command even though model metadata
    now exposes service tiers as catalog data. That meant adding another
    tier, such as a slower/cheaper tier, would require more hardcoded TUI
    plumbing instead of letting the model catalog drive the available
    commands.
    
    This change makes service-tier commands data-driven: each advertised
    `service_tiers` entry becomes a `/name` command using the catalog
    description, while the request path sends the tier `id` only when the
    selected model supports it.
    
    ## What Changed
    
    - Removed the hardcoded `/fast` slash-command variant and introduced
    dynamic service-tier command items in the composer and command popup.
    - Added toggle behavior for service-tier commands: invoking `/name`
    selects that tier, and invoking it again clears the selection.
    - Preserved the existing Fast-mode keybinding/status affordances by
    resolving the current model tier whose name is `fast`, while still
    sending the tier request value such as `priority`.
    - Persisted service-tier selections as raw request strings so non-fast
    tiers can round-trip through config.
    - Updated the Bedrock catalog entry to advertise fast support through
    `service_tiers` with `id: "priority"` and `name: "fast"`.
    - Added defensive filtering in core so unsupported selected service
    tiers are omitted from `/responses` requests.
    
    ## Validation
    
    - Added/updated coverage for dynamic service-tier slash command lookup,
    popup descriptions, composer dispatch, TUI fast toggling, and
    unsupported-tier omission in core request construction.
    - Local tests were not run per request.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Remove skills list extra roots (#21485)
    ## Summary
    - Remove `perCwdExtraUserRoots` / `SkillsListExtraRootsForCwd` from the
    `skills/list` app-server API.
    - Drop Rust app-server and `codex-core-skills` extra-root plumbing so
    skill scans are keyed by the normal cwd/user/plugin roots only.
    - Regenerate app-server schemas and update docs/tests that only existed
    for the removed extra-roots behavior.
    
    ## Validation
    - `just write-app-server-schema`
    - `just fmt`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-core-skills`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-core-skills`
    - `just fix -p codex-app-server`
    - `just fix -p codex-tui`
    
    ## Notes
    - `cargo test -p codex-app-server --test all skills_list` ran the edited
    skills-list cases, but the full filtered run ended on existing
    `skills_changed_notification_is_emitted_after_skill_change` timeout
    after a websocket `401`.
    - `cargo test -p codex-tui --lib` compiled the changed TUI callers, then
    failed two unrelated status permission tests because local
    `/etc/codex/requirements.toml` forbids `DangerFullAccess`.
    - Source-truth check found the OpenAI monorepo still has
    generated/app-server-kit mirror references to the removed field; those
    should be cleaned up when generated app-server types are synced or in a
    companion OpenAI cleanup.
  • [codex-analytics] plumb protocol-native review timing (#21434)
    ## Why
    
    We want terminal tool review analytics, but the reducer should not stamp
    review timing from its own wall clock.
    
    This PR plumbs review timing through the real protocol and app-server
    seams so downstream analytics can consume the emitter's timestamps
    directly. Guardian reviews keep their enriched `started_at` /
    `completed_at` analytics fields by deriving those legacy second-based
    values from the same protocol-native millisecond lifecycle timestamps,
    rather than sampling a separate analytics clock.
    
    ## What changed
    
    - add `started_at_ms` to user approval request payloads
    - add `started_at_ms` / `completed_at_ms` to guardian review
    notifications
    - preserve Guardian review `started_at` / `completed_at` enrichment from
    the protocol-native timing source
    - stamp typed `ServerResponse` analytics facts with app-server-observed
    `completed_at_ms`
    - thread the new timing fields through core, protocol, app-server, TUI,
    and analytics fixtures
    
    ## Verification
    
    - `cargo test -p codex-app-server outgoing_message --manifest-path
    codex-rs/Cargo.toml`
    - `cargo test -p codex-app-server-protocol guardian --manifest-path
    codex-rs/Cargo.toml`
    - `cargo test -p codex-tui guardian --manifest-path codex-rs/Cargo.toml`
    - `cargo test -p codex-analytics analytics_client_tests --manifest-path
    codex-rs/Cargo.toml`
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/21434).
    * #18748
    * __->__ #21434
    * #18747
    * #17090
    * #17089
    * #20514
  • feat: Add marketplace source filtering and plugin share context (#21419)
    Adds marketplaceKinds to plugin/list for local, workspace-directory, and
    shared-with-me; omitted params keep default local plus gated global
    behavior, while explicit kinds are exact.
    
    Exposes shareContext on plugin summaries from local share mappings and
    remote workspace/shared responses, including remotePluginId and nullable
    creator metadata.
    
    Adds shared-with-me listing through /ps/plugins/workspace/shared,
    renames the workspace remote namespace to workspace-directory, and keeps
    direct remote read/share/install/update/delete paths gated by plugins
    rather than remote_plugin.
  • Move message history out of core (#21278)
    ## Why
    
    Message history was implemented inside `codex-core` and surfaced through
    core protocol ops and `SessionConfiguredEvent` fields even though the
    current consumer is TUI-local prompt recall. That made core own UI
    history persistence and exposed `history_log_id` / `history_entry_count`
    through surfaces that app-server and other clients do not need.
    
    This change moves message history persistence out of core and keeps the
    recall plumbing local to the TUI.
    
    ## What changed
    
    - Added a new `codex-message-history` crate for appending, looking up,
    trimming, and reading metadata from `history.jsonl`.
    - Removed core protocol history ops/events: `AddToHistory`,
    `GetHistoryEntryRequest`, and `GetHistoryEntryResponse`.
    - Removed `history_log_id` and `history_entry_count` from
    `SessionConfiguredEvent` and updated exec/MCP/test fixtures accordingly.
    - Updated the TUI to dispatch local app events for message-history
    append/lookup and keep its persistent-history metadata in TUI session
    state.
    
    ## Validation
    
    - `cargo test -p codex-message-history -p codex-protocol`
    - `cargo test -p codex-exec event_processor_with_json_output`
    - `cargo test -p codex-mcp-server outgoing_message`
    - `cargo test -p codex-tui`
    - `just fix -p codex-message-history -p codex-protocol -p codex-core -p
    codex-tui -p codex-exec -p codex-mcp-server`
  • 2- Use string service tiers in session protocol (#20971)
    ## Summary
    - break service tier session/op/app-server protocol fields from the
    closed enum to string tier ids
    - send the service tier string directly through model requests, prewarm,
    compaction, memories, and TUI/app-server turn starts
    - regenerate app-server protocol JSON/TypeScript schemas, removing the
    standalone ServiceTier TS enum
    
    ## Verification
    - just fmt
    - cargo check -p codex-core -p codex-app-server -p codex-tui
    - just write-app-server-schema
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat(app-server): move v2 sessionId onto Thread (#21336)
    ## Why
    
    `session_id` and `thread_id` are separate identities after #20437, but
    app-server only surfaced `sessionId` on the `thread/start`,
    `thread/resume`, and `thread/fork` response envelopes. Other
    thread-bearing surfaces such as `thread/list`, `thread/read`,
    `thread/started`, `thread/rollback`, `thread/metadata/update`, and
    `thread/unarchive` either lacked the grouping key or forced clients to
    special-case those three responses.
    
    Making `sessionId` part of the reusable `Thread` payload gives every v2
    API surface one place to expose session-tree identity.
    
    ## Mental model
      1. thread.sessionId lives on `Thread`
    2. It is a view/runtime identity for the current live session tree, not
    durable stored lineage metadata
    3. When app-server has a live loaded thread, it copies the real value
    from core’s session_configured.session_id
    4. When it only has stored/unloaded data, it falls back to
    thread.sessionId = thread.id
    
    ## What changed
    
    - Added `sessionId` to the v2
    [`Thread`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs#L105-L109).
    - Removed the duplicate top-level `sessionId` fields from
    `thread/start`, `thread/resume`, and `thread/fork`; clients should now
    read `response.thread.sessionId`.
    - Populated `thread.sessionId` when building live thread responses,
    replaying loaded threads, and returning stored-thread summaries so the
    field is present across start, resume, fork, list, read, rollback,
    metadata-update, unarchive, and `thread/started` paths. See
    [`load_thread_from_resume_source_or_send_internal`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/src/request_processors/thread_processor.rs#L2824-L2918)
    and
    [`thread_from_stored_thread`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/src/request_processors/thread_processor.rs#L3671-L3719).
    - Preserved the stored-thread fallback: if a thread has not been loaded
    into a live session tree yet, `thread.sessionId` falls back to
    `thread.id`; once the thread is live again, the field reports the active
    session tree root.
    - Regenerated the JSON/TypeScript schemas and updated the app-server
    README examples to show
    [`thread.sessionId`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/README.md#L306-L310)
    on the thread object.
  • Support Codex Apps auth elicitations (#19193)
    ## Summary
    
    - request URL-mode MCP elicitations when Codex Apps tool calls fail with
    connector auth metadata
    - route Codex Apps auth URL elicitations into the TUI app-link flow
    
    ## Test plan
    
    - `just fmt`
    - `cargo test -p codex-core mcp_tool_call::tests`
    - `cargo test -p codex-mcp`
    - `cargo test -p codex-tui bottom_pane::app_link_view::tests`
    - `just fix -p codex-core`
    - `just fix -p codex-mcp`
    - `just fix -p codex-tui`
    
    Also attempted broader local runs:
    
    - `cargo test -p codex-core` fails in unrelated
    config/request-permission/proxy-sensitive tests under the current Codex
    Desktop environment.
    - `cargo test -p codex-tui` fails in unrelated status
    snapshots/trust-default tests because the ambient environment renders
    workspace-write/network permission defaults.
  • [codex-analytics] rework thread_source for thread analytics (#20949)
    ## Summary
    - make `thread_source` an explicit optional thread-level field on
    `thread/start`, `thread/fork`, and returned thread payloads
    - persist `thread_source` in rollout/session metadata so resumed live
    threads retain the original value
    - replace the old best-effort `session_source` -> `thread_source`
    mapping with an explicit caller-supplied analytics classification
    
    ## Why
    Before this change, analytics `thread_source` was populated by a
    best-effort mapping from `session_source`. `session_source` describes
    the runtime/client surface, not the actual thread-level origin, so that
    projection was not accurate enough to distinguish cases such as `user`,
    `subagent`, `memory_consolidation`, and future thread origins reliably.
    
    Making `thread_source` explicit keeps one thread-level analytics field
    while letting callers provide the real classification directly instead
    of recovering it indirectly from `session_source`.
    
    ## Impact
    For new analytics events, `thread_source` now reflects the explicit
    thread-level classification supplied by the caller rather than an
    inferred value derived from `session_source`. Existing protocol fields
    remain optional; callers that omit `threadSource` now produce `null`
    instead of a best-effort inferred value.
    
    ## Validation
    - `just write-app-server-schema`
    - `cargo test -p codex-analytics -p codex-core -p
    codex-app-server-protocol --no-run`
    - `cargo test -p codex-app-server-protocol
    generated_ts_optional_nullable_fields_only_in_params`
    - `cargo test -p codex-analytics
    thread_initialized_event_serializes_expected_shape`
    - `cargo test -p codex-core
    resume_stopped_thread_from_rollout_preserves_thread_source`
  • feat(tui): redesign session picker (#20065)
    ## Why
    
    The resume/fork picker is becoming the main way users recover previous
    work, but the old fixed table made sessions hard to scan once thread
    names, branches, working directories, and timestamps all mattered. This
    redesign makes the picker denser by default, easier to search, and safer
    to inspect before resuming or forking.
    
    <table>
    <tr>
    <td>
    <img width="1660" height="1103" alt="CleanShot 2026-05-03 at 12 34 10"
    src="https://github.com/user-attachments/assets/313ede1d-1da4-4863-acd2-56b3e27e9703"
    />
    </td>
    <td>
    <img width="1662" height="1100" alt="CleanShot 2026-05-03 at 12 34 15"
    src="https://github.com/user-attachments/assets/cfde7d5c-bab0-4994-a807-254e53f344ea"
    />
    </td>
    </tr>
    <tr>
    <td>
    <img width="1664" height="1107" alt="CleanShot 2026-05-03 at 12 39 22"
    src="https://github.com/user-attachments/assets/e1ee58ca-4dc5-4a35-ae0f-47562da3974c"
    />
    </td>
    <td>
    <img width="1662" height="1100" alt="CleanShot 2026-05-03 at 12 35 09"
    src="https://github.com/user-attachments/assets/9c888072-eedf-4f45-985c-0c14df28bcc7"
    />
    </td>
    </tr>
    </table>
    
    ## What Changed
    
    - Replaces the old session table with responsive session rows that
    prioritize the session name or preview, then show timestamp, cwd, and
    branch metadata.
    - Makes dense view the default while keeping comfortable view available
    through `Ctrl+O`.
    - Persists the picker view preference in `[tui].session_picker_view`,
    including active profile-scoped config.
    - Adds sort/filter controls for updated time, created time, cwd, and all
    sessions.
    - Expands search matching across session name, preview, thread id,
    branch, and cwd.
    - Makes `Esc` safer in search mode: it clears an active query before
    starting a new session.
    - Adds lazy transcript inspection:
      - `Space` expands recent transcript context inline.
      - `Ctrl+T` opens a transcript overlay.
      - raw reasoning visibility follows `show_raw_agent_reasoning`.
    - Keeps remote cwd filtering server-side for remote app-server sessions
    so local path normalization does not incorrectly hide remote results.
    - Updates snapshots and config schema for the new picker states and
    config option.
    
    ## How to Test
    
    1. Start Codex in a repo with several saved sessions.
    2. Press `Ctrl+R` / resume picker entry point.
    3. Confirm the picker opens in dense mode and shows session name or
    preview, timestamp, cwd, and branch metadata.
    4. Press `Ctrl+O` and confirm it switches between dense and comfortable
    views.
    5. Restart Codex and confirm the selected view persists.
    6. Type a query that matches a branch, cwd, thread id, or session name;
    confirm matching sessions appear.
    7. Press `Esc` while the query is non-empty and confirm it clears search
    instead of starting a new session.
    8. Select a session and press `Space`; confirm recent transcript context
    expands inline.
    9. Press `Ctrl+T`; confirm the transcript overlay opens and respects
    raw-reasoning visibility settings.
    
    Targeted tests:
    - `cargo test -p codex-tui resume_picker --no-fail-fast`
    - `cargo test -p codex-core
    runtime_config_resolves_session_picker_view_default_and_override`
    - `cargo test -p codex-core profile_tui_rejects_unsupported_settings`
    - `cargo check -p codex-thread-manager-sample`
    - `cargo insta pending-snapshots`
  • add turn items view to app-server turns (#21063)
    ## Why
    
    `Turn.items` currently overloads an empty array to mean either that no
    items exist or that the server intentionally did not load them for this
    response. That ambiguity blocks future lazy-loading work where clients
    need to distinguish unloaded, summary, and fully hydrated turn payloads.
    
    ## What changed
    
    - add a new `TurnItemsView` enum with `notLoaded`, `summary`, and `full`
    variants
    - add required `itemsView` metadata to app-server `Turn` payloads
    - mark reconstructed persisted history as `full` and live shell-style
    turn payloads as `notLoaded`
    - keep current `thread/turns/list` behavior unchanged and document that
    it still returns `full` turns today
    - regenerate the JSON and TypeScript protocol fixtures
    
    ## Verification
    
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server thread_read_can_include_turns`
    - `cargo test -p codex-app-server
    thread_turns_list_can_page_backward_and_forward`
    - `cargo test -p codex-app-server
    thread_resume_rejects_history_when_thread_is_running`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-app-server`
    - `just fmt`
  • hook trust metadata and enforcement (#20321)
    # Why
    
    We want shared hook trust that both the app and the TUI can build on,
    but the metadata is only useful if runtime behavior agrees with it. This
    PR adds a single backend trust model for hooks so unmanaged hooks cannot
    run until the current definition has been reviewed, while managed hooks
    remain runnable and non-configurable.
    
    # What
    
    - persist `trusted_hash` alongside hook state in `config.toml`
    - expose `currentHash` and derived `trustStatus` through `hooks/list`
    - derive trust from normalized hook definitions so equivalent hooks from
    `config.toml` and `hooks.json` share the same trust identity
    - gate unmanaged hooks on trust before they enter the runnable handler
    set
    
    # Reviewer Notes
    
    - key file to review is `codex-rs/hooks/src/engine/discovery.rs`
    - the only **core** change is schema related
  • feat(tui): add raw scrollback mode (#20819)
    ## Why
    
    Granular copy is particularly difficult with the current output. Part of
    it was solved with the introduction of the `/copy` command but when you
    only need to copy parts of a response, you still encounter some issues:
    
    - When you copy a paragraph, the result is a sequence of separate lines
    instead of one correctly joined paragraph.
    - When a word wraps, part of it stays on the original line and the rest
    appears at the start of the next line.
    - When you copy a long command, extra line breaks are often inserted,
    and command arguments can be split across multiple lines.
    
    
    https://github.com/user-attachments/assets/0ef85c84-9363-4aad-b43a-15fce062a443
    
    ## Solution
    
    Now that we own the scrollback and we re-create it when we resize, we
    have the opportunity of toggling between the raw text and the rich text
    we see today.
    
    - Add TUI raw scrollback mode with `tui.raw_output_mode`, `/raw
    [on|off]`, and the configurable `tui.keymap.global.toggle_raw_output`
    action.
    - Render transcript cells through rich/raw-aware paths so raw mode
    preserves source text and lets the terminal soft-wrap selection-friendly
    output.
    - Bind raw-mode toggle to `alt-r` by default, with the keybinding path
    toggling silently while `/raw` continues to emit confirmation messages.
    
    ## Related Issues
    
    Likely addressed by raw mode:
    
    - #12200: clean copy for multiline and soft-wrapped output. Raw mode
    removes Codex-inserted wrapping/indentation and lets the terminal
    soft-wrap logical lines.
    - #9252: command suggestions gain unwanted leading spaces when copied.
    Raw mode renders transcript text without the rich-mode left
    padding/gutter.
    - #8258: prompt output is hard to copy because of leading indentation.
    Raw mode renders user/source-backed transcript text without that
    decorative indentation.
    
    Partially or conditionally addressed:
    
    - #2880: copy/export message as Markdown. Raw mode exposes raw Markdown
    for terminal selection, but this PR does not add a dedicated
    export/copy-message command.
    - #19820: mouse drag selection + copy in the TUI. Raw mode improves
    terminal-native selection of output/history text, but this PR does not
    implement in-TUI mouse selection, highlighting, auto-copy, or composer
    selection.
    - #18979: copied content is divided into two parts. This should improve
    cases caused by Codex-inserted wraps/padding in rendered output; if the
    report is about pasting into the composer/input path, that remains
    outside this PR.
    
    ## Validation
    
    - `just write-config-schema`
    - `just fmt`
    - `cargo test -p codex-config`
    - `cargo test -p codex-tui`
    - `just fix -p codex-tui`
    - `just argument-comment-lint`
    - `cargo test -p codex-tui
    raw_output_mode_can_change_without_inserting_notice -- --nocapture`
    - `cargo test -p codex-tui
    raw_slash_command_toggles_and_accepts_on_off_args -- --nocapture`
    - `cargo test -p codex-tui raw_output_toggle -- --nocapture`
    - `git diff --check`
    - `cargo insta pending-snapshots`
  • [codex] Add unsandboxed process exec API (#19040)
    ## Why
    
    App-server clients sometimes need argv-based local process execution
    while sandbox policy is controlled outside Codex. Those environments can
    reject sandbox-disabling paths before a command ever starts, even when
    the caller intentionally wants unsandboxed execution.
    
    This PR adds a distinct `process/*` API for that use case instead of
    extending `command/exec` with another sandbox-disabling shape. Keeping
    the new surface separate also makes the future removal of `command/exec`
    simpler: clients that need explicit process lifecycle control can move
    to the newer handle-based API without depending on `command/exec`
    business logic.
    
    ## What changed
    
    - Added v2 process lifecycle methods: `process/spawn`,
    `process/writeStdin`, `process/resizePty`, and `process/kill`.
    - Added process notifications: `process/outputDelta` for streamed
    stdout/stderr chunks and `process/exited` for final exit status and
    buffered output.
    - Made `process/spawn` intentionally unsandboxed and omitted
    sandbox-selection fields such as `sandboxPolicy` and
    `permissionProfile`.
    - Added client-supplied, connection-scoped `processHandle` values for
    follow-up control requests and notification routing.
    - Supported cwd, environment overrides, PTY mode and size, stdin
    streaming, stdout/stderr streaming, per-stream output caps, and timeout
    controls.
    - Killed active process sessions when the originating app-server
    connection closes.
    - Wired the implementation through the modular `request_processors/`
    app-server layout, with process-handle request serialization for
    follow-up control calls.
    - Updated generated JSON/TypeScript schema fixtures and documented the
    new API in `codex-rs/app-server/README.md`.
    - Added v2 app-server integration coverage in
    `codex-rs/app-server/tests/suite/v2/process_exec.rs` for spawn
    acknowledgement before exit, buffered output caps, and process
    termination.
    
    ## Verification
    
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server`
    
    ---------
    
    Co-authored-by: Owen Lin <owen@openai.com>
  • [codex-analytics] add item lifecycle timing (#20514)
    ## Why
    
    Tool families already disagree on what their existing `duration` fields
    mean, so lifecycle latency should live on the shared item envelope
    instead of being inferred from per-tool execution fields. Carrying that
    envelope through app-server notifications gives downstream consumers one
    reusable timing signal without pretending every tool has the same
    execution semantics.
    
    ## What changed
    
    - Adds `started_at_ms` to core `ItemStartedEvent` values and
    `completed_at_ms` to core `ItemCompletedEvent` values.
    - Populates those timestamps in the shared session lifecycle emitters,
    so protocol-native items get timing without each producer tracking its
    own clock state.
    - Exposes `startedAtMs` on app-server `item/started` notifications and
    `completedAtMs` on `item/completed` notifications.
    - Maps the lifecycle timestamps through the app-server boundary while
    leaving legacy-converted notifications nullable when no lifecycle
    timestamp exists.
    - Regenerates the app-server JSON schema and TypeScript fixtures for the
    notification-envelope change and updates downstream fixtures that
    construct those notifications directly.
    - Extends the existing web-search and image-generation integration flows
    to assert the new lifecycle timestamps on the native item events.
    
    ## Verification
    
    - `cargo check -p codex-protocol -p codex-core -p
    codex-app-server-protocol -p codex-app-server -p codex-tui -p codex-exec
    -p codex-app-server-client`
    - `cargo test -p codex-core --test all web_search_item_is_emitted`
    - `cargo test -p codex-core --test all
    image_generation_call_event_is_emitted`
    - `cargo test -p codex-app-server-protocol`
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/20514).
    * #18748
    * #18747
    * #17090
    * #17089
    * __->__ #20514
  • feat(tui): improve TUI keymap coverage (#20798)
    ## Summary
    - normalize terminal-emitted C0 control characters through configurable
    editor keymaps, covering raw control-key fallbacks like
    Shift+Enter-as-LF in terminals from #20555 and #20898, plus part of the
    modified-Enter behavior in #20580
    - add default-unbound keymap actions for toggling Fast mode and killing
    the current composer line, giving #20698 users a configurable zsh-style
    Ctrl+U option without changing the existing default Ctrl+U behavior
    - wire the new actions through gated /keymap picker entries, schema
    generation, and snapshot coverage
    
    Fixes #20555.
    Fixes #20898.
    
    ## Testing
    - just write-config-schema
    - just fmt
    - cargo test -p codex-config
    - cargo test -p codex-tui keymap::tests
    - cargo test -p codex-tui bottom_pane::textarea::tests
    - cargo test -p codex-tui keymap_setup::tests
    - cargo insta pending-snapshots
    - just fix -p codex-tui
    - git diff --check
    - just argument-comment-lint
  • feat(tui): add PR summary statusline items (#20892)
    ## Why?
    
    The Codex App already exposes branch and PR context in its
    branch-details UI. This brings the same context into the CLI footer as
    opt-in statusline items, so users can choose the extra signal without
    making the default footer busier.
    
    ## What?
    
    Add optional `pull-request-number` and `branch-changes` items to the
    configurable TUI status line.
    
    - `pull-request-number` shows the open PR for the current checkout and
    renders as a clickable terminal hyperlink when OSC 8 links are
    supported.
    - `branch-changes` shows committed additions/deletions against the
    repository default branch, or `No changes` when the branch has no
    committed diff.
    
    <img width="1257" height="261" alt="CleanShot 2026-05-03 at 20 44 15"
    src="https://github.com/user-attachments/assets/10b4380b-c3e9-4729-9ee1-3f742068fa47"
    />
    
    ## Architecture
    
    This follows the same client/app-server split as the Codex App: the TUI
    owns presentation, caching, and optional rendering, while
    workspace-sensitive `git` and `gh` discovery runs through app-server.
    
    The new TUI-local `workspace_command` layer sends bounded,
    non-interactive `command/exec` requests to the active app-server. That
    makes the implementation remote-friendly: the TUI does not decide
    whether commands run in an embedded local workspace or a remote
    workspace, and it does not bypass app-server sandbox or permission
    policy.
    
    The branch summary logic stays internal to `codex-tui` because this PR
    only needs TUI statusline behavior. The command boundary is still
    isolated behind `WorkspaceCommandExecutor`, so the lookup code can be
    lifted or reused later without changing statusline rendering.
    
    ## How?
    
    - Add a TUI `WorkspaceCommandExecutor` abstraction backed by app-server
    `command/exec`.
    - Add branch summary probes for:
      - current branch name,
      - open PR metadata,
      - committed branch diff stats against the default branch.
    - Prefer remote-tracking default branch refs for diff stats, avoiding
    stale or absent local `main` branches.
    - Resolve PRs with `gh pr view` first, then fall back to
    commit-associated PR lookup across parent/fork repos.
    - Add `/statusline` picker entries, preview values, rendering, and OSC 8
    clickable PR links.
    - Keep all probes best-effort so missing `git`, missing `gh`, auth
    failures, or non-git directories hide optional items instead of
    surfacing footer errors.
    
    ## Validation
    
    - `cargo test -p codex-tui branch_summary -- --nocapture`
    - Snapshot coverage for the `/statusline` preview/setup rendering paths
    - Hyperlink rendering coverage for clickable PR statusline cells
  • state: pass state db handles through consumers (#20561)
    ## Why
    
    SQLite state was still being opened from consumer paths, including lazy
    `OnceCell`-backed thread-store call sites. That let one process
    construct multiple state DB connections for the same Codex home, which
    makes SQLite lock contention and `database is locked` failures much
    easier to hit.
    
    State DB lifetime should be chosen by main-like entrypoints and tests,
    then passed through explicitly. Consumers should use the supplied
    `Option<StateDbHandle>` or `StateDbHandle` and keep their existing
    filesystem fallback or error behavior when no handle is available.
    
    The startup path also needs to keep the rollout crate in charge of
    SQLite state initialization. Opening `codex_state::StateRuntime`
    directly bypasses rollout metadata backfill, so entrypoints should
    initialize through `codex_rollout::state_db` and receive a handle only
    after required rollout backfills have completed.
    
    ## What Changed
    
    - Initialize the state DB in main-like entrypoints for CLI, TUI,
    app-server, exec, MCP server, and the thread-manager sample.
    - Pass `Option<StateDbHandle>` through `ThreadManager`,
    `LocalThreadStore`, app-server processors, TUI app wiring, rollout
    listing/recording, personality migration, shell snapshot cleanup,
    session-name lookup, and memory/device-key consumers.
    - Remove the lazy local state DB wrapper from the thread store so
    non-test consumers use only the supplied handle or their existing
    fallback path.
    - Make `codex_rollout::state_db::init` the local state startup path: it
    opens/migrates SQLite, runs rollout metadata backfill when needed, waits
    for concurrent backfill workers up to a bounded timeout, verifies
    completion, and then returns the initialized handle.
    - Keep optional/non-owning SQLite helpers, such as remote TUI local
    reads, as open-only paths that do not run startup backfill.
    - Switch app-server startup from direct
    `codex_state::StateRuntime::init` to the rollout state initializer so
    app-server cannot skip rollout backfill.
    - Collapse split rollout lookup/list APIs so callers use the normal
    methods with an optional state handle instead of `_with_state_db`
    variants.
    - Restore `getConversationSummary(ThreadId)` to delegate through
    `ThreadStore::read_thread` instead of a LocalThreadStore-specific
    rollout path special case.
    - Keep DB-backed rollout path lookup keyed on the DB row and file
    existence, without imposing the filesystem filename convention on
    existing DB rows.
    - Verify readable DB-backed rollout paths against `session_meta.id`
    before returning them, so a stale SQLite row that points at another
    thread's JSONL falls back to filesystem search and read-repairs the DB
    row.
    - Keep `debug prompt-input` filesystem-only so a one-off debug command
    does not initialize or backfill SQLite state just to print prompt input.
    - Keep goal-session test Codex homes alive only in the goal-specific
    helper, rather than leaking tempdirs from the shared session test
    helper.
    - Update tests and call sites to pass explicit state handles where DB
    behavior is expected and explicit `None` where filesystem-only behavior
    is intended.
    
    ## Validation
    
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo check -p
    codex-rollout -p codex-thread-store -p codex-app-server -p codex-core -p
    codex-tui -p codex-exec -p codex-cli --tests`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-rollout state_db_`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-rollout find_thread_path`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-rollout find_thread_path -- --nocapture`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-rollout try_init_ -- --nocapture`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-rollout`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo clippy -p
    codex-rollout --lib -- -D warnings`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-thread-store
    read_thread_falls_back_when_sqlite_path_points_to_another_thread --
    --nocapture`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-thread-store`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    shell_snapshot`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    --test all personality_migration`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    --test all rollout_list_find`
    - `RUST_MIN_STACK=8388608 CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    --test all rollout_list_find::find_prefers_sqlite_path_by_id --
    --nocapture`
    - `RUST_MIN_STACK=8388608 CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    --test all rollout_list_find -- --nocapture`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    interrupt_accounts_active_goal_before_pausing`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-app-server get_auth_status -- --test-threads=1`
    - `CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-app-server --lib`
    - `CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo check -p codex-rollout
    -p codex-app-server --tests`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db just fix -p codex-rollout
    -p codex-thread-store -p codex-core -p codex-app-server -p codex-tui -p
    codex-exec -p codex-cli`
    - `CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db just fix -p codex-rollout -p
    codex-app-server`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db just fix -p
    codex-rollout`
    - `CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db just fix -p codex-core`
    - `just argument-comment-lint -p codex-core`
    - `just argument-comment-lint -p codex-rollout`
    
    Focused coverage added in `codex-rollout`:
    
    - `recorder::tests::state_db_init_backfills_before_returning` verifies
    the rollout metadata row exists before startup init returns.
    - `state_db::tests::try_init_waits_for_concurrent_startup_backfill`
    verifies startup waits for another worker to finish backfill instead of
    disabling the handle for the process.
    -
    `state_db::tests::try_init_times_out_waiting_for_stuck_startup_backfill`
    verifies startup does not hang indefinitely on a stuck backfill lease.
    -
    `tests::find_thread_path_accepts_existing_state_db_path_without_canonical_filename`
    verifies DB-backed lookup accepts valid existing rollout paths even when
    the filename does not include the thread UUID.
    -
    `tests::find_thread_path_falls_back_when_db_path_points_to_another_thread`
    verifies DB-backed lookup ignores a stale row whose existing path
    belongs to another thread and read-repairs the row after filesystem
    fallback.
    
    Focused coverage updated in `codex-core`:
    
    - `rollout_list_find::find_prefers_sqlite_path_by_id` now uses a
    DB-preferred rollout file with matching `session_meta.id`, so it still
    verifies that valid SQLite paths win without depending on stale/empty
    rollout contents.
    
    `cargo test -p codex-app-server thread_list_respects_search_term_filter
    -- --test-threads=1 --nocapture` was attempted locally but timed out
    waiting for the app-server test harness `initialize` response before
    reaching the changed thread-list code path.
    
    `bazel test //codex-rs/thread-store:thread-store-unit-tests
    --test_output=errors` was attempted locally after the thread-store fix,
    but this container failed before target analysis while fetching `v8+`
    through BuildBuddy/direct GitHub. The equivalent local crate coverage,
    including `cargo test -p codex-thread-store`, passes.
    
    A plain local `cargo check -p codex-rollout -p codex-app-server --tests`
    also requires system `libcap.pc` for `codex-linux-sandbox`; the
    follow-up app-server check above used `CODEX_SKIP_VENDORED_BWRAP=1` in
    this container.
  • tui: retire /approvals and rename /autoreview to /approve (#21034)
    ## Why
    
    The TUI currently exposes overlapping command names for the same
    permissions flow: `/permissions` and the older `/approvals` alias. It
    also uses `/autoreview` for the manual retry flow, even though the
    action users take there is approving one denied auto-review request.
    
    This change makes the command surface consistent with the hard rebrand:
    - `/permissions` is the only command for permission settings.
    - `/approve` is the command for approving a recent auto-review denial.
    
    ## What changed
    
    - Removed the legacy `/approvals` slash command and its dispatch path.
    - Kept `/permissions` as the single permissions command shown and
    accepted by the TUI.
    - Renamed the auto-review denial command from `/autoreview` to
    `/approve`.
    - Updated nearby comments so they refer to `/permissions` rather than
    the retired `/approvals` name.
    
    ## Verification
    
    - Updated the slash-command unit test to assert that `AutoReview` now
    renders and parses as `approve`.
  • feat(tui): add keymap debug inspector (#20794)
    ## Why
    
    We constantly get bug reports about keys not being recognized by Codex
    when the terminal is not handling the key press. Running `/keymap debug`
    or `/keymap` and going to the Debug tab, we can allow the user to either
    understand that the key being pressed is not being recognized or to
    check what it's being recognized as and report or reassign that key.
    
    | Menu | Inspector | Hint |
    |---|---|---|
    | <img width="1369" height="796" alt="CleanShot 2026-05-02 at 12 57 12"
    src="https://github.com/user-attachments/assets/512b6faa-344e-4aee-9c00-b4bdc633a662"
    /> | <img width="1261" height="754" alt="CleanShot 2026-05-02 at 12 56
    36"
    src="https://github.com/user-attachments/assets/a6ddae7d-e174-4ee4-893f-e6bec4fff4ab"
    /> | <img width="1369" height="796" alt="CleanShot 2026-05-02 at 12 57
    30"
    src="https://github.com/user-attachments/assets/db507784-f40a-4cff-ac23-a61d9703769b"
    /> |
    ## Summary
    - add a Debug tab to `/keymap` and support `/keymap debug` for direct
    access
    - show what key Codex receives, the config key representation, raw event
    details, and matching actions
    - add a progressive missing-key hint that escalates after a few seconds
    with no detected keypress
    
    ## Validation
    - `just fmt`
    - `cargo test -p codex-tui keymap_setup::tests::debug_view`
    - `cargo test -p codex-tui keymap_setup::tests`
    - `cargo test -p codex-tui slash_keymap`
    - `cargo test -p codex-tui` (unit tests passed; integration test
    `suite::model_availability_nux::resume_startup_does_not_consume_model_availability_nux_count`
    failed locally by itself with `codex resume` exiting 1 and terminal
    probe escape output)
    - `just fix -p codex-tui`
    - `just argument-comment-lint`
    - `cargo insta pending-snapshots`
    - `git diff --check`
  • Speed up /side parent restore replay (#20815)
    ## Why
    
    Returning from a `/side` conversation restores the parent thread by
    replaying its snapshot into the TUI. For very long parent threads,
    replaying every transcript row can take noticeable time even though most
    rows immediately scroll out of terminal history.
    
    ## What Changed
    
    - Buffer thread-switch replay for parent restores when terminal resize
    reflow is enabled.
    - Reuse the existing resize-reflow tail renderer so only the retained
    transcript tail is written back to scrollback when a row cap is
    configured.
  • Keep paused goals paused on thread resume (#20790)
    ## Summary
    
    Early adopters of the `/goal` feature have provided feedback that they
    expect a goal they explicitly paused to remain paused when they resume a
    thread. Previously, resuming a thread would reactivate a paused goal.
    
    This PR keeps persisted goal status unchanged during thread resume. This
    honors the user feedback while also simplifying the core goal logic.
    
    Rather than have the core logic automatically resume a paused goal, that
    responsibility is transferred to the client. The TUI now detects a
    resumed thread with a paused goal and asks the user whether to `Resume
    goal` or `Leave paused`. The prompt appears only for quiet resume flows,
    so users who resume with an immediate prompt are not interrupted.
    
    <img width="544" height="111" alt="image"
    src="https://github.com/user-attachments/assets/0ac9de1c-6ee6-47ba-b223-c03c8eb4c192"
    />
  • /plugins: add marketplace upgrade flow (#20478)
    This PR adds marketplace upgrade to the `/plugins` menu so users can
    update configured marketplaces. It adds a `Ctrl+U` shortcut on eligible
    marketplace tabs, a loading state, and the app-server request flow
    needed to perform `marketplace/upgrade`. After a successful upgrade, the
    TUI refreshes plugin data, plugin mentions, and user config so updated
    marketplace contents show up across the menu and other plugin surfaces.
    It also preserves the current marketplace tab on no-op and failure paths
    and surfaces backend error details directly in the TUI.
    
    - Add a `Ctrl+U` upgrade option for user-configured marketplace tabs in
    `/plugins`
    - Show the upgrade footer hint only on upgradeable marketplace tabs
    - Show a loading state during `marketplace/upgrade`
    - Surface already-up-to-date and per-marketplace failure results from
    the backend
    - Refresh plugin data, plugin mentions, and user config after successful
    upgrades
    - Add tests and snapshot updates for the shortcut flow, loading state,
    and failure messaging
    
    Steps to test:
    1. Add a `/plugin` marketplace to Codex TUI.
    2. Open `/plugins`, move to that marketplace tab, and confirm the footer
    shows `Ctrl+U` to upgrade.
    3. Press `Ctrl+U` and confirm the popup switches into an upgrade loading
    state.
    4. When the request finishes, confirm you see the expected result:
    updated marketplace contents on success, an already-up-to-date message
    on no-op, or backend error details on failure. On no-op or failure,
    confirm the popup stays on the same marketplace tab.
  • Color TUI statusline from active theme (#19631)
    ## Why
    
    Users have shared that the TUI can feel too visually flat because themes
    mostly show up in code syntax highlighting. The configurable statusline
    is a natural place to make the active theme more visible, while still
    letting users keep the existing monotone statusline if they prefer it.
    
    ## What Changed
    
    - Added a statusline styling helper that builds the rendered statusline
    from `(StatusLineItem, text)` segments, preserving item identity while
    keeping the plain text output unchanged.
    - Derived foreground accent colors from the active syntax theme by
    looking up TextMate scopes through the existing syntax highlighter, with
    conservative ANSI fallbacks when a scope does not provide a foreground.
    - Tuned theme-derived colors to keep the accents visible without making
    the statusline feel overly bright.
    - Added `[tui].status_line_use_colors`, defaulting to `true`, plus a
    separated `/statusline` toggle so users can enable or disable
    theme-derived statusline colors from the setup UI.
    - Updated the live statusline and `/statusline` preview to use the same
    styled builder, while keeping terminal-title preview text plain.
    - Kept statusline separators and active-agent add-ons subdued while
    removing blanket dimming from the whole passive statusline.
    
    ## Verification
    
    - `cargo test -p codex-tui status_line`
    - `cargo test -p codex-tui theme_picker`
    - `cargo test -p codex-tui foreground_style_for_scopes`
    - `cargo test -p codex-tui`
    - `cargo test -p codex-config`
    - `cargo test -p codex-core status_line_use_colors`
    - `cargo insta pending-snapshots --manifest-path tui/Cargo.toml`
    
    ## Visual
    
    <img width="369" height="23" alt="Screenshot 2026-04-30 at 6 16 08 PM"
    src="https://github.com/user-attachments/assets/11d03efb-8e4f-4450-8f4d-00a9659ef4cd"
    />
    
    <img width="385" height="23" alt="Screenshot 2026-04-30 at 6 16 02 PM"
    src="https://github.com/user-attachments/assets/a3d89f36-bdc1-42e8-8e84-61350e3999e2"
    />
  • Make thread store process-scoped (#19474)
    - Build one app-server process ThreadStore from startup config and share
    it with ThreadManager and CodexMessageProcessor.
    - Remove per-thread/fork store reconstruction so effective thread config
    cannot switch the persistence backend.
    - Add params to ThreadStore create/resume for specifying thread
    metadata, since otherwise the metadata from store creation would be used
    (incorrectly).
  • feat(tui): add vim composer mode (#18595)
    ## Why
    
    Codex now has configurable TUI keymaps, but the composer still behaves
    like a plain text field. Users who prefer modal editing need a way to
    keep Vim muscle memory while drafting prompts, and the keymap picker
    needs to expose Vim-specific actions if those bindings are configurable
    instead of hardcoded.
    
    ## What Changed
    
    - Adds composer Vim mode with insert/normal state, common normal-mode
    movement and editing commands, `d`/`y` operator-pending flows, and
    mode-aware footer and cursor indicators.
    - Adds `/vim`, an optional global `toggle_vim_mode` binding, and
    `tui.vim_mode_default` so Vim mode can be toggled per session or enabled
    as the default composer state.
    - Extends runtime and config keymaps with `vim_normal` and
    `vim_operator` contexts, exposes those contexts in `/keymap`, refreshes
    the config schema, and validates Vim bindings separately.
    - Integrates Vim normal mode with existing composer behavior: `/` opens
    slash command entry, `!` enters shell mode, `j`/`k` navigate history at
    history boundaries, successful submissions reset back to normal mode,
    and paste burst handling remains insert-mode only.
    - Teaches the TUI render path to apply and restore cursor style so Vim
    insert mode can use a bar cursor without leaving the terminal in that
    state after exit.
    
    ## Validation
    
    - `cargo test -p codex-tui keymap -- --nocapture` on the keymap/Vim
    coverage
    - `cargo insta pending-snapshots`
    
    ## Docs
    
    This introduces user-facing `/vim`, `tui.vim_mode_default`, and Vim
    keymap contexts under `tui.keymap`, so the public CLI configuration and
    slash-command docs should be updated before the feature ships.
  • fix: show correct Bedrock runtime endpoint in /status (#20275)
    ## Why
    
    `/status` was showing the configured `ModelProviderInfo.base_url` for
    Amazon Bedrock, which can be stale or misleading because the actual
    Bedrock Mantle endpoint is derived at runtime from the resolved AWS
    region. This made sessions report the wrong provider endpoint even
    though requests used the correct runtime URL.
    
    ## What changed
    
    - Added `ModelProvider::runtime_base_url()` so provider implementations
    can expose the request-time base URL through the shared runtime provider
    abstraction.
    - Moved Bedrock region-to-Mantle URL resolution into
    `amazon_bedrock::mantle::runtime_base_url()`, keeping region resolution
    private to the Mantle module.
    - Overrode `runtime_base_url()` for Amazon Bedrock so it returns the
    resolved Mantle endpoint instead of the configured default.
    - Resolved and cached the runtime provider base URL during TUI startup,
    then used that cached value when rendering `/status`.
    - Added status coverage that verifies Bedrock displays the runtime URL
    and ignores the configured Bedrock `base_url` when they differ.
    
    ## Verification
    model provider is resolved correctly in local build:
    <img width="696" height="245" alt="Screenshot 2026-04-29 at 5 01 36 PM"
    src="https://github.com/user-attachments/assets/a13c10a5-3720-41ab-8ace-3c4bc573f971"
    />
  • Add /hooks browser for lifecycle hooks (#19882)
    ## Why
    
    `hooks/list` and `hooks/config/write` give us read/write access to hooks
    and their state. This hooks up the TUI as a client so users can inspect
    and manage that state directly.
    
    ## What
    
    - add a two-page `/hooks` browser in the TUI: an event overview with
    installed/active counts, followed by a per-event handler page with
    toggle controls and detail rendering
    - thread managed-state metadata through hook discovery and `hooks/list`
    so the UI can label admin-managed hooks and suppress toggles for them
    - persist hook toggles through the existing config-write path and add
    snapshot coverage for the event list, handler list, managed-hook, and
    empty states
    
    ## Stack
    
    1. openai/codex#19705
    2. openai/codex#19778
    3. openai/codex#19840
    4. This PR - openai/codex#19882
    
    ## Reviewer Notes
    
    - Main UI logic is in
    `codex-rs/tui/src/bottom_pane/hooks_browser_view.rs`; most of the diff
    is the new view plus its snapshot coverage
    - Request / write plumbing for opening the browser and persisting
    toggles is in `codex-rs/tui/src/app/background_requests.rs` and
    `codex-rs/tui/src/chatwidget/hooks.rs`
    - Outside the TUI, the only behavioral change in this PR is threading
    `is_managed` through hook discovery and `hooks/list` so managed hooks
    render as non-toggleable
    - The `codex-rs/tui/src/status/snapshots/` churn is unrelated merge
    fallout from the stacked base branch's newer permission-label rendering
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Remove core protocol dependency [2/2] (#20325)
    ## Why
    
    With the local model layer and app-server routing in place from PR1,
    this PR moves the active TUI runtime onto app-server notifications. The
    affected pieces share the same event flow, so the command surface,
    session state, bottom-pane prompts, chat rendering, history/status
    views, and tests move together to keep the stacked branch buildable.
    
    This PR also removes the obsolete compatibility surface that is no
    longer used after the migration. The proposed protocol-boundary verifier
    layer was dropped from the stack; enforcing that final boundary will be
    simpler once `codex-tui` no longer needs any `codex_protocol`
    references.
    
    This PR is part 2 of a 2-PR stack:
    
    1. Add TUI-owned replacement models and extract app-server event
    routing.
    2. Move the active TUI flow to app-server notifications and delete
    obsolete adapter code.
    
    ## What changed
    
    - Rewired app command and session handling to use app-server request and
    notification shapes.
    - Moved approval overlays, request-user-input flows, MCP elicitation,
    realtime events, and review commands onto the app-server-facing model
    surface.
    - Updated chat rendering, history cells, status views, multi-agent UI,
    replay state, and TUI tests to use app-server notifications plus the
    local models introduced in PR1.
    - Deleted `codex-rs/tui/src/app/app_server_adapter.rs` and the
    superseded `chatwidget/tests/background_events.rs` fixture path.
    
    ## Verification
    
    - `cargo check -p codex-tui --tests`
    - Top of stack: `cargo test -p codex-tui`
  • Remove core protocol dependency [1/2] (#20324)
    ## Why
    
    This stack moves `codex-tui` away from the core protocol event surface
    and toward app-server API shapes plus TUI-owned local models. This first
    PR sets up the lower-risk foundation: it introduces the local model
    surface and extracts app-server event routing into focused TUI modules
    while preserving the existing behavior for the larger migration in PR2.
    
    This PR is part 1 of a 2-PR stack:
    
    1. Add TUI-owned replacement models and extract app-server event
    routing.
    2. Move the active TUI flow to app-server notifications and delete
    obsolete adapter code.
    
    ## What changed
    
    - Added TUI-owned approval, diff, session state, session resume, token
    usage, and user-message models.
    - Added `app/app_server_event_targets.rs` and `app/app_server_events.rs`
    to hold app-server event targeting and dispatch logic outside `app.rs`.
    - Updated app/status tests to use the local model layer and added
    focused routing coverage.
    - Boxed a few large async TUI test futures so this base layer remains
    checkable without overflowing the default test stack.
    
    ## Verification
    
    - `cargo check -p codex-tui --tests`
  • /plugins: remove marketplace (#19843)
    This PR adds marketplace removal to the /plugins menu, giving users a
    way to remove user-configured plugin marketplaces. It adds a `Ctrl+R`
    shortcut to remove selected marketplace tabs, a confirmation prompt,
    loading and error states, and the app-server request flow needed to
    perform marketplace/remove. After a successful removal, the TUI
    refreshes config, plugin mentions, user config, and plugin data so the
    removed marketplace disappears from the menu and other surfaces in the
    TUI.
    
    - Add `Ctrl+R` removal option for user-configured marketplace tabs
    - Show marketplace removal confirmation, loading, and error states
    - Route `marketplace/remove` through the TUI background request flow
    - Refresh config, plugin mentions, and plugin data after successful
    removal
    - Adds reusable per-tab footer hints so removal guidance only appears on
    applicable tabs
    - Add test coverage for `Ctrl+R` behavior while plugin search is active
    
    Steps to test:
    - Add a marketplace using the TUI /plugins menu
    - Use Ctrl+R to remove the marketplace
    - Accept the confirmation prompt
    - Confirm the marketplace is removed when the process completes.
  • realtime: rename provider session ids (#20361)
    ## Summary
    
    Codex is repurposing `session` to mean a thread group, so the realtime
    provider session id should no longer use `session_id` / `sessionId` in
    Codex-facing protocol payloads. This PR renames that provider-specific
    field to `realtime_session_id` / `realtimeSessionId` and intentionally
    breaks clients that still send the old field names.
    
    ## What Changed
    
    - Renamed realtime provider session fields in `ConversationStartParams`,
    `RealtimeConversationStartedEvent`, and `RealtimeEvent::SessionUpdated`.
    - Renamed app-server v2 realtime request and notification fields to
    `realtimeSessionId`.
    - Removed legacy serde aliases for `session_id` / `sessionId`; clients
    must send the new names.
    - Propagated the rename through core realtime startup, app-server
    adapters, codex-api websocket handling, and TUI realtime state.
    - Regenerated app-server protocol schema/TypeScript outputs and updated
    app-server README examples.
    - Kept upstream Realtime API concepts unchanged: provider `session.id`
    parsing and `x-session-id` headers still use the upstream wire names.
    
    ## Testing
    
    - CI is running on the latest pushed commit.
    - Earlier local verification on this PR:
      - `cargo test -p codex-protocol`
    - `CODEX_SKIP_VENDORED_BWRAP=1 cargo test -p codex-core
    realtime_conversation`
      - `cargo test -p codex-app-server-protocol`
    - `CODEX_SKIP_VENDORED_BWRAP=1 cargo test -p codex-app-server
    realtime_conversation`
    - attempted `CODEX_SKIP_VENDORED_BWRAP=1 cargo test -p codex-tui` (local
    linker bus error while linking the test binary)
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>