Commit Graph

1888 Commits

  • TUI: Unified mentions tweaks + polish mentions rendering (#23363)
    This change keeps unified @mentions behind the mentions_v2 gate, moves
    the flag to under-development, and polishes mention rendering/history
    behavior.
    
    It also adds a few small improvements to the mentions feature around
    mention rendering and history round-tripping for plugin/tool mentions in
    message edit scenarios. Plugin selections now insert `@` mentions with
    better casing, and saved history preserves the visible sigil so recalled
    messages look the same as what the user typed.
    
    - Preserves `@` sigils when encoding/decoding mention history for
    tool/plugin paths.
    - Improves plugin mention insertion so display names/casing are
    reflected more cleanly in the composer.
    - Update composer to render user-entered plugin mentions in the same
    color as the mentions menu. ALso applies to recalled/edited messages.
    - Left/right arrows no longer switch unified-mention search modes after
    an @mention has already been accepted (Ex: arrowing left through a
    composed message that contains @mentions).
    - Keeps bound mentions stable around punctuation, so accepted `@`
    mentions do not reopen the popup and punctuated `$` mentions still
    persist to cross-session history.
    
    **Steps to test**
    - Ensure mentions_v2 is enabled through configuration or `--enable
    mentions_v2`
    - Type `@` in the TUI composer and verify filesystem/plugin/skill
    results are displayed in the unified mentions menu.
    - Select a plugin mention from the `@` popup and confirm the inserted
    text is an `@...` mention with casing, then recall/edit the message and
    confirm it still renders as `@...`.
    - Mention a skill and verify that skills still insert as `$skill`
    mentions rather than `@` mentions.
    - Verify punctuated mentions such as `@plugin.` and `($skill)` keep
    their bound mention behavior across editing and history recall.
  • Expose MCP server info as part of server status (#24698)
    # Summary
    
    Expose MCP server info via App Server (when available) so apps can
    render a richer MCP experience
  • feat(app-server): include turns page on thread resume (#23534)
    ## Summary
    
    The client currently calls `thread/resume` to establish live updates and
    immediately follows it with `thread/turns/list` to hydrate recent turns.
    This lets `thread/resume` return that page directly, eliminating a round
    trip and the ordering/deduplication gap between the two calls.
    
    Experimental clients opt in with `initialTurnsPage: { limit,
    sortDirection, itemsView }`. The response returns `initialTurnsPage` as
    a `TurnsPage`, including cursors for paging further back in history.
    Keeping the controls in a nested opt-in object provides the useful
    `thread/turns/list` knobs without spreading page-specific parameters
    across `thread/resume`.
    
    ## Verification
    
    - `just fmt`
    - `just write-app-server-schema --experimental`
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server
    thread_resume_initial_turns_page_matches_requested_turns_list_page
    --tests`
    - `cargo test -p codex-app-server
    thread_resume_rejoins_running_thread_even_with_override_mismatch
    --tests`
    - `just fix -p codex-app-server-protocol -p codex-app-server`
  • [codex] Fix hyperlink-aware key-value table rendering (#24825)
    ## Why
    
    The key/value markdown table renderer added in #24636 still operates on
    `Line` values, while table cells and rendered table output now carry
    `HyperlinkLine`. That mismatch breaks `codex-tui` compilation on `main`
    and would risk losing semantic web-link annotations if corrected by
    flattening the values.
    
    ## What changed
    
    - Make key/value record rendering wrap and emit `HyperlinkLine` values
    consistently with the existing grid renderer.
    - Remap wrapped hyperlink ranges and shift them when value content is
    prefixed by record-mode indentation or labels.
    - Add focused coverage verifying key/value fallback output preserves
    web-link destinations.
    
    ## Verification
    
    - `just test -p codex-tui -E
    'test(key_value_table_keeps_web_annotations) |
    test(/table_renders_(key_value_records_when_compact_fragmentation_is_systemic_snapshot|stacked_key_value_records_when_path_column_becomes_too_narrow_snapshot|records_when_multiple_prose_columns_are_starved_snapshot)/)'`
  • feat(tui): render cramped markdown tables as key-value records [2 of 2] (#24636)
    ## Stack
    
    - **Base: #24489 [1 of 2]** - render markdown tables in app style.
    - **Current: #24636 [2 of 2]** - render cramped markdown tables as
    key/value records.
    
    Review this PR against `fcoury/app-style-markdown-tables`; it contains
    only the fallback behavior for cramped tables.
    
    ## Why
    
    The row-separated markdown table rendering in #24489 remains readable
    while columns have usable room. Once long links or multiple prose-heavy
    columns are compressed into narrow allocations, however, the grid can
    turn words and paths into tall vertical strips that are difficult to
    scan. In those cases the content matters more than preserving the grid
    shape.
    
    ## What Changed
    
    <table>
    <tr><td>
    <p align="center"><b>
    Normal
    </b></p>
    <img width="1722" height="619" alt="CleanShot 2026-05-27 at 14 32 57"
    src="https://github.com/user-attachments/assets/d04f5fbd-6064-4acd-91bd-072d19b983df"
    />
    </td></tr>
    <tr><td>
    <p align="center"><b>
    Narrow
    </b></p>
    <img width="863" height="1013" alt="CleanShot 2026-05-27 at 14 33 12"
    src="https://github.com/user-attachments/assets/6a7d2968-0a68-48fd-ab5d-209b3dbaf03e"
    />
    </td></tr>
    <tr><td>
    <p align="center"><b>
    Very narrow
    </b></p>
    <img width="435" height="746" alt="CleanShot 2026-05-27 at 14 33 47"
    src="https://github.com/user-attachments/assets/f6a59e30-b1d2-4063-9c05-43933abc77d6"
    />
    </td></tr>
    </table>
    
    - Detect tables whose grid allocation causes systemic token
    fragmentation or starves multiple prose-heavy columns.
    - Render those tables as repeated key/value records instead of retaining
    an unreadable grid.
    - Use aligned label/value records when there is useful horizontal room,
    and switch to a stacked narrow-record layout where each label is
    followed by a full-width value when width is especially constrained.
    - Preserve the themed label color, rich inline formatting, links, and
    the existing grid presentation for tables that remain readable.
    - Add snapshot coverage for path-heavy narrow tables, prose-heavy issue
    tables, systemic compact fragmentation, and a control case that should
    continue to render as a grid.
    
    ## How to Test
    
    1. Start Codex from this branch and render a normal multi-column
    markdown table at a comfortable terminal width. Confirm it still appears
    as the styled row-separated grid from #24489.
    2. Render a table containing a long linked record identifier or
    file-like value, then narrow the terminal until the grid would split the
    value into vertical fragments. Confirm it switches to key/value records,
    with labels above values at very narrow widths.
    3. Render a table with multiple prose-heavy columns, such as an issue
    summary table with `Issue`, `Activity`, `Complexity`, and `Why start`.
    Confirm a cramped width switches to records rather than wrapping several
    columns into hard-to-read strips.
    4. Render a compact table where only one value wraps mildly. Confirm it
    stays in grid form rather than switching prematurely.
    
    ## Validation
    
    - Ran `just test -p codex-tui` while developing the fallback and
    reviewed/accepted the intended new markdown-render snapshots. The
    command still reports two unrelated existing guardian feature-flag test
    failures outside this diff.
    - Ran `just fix -p codex-tui` and `just fmt` after the Rust changes were
    complete.
    - `just argument-comment-lint` cannot reach source linting locally
    because Bazel fails while resolving LLVM sanitizer headers; touched
    positional literal callsites were inspected manually and annotated where
    needed.
  • feat(tui): add OSC 8 web links to rich content (#24472)
    ## Why
    
    Wrapped URLs in rich TUI output, especially URLs rendered inside
    Markdown tables, are split across terminal rows. In terminals that
    support OSC 8 hyperlinks, treating each visible fragment as part of the
    complete destination enables reliable open-link and copy-link actions
    even after table layout wraps the URL.
    
    This addresses the semantic-link portion of #12200 and the behavior
    described in
    https://github.com/openai/codex/issues/12200#issuecomment-4535452980. It
    does not change ordinary drag-selection across bordered table rows.
    
    ## What Changed
    
    - Added shared TUI OSC 8 support that validates `http://` and `https://`
    destinations, sanitizes terminal payloads, and applies metadata
    separately from visible line width/layout.
    - Added semantic web-link annotations to assistant and proposed-plan
    Markdown, including explicit web links and bare web URLs in prose and
    table cells while excluding code and non-web Markdown destinations.
    - Preserved complete URL targets through table wrapping, narrow pipe
    fallback, streaming, transcript overlay rendering, history insertion,
    and resize replay.
    - Routed intentional Codex-owned links in notices,
    status/setup/app-link, feedback, onboarding, MCP/plugin help, memories,
    and update surfaces through the shared hyperlink handling.
    
    ## How to Test
    
    1. Run Codex in a terminal with OSC 8 link support, such as Ghostty, and
    request an assistant response containing a Markdown table whose last
    column contains a long `https://` URL.
    2. Make the terminal narrow enough for the URL to wrap across multiple
    bordered table rows.
    3. Use the terminal's open-link or copy-link action on more than one
    wrapped URL fragment and confirm each fragment resolves to the complete
    original URL.
    4. Resize the terminal after the table is rendered and repeat the link
    action to confirm the destination survives scrollback replay.
    5. Open the transcript overlay while rich output is present and confirm
    web links remain interactive there.
    6. As a regression check, render inline/fenced code containing URL text
    and a Markdown link such as
    `[https://example.com](mailto:support@example.com)`; confirm these do
    not acquire a web OSC 8 destination.
    
    Targeted automated coverage exercised Markdown links and exclusions,
    wrapped and pipe-fallback tables, streaming/transcript overlay
    propagation, status-link truncation, and rendered word-wrapping cell
    alignment. `just test -p codex-tui` was also run; it passed the
    hyperlink coverage and reproduced two unrelated existing guardian
    feature-flag test failures.
  • feat(tui): render markdown tables in app style [1 of 2] (#24489)
    ## Stack
    
    - **Current: #24489 [1 of 2]** - render markdown tables in app style.
    - **Stacked follow-up: #24636 [2 of 2]** - render cramped markdown
    tables as key/value records.
    
    ## Why
    
    Markdown tables currently render as boxed terminal grids, which gives
    ordinary assistant output a heavier visual treatment than surrounding
    rich text. This row-separated layout is the best match for how the App
    renders tables, while accented headers remain distinguishable even when
    a terminal font renders bold subtly.
    
    <table>
    <tr><td>
    <p align="center">Codex CLI - Before</p>
    <img width="1722" height="742" alt="CleanShot 2026-05-25 at 18 46 17"
    src="https://github.com/user-attachments/assets/f673d92a-ebd8-46e2-b414-3d985e41b6a4"
    />
    </td></tr>
    <tr><td>
    <p align="center">Codex CLI - After</p>
    <img width="1720" height="957" alt="image"
    src="https://github.com/user-attachments/assets/36a3d331-bea1-439b-b5be-e97b0731bd6f"
    />
    </td></tr>
    <tr><td>
    <p align="center">Codex App</p>
    <img width="979" height="1293" alt="CleanShot 2026-05-25 at 18 45 04"
    src="https://github.com/user-attachments/assets/7d97cae0-9256-4f6e-a4b3-8b8f22b0d901"
    />
    </td></tr>
    </table>
    
    ## What Changed
    
    - Render markdown tables as padded, aligned rows without an enclosing
    box.
    - Style table headers with the active syntax-theme accent plus bold
    text, while keeping separators low contrast and theme-aware.
    - Use a segmented heavy header rule and thin body-row rules, preserving
    wrapping, narrow-width fallback, streaming parity, and rich-history
    rendering.
    - Update focused assertions and snapshots for the final table layout.
    
    ## How to Test
    
    1. Render a markdown table in the TUI with several rows and columns.
    2. Confirm the header uses the active theme accent, rows use
    one-character interior padding, and the table has no enclosing box.
    3. Confirm the header is followed by segmented `━` rules and multiple
    body rows are separated by muted segmented `─` rules.
    4. Render the same table while streaming and in history/raw-mode
    toggles; the final rich layout should remain stable.
    5. Render a narrow table with long content and verify wrapping or pipe
    fallback does not overflow.
    
    ## Validation
    
    - `just test -p codex-tui table`
    - `just test -p codex-tui streaming::controller::tests`
    - `just argument-comment-lint-from-source -p codex-tui -- --all-targets`
    - `just fix -p codex-tui`
    - `just fmt`
    
    `just test -p codex-tui` was also run after accepting the snapshots; it
    fails only in the unrelated existing guardian app tests
    `update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
    and
    `update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`.
  • feat(tui): make turn interruption keybind configurable (#24766)
    ## Why
    
    Interrupting an active turn is currently fixed to `Esc`, which is easy
    to hit accidentally and cannot be customized through `/keymap`. This
    gives users a less accidental binding while preserving the existing
    default.
    
    ## What Changed
    
    - Adds `tui.keymap.chat.interrupt_turn` to `/keymap`, defaulting to
    `esc` and supporting remapping or unbinding.
    - Uses the configured interrupt binding for running-turn status, queued
    steer interruption, and `request_user_input`, including the visible
    hints.
    - Preserves local `Esc` behavior for popups, Vim insert mode, and
    `/agent` editing while validating conflicts with fixed/backtrack and
    request-input navigation bindings.
    - Adds behavior and snapshot coverage for remapped interruption paths.
    
    ## How to Test
    
    1. Run Codex and open `/keymap`, then set **Interrupt Turn** to `f12`.
    2. Start a turn and confirm `Esc` no longer interrupts it while `f12`
    does; the running hint should display `f12 to interrupt`.
    3. Queue a steer while a turn is running and confirm the preview
    displays `f12`; pressing it should interrupt and submit the steer
    immediately.
    4. Trigger a `request_user_input` prompt and confirm its footer uses
    `f12`; with notes open, `Esc` should still clear notes while `f12`
    interrupts the turn.
    5. Clear the Interrupt Turn binding and confirm the key-specific
    interrupt hint is removed while `Ctrl+C` remains available.
    
    Targeted validation:
    
    - `just write-config-schema`
    - `just fix -p codex-config`
    - `just fix -p codex-tui`
    - `just fmt`
    - `just argument-comment-lint-from-source -p codex-config -p codex-tui`
    - `just test -p codex-config`
    - `cargo insta pending-snapshots --manifest-path tui/Cargo.toml`
    - `just test -p codex-tui keymap_setup::tests`
    - `just test -p codex-tui` (fails in two pre-existing guardian
    feature-flag tests unrelated to this diff; the intentional picker
    snapshot updates were reviewed and accepted)
  • feat(tui): add vim text object bindings (#24382)
    ## Why
    
    Vim mode currently supports some normal-mode operators and motions, but
    common text-object combinations like `ciw`, `daw`, `di(`, and
    quote/bracket variants are still missing. That makes the composer feel
    incomplete for users who expect operator + text object editing to work
    inside prompts.
    
    Closes #21383.
    
    ## What Changed
    
    - Add Vim pending-state support for operator/text-object sequences.
    - Add `c` as a normal-mode operator for text objects, so combinations
    like `ciw` delete the object and enter insert mode.
    - Support word, WORD, delimiter, and quote text objects:
      - `iw`, `aw`, `iW`, `aW`
      - `i(`, `a(`, `i)`, `a)`, `ib`, `ab`
      - `i[`, `a[`, `i]`, `a]`
      - `i{`, `a{`, `i}`, `a}`, `iB`, `aB`
      - `i"`, `a"`, `i'`, `a'`, `i\``, `a\``
    - Add configurable keymap entries and keymap picker coverage for the new
    Vim text-object context.
    - Regenerate the config schema and update keymap picker snapshots.
    
    ## How to Test
    
    Manual smoke test:
    
    1. Start Codex with Vim composer mode enabled.
    2. Type a draft such as:
       ```text
       alpha beta gamma
       call(foo[bar], {"x": "hello world"})
       say "one \"two\" three" now
       ```
    3. Put the cursor on `beta`, press `ciw`, and confirm `beta` is removed
    and the composer enters insert mode.
    4. Escape back to normal mode, put the cursor on `gamma`, press `daw`,
    and confirm `gamma` plus surrounding whitespace is removed.
    5. Put the cursor inside `foo[bar]`, press `di[`, and confirm only `bar`
    is removed.
    6. Put the cursor inside `call(...)`, press `da(`, and confirm the whole
    parenthesized section is removed.
    7. Put the cursor inside the quoted text, press `ci"`, and confirm the
    quote contents are removed and insert mode starts.
    8. Verify cancellation does not edit text: press `d` then `Esc`, and
    press `d` then `i` then `Esc`.
    
    Targeted tests:
    
    - `cargo test -p codex-tui --lib vim_`
    - `cargo nextest run -p codex-tui keymap_setup::tests`
    
    Additional local checks:
    
    - `just write-config-schema`
    - `just fmt`
    - `just fix -p codex-tui`
    - `git diff --check`
    - `cargo insta pending-snapshots --manifest-path tui/Cargo.toml`
    
    Local full-suite note: `just test -p codex-tui` ran to completion. The
    keymap snapshot failures were expected and accepted. Two unrelated
    guardian feature-flag tests still fail locally:
    -
    `app::tests::update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
    -
    `app::tests::update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`
    
    `just argument-comment-lint` is currently blocked locally by Bazel
    analysis before the lint runs because `compiler-rt` has an empty
    `include/sanitizer/*.h` glob in the local Bazel cache. The touched Rust
    diff was manually inspected for opaque positional literals.
  • [codex] Remove stale composer narrative doc references (#24641)
    ## Context
    
    `docs/tui-chat-composer.md` was removed by #20896 as part of removing
    local-only docs/specs from the repository. I checked the #20896 file
    list and the merge commit: the composer doc was deleted, not moved or
    copied, and current `main` does not contain a replacement composer
    narrative doc.
    
    Current guidance should keep contributors and agents focused on the docs
    that still exist: the module docs in `chat_composer.rs` and
    `paste_burst.rs`.
    
    ## Summary
    
    - Removes the scoped TUI bottom-pane AGENTS.md requirement to update
    `docs/tui-chat-composer.md`.
    - Removes stale module-doc references to that deleted narrative doc from
    `chat_composer.rs` and `paste_burst.rs`.
    
    ## Validation
    
    - Checked #20896 and the merge commit with rename/copy detection to
    confirm `docs/tui-chat-composer.md` was deleted rather than moved.
    - Searched current `main` for a replacement composer narrative doc.
    - Not run; documentation-only change.
  • fix: Preserve draft text when completing argument-taking slash commands (#23950)
    This adds slash command completion behavior for argument-taking
    commands, where text after the partially typed command becomes inline
    arguments instead of being discarded. This addresses the workflow of
    drafting text first, moving to the start, and completing a slash command
    around that existing draft. Before this change, this workflow would
    remove all user-input text aside from the slash command, which can be
    frustrating if the user had just typed out a long and well thought out
    goal.
    
    - Preserves the draft tail for inline-argument slash commands like
    `/goal` and `/review` when completing with `Tab` or `Enter`.
    - Keeps popup filtering focused on the command fragment under the cursor
    rather than the full draft text.
    - Leaves slash commands that do not support inline arguments unchanged,
    so completion still replaces the existing draft tail for those commands.
    - Adds focused TUI tests under slash input covering preserved arguments,
    cursor edge cases, and the negative case for a command without inline
    args.
      
    Follow-up simplification and test relocation from #24683 folded into
    this PR.
    
    ---------
    
    Co-authored-by: Eric Traut <etraut@openai.com>
  • fix: run standalone updates noninteractively (#24637)
    # Summary
    
    The standalone update action currently downloads and runs the Codex
    installer as an interactive command. When an existing managed Codex
    install is present, accepting an update can therefore enter an installer
    prompt instead of completing the update.
    
    This change runs the standalone installer with `CODEX_NON_INTERACTIVE=1`
    on macOS/Linux and Windows. The installer environment-variable support
    is introduced by the parent PR; this PR wires that behavior into the
    Codex CLI update action. The rendered Windows command remains
    shell-safe, and long update commands wrap within the update-notice card.
    The standard test target snapshots the standalone notice for both
    platforms.
    
    # Stack
    
    1. [#21567](https://github.com/openai/codex/pull/21567) - Adds
    environment-controlled release selection and noninteractive installer
    behavior.
    2. [#24637](https://github.com/openai/codex/pull/24637) - Runs
    standalone updates with `CODEX_NON_INTERACTIVE=1`. (current)
    3. [#24639](https://github.com/openai/codex/pull/24639) - Removes
    explicit release argument inputs in favor of `CODEX_RELEASE`.
    
    # Evidence
    
    Standalone updater-shaped macOS install with an existing npm-managed
    Codex on `PATH`:
    
    
    https://github.com/user-attachments/assets/a27fe9e9-db3a-4c39-a514-24bd3d1f01e8
    
    # Testing
    
    Tests: targeted `codex-tui` update-action and update-notice snapshot
    tests, Rust formatting, benchmark smoke validation, macOS live-terminal
    standalone-update smoke testing, Windows ARM64 PowerShell
    standalone-update smoke testing through Parallels, and CI.
  • fix(tui): complete vim word-end and line-end behavior (#24380)
    ## Why
    
    The TUI Vim composer currently diverges from normal Vim editing in two
    common workflows: pressing `e` repeatedly can remain stuck at an
    existing word end, and normal mode does not support `C` for changing
    through the end of the line. The existing `D` behavior also removes the
    newline when the cursor is already at the line boundary, which makes the
    new `C` action and existing deletion action surprising in multiline
    prompts.
    
    Closes #23926.
    Closes #24238.
    
    ## What Changed
    
    - Make normal-mode `e` advance from the current word end to the next
    word end, including for operator motions such as `de`.
    - Add configurable Vim normal-mode `change_to_line_end` behavior, bound
    to `C` by default, which deletes to the end of the current line and
    enters Insert mode.
    - Keep the newline intact when `D` or `C` is pressed at the end-of-line
    boundary.
    - Add regression coverage for repeated `e`, `de`, `C`, and the multiline
    `C`/`D` boundary behavior.
    - Regenerate the config schema and update the keymap picker snapshots
    for the new Vim action.
    
    ## How to Test
    
    1. Run Codex with Vim composer mode enabled:
       ```bash
       cd codex-rs
       cargo run --bin codex -- -c tui.vim_mode_default=true
       ```
    2. Enter `alpha beta gamma`, press `Esc`, `0`, then press `e`
    repeatedly.
    Confirm the cursor advances through the ends of `alpha`, `beta`, and
    `gamma`.
    3. Enter `hello world`, press `Esc`, `0`, `w`, then `C`.
       Confirm `world` is deleted and the composer enters Insert mode.
    4. Enter a multiline prompt with `hello` above `world`, press `Esc`,
    `k`, `$`, and then `D`.
       Confirm the newline is preserved and the two lines do not join.
    5. At the same boundary, press `C` and type `!`.
    Confirm the composer enters Insert mode and yields `hello!` above
    `world`, preserving the newline.
    
    Targeted automated verification:
    - `just fix -p codex-tui`
    - `just argument-comment-lint-from-source -p codex-tui -p codex-config`
    - `cargo insta pending-snapshots` reports no pending snapshots.
    - `just test -p codex-tui` validates the new Vim and keymap snapshot
    coverage, but the command remains red due to two reproducible unrelated
    failures in `app::tests::update_feature_flags_disabling_guardian_*`.
    
    ## Validation Note
    
    The workspace-wide `just argument-comment-lint` form is currently
    blocked during Bazel analysis by the existing LLVM `compiler-rt` missing
    `include/sanitizer/*.h` failure; package-scoped source linting for the
    changed Rust crates passed.
  • TUI config cleanup: plugin marketplace (#24257)
    ## Why
    
    Plugin and marketplace mutations are applied by the app server, but
    several TUI follow-up paths still refreshed state from the TUI host
    config. In remote workspace mode, that can leave plugin UI state tied to
    stale client-local `config.toml` after the server has already applied
    the mutation.
    
    ## What
    
    - Stop reloading the TUI host config after app-server-owned plugin,
    marketplace, skill, and app mutations.
    - Use the same app-server-owned refresh path for local and remote
    sessions: ask the app server to reload user config where the running
    session needs it, then refetch plugin list/detail state from the app
    server.
    - Build plugin mention candidates from existing app-server `plugin/list`
    and `plugin/read` data in both local and remote sessions instead of
    TUI-host plugin config.
    - Avoid the duplicate local config reload after `ReloadUserConfig` asks
    the app server to reload config.
    
    ## Verification
    
    Manually launched a local WebSocket app-server with a temp server
    `CODEX_HOME`, launched the TUI with a separate temp host `CODEX_HOME`
    and `--remote`, installed a sample plugin from a temp local marketplace
    through `/plugins`, and confirmed the TUI refreshed to installed state
    while only the server config gained `[plugins."sample@debug"]`. Trace
    logs showed the TUI using app-server `plugin/list` and `plugin/read` for
    the refresh path.
  • Uprev Rust toolchain pins to 1.95.0 (#24684)
    ## Summary
    - Bump the workspace Rust toolchain from `1.93.0` to `1.95.0` across
    Cargo, Bazel, CI, release workflows, devcontainers, and the Codex
    environment config.
    - Refresh `MODULE.bazel.lock` so the Bazel Rust toolchain artifacts
    match the new version.
    - Leave purpose-specific toolchains unchanged, including the
    `argument-comment-lint` nightly and the upstream `rusty_v8` `1.91.0`
    build pin.
    - Includes fixes for new lints from `just fix` and a few codex-authored
    fixes for lints without a suggestion.
  • Attach Windows sandbox log to feedback reports (#24623)
    ## Why
    
    Windows sandbox diagnostics are currently hard to recover from
    `/feedback` even though they are often the most useful artifact when
    debugging sandbox behavior. Now that sandbox logging uses daily rolling
    files, feedback can safely include the current day's sandbox log without
    uploading the old ever-growing legacy `sandbox.log`.
    
    ## What changed
    
    - Add a `codex-windows-sandbox` helper that resolves the current daily
    sandbox log from `codex_home`.
    - When feedback is submitted with logs enabled on Windows, app-server
    attaches today's sandbox log if it exists.
    - Upload the attachment under the stable filename `windows-sandbox.log`,
    independent of the dated on-disk filename.
    - Keep existing raw `extra_log_files` behavior unchanged for rollout and
    desktop log attachments.
    
    ## Verification
    
    - `cargo fmt -p codex-app-server -p codex-windows-sandbox`
    - `cargo test -p codex-windows-sandbox
    current_log_file_path_for_codex_home_uses_sandbox_dir`
    - `cargo test -p codex-app-server
    windows_sandbox_log_attachment_uses_current_log`
    - Manual CLI/TUI `/feedback` test confirmed Sentry received
    `windows-sandbox.log`.
  • windows-sandbox: remove SandboxPolicy runner plumbing (#23813)
    ## Why
    
    The Windows sandbox runner still carried the old `SandboxPolicy`
    compatibility path even though core now computes `PermissionProfile`.
    That meant Windows command-runner execution could only see the legacy
    projection, so profile-only filesystem rules such as deny globs were not
    part of the runner input.
    
    ## What Changed
    
    - Removed the Windows-local `SandboxPolicy` parser/export and deleted
    `windows-sandbox-rs/src/policy.rs`.
    - Changed restricted-token capture/session setup, elevated setup,
    world-writable audit, read-root grant, and command-runner session APIs
    to accept `PermissionProfile` plus the profile cwd.
    - Bumped the elevated command-runner IPC protocol to version 2 because
    `SpawnRequest` now carries `permission_profile` /
    `permission_profile_cwd` instead of the legacy `policy_json_or_preset` /
    `sandbox_policy_cwd` fields.
    - Updated core exec, unified exec, debug-sandbox, TUI setup/grant flows,
    and app-server setup to pass the actual effective `PermissionProfile`.
    - Left regression coverage asserting the old IPC policy fields are
    absent and the runner serializes tagged `PermissionProfile` JSON.
    
    ## Verification
    
    - `cargo test -p codex-windows-sandbox`
    - `cargo test -p codex-core windows_sandbox`
    - `cargo test -p codex-app-server
    request_processors::windows_sandbox_processor`
    - `just fix -p codex-windows-sandbox -p codex-core -p codex-app-server
    -p codex-cli -p codex-tui`
    - `just fix -p codex-cli -p codex-tui`
    - `just fix -p codex-windows-sandbox -p codex-tui`
    - `rg "\\bSandboxPolicy\\b" codex-rs/windows-sandbox-rs` returned no
    matches.
    
    Note: `cargo test -p codex-cli` was attempted but did not reach crate
    tests because local disk filled while compiling dependencies (`No space
    left on device`). The targeted clippy pass compiled the affected CLI/TUI
    surfaces afterward.
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23813).
    * #24108
    * __->__ #23813
  • TUI config cleanup: plugin mentions (#24266)
    ## Summary
    
    TUI plugin mention refresh still joined app-server plugin inventory with
    client-local plugin config, which can diverge once plugin state is owned
    by the app server.
    
    This changes the TUI to mirror the GUI client: `plugin/list` is the
    autocomplete source, and mention candidates are plugin-level entries
    filtered to installed, enabled, and not disabled by admin. The TUI no
    longer reads local plugin config or calls `plugin/read` while refreshing
    plugin mention candidates.
    
    ## API shape and limitations
    
    The current app-server API does not expose effective per-session plugin
    capability summaries for mention autocomplete. As in the GUI,
    autocomplete now trusts `plugin/list` metadata rather than proving which
    plugin capabilities are loaded in the active session.
    
    That avoids stale client-local reads and the cwd/remote detail gaps in
    `plugin/read`, but intentionally accepts the same list-level tradeoff as
    the app: if `plugin/list` reports a remote plugin before its local
    bundle is materialized, the plugin can still appear as a mention
    candidate.
  • Add experimental turn additional context (#24154)
    ## Summary
    
    Adds experimental `additionalContext` support to `turn/start` and
    `turn/steer` so clients can provide ephemeral external context, such as
    browser or automation state, without turning that plumbing into a
    visible user prompt or triggering user-prompt lifecycle behavior.
    
    ## API Shape
    
    The parameter shape is:
    
    ```ts
    additionalContext?: Record<string, {
      value: string
      kind: "untrusted" | "application"
    }> | null
    ```
    
    Example:
    
    ```json
    {
      "additionalContext": {
        "browser_info": {
          "value": "Active tab is CI failures.",
          "kind": "untrusted"
        },
        "automation_info": {
          "value": "CI rerun is in progress.",
          "kind": "application"
        }
      }
    }
    ```
    
    The keys are opaque and caller-defined.
    
    ## Context Injection
    
    When provided, accepted entries are inserted into model context as
    hidden contextual message items, not as visible thread user-message
    items.
    
    `kind: "untrusted"` entries are inserted with role `user`:
    
    ```text
    <external_${key}>${value}</external_${key}>
    ```
    
    `kind: "application"` entries are inserted with role `developer`:
    
    ```text
    <${key}>${value}</${key}>
    ```
    
    Values are not escaped. Each value is truncated to 1k approximate tokens
    before wrapping.
    
    For `turn/start`, accepted additional context is inserted before normal
    user input. For `turn/steer`, additional context is merged only when the
    steer includes non-empty user input; context-only steers still reject as
    empty input.
    
    ## Dedupe Strategy
    
    `AdditionalContextStore` lives on session state and stores the latest
    complete additional-context map.
    
    Each `turn/start` or non-empty `turn/steer` treats its
    `additionalContext` as the current complete set of values. Entries are
    injected only when the key is new or the exact entry for that key
    changed, including `value` or `kind`. After merging, the store is
    replaced with the provided map, so omitted keys are removed from the
    retained set and can be injected again later if reintroduced.
    
    Omitting `additionalContext`, passing `null`, or passing an empty object
    resets the store to empty and injects nothing.
    
    ## What Changed
    
    - Threads experimental v2 `additionalContext` through app-server into
    core turn start and steer handling.
    - Adds separate contextual fragment types for untrusted user-role
    context and application developer-role context.
    - Uses pending response input items so additional context can be
    combined with normal user input without treating it as prompt text.
    - Adds integration coverage for start/steer flow, role routing,
    dedupe/reset behavior, deletion/re-add behavior, hook-blocked input
    behavior, empty context-only steer rejection, external-fragment marker
    matching, and truncation.
  • tui: keep inaccessible apps out of mentions (#24625)
    ## Summary
    
    Fix the TUI `$` app mention paths so App Directory rows that are not
    accessible are not treated as usable apps.
    
    This includes the core preservation fix from #24104, but expands it to
    the other app mention paths:
    
    - preserve app-server `is_accessible` flags when partial
    `app/list/updated` snapshots reach the TUI
    - require apps to be both accessible and enabled when resolving exact
    `$slug` mentions
    - require restored/stale `app://...` bindings to point at accessible,
    enabled apps before emitting structured app mentions
    - remove the now-unused `codex-chatgpt` dependency from `codex-tui`,
    which addresses the `cargo shear` failure seen on #24104
    
    ## Root Cause
    
    The app server already sends merged app snapshots with accessibility
    computed. The TUI handled app-server app list updates as partial app
    loads and re-ran the old accessible-app merge path. That path treated
    every notification row as accessible, so App Directory entries with
    `isAccessible=false` could appear in `$` suggestions.
    
    Regression source: #22914 routed app-list updates through the app server
    while reusing the old TUI partial-load handling. Related precursor:
    #14717 introduced the partial-load path, but #22914 made it user-visible
    for app-server updates.
    
    ## Issues
    
    Fixes #24145
    Fixes #24205
    Fixes #24319
    
    ## Validation
    
    - `just fmt`
    - `git diff --check`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    - `just argument-comment-lint -p codex-tui`
    - `just test -p codex-tui
    chatwidget::tests::popups_and_settings::apps_notification_update_excludes_inaccessible_apps_from_mentions
    chatwidget::tests::composer_submission::submit_user_message_ignores_inaccessible_app_mentions_from_bindings
    chatwidget::skills::tests::find_app_mentions_requires_accessible_enabled_apps_for_bound_paths
    chatwidget::skills::tests::find_app_mentions_requires_accessible_enabled_apps_for_slugs`
  • fix(tui): keep raw output above composer in zellij (#24593)
    ## Why
    
    Raw output mode intentionally sends logical source lines to the terminal
    without Codex-inserted wrapping so copied content retains its original
    line structure. In Zellij, soft-wrapped continuation rows from those raw
    lines are not confined by the inline history scroll region. When raw
    mode replays a long transcript, continuation rows can occupy the
    composer viewport and are overwritten on the following draw, leaving the
    transcript visibly truncated underneath the composer.
    
    This is specific to the combination of Zellij and raw terminal-wrapped
    history. Rich output and non-Zellij terminals should continue using the
    existing insertion behavior.
    
    Related context: #20819 introduced raw output mode, and #22214 removed
    the broad Zellij insertion workaround after the standard rich-output
    path no longer required it.
    
    | Before | After |
    |---|---|
    | <img width="1728" height="916" alt="image"
    src="https://github.com/user-attachments/assets/f85398a5-e930-46d9-bcfd-106a24c41466"
    /> | <img width="1723" height="912" alt="image"
    src="https://github.com/user-attachments/assets/5c62e16a-a6e5-4842-bcb2-eab163cda04c"
    /> |
    
    ## What Changed
    
    - Cache Zellij detection in `Tui` and select a dedicated insertion mode
    only for `HistoryLineWrapPolicy::Terminal` batches in Zellij.
    - For that guarded path, clear the existing viewport, append raw source
    lines through the terminal so its soft wrapping remains
    selection-friendly, and reserve empty viewport rows before redrawing the
    composer.
    - Add snapshot regressions for both an incremental soft-wrapped raw
    insert and an overflowing raw transcript replay that starts at the top
    of the cleared terminal.
    
    ## How to Test
    
    1. Start Codex inside Zellij with raw output enabled or toggle raw
    output after a multiline response is in history.
    2. Produce or replay output containing long logical lines, such as a
    fenced shell command with several wrapped lines.
    3. Confirm the wrapped history remains visible above the composer and
    the composer no longer overwrites the end of the response.
    4. Toggle back to rich output or run outside Zellij and confirm standard
    history rendering still behaves normally.
    
    Targeted tests run:
    
    - `just test -p codex-tui vt100_zellij_raw -- --nocapture`
    
    Additional validation notes:
    
    - `just test -p codex-tui` was attempted; the two new Zellij raw
    insertion tests passed, while two existing
    `app::tests::update_feature_flags_disabling_guardian_*` tests failed
    outside this history insertion path.
    - `just argument-comment-lint` was attempted but local Bazel analysis
    fails before reaching the changed source because the LLVM `compiler-rt`
    package is missing `include/sanitizer/*.h`. Modified literal callsites
    were inspected manually.
  • fix(tui): avoid modifyOtherKeys for unknown tmux formats (#24371)
    ## Why
    
    Codex 0.131 started enabling tmux `modifyOtherKeys` mode 2 when the
    active tmux session reported `extended-keys-format csi-u`, and also when
    that format could not be queried. The fallback was meant to help
    compatible tmux panes enter extended-key mode, but it breaks iTerm2
    control-mode sessions on older tmux.
    
    Issue #23711 reproduces with:
    
    ```bash
    ssh -t ubuntu@192.168.68.149 'tmux -CC new -A -s main'
    ```
    
    On tmux 3.2a, `extended-keys-format` is not available. With mode 2
    enabled, `Ctrl-C` is delivered as `^[[27;5;99~` instead of the normal
    interrupt/control key path, so Codex does not handle it. Running with
    `CODEX_TUI_DISABLE_KEYBOARD_ENHANCEMENT=1` restores `Ctrl-C`, which
    points at keyboard mode setup rather than chat input routing.
    
    ## What Changed
    
    - Only request `modifyOtherKeys` mode 2 when tmux explicitly reports
    `extended-keys-format csi-u`.
    - Treat an unknown or unavailable tmux extended-key format as
    unsupported for this mode.
    - Update the keyboard mode unit coverage so `None` no longer opts into
    `modifyOtherKeys`.
    
    This preserves the explicit modern tmux `csi-u` path from #21943 while
    avoiding the unsafe fallback on older or unqueryable tmux setups.
    
    ## How to Test
    
    Regression path from #23711:
    
    1. Start iTerm2 tmux integration against an older tmux host:
       ```bash
       ssh -t ubuntu@192.168.68.149 'tmux -CC new -A -s main'
       ```
    2. Start patched Codex.
    3. Run `/keymap debug`, press a regular key, then press `Ctrl-C`.
    4. Confirm `Ctrl-C` closes the inspector and Codex remains responsive
    without `CODEX_TUI_DISABLE_KEYBOARD_ENHANCEMENT=1`.
    5. Confirm `Shift+Enter` still inserts a newline in the same session.
    
    Modern tmux compatibility path:
    
    1. Start an ordinary tmux 3.6a server with explicit `csi-u`:
       ```bash
       tmux -L codex-csiu -f /dev/null new-session -d -s repro
       tmux -L codex-csiu set-option -g extended-keys on
       tmux -L codex-csiu set-option -g extended-keys-format csi-u
       tmux -L codex-csiu attach -t repro
       ```
    2. Start patched Codex.
    3. From another terminal, confirm the Codex pane reports `mode=Ext 2`:
       ```bash
    tmux -L codex-csiu list-panes -a -F '#{pane_id} mode=#{pane_key_mode}
    cmd=#{pane_current_command}'
       ```
    4. Type `one`, press `Shift+Enter`, type `two`, and confirm the composer
    shows two lines without submitting.
    5. Press `Ctrl-C` and confirm Codex handles it normally.
    
    Targeted tests:
    
    - `./tools/argument-comment-lint/run.py -p codex-tui -- --lib`
    - `just test -p codex-tui` runs the new keyboard mode test successfully;
    the full run currently reports two unrelated guardian feature-flag test
    failures:
    -
    `app::tests::update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`
    -
    `app::tests::update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
    
    No documentation update is needed.
  • Move slash input logic out of chat composer (#23964)
    Recent composer cleanups split state ownership out of `ChatComposer`,
    but slash-command handling still mixed parsing, popup coordination,
    completion, submission validation, queue behavior, and argument element
    rebasing into the main composer file. Pending changes to slash command
    parsing and selection inspired this code move to prevent
    `chat_composer.rs` bloat.
    
    This is just a refactor, no functional or behavioral changes are
    intended.
    
    ## What changed
    
    - Move slash-command parsing and lookup helpers into
    `bottom_pane/chat_composer/slash_input.rs`.
    - Move slash popup key handling, command-name completion, and popup
    construction into the slash input helper module.
    - Centralize bare-command, inline-args, submission-validation, and
    queued-input action selection behind slash-specific helpers.
    - Move command argument text-element rebasing into the slash input
    module so inline command submission keeps the same element behavior with
    less composer-local logic.
    
    ## Verification
    
    - `just fmt`
    - `just test -p codex-tui`
    - `cargo insta pending-snapshots -p codex-tui`
  • tui: add named permission profile picker (#21559)
    ## Why
    
    Users who opt into named permission profiles through
    `default_permissions` or `[permissions.*]` should stay in named-profile
    semantics when they open `/permissions`. The legacy picker rewrites
    those users into anonymous preset state, which loses the active profile
    identity and hides custom configured profiles.
    
    ## What changed
    
    - Switch `/permissions` to a profile-aware picker when profile mode is
    active.
    - Show friendly built-in labels instead of raw `:` profile syntax.
    - Include configured custom profiles and their descriptions in the
    picker.
    - Route selections through the split TUI profile-selection flow below
    this PR.
    - Add TUI snapshots and regression coverage for built-ins, custom
    profiles, and conflicting legacy runtime overrides.
    
    ## Stack
    
    1. [#22931](https://github.com/openai/codex/pull/22931):
    runtime/session/network propagation for active permission profiles.
    2. [#23708](https://github.com/openai/codex/pull/23708): TUI selection
    plumbing and guardrail flow.
    3. **This PR**: profile-aware `/permissions` menu and custom profile
    display.
    
    ## UX impact
    
    In profile mode, `/permissions` shows the same human-facing built-ins
    users already know:
    
    ```text
    Default
    Auto-review
    Full Access
    Read Only
    locked-down
    web-enabled
    ```
    
    Selecting `locked-down` keeps `active_permission_profile =
    Some("locked-down")`; selecting a built-in keeps the friendly label
    while switching to its named built-in profile.
    
    ## Screenshots
    
    Live `$test-tui` smoke screenshots uploaded through GitHub attachments:
    
    **Profile mode with built-ins and custom profiles**
    
    <img width="832" alt="Profile mode permissions picker with custom
    profiles"
    src="https://github.com/user-attachments/assets/58b72431-418c-4839-9e39-575076db4c8f"
    />
    
    **Legacy mode remains anonymous preset picker**
    
    <img width="1232" alt="Legacy permissions picker"
    src="https://github.com/user-attachments/assets/95f413ab-4cee-411c-9afb-92580a885c97"
    />
    
    <img width="1296" height="906" alt="image"
    src="https://github.com/user-attachments/assets/ea381a78-9904-4aa2-828f-b7f2e43f60f2"
    />
    
    <img width="705" height="207" alt="Screenshot 2026-05-18 at 2 58 00 PM"
    src="https://github.com/user-attachments/assets/2fa6dd71-0296-449e-a6de-a72d78a1cb70"
    />
    
    ## Validation
    
    - `git diff --cached --check` before commit.
    - Full test run skipped at the user request while pushing the split
    stack.
  • tui: include exec sessions in resume list (#24503)
    ## Why
    
    Fixes #24502.
    
    `codex resume --include-non-interactive` should include sessions created
    by `codex exec`, but the TUI was sending no `sourceKinds` filter to
    `thread/list` for that mode. `thread/list` treats omitted or empty
    `sourceKinds` as interactive-only (`cli`, `vscode`), so exec sessions
    were still filtered out.
    
    ## What Changed
    
    - Added a shared TUI `resume_source_kinds` helper so both resume lookup
    paths always pass explicit `sourceKinds` to `thread/list`.
    - Kept the default resume behavior scoped to `cli` and `vscode`.
    - Made `--include-non-interactive` include `exec` and `appServer`
    sessions, while continuing to exclude subagent and unknown sources.
    
    ## Verification
    
    Added focused coverage for both affected TUI request builders:
    
    - `latest_session_lookup_params_can_include_non_interactive_sources`
    - `remote_thread_list_params_can_include_non_interactive_sources`
  • Use thread config for TUI MCP inventory (#24532)
    ## Summary
    `/mcp` in the TUI should reflect the current loaded thread, including
    project-local MCP servers from that thread config. Before this change,
    `mcpServerStatus/list` only read the latest global MCP config, so the
    active chat could miss project-local servers.
    
    This adds optional `threadId` to `mcpServerStatus/list`. When present,
    app-server resolves the loaded thread and lists MCP status from the
    refreshed effective config for that thread; when omitted, existing
    global config behavior stays unchanged.
    
    The TUI now sends the active chat thread id for `/mcp` and `/mcp
    verbose`, carries that origin through the async inventory result, and
    ignores stale completions if the user has switched threads before the
    fetch returns. The app-server schemas were regenerated.
    
    ## Follow-up
    Once this app-server API change lands, the desktop app should make the
    same `threadId` plumbing so its MCP inventory also uses the current
    thread config.
    
    Fixes #23874
  • fix(tui): prevent macos stderr from corrupting composer (#24459)
    ## Why
    
    Fixes #17139.
    
    On macOS, runtime diagnostics such as `MallocStackLogging` messages can
    be written directly to process stderr while the inline TUI owns the
    terminal. Those bytes paint into the same viewport as the composer
    without passing through the renderer or composer state, making
    diagnostic output appear to leak into the input area.
    
    ## What Changed
    
    - Add a macOS terminal stderr guard while the inline TUI owns the
    viewport.
    - Restore stderr when Codex returns terminal ownership for external
    interactive programs, suspend/resume, panic handling, and normal
    shutdown.
    - Add an fd-level regression test that verifies output is suppressed
    only while terminal ownership is held and restored at each handoff
    boundary.
    
    ## How to Test
    
    1. On macOS, launch the interactive TUI and leave the composer visible.
    2. Exercise the workflow that triggers an allocator/runtime stderr
    diagnostic during an active session, as reported in #17139.
    3. Confirm the diagnostic no longer overwrites the active composer
    region.
    4. Suspend or exit the TUI and confirm subsequent terminal stderr output
    remains visible.
    
    The platform diagnostic is environment-dependent, so the deterministic
    regression check is the new fd-lifecycle test in
    `tui::terminal_stderr::tests::suppresses_stderr_only_while_terminal_is_owned`.
    
    Targeted validation:
    - `just argument-comment-lint-from-source -p codex-tui` passed.
    - `just test -p codex-tui` exercised and passed the new stderr-guard
    regression test. The full invocation currently fails in two unrelated
    guardian-policy tests,
    `update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
    and
    `update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`,
    which reproduce when rerun in isolation.
  • fix(tui): improve multiline markdown list readability (#24351)
    ## Why
    
    Numbered Markdown findings become hard to scan when long items visually
    run together or when wrapped explanatory paragraphs lose their list
    indentation. This is especially visible in review output: the next
    number can look attached to the previous finding, and paragraph
    continuation rows can jump back toward the left margin instead of
    staying grouped beneath their item.
    
    <table><tr><td>
    <center>Before</center>
    <img width="1718" height="836" alt="CleanShot 2026-05-24 at 14 00 49"
    src="https://github.com/user-attachments/assets/f1ee0023-50fa-4f81-a641-ae08b17b99bd"
    />
    </td></tr>
    <tr><td> 
    <center>After</center>
    <img width="1714" height="906" alt="image"
    src="https://github.com/user-attachments/assets/b123a5e0-a232-47bf-96d5-c935295f7c0a"
    />
    </td></tr>
    </table>
    
    ## What Changed
    
    - Insert a blank separator before a sibling list item when the previous
    item occupies more than one rendered line.
    - Preserve compact rendering for lists whose sibling items each render
    on one line.
    - Preserve list-body leading whitespace when transient streamed
    assistant rows require another wrapping pass for history display, so
    wrapped paragraphs stay aligned beneath their item.
    - Share the existing leading-whitespace prefix logic used by history
    insertion instead of introducing a second indentation rule.
    - Keep streamed Markdown output aligned with completed rendering and add
    snapshots for findings-style spacing and streamed paragraph indentation.
    
    ## How to Test
    
    1. Start Codex from this branch and open the recorded repro session
    `019e563f-7d58-7ff2-8ec7-828f20fa61ca`.
    2. Inspect the numbered `Findings` list whose items contain explanatory
    paragraphs.
    3. Confirm each multiline finding is separated from the next numbered
    finding by one blank line.
    4. Confirm wrapped rows of each indented paragraph remain aligned
    beneath the finding body, rather than returning to the left edge.
    5. Render a short one-line numbered or unordered list and confirm its
    items remain compact without added blank rows.
    
    Targeted tests:
    
    - `just test -p codex-tui history_cell insert_history markdown_render
    markdown_stream streaming::controller`
    - `just argument-comment-lint-from-source -p codex-tui`
    
    ## Related Work
    
    PR #24346 changes Markdown table column allocation in parallel. This PR
    is intentionally limited to list-item readability and history wrapping;
    both branches touch `codex-rs/tui/src/markdown_render.rs`, so a small
    merge conflict may need resolution depending on merge order.
  • fix(tui): improve markdown table column allocation (#24346)
    ## Why
    
    Markdown tables with a long path-heavy column could allocate almost all
    available width to that column and collapse neighboring prose columns to
    only a few characters. In rollout summaries this made `Unit` and `What
    It Adds` difficult to read, even though the long `Files` values were the
    content best suited to wrapping.
    
    The affected example also specified `Files` as right aligned in its
    markdown delimiter (`---:`). This change preserves that requested
    alignment while improving how width is distributed.
    
    | Before | After |
    |---|---|
    | <img width="1709" height="764" alt="image"
    src="https://github.com/user-attachments/assets/932ab21c-b72d-48a2-9aad-b69da87a0968"
    /> | <img width="1711" height="855" alt="image"
    src="https://github.com/user-attachments/assets/4028bd20-2228-4c2f-be8a-1866325b7f62"
    /> |
    
    
    ## What Changed
    
    - Classify table columns as narrative, token-heavy, or compact during
    width allocation.
    - Shrink token-heavy path and URL columns before shrinking narrative
    prose, while preserving compact counts and short labels longest.
    - Use readable soft floors for narrative and token-heavy content before
    falling back to tighter layouts.
    - Add snapshot coverage for a rollout-shaped table containing
    right-aligned file paths and prose columns.
    
    ## How to Test
    
    1. Render a markdown table with `Unit`, right-aligned `Files`, `Adds`,
    `Removes`, and `What It Adds` columns at a constrained terminal width.
    2. Put long repository paths in `Files` and sentence-length content in
    `Unit` and `What It Adds`.
    3. Confirm that `Files` remains right aligned but wraps before the
    narrative columns become unreadable.
    4. Confirm that the compact numeric columns remain easy to scan.
    
    Targeted tests:
    - `just test -p codex-tui markdown_render`
    
    Validation note: `just test -p codex-tui` was also attempted and reached
    two existing unrelated failures in
    `app::tests::update_feature_flags_disabling_guardian_*`; the markdown
    rendering regression test passes in the targeted run.
  • TUI config cleanup: MCP inventory (#24265)
    ## Summary
    
    The TUI `/mcp` inventory flow should reflect the app server’s MCP status
    response. It was also joining those results with the TUI process’s local
    `config.mcp_servers`, which can diverge once MCP state is owned by a
    remote app server and cause stale local command, URL, status, or
    empty-state details to render.
    
    This change removes the local config join from the app-server-backed
    inventory renderer. The TUI now renders directly from the existing
    `mcpServerStatus/list` payload and treats an empty status response as
    the empty MCP inventory state.
    
    ## Known limitation
    
    The existing `mcpServerStatus/list` payload does not include
    disabled-state or disabled-reason fields. To preserve the current
    app-server API, this PR does not try to infer that state from
    client-local config. If remote `/mcp` needs to show disabled/reason
    details again, that should come from app-server-owned status data in a
    follow-up.
    
    Related to #22914, #22915, and #22916.
  • TUI config cleanup: trusted projects (#24255)
    ## Why
    TUI onboarding trusted-project persistence should go through the same
    app-server config write path as other config mutations. Writing
    `config.toml` directly from the trust widget bypasses that layer and can
    let onboarding proceed even when the trust decision was not actually
    persisted.
    
    ## What changed
    - Added a TUI config helper that writes the existing project trust
    structure through `config/batchWrite`.
    - Persists trust decisions as `projects.<project>.trust_level =
    "trusted"` using the existing project trust key helper.
    - Changed the trust directory widget to only record the user selection;
    onboarding performs the app-server write before reporting success.
    - Keeps the user on the trust screen and shows an error if app-server
    persistence fails.
    
    ## Verification
    - `cargo test -p codex-tui --lib
    trust_persistence_failure_keeps_trust_step_in_progress`
    - `cargo test -p codex-tui --lib
    trusted_project_edit_targets_project_trust_level`
    - Manual: built the local `codex-cli`, accepted the trust prompt in a
    temp project, confirmed `projects.<project>.trust_level = "trusted"`,
    and simulated an unwritable config to verify onboarding stays on the
    trust screen without writing trust.
  • TUI config cleanup: oss_provider (#24254)
    ## Summary
    
    Manual provider selection during `codex --oss` startup was still
    persisting `oss_provider` through the legacy local `config.toml` writer.
    That bypasses the app-server-owned config mutation path used by the TUI,
    so this routes the write through the app server config API instead.
    
    The net behavior is intentionally narrow: only an interactive picker
    selection is persisted. Auto-detected single-running-provider startup
    and explicit `--local-provider` startup remain ephemeral, so merely
    having one backend running does not make that provider sticky for future
    runs.
    
    ## What Changed
    
    - Removed the TUI picker’s direct dependency on
    `set_default_oss_provider`.
    - Had `oss_selection` report whether the returned provider came from the
    interactive picker.
    - Carried only manually selected providers into startup persistence.
    - Wrote `oss_provider` via `config/batchWrite` once the app server
    session is available.
    - Logged a warning and continued startup if the app-server config write
    fails.
    
    ## Verification
    
    Manually smoke-tested the real `codex-tui` binary with a temporary
    `CODEX_HOME`, pseudo-terminal input, and a fake LM Studio HTTP server:
    
    - Interactive picker selection persisted `oss_provider = "lmstudio"`.
    - Non-picker `--local-provider lmstudio` startup did not persist
    `oss_provider`.
  • Respect hook trust bypass during TUI startup (#24317)
    Fixes #24093.
    
    ## Why
    
    `--dangerously-bypass-hook-trust` is a supported CLI flag intended for
    headless or automated runs where enabled hooks should be allowed to run
    without requiring persisted trust. In the TUI, startup hook review still
    opened whenever hooks looked untrusted, so a launch using the bypass
    could block on the interactive "Hooks need review" prompt.
    
    The tricky case is persistent app-server resume: a resume may attach to
    an already-running thread, where resume config overrides are ignored. In
    that path, hiding the startup review would be wrong because the existing
    hook engine may still filter untrusted hooks.
    
    ## What Changed
    
    - Startup hook review now skips the prompt only when hook trust bypass
    is actually safe for that launch.
    - The TUI forwards `bypass_hook_trust` through the app-server request
    config for fresh thread start/resume/fork paths, and the app-server
    applies it as a runtime-only `ConfigOverrides` value rather than
    treating it like a `config.toml` setting.
    - Persistent app-server resumes keep the startup review prompt so users
    still have a chance to trust hooks when the running thread cannot
    receive the bypass override.
    
    ## Verification
    
    - Added focused coverage for startup hook review with and without
    `bypass_hook_trust`.
    - Extended existing TUI/app-server config override tests to cover
    forwarding and applying `bypass_hook_trust`.
  • Show remote connection details in /status (#24420)
    ## Summary
    
    Fixes #24411.
    
    `/status` currently has no way to show when the TUI is talking to Codex
    through a remote transport. That makes embedded local sessions, local
    daemon sessions, and true remote sessions look the same, and it hides
    the remote server version when debugging connection-specific behavior.
    
    This PR adds a single `Remote` row for non-embedded connections only.
    The row shows the sanitized connection address and a dimmed version
    parenthetical, preserving the existing status output for embedded local
    sessions.
    
    <img width="791" height="144" alt="image"
    src="https://github.com/user-attachments/assets/529d7940-1c45-4586-8b06-f20a1f04b771"
    />
    
    
    ## Verification
    
    - Manually validated when connecting remotely (either implicitly to
    local daemon or explicitly)
  • tui: label compact rate-limit percentages (#24314)
    ## Summary
    
    The compact TUI status line already renders rate-limit percentages as
    remaining capacity, but the text did not say so. That made high-usage
    red indicators ambiguous because values like `weekly 6%` could be read
    as either used or remaining.
    
    This PR labels the compact rate-limit values explicitly as `left` across
    the status line, terminal title, and setup previews.
    
    Addresses #24274
  • fix(tui): restore Windows VT before TUI renders (#24082)
    ## Why
    
    Older Git for Windows versions can leave the Windows console output mode
    without virtual terminal processing after Codex runs git metadata
    commands in a repository. When the TUI later emits ANSI control
    sequences for redraws, restore, or image rendering, Windows Terminal can
    show raw escape bytes or leave the prompt/status area corrupted.
    
    This is a targeted mitigation for the repo-conditioned Windows rendering
    corruption reported in #23888 and related reports #23512 and #23628.
    Updating Git avoids the trigger for affected users, but Codex should
    also reassert the terminal mode before it writes TUI control sequences.
    
    | Before | After |
    |---|---|
    | <img width="2100" height="1359" alt="CleanShot 2026-05-22 at 11 23 21"
    src="https://github.com/user-attachments/assets/3218c379-5f97-4c71-ab25-805c9d20578a"
    /> | <img width="2100" height="1359" alt="CleanShot 2026-05-22 at 11 23
    58"
    src="https://github.com/user-attachments/assets/55ac72bb-37d0-400e-99bc-12dd5ea4092d"
    /> |
    
    
    ## What Changed
    
    - Re-enable Windows virtual terminal processing for stdout and stderr
    before TUI mode setup, restore, redraw, resume, and pet image render
    paths.
    - Treat invalid, null, or non-console handles as no-ops so redirected or
    non-console output is unaffected.
    - Keep the helper as a no-op on non-Windows platforms.
    
    ## How to Test
    
    1. On Windows Terminal with a Git 2.28.0 for Windows install, start
    Codex inside a valid Git repository.
    2. Start a new Codex CLI session.
    3. Confirm the prompt, working indicator, and bottom status line remain
    readable instead of showing raw ANSI escape sequences.
    4. Repeat outside a Git repository to confirm the ordinary non-repo
    startup path is unchanged.
    
    Targeted tests:
    - Not run locally; the behavior depends on Windows console mode APIs and
    the current worktree is on macOS.
  • docs: update README.md to mention curl-based installer (#24106)
    Now that users can install via `curl` (or `irm`), we should tell them
    about it so they no longer need to use `npm`!
    
    Note that on one Windows machine I tested on, when I ran:
    
    ```
    irm https://chatgpt.com/codex/install.ps1 | iex
    ```
    
    I got this error:
    
    ```
    iex : The property 'OSArchitecture' cannot be found on this object. Verify that the property exists.
    At line:1 char:45
    + irm https://chatgpt.com/codex/install.ps1 | iex
    +                                             ~~~
        + CategoryInfo          : NotSpecified: (:) [Invoke-Expression], PropertyNotFoundException
        + FullyQualifiedErrorId : PropertyNotFoundStrict,Microsoft.PowerShell.Commands.InvokeExpressionCommand
    ```
    
    so we'll recommend the following that works from both `cmd.exe` and
    PowerShell:
    
    ```
    powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"
    ```
    
    This PR makes a slight update to `codex-rs/tui/src/update_action.rs` to
    match.
  • Add new enterprise requirement gate (#23736)
    Add new enterprise requirement gate.
    
    Validation:
    - `cargo test -p codex-config --lib`
    - `cargo test -p codex-app-server-protocol --lib`
    - `cargo test -p codex-tui --lib debug_config`
    - `cargo test -p codex-app-server --lib` *(fails: stack overflow in
    `in_process::tests::in_process_start_initializes_and_handles_typed_v2_request`;
    reproduces when run alone)*
  • tui: make codex-tui.log opt-in (#24081)
    ## Why
    
    The TUI currently creates a shared plaintext `codex-tui.log` under the
    default log directory. That append-only file can keep growing across
    runs even though the TUI already records diagnostics in bounded local
    stores.
    
    Make the plaintext file log an explicit troubleshooting choice instead
    of a default side effect.
    
    This is possible because logs are also stored in the DB with proper
    rotation
    
    ## What changed
    
    - Only install the TUI file logging layer when `log_dir` is explicitly
    set.
    - Remove the prior `codex-tui.log` at startup before an opt-in file
    layer is created.
    - Clarify the `log_dir` config/schema text and `docs/install.md` example
    so users opt in with `codex -c log_dir=...` when they need a plaintext
    log.
  • config: remove legacy profile write paths (#24055)
    ## Why
    
    [#23883](https://github.com/openai/codex/pull/23883) moved the
    user-facing `--profile` flag onto profile v2 and
    [#23886](https://github.com/openai/codex/pull/23886) removed CLI
    forwarding for the legacy profile-v1 path. Core and TUI config
    persistence still carried `active_profile` and
    `ConfigEditsBuilder::with_profile`, which let later writes continue
    targeting legacy `[profiles.<name>]` tables after profile selection
    moved to profile-v2 config files.
    
    ## What
    
    - Remove legacy profile routing from
    [`ConfigEditsBuilder`](https://github.com/openai/codex/blob/4b38e9c22e762261d7f7eef49d8a21792e241a06/codex-rs/core/src/config/edit.rs#L1064-L1294),
    so core config edits no longer carry `with_profile` or infer
    `[profiles.*]` write targets from a `profile` key.
    - Drop `active_profile` plumbing from runtime `Config`, TUI
    startup/state, app-server config override forwarding, and Windows
    sandbox setup persistence.
    - Make app-server-backed TUI config edits use unscoped model,
    service-tier, feature, Auto-review, plan-mode, and Windows sandbox paths
    through
    [`tui/src/config_update.rs`](https://github.com/openai/codex/blob/4b38e9c22e762261d7f7eef49d8a21792e241a06/codex-rs/tui/src/config_update.rs#L43-L112).
    - Update config edit coverage so legacy `profile` state stays untouched
    by direct model writes, and remove tests whose only contract was the
    deleted profile-scoped persistence path.
    
    ## Testing
    
    - Not run locally.
  • config: remove legacy profile v1 resolution (#24051)
    ## Why
    
    [#23883](https://github.com/openai/codex/pull/23883) moved user-facing
    `--profile` selection onto profile v2, and
    [#23886](https://github.com/openai/codex/pull/23886) removed the old CLI
    `config_profile` override path. Core still had a second legacy path:
    `profile = "..."` could select `[profiles.*]` values while runtime
    config was built. Keeping that resolver alive preserves the old
    precedence model and profile-carrying surfaces even though profile
    selection now points at `$CODEX_HOME/<name>.config.toml`.
    
    ## What
    
    - Reject legacy top-level `profile = "..."` config while loading runtime
    config, with an error that points callers at `--profile <name>` and
    `<name>.config.toml` in the [core load
    path](https://github.com/openai/codex/blob/3d923366eca10a29143623124c6c6e538f058269/codex-rs/core/src/config/mod.rs#L2524-L2531).
    - Remove the remaining profile-v1 merge points from runtime config
    resolution, including features, permissions, model/provider selection,
    web search, Windows sandbox settings, TUI settings, role reloads, and
    OSS provider lookup.
    - Drop the leftover profile override surface from
    [`ConfigOverrides`](https://github.com/openai/codex/blob/3d923366eca10a29143623124c6c6e538f058269/codex-rs/core/src/config/mod.rs#L2118-L2148)
    and from the MCP server `codex` tool schema.
    - Prune profile-precedence tests that only exercised the removed
    resolver and replace them with rejection coverage for the legacy
    selector.
    
    ## Testing
    
    - Not run in this metadata pass.
    - Added
    [`legacy_profile_selection_is_rejected`](https://github.com/openai/codex/blob/3d923366eca10a29143623124c6c6e538f058269/codex-rs/core/src/config/config_tests.rs#L7942-L7965)
    coverage for the new runtime guard.
  • Fix auto-review permission profile override (#23956)
    ## Summary
    The auto-review runtime sync path was assigning a raw
    `PermissionProfile` into `runtime_permission_profile_override`, whose
    field now expects `RuntimePermissionProfileOverride`. That broke the TUI
    Bazel build.
    
    This changes the assignment to store
    `RuntimePermissionProfileOverride::from_config(&self.config)`, matching
    the other runtime override paths and preserving the active profile and
    network metadata with the permission profile.
  • [3 of 4] tui: route feature and memory toggles through app server (#22915)
    ## Why
    Experimental feature toggles and memory settings can update several
    related config values in one interaction. Keeping those writes local in
    a remote TUI session is especially dangerous because the UI can diverge
    from the app-server config while also leaving behind partially stale
    supporting keys.
    
    This is **[3 of 4]** in a stacked series that moves TUI-owned config
    mutations onto app-server APIs.
    
    ## What changed
    - Routed feature flag persistence through app-server batch writes,
    including the supporting reviewer and permission updates used by
    guardian approval.
    - Routed Windows sandbox mode persistence and legacy Windows feature
    cleanup through app-server writes.
    - Routed memory settings through app-server batch writes and updated the
    TUI tests to exercise the embedded app-server path.
    
    ## Config keys affected
    - `features.<feature_key>`
    - `profiles.<profile>.features.<feature_key>`
    - `approval_policy`
    - `sandbox_mode`
    - `approvals_reviewer`
    - `windows.sandbox`
    - `features.experimental_windows_sandbox`
    - `features.elevated_windows_sandbox`
    - `features.enable_experimental_windows_sandbox`
    - Profile-scoped Windows legacy feature variants under
    `profiles.<profile>.features.*`
    - `memories.use_memories`
    - `memories.generate_memories`
    - Profile-scoped memory variants under `profiles.<profile>.memories.*`
    
    ## Suggested manual validation
    - Connect the TUI to a remote app server, toggle guardian approval on
    and off, and confirm the remote config updates
    `features.guardian_approval`, reviewer state, approval policy, and
    sandbox mode coherently.
    - Toggle a default-false experimental feature at the root level, disable
    it again, and confirm the key clears instead of lingering as an
    unnecessary explicit `false`.
    - Change memory settings and confirm the remote config updates both
    memory keys while the running TUI reflects the new state.
    - On Windows, switch sandbox mode through the TUI and confirm
    `windows.sandbox` is updated while the legacy Windows feature keys are
    cleared.
    
    ## Stack
    1. [#22913](https://github.com/openai/codex/pull/22913) `[1 of 4]`
    primary settings writes
    2. [#22914](https://github.com/openai/codex/pull/22914) `[2 of 4]` app
    and skill enablement
    3. [#22915](https://github.com/openai/codex/pull/22915) `[3 of 4]`
    feature and memory toggles
    4. [#22916](https://github.com/openai/codex/pull/22916) `[4 of 4]`
    startup and onboarding bookkeeping
  • TUI: skip goal replace prompt for completed goals (#23792)
    ## Why
    Users reported that the replacement confirmation feels unnecessary when
    the current thread goal is already complete. In that state, `/goal
    <objective>` is starting fresh rather than interrupting active work.
    
    ## What changed
    `/goal <objective>` now skips the replace confirmation when the existing
    goal has `complete` status and uses the existing fresh replacement path.
    Goals that are active, paused, blocked, usage-limited, or budget-limited
    still require confirmation before being replaced.
  • Improve /goal error messages for ephemeral sessions (#23796)
    ## Why
    
    When a user runs `/goal` in a temporary session, the TUI can currently
    surface an internal app-server failure such as `thread/goal/get failed
    in TUI`. That message is technically true, but it does not explain the
    actual constraint: goals require a saved session because goal state is
    persisted with the thread.
    
    This is especially confusing when `codex doctor` reports the background
    app-server as running in ephemeral mode, since that wording is easy to
    conflate with ephemeral thread/session behavior.
    
    ## What changed
    
    - Added a TUI-side formatter for thread-goal RPC failures in
    `codex-rs/tui/src/app/thread_goal_actions.rs`.
    - Detects app-server/core errors that indicate goals are unsupported for
    an ephemeral thread/session.
    - Replaces the internal RPC failure with a user-facing explanation:
    
    ```text
    Goals need a saved session. This session is temporary.
    Run `codex` to start a saved session, or `codex resume` / `/resume` to reopen one.
    ```
    
    - Preserves the existing generic failure wording for non-ephemeral goal
    errors.
    
    ## Verification
    
    - `cargo test -p codex-tui thread_goal_error_message --lib`
    
    I also tried `cargo test -p codex-tui`; it built successfully but the
    test runner aborted in an unrelated side-thread stack overflow
    (`app::tests::discard_side_thread_removes_agent_navigation_entry`),
    which reproduced when run by itself.
  • tui: plumb permission profile selection (#23708)
    ## Why
    
    The named-profile `/permissions` picker needs a small TUI action path
    that can select permission profiles without folding the menu UI and
    profile metadata into the same review.
    
    ## What changed
    
    - Carry permission-profile selections through the TUI app event flow.
    - Persist selected profiles while preserving the existing approval
    settings and guardrail prompts.
    - Keep the legacy `/permissions` picker behavior in this layer; the
    profile-mode menu stays in the follow-up PR.
    
    ## Stack
    
    1. [#22931](https://github.com/openai/codex/pull/22931):
    runtime/session/network propagation for active permission profiles.
    2. **This PR**: TUI selection plumbing and guardrail flow.
    3. [#21559](https://github.com/openai/codex/pull/21559): profile-aware
    `/permissions` menu and custom profile display.
    
    <img width="1632" height="1186" alt="image"
    src="https://github.com/user-attachments/assets/69ddcd5e-b57c-468d-8c1d-246916323c15"
    />
    
    ## Validation
    
    - `git diff --cached --check` before commit.
    - Full test run skipped at the user request while pushing the split
    stack.
  • cli: remove legacy profile v1 plumbing (#23886)
    ## Why
    
    [#23883](https://github.com/openai/codex/pull/23883) moved the
    user-facing `--profile` flag onto profile v2. The shared CLI option
    layer still carried the old `config_profile` slot and several CLI
    entrypoints still copied that value into legacy config overrides.
    Leaving that path around makes the CLI surface look like it still
    selects legacy `[profiles.*]` state even though `--profile` now means
    `$CODEX_HOME/<name>.config.toml`.
    
    ## What
    
    - Remove the legacy `config_profile` field and merge/copy path from
    [`SharedCliOptions`](https://github.com/openai/codex/blob/95baaf72920c8db22097df8d15a0bb76c84528b6/codex-rs/utils/cli/src/shared_options.rs#L8-L177).
    - Stop forwarding profile-v1 overrides from CLI, exec, TUI, doctor,
    debug, feature, and exec-server paths; runtime profile selection remains
    on `config_profile_v2` through
    [`loader_overrides_for_profile`](https://github.com/openai/codex/blob/95baaf72920c8db22097df8d15a0bb76c84528b6/codex-rs/cli/src/main.rs#L1606-L1619).
    - Resolve local OSS provider selection from the base config in exec and
    TUI now that the legacy profile argument is gone.
    
    ## Testing
    
    - Not run (cleanup-only follow-up to #23883).
  • feat: support managed permission profiles in requirements.toml (#23433)
    ## Why
    
    Cloud-managed `requirements.toml` should be able to define the managed
    permission profiles a client may select and constrain that selectable
    set without requiring local user config to recreate the profile catalog.
    
    This keeps requirements focused on restrictions. The selected default
    remains a config or session choice, while requirements contribute the
    managed profile bodies and `allowed_permissions` allowlist that the
    config-loading boundary validates before a resolved runtime
    `PermissionProfile` is installed.
    
    ## What changed
    
    - Add `requirements.toml` support for a managed permission-profile
    catalog plus its allowlist:
    
    ```toml
    allowed_permissions = ["review", "build"]
    
    [permissions.review]
    extends = ":read-only"
    
    [permissions.build]
    extends = ":workspace"
    ```
    
    - Merge requirements-defined profile bodies into the effective
    permission catalog and reject profile ids that collide with
    config-defined profiles.
    - Validate that every `allowed_permissions` entry resolves to a built-in
    or catalog profile before selection uses it.
    - Preserve allowed configured named-profile selections. When a
    configured named profile is disallowed, fall back to the first allowed
    requirements profile with a startup warning.
    - Keep built-in selections and the stock trust-based `:read-only` /
    `:workspace` fallback path intact when no permission profile is
    explicitly selected.
    - Centralize the managed catalog and allowlist selection path in
    `EffectivePermissionSelection` so the requirements boundary is visible
    in config loading.
    - Surface `allowedPermissions` through `configRequirements/read`, and
    update the generated app-server schema fixtures plus the app-server
    README.
    
    ## Validation
    
    - `cargo test -p codex-config`
    - `cargo test -p codex-core system_requirements_`
    - `cargo test -p codex-core system_allowed_permissions_`
    - `cargo test -p codex-app-server-protocol`
    - `just write-app-server-schema`
    
    ## Related work
    
    - Uses merged permission-profile inheritance support from #22270 and
    #23705.
    - Kept separate from the in-flight permission profile listing API in
    #23412.
  • [codex] Add plugin id to MCP tool call items (#23737)
    Add owning plugin id to MCP tool call items so we can better filter them
    at plugin level.
    
    ## Summary
    - add optional `plugin_id` to MCP tool-call items and legacy begin/end
    events
    - propagate plugin metadata into emitted core items and app-server v2
    `ThreadItem::McpToolCall`
    - preserve plugin ids through app-server replay/redaction paths and
    regenerate v2 schema fixtures
    
    ## Testing
    - `just write-app-server-schema`
    - `just fmt`
    - `just fix -p codex-core`
    - `cargo test -p codex-protocol -p codex-app-server-protocol`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-core mcp_tool_call_item_includes_plugin_id --lib`
    - `cargo check -p codex-tui --tests`
    - `cargo check -p codex-app-server --tests`
    - `git diff --check`
    
    ## Notes
    - `just fix -p codex-core` completed with two non-fatal
    `too_many_arguments` warnings on the touched MCP notification helpers.
    - A broader `cargo test -p codex-core` run passed core unit tests, then
    hit shell/sandbox/snapshot failures in the integration target.
    - A broader app-server downstream run hit the existing
    `in_process::tests::in_process_start_clamps_zero_channel_capacity` stack
    overflow; `cargo test -p codex-exec` also hit the existing sandbox
    expectation mismatch in
    `thread_lifecycle_params_include_legacy_sandbox_when_no_active_profile`.
  • Honor client-resolved service tier defaults (#23537)
    ## Why
    
    Model catalog responses can now advertise a nullable
    `default_service_tier` for each model. Codex needs to preserve three
    distinct states all the way from config/app-server inputs to inference:
    
    - no explicit service tier, so the client may apply the current model
    catalog default when FastMode is enabled
    - explicit `default`, meaning the user intentionally wants standard
    routing
    - explicit catalog tier ids such as `priority`, `flex`, or future tiers
    
    Keeping those states distinct prevents the UI from showing one tier
    while core sends another, especially after model switches or app-server
    `thread/start` / `turn/start` updates.
    
    ## What Changed
    
    - Plumbed `default_service_tier` through model catalog protocol types,
    app-server model responses, generated schemas, model cache fixtures, and
    provider/model-manager conversions.
    - Added the request-only `default` service tier sentinel and normalized
    legacy config spelling so `fast` in `config.toml` still materializes as
    the runtime/request id `priority`.
    - Moved catalog default resolution to the TUI/client side, including
    recomputing the effective service tier when model/FastMode-dependent
    surfaces change.
    - Updated app-server thread lifecycle config construction so
    `serviceTier: null` preserves explicit standard-routing intent by
    mapping to `default` instead of internal `None`.
    - Kept core responsible for validating explicit tiers against the
    current model and stripping `default` before `/v1/responses`, without
    applying catalog defaults itself.
    
    ## Validation
    
    - `CARGO_INCREMENTAL=0 cargo build -p codex-cli`
    - `CARGO_INCREMENTAL=0 cargo test -p codex-app-server model_list`
    - `cargo test -p codex-tui service_tier`
    - `cargo test -p codex-protocol service_tier_for_request`
    - `cargo test -p codex-core get_service_tier`
    - `RUST_MIN_STACK=8388608 CARGO_INCREMENTAL=0 cargo test -p codex-core
    service_tier`