Commit Graph

988 Commits

  • [skills] Auto install MCP dependencies when running skils with dependency specs. (#9982)
    Auto install MCP dependencies when running skils with dependency specs.
  • Restore image attachments/text elements when recalling input history (Up/Down) (#9628)
    **Summary**
    - Up/Down input history now restores image attachments and text elements
    for local entries.
    - Composer history stores rich local entries (text + text elements +
    local image paths) while persistent history remains text-only.
    - Added tests to verify history recall rehydrates image placeholders and
    attachments in both `tui` and `tui2`.
    
    **Changes**
    - `tui/src/bottom_pane/chat_composer_history.rs`: store `HistoryEntry`
    (text + elements + image paths) for local history; adapt navigation +
    tests.
    - `tui2/src/bottom_pane/chat_composer_history.rs`: same as above.
    - `tui/src/bottom_pane/chat_composer.rs`: record rich history entries
    and restore them on Up/Down; update Ctrl+C history and tests.
    - `tui2/src/bottom_pane/chat_composer.rs`: same as above.
  • tui: stabilize shortcut overlay snapshots on WSL (#9359)
    Fixes #9361
    
    ## Context
    Split out from #9059 per review:
    https://github.com/openai/codex/pull/9059#issuecomment-3757859033
    
    ## Summary
    The shortcut overlay renders different paste-image bindings on WSL
    (Ctrl+Alt+V) vs non-WSL (Ctrl+V), which makes snapshot tests
    non-deterministic when run under WSL.
    
    ## Changes
    - Gate WSL detection behind `cfg(not(test))` so snapshot tests are
    deterministic across environments.
    - Add a focused unit test that still asserts the WSL-specific
    paste-image binding.
    
    ## Testing
    - `just fmt`
    - `just fix -p codex-tui`
    - `just fix -p codex-tui2`
    - `cargo test -p codex-tui`
    - `cargo test -p codex-tui2`
  • Ask user question UI footer improvements (#9949)
    ## Summary
    
    Polishes the `request_user_input` TUI overlay
    
    Question 1 (unanswered)
    <img width="853" height="167" alt="Screenshot 2026-01-27 at 1 30 09 PM"
    src="https://github.com/user-attachments/assets/3c305644-449e-4e8d-a47b-d689ebd8702c"
    />
    
    Tab to add notes
    <img width="856" height="198" alt="Screenshot 2026-01-27 at 1 30 25 PM"
    src="https://github.com/user-attachments/assets/0d2801b0-df0c-49ae-85af-e6d56fc2c67c"
    />
    
    Question 2 (unanswered)
    <img width="854" height="168" alt="Screenshot 2026-01-27 at 1 30 55 PM"
    src="https://github.com/user-attachments/assets/b3723062-51f9-49c9-a9ab-bb1b32964542"
    />
    
    Ctrl+p or h to go back to q1 (answered)
    <img width="853" height="195" alt="Screenshot 2026-01-27 at 1 31 27 PM"
    src="https://github.com/user-attachments/assets/c602f183-1c25-4c51-8f9f-e565cb6bd637"
    />
    
    Unanswered freeform
    <img width="856" height="126" alt="Screenshot 2026-01-27 at 1 31 42 PM"
    src="https://github.com/user-attachments/assets/7e3d9d8b-820b-4b9a-9ef2-4699eed484c5"
    />
    
    ## Key changes
    
    - Footer tips wrap at tip boundaries (no truncation mid‑tip); footer
    height scales to wrapped tips.
    - Keep tooltip text as Esc: interrupt in all states.
    - Make the full Tab: add notes tip cyan/bold when applicable; hide notes
    UI by default.
    - Notes toggling/backspace:
    - Tab opens notes when an option is selected; Tab again clears notes and
    hides the notes UI.
        - Backspace in options clears the current selection.
        - Backspace in empty notes closes notes and returns to options.
    - Selection/answering behavior:
    - Option questions highlight a default option but are not answered until
    Enter.
    - Enter no longer auto‑selects when there’s no selection (prevents
    accidental answers).
        - Notes submission can commit the selected option when present.
    - Freeform questions require Enter with non‑empty text to mark answered;
    drafts are not submitted unless committed.
    - Unanswered cues:
        - Skipped option questions count as unanswered.
        - Unanswered question titles are highlighted for visibility.
    - Typing/navigation in options:
        - Typing no longer opens notes; notes are Tab‑only.
    - j/k move option selection; h/l switch questions (Ctrl+n/Ctrl+p still
    work).
    
    ## Tests
    
    - Added unit coverage for:
        - tip‑level wrapping
        - focus reset when switching questions with existing drafts
        - backspace clearing selection
        - backspace closing empty notes
        - typing in options does not open notes
        - freeform draft submission gating
        - h/l question navigation in options
    - Updated snapshots, including narrow footer wrap.
    
    ## Why
    
    These changes make the ask‑user‑question overlay:
    
    - safer (no silent auto‑selection or accidental freeform submission),
    - clearer (tips wrap cleanly and unanswered states stand out),
    - more ergonomic (Tab explicitly controls notes; backspace acts like
    undo/close).
    
    ## Codex author
    `codex fork 019bfc3c-2c42-7982-9119-fee8b9315c2f`
    
    ---------
    
    Co-authored-by: Ahmed Ibrahim <aibrahim@openai.com>
  • Clarify external editor env var message (#10030)
    ### Motivation
    - Improve UX by making it explicit that `VISUAL`/`EDITOR` must be set
    before launching Codex, not during a running session.
    
    ### Description
    - Update the external editor error text in `codex-rs/tui/src/app.rs` to:
    `"Cannot open external editor: set $VISUAL or $EDITOR before starting
    Codex."` and run `just fmt` to apply formatting.
    
    ### Testing
    - Ran `just fmt` successfully; attempted `cargo test -p codex-tui` but
    it failed due to network errors when fetching git dependencies (tests
    did not complete).
    
    ------
    [Codex
    Task](https://chatgpt.com/codex/tasks/task_i_6972c2c984948329b1a37d5c5839aff3)
  • Fix: Render MCP image outputs regardless of ordering (#9815)
    ## What?
    - Render an MCP image output cell whenever a decodable image block
    exists in `CallToolResult.content` (including text-before-image or
    malformed image before valid image).
    
    ## Why?
    - Tool results that include caption text before the image currently drop
    the image output cell.
    - A malformed image block can also suppress later valid image output.
    
    ## How?
    - Iterate `content` and return the first successfully decoded image
    instead of only checking the first block.
    - Add unit tests that cover text-before-image ordering and
    invalid-image-before-valid.
    
    ## Before
    ```rust
    let image = match result {
        Ok(mcp_types::CallToolResult { content, .. }) => {
            if let Some(mcp_types::ContentBlock::ImageContent(image)) = content.first() {
                // decode image (fails -> None)
            } else {
                None
            }
        }
        _ => None,
    }?;
    ```
    ## After
    ```rust
    let image = result
        .as_ref()
        .ok()?
        .content
        .iter()
        .find_map(decode_mcp_image)?;
    ```
    
    ## Risk / Impact
    - Low: only affects image cell creation for MCP tool results; no change
    for non-image outputs.
    
    ## Tests
    - [x] `just fmt`
    - [x] `cargo test -p codex-tui`
    - [x] Rerun after branch update (2026-01-27): `just fmt`, `cargo test -p
    codex-tui`
    
    Manual testing
    
    # Manual testing: MCP image tool result rendering (Codex TUI)
    
    # Build the rmcp stdio test server binary:
    cd codex-rs
    cargo build -p codex-rmcp-client --bin test_stdio_server
    
    # Register the server as an MCP server (absolute path to the built binary):
    codex mcp add mcpimg -- /Users/joshka/code/codex-pr-review/codex-rs/target/debug/test_stdio_server
    
    # Then in Codex TUI, ask it to call:
    - mcpimg.image_scenario({"scenario":"image_only"})
    - mcpimg.image_scenario({"scenario":"text_then_image","caption":"Here is the image:"})
    - mcpimg.image_scenario({"scenario":"invalid_base64_then_image"})
    - mcpimg.image_scenario({"scenario":"invalid_image_bytes_then_image"})
    - mcpimg.image_scenario({"scenario":"multiple_valid_images"})
    - mcpimg.image_scenario({"scenario":"image_then_text","caption":"Here is the image:"})
    - mcpimg.image_scenario({"scenario":"text_only","caption":"Here is the image:"})
    
    # Expected:
    # - You should see an extra history cell: "tool result (image output)" when the
    #   tool result contains at least one decodable image block (even if earlier
    #   blocks are text or invalid images).
    
    
    Fixes #9814
    
    ---------
    
    Co-authored-by: Josh McKinney <joshka@openai.com>
  • remove sandbox globals. (#9797)
    Threads sandbox updates through OverrideTurnContext for active turn
    Passes computed sandbox type into safety/exec
  • TUI footer: right-align context and degrade shortcut summary + mode cleanly (#9944)
    ## Summary
    Refines the bottom footer layout to keep `% context left` right-aligned
    while making the left side degrade cleanly
    
    ## Behavior with empty textarea
    Full width:
    <img width="607" height="62" alt="Screenshot 2026-01-26 at 2 59 59 PM"
    src="https://github.com/user-attachments/assets/854f33b7-d714-40be-8840-a52eb3bda442"
    />
    Less:
    <img width="412" height="66" alt="Screenshot 2026-01-26 at 2 59 48 PM"
    src="https://github.com/user-attachments/assets/9c501788-c3a2-4b34-8f0b-8ec4395b44fe"
    />
    Min width:
    <img width="218" height="77" alt="Screenshot 2026-01-26 at 2 59 33 PM"
    src="https://github.com/user-attachments/assets/0bed2385-bdbf-4254-8ae4-ab3452243628"
    />
    
    ## Behavior with message in textarea and agent running (steer enabled)
    Full width:
    <img width="753" height="63" alt="Screenshot 2026-01-26 at 4 33 54 PM"
    src="https://github.com/user-attachments/assets/1856b352-914a-44cf-813d-1cb50c7f183b"
    />
    
    Less:
    <img width="353" height="61" alt="Screenshot 2026-01-26 at 4 30 12 PM"
    src="https://github.com/user-attachments/assets/d951c4d5-f3e7-4116-8fe1-6a6c712b3d48"
    />
    
    Less:
    <img width="304" height="64" alt="Screenshot 2026-01-26 at 4 30 51 PM"
    src="https://github.com/user-attachments/assets/1433e994-5cbc-4e20-a98a-79eee13c8699"
    />
    
    Less:
    <img width="235" height="61" alt="Screenshot 2026-01-26 at 4 30 56 PM"
    src="https://github.com/user-attachments/assets/e216c3c6-84cd-40fc-ae4d-83bf28947f0e"
    />
    
    Less:
    <img width="165" height="59" alt="Screenshot 2026-01-26 at 4 31 08 PM"
    src="https://github.com/user-attachments/assets/027de5de-7185-47ce-b1cc-5363ea33d9b1"
    />
    
    ## Notes / Edge Cases
    - In steer mode while typing, the queue hint no longer replaces the mode
    label; it renders as `tab to queue message · {Mode}`.
    - Collapse priorities differ by state:
    - With the queue hint active, `% context left` is hidden before
    shortening or dropping the queue hint.
    - In the empty + non-running state, `? for shortcuts` is dropped first,
    and `% context left` is only shown if `(shift+tab to
    cycle)` can also fit.
    - Transient instructional states (`?` overlay, Esc hint, Ctrl+C/D
    reminders, and flash/override hints) intentionally suppress the
    mode label (and context) to focus the next action.
    
    ## Implementation Notes
    - Renamed the base footer modes to make the state explicit:
    `ComposerEmpty` and `ComposerHasDraft`, and compute the base mode
    directly from emptiness.
    - Unified collapse behavior in `single_line_footer_layout` for both base
    modes, with:
    - Queue-hint behavior that prefers keeping the queue hint over context.
    - A cycle-hint guard that prevents context from reappearing after
    `(shift+tab to cycle)` is dropped.
    - Kept rendering responsibilities explicit:
      - `single_line_footer_layout` decides what fits.
      - `render_footer_line` renders a chosen line.
    - `render_footer_from_props` renders the canonical mode-to-text mapping.
    - Expanded snapshot coverage:
    - Added `footer_collapse_snapshots` in `chat_composer.rs` to lock the
    distinct collapse states across widths.
    - Consolidated the width-aware snapshot helper usage (e.g.,
    `snapshot_composer_state_with_width`,
    `snapshot_footer_with_mode_indicator`).
  • Fixing main and make plan mode reasoning effort medium (#9980)
    It's overthinking so much on high and going over the context window.
  • fix: handle all web_search actions and in progress invocations (#9960)
    ### Summary
    - Parse all `web_search` tool actions (`search`, `find_in_page`,
    `open_page`).
    - Previously we only parsed + displayed `search`, which made the TUI
    appear to pause when the other actions were being used.
    - Show in progress `web_search` calls as `Searching the web`
      - Previously we only showed completed tool calls
    
    <img width="308" height="149" alt="image"
    src="https://github.com/user-attachments/assets/90a4e8ff-b06a-48ff-a282-b57b31121845"
    />
    
    ### Tests
    Added + updated tests, tested locally
    
    ### Follow ups
    Update VSCode extension to display these as well
  • Reuse ChatComposer in request_user_input overlay (#9892)
    Reuse the shared chat composer for notes and freeform answers in
    request_user_input.
    
    - Build the overlay composer with ChatComposerConfig::plain_text.
    - Wire paste-burst flushing + menu surface sizing through the bottom
    pane.
  • fix: try to fix freezes 2 (#9951)
    Fixes a TUI freeze caused by awaiting `mpsc::Sender::send()` that blocks
    the tokio thread, stopping the consumption runtime and creating a
    deadlock. This could happen if the server was producing enough chunks to
    fill the `mpsc` fast enough. To solve this we try on insert using a
    `try_send()` (not requiring an `await`) and delegate to a tokio task if
    this does not work
    
    This is a temporary solution as it can contain races for delta elements
    and a stronger design should come here
  • fix: use brew upgrade --cask codex to avoid warnings and ambiguity (#9823)
    Fixes #9822 
    
    ### Summary
    
    Make the Homebrew upgrade command explicit by using `brew upgrade --cask
    codex`.
    
    ### Motivation
    
    During the Codex self-update, Homebrew can emit an avoidable warning
    because the
    name `codex` resolves to a cask:
    
    ```
    Warning: Formula codex was renamed to homebrew/cask/codex.
    ````
    
    While the upgrade succeeds, this relies on implicit name resolution and
    produces
    unnecessary output during the update flow.
    
    ### Why `--cask`
    
    * Eliminates warning/noise for users
    * Explicitly matches how Codex is distributed via Homebrew
    * Avoids reliance on name resolution behavior
    * Makes the command more robust if a `codex` formula is ever introduced
    
    ### Context
    
    This restores the `--cask` flag that was removed in #6238 after being
    considered
    “not necessary” during review:
    [https://github.com/openai/codex/pull/6238#discussion_r2505947880](https://github.com/openai/codex/pull/6238#discussion_r2505947880).
    
    Co-authored-by: Eric Traut <etraut@openai.com>
  • fix: remove cli tooltip references to custom prompts (#9901)
    Custom prompts are now deprecated, however are still references in
    tooltips. Remove the relevant tips from the repository.
    
    Closes #9900
  • Add composer config and shared menu surface helpers (#9891)
    Centralize built-in slash-command gating and extract shared menu-surface
    helpers.
    
    - Add bottom_pane::slash_commands and reuse it from composer + command
    popup.
    - Introduce ChatComposerConfig + shared menu surface rendering without
    changing default behavior.
  • Add MCP server scopes config and use it as fallback for OAuth login (#9647)
    ### Motivation
    - Allow MCP OAuth flows to request scopes defined in `config.toml`
    instead of requiring users to always pass `--scopes` on the CLI.
    CLI/remote parameters should still override config values.
    
    ### Description
    - Add optional `scopes: Option<Vec<String>>` to `McpServerConfig` and
    `RawMcpServerConfig`, and propagate it through deserialization and the
    built config types.
    - Serialize `scopes` into the MCP server TOML via
    `serialize_mcp_server_table` in `core/src/config/edit.rs` and include
    `scopes` in the generated config schema (`core/config.schema.json`).
    - CLI: update `codex-rs/cli/src/mcp_cmd.rs` `run_login` to fall back to
    `server.scopes` when the `--scopes` flag is empty, with explicit CLI
    scopes still taking precedence.
    - App server: update
    `codex-rs/app-server/src/codex_message_processor.rs`
    `mcp_server_oauth_login` to use `params.scopes.or_else(||
    server.scopes.clone())` so the RPC path also respects configured scopes.
    - Update many test fixtures to initialize the new `scopes` field (set to
    `None`) so test code builds with the new struct field.
    
    ### Testing
    - Ran config tooling and formatters: `just write-config-schema`
    (succeeded), `just fmt` (succeeded), and `just fix -p codex-core`, `just
    fix -p codex-cli`, `just fix -p codex-app-server` (succeeded where
    applicable).
    - Ran unit tests for the CLI: `cargo test -p codex-cli` (passed).
    - Ran unit tests for core: `cargo test -p codex-core` (ran; many tests
    passed but several failed, including model refresh/403-related tests,
    shell snapshot/timeouts, and several `unified_exec` expectations).
    - Ran app-server tests: `cargo test -p codex-app-server` (ran; many
    integration-suite tests failed due to mocked/remote HTTP 401/403
    responses and wiremock expectations).
    
    If you want, I can split the tests into smaller focused runs or help
    debug the failing integration tests (they appear to be unrelated to the
    config change and stem from external HTTP/mocking behaviors encountered
    during the test runs).
    
    ------
    [Codex
    Task](https://chatgpt.com/codex/tasks/task_i_69718f505914832ea1f334b3ba064553)
  • Aligned feature stage names with public feature maturity stages (#9929)
    We've recently standardized a [feature maturity
    model](https://developers.openai.com/codex/feature-maturity) that we're
    using in our docs and support forums to communicate expectations to
    users. This PR updates the internal stage names and descriptions to
    match.
    
    This change involves a simple internal rename and updates to a few
    user-visible strings. No functional change.
  • Feat: add isOther to question returned by request user input tool (#9890)
    ### Summary
    Add `isOther` to question object from request_user_input tool input and
    remove `other` option from the tool prompt to better handle tool input.
  • Fix up config disabled err msg (#9916)
    **Before:**
    <img width="745" height="375" alt="image"
    src="https://github.com/user-attachments/assets/d6c23562-b87f-4af9-8642-329aab8e594d"
    />
    
    **After:**
    <img width="1042" height="354" alt="image"
    src="https://github.com/user-attachments/assets/c9a2413c-c945-4c34-8b7e-c6c9b8fbf762"
    />
    
    Two changes:
    1. only display if there is a `config.toml` that is skipped (i.e. if
    there is just `.codex/skills` but no `.codex/config.toml` we do not
    display the error)
    2. clarify the implications and the fix in the error message.
  • feat: dynamic tools injection (#9539)
    ## Summary
    Add dynamic tool injection to thread startup in API v2, wire dynamic
    tool calls through the app server to clients, and plumb responses back
    into the model tool pipeline.
    
    ### Flow (high level)
    - Thread start injects `dynamic_tools` into the model tool list for that
    thread (validation is done here).
    - When the model emits a tool call for one of those names, core raises a
    `DynamicToolCallRequest` event.
    - The app server forwards it to the client as `item/tool/call`, waits
    for the client’s response, then submits a `DynamicToolResponse` back to
    core.
    - Core turns that into a `function_call_output` in the next model
    request so the model can continue.
    
    ### What changed
    - Added dynamic tool specs to v2 thread start params and protocol types;
    introduced `item/tool/call` (request/response) for dynamic tool
    execution.
    - Core now registers dynamic tool specs at request time and routes those
    calls via a new dynamic tool handler.
    - App server validates tool names/schemas, forwards dynamic tool call
    requests to clients, and publishes tool outputs back into the session.
    - Integration tests
  • feat(tui) /personality (#9718)
    ## Summary
    Adds /personality selector in the TUI, which leverages the new core
    interface in #9644
    
    Notes:
    - We are doing some of our own state management for model_info loading
    here, but not sure if that's ideal. open to opinions on simpler
    approach, but would like to avoid blocking on a larger refactor
    - Right now, the `/personality` selector just hides when the model
    doesn't support it. we can update this behavior down the line
    
    ## Testing
    - [x] Tested locally
    - [x] Added snapshot tests
  • Use collaboration mode masks without mutating base settings (#9806)
    Keep an unmasked base collaboration mode and apply the active mask on
    demand. Simplify the TUI mask helpers and update tests/docs to match the
    mask contract.
  • Ask for cwd choice when resuming session from different cwd (#9731)
    # Summary
    - Fix resume/fork config rebuild so cwd changes inside the TUI produce a
    fully rebuilt Config (trust/approval/sandbox) instead of mutating only
    the cwd.
    - Preserve `--add-dir` behavior across resume/fork by normalizing
    relative roots to absolute paths once (based on the original cwd).
    - Prefer latest `TurnContext.cwd` for resume/fork prompts but fall back
    to `SessionMeta.cwd` if the latest cwd no longer exists.
    - Align resume/fork selection handling and ensure UI config matches the
    resumed thread config.
    - Fix Windows test TOML path escaping in trust-level test.
    
    # Details
    - Rebuild Config via `ConfigBuilder` when resuming into a different cwd;
    carry forward runtime approval/sandbox overrides.
    - Add `normalize_harness_overrides_for_cwd` to resolve relative
    `additional_writable_roots` against the initial cwd before reuse.
    - Guard `read_session_cwd` with filesystem existence check for the
    latest `TurnContext.cwd`.
    - Update naming/flow around cwd comparison and prompt selection.
    
    <img width="603" height="150" alt="Screenshot 2026-01-23 at 5 42 13 PM"
    src="https://github.com/user-attachments/assets/d1897386-bb28-4e8a-98cf-187fdebbecb0"
    />
    
    And proof the model understands the new cwd:
    
    <img width="828" height="353" alt="Screenshot 2026-01-22 at 5 36 45 PM"
    src="https://github.com/user-attachments/assets/12aed8ca-dec3-4b64-8dae-c6b8cff78387"
    />
  • Raise welcome animation breakpoint to 37 rows (#9778)
    ### Motivation
    - The large ASCII welcome animation can push onboarding content below
    the fold on default-height terminals, making the CLI appear
    unresponsive; raising the breakpoint prevents that.
    - The existing test measured an arbitrary row count rather than
    asserting the welcome line position relative to the animation frame,
    which made the intent unclear.
    
    ### Description
    - Increase `MIN_ANIMATION_HEIGHT` from `20` to `37` in
    `codex-rs/tui/src/onboarding/welcome.rs` so the animation is skipped
    unless there is enough vertical space.
    - Replace the brittle measurement logic in the welcome render test with
    a `row_containing` helper and assert the welcome row equals the frame
    height plus the spacer line (`frame_lines + 1`).
    - Add a regression test
    `welcome_skips_animation_below_height_breakpoint` that verifies the
    animation is not rendered when the viewport height is one row below the
    breakpoint.
    
    ### Testing
    - Ran formatting with `~/.cargo/bin/just fmt` which completed
    successfully.
    - Ran unit tests for the crate with `cargo test -p codex-tui --lib` and
    they passed (unit test suite succeeded).
    - Ran `cargo test -p codex-tui` which reported a failing integration
    test in this environment because the test cannot locate the `codex`
    binary, so full crate tests are blocked here (environment limitation).
    
    ------
    [Codex
    Task](https://chatgpt.com/codex/tasks/task_i_6973b0a710d4832c9ff36fac26eb1519)
  • Prevent backspace from removing a text element when the cursor is at the element’s left edge (#9630)
    **Summary**
    - Prevent backspace from removing a text element when the cursor is at
    the element’s left edge.
    - Instead just delete the char before the placeholder (moving it to the
    left).
  • feat: ephemeral threads (#9765)
    Add ephemeral threads capabilities. Only exposed through the
    `app-server` v2
    
    The idea is to disable the rollout recorder for those threads.
  • Another round of improvements for config error messages (#9746)
    In a [recent PR](https://github.com/openai/codex/pull/9182), I made some
    improvements to config error messages so errors didn't leave app server
    clients in a dead state. This is a follow-on PR to make these error
    messages more readable and actionable for both TUI and GUI users. For
    example, see #9668 where the user was understandably confused about the
    source of the problem and how to fix it.
    
    The improved error message:
    1. Clearly identifies the config file where the error was found (which
    is more important now that we support layered configs)
    2. Provides a line and column number of the error
    3. Displays the line where the error occurred and underlines it
    
    For example, if my `config.toml` includes the following:
    ```toml
    [features]
    collaboration_modes = "true"
    ```
    
    Here's the current CLI error message:
    ```
    Error loading config.toml: invalid type: string "true", expected a boolean in `features`
    ```
    
    And here's the improved message:
    ```
    Error loading config.toml:
    /Users/etraut/.codex/config.toml:43:23: invalid type: string "true", expected a boolean
       |
    43 | collaboration_modes = "true"
       |                       ^^^^^^
    ```
    
    The bulk of the new logic is contained within a new module
    `config_loader/diagnostics.rs` that is responsible for calculating the
    text range for a given toml path (which is more involved than I would
    have expected).
    
    In addition, this PR adds the file name and text range to the
    `ConfigWarningNotification` app server struct. This allows GUI clients
    to present the user with a better error message and an optional link to
    open the errant config file. This was a suggestion from @.bolinfest when
    he reviewed my previous PR.
  • change collaboration mode to struct (#9793)
    Shouldn't cause behavioral change
  • Print warning if we skip config loading (#9611)
    https://github.com/openai/codex/pull/9533 silently ignored config if
    untrusted. Instead, we still load it but disable it. Maybe we shouldn't
    try to parse it either...
    
    <img width="939" height="515" alt="Screenshot 2026-01-21 at 14 56 38"
    src="https://github.com/user-attachments/assets/e753cc22-dd99-4242-8ffe-7589e85bef66"
    />
  • Persist text element ranges and attached images across history/resume (#9116)
    **Summary**
    - Backtrack selection now rehydrates `text_elements` and
    `local_image_paths` from the chosen user history cell so Esc‑Esc history
    edits preserve image placeholders and attachments.
    - Composer prefill uses the preserved elements/attachments in both `tui`
    and `tui2`.
    - Extended backtrack selection tests to cover image placeholder elements
    and local image paths.
    
    **Changes**
    - `tui/src/app_backtrack.rs`: Backtrack selection now carries text
    elements + local image paths; composer prefill uses them (removes TODO).
    - `tui2/src/app_backtrack.rs`: Same as above.
    - `tui/src/app.rs`: Updated backtrack test to assert restored
    elements/paths.
    - `tui2/src/app.rs`: Same test updates.
    
    ### The original scope of this PR (threading text elements and image
    attachments through the codex harness thoroughly/persistently) was
    broken into the following PRs other than this one:
    
    The diff of this PR was reduced by changing types in a starter PR:
    https://github.com/openai/codex/pull/9235
    
    Then text element metadata was added to protocol, app server, and core
    in this PR: https://github.com/openai/codex/pull/9331
    
    Then the end-to-end flow was completed by wiring TUI/TUI2 input,
    history, and restore behavior in
    https://github.com/openai/codex/pull/9393
    
    Prompt expansion was supported in this PR:
    https://github.com/openai/codex/pull/9518
    
    TextElement optional placeholder field was protected in
    https://github.com/openai/codex/pull/9545
  • Hide mode cycle hint while a task is running (#9730)
    ## Summary
    - hide the “(shift+tab to cycle)” suffix on the collaboration mode label
    while a task is running
    - keep the cycle hint visible when idle
    - add a snapshot to cover the running-task label state
  • TUI: prompt to implement plan and switch to Execute (#9712)
    ## Summary
    - Replace the plan‑implementation prompt with a standard selection
    popup.
    - “Yes” submits a user turn in Execute via a dedicated app event to
    preserve normal transcript behavior.
    - “No” simply dismisses the popup.
    
    <img width="977" height="433" alt="Screenshot 2026-01-22 at 2 00 54 PM"
    src="https://github.com/user-attachments/assets/91fad06f-7b7a-4cd8-9051-f28a19b750b2"
    />
    
    ## Changes
    - Add a plan‑implementation popup using `SelectionViewParams`.
    - Add `SubmitUserMessageWithMode` so “Yes” routes through
    `submit_user_message` (ensures user history + separator state).
    - Track `saw_plan_update_this_turn` so the prompt appears even when only
    `update_plan` is emitted.
    - Suppress the plan popup on replayed turns, when messages are queued,
    or when a rate‑limit prompt is pending.
    - Add `execute_mode` helper for collaboration modes.
    - Add tests for replay/queued/rate‑limit guards and plan update without
    final message.
    - Add snapshots for both the default and “No”‑selected popup states.
  • feat(core) update Personality on turn (#9644)
    ## Summary
    Support updating Personality mid-Thread via UserTurn/OverwriteTurn. This
    is explicitly unused by the clients so far, to simplify PRs - app-server
    and tui implementations will be follow-ups.
    
    ## Testing
    - [x] added integration tests
  • Modes label below textarea (#9645)
    # Summary
    - Add a collaboration mode indicator rendered at the bottom-right of the
    TUI composer footer.
    - Style modes per design (Plan in #D72EE1, Execute matching dim context
    style, Pair Programming using the same cyan as text elements).
    - Add shared “(shift+tab to cycle)” hint text for all mode labels and
    align the indicator with the left footer margin.
    
    NOTE: currently this is hidden if the Collaboration Modes feature flag
    is disabled, or in Custom mode. Maybe we should show it in Custom mode
    too? I'll leave that out of this PR though
    
    # UI
    - Mode indicator appears below the textarea, bottom-right of the footer
    line.
    - Includes “(shift+tab to cycle)” and keeps right padding aligned to the
    left footer indent.
    
    <img width="983" height="200" alt="Screenshot 2026-01-21 at 7 17 54 PM"
    src="https://github.com/user-attachments/assets/d1c5e4ed-7d7b-4f6c-9e71-bc3cf6400e0e"
    />
    
    <img width="980" height="200" alt="Screenshot 2026-01-21 at 7 18 53 PM"
    src="https://github.com/user-attachments/assets/d22ff0da-a406-4930-85c5-affb2234e84b"
    />
    
    <img width="979" height="201" alt="Screenshot 2026-01-21 at 7 19 12 PM"
    src="https://github.com/user-attachments/assets/862cb17f-0495-46fa-9b01-a4a9f29b52d5"
    />
  • feat(tui) /permissions flow (#9561)
    ## Summary
    Adds the `/permissions` command, with a (usually) shorter set of
    permissions. `/approvals` still exists, for backwards compatibility.
    
    <img width="863" height="309" alt="Screenshot 2026-01-20 at 4 12 51 PM"
    src="https://github.com/user-attachments/assets/c49b5ba5-bc47-46dd-9067-e1a5670328fe"
    />
    
    
    ## Testing
    - [x] updated unit tests
    - [x] Tested locally
  • Add UI for skill enable/disable. (#9627)
    "/skill" will now allow you to enable/disable skills:
    <img width="658" height="199" alt="image"
    src="https://github.com/user-attachments/assets/bf8994c8-d6c1-462f-8bbb-f1ee9241caa4"
    />
  • feat(tui): retire the tui2 experiment (#9640)
    ## Summary
    - Retire the experimental TUI2 implementation and its feature flag.
    - Remove TUI2-only config/schema/docs so the CLI stays on the
    terminal-native path.
    - Keep docs aligned with the legacy TUI while we focus on redraw-based
    improvements.
    
    ## Customer impact
    - Retires the TUI2 experiment and keeps Codex on the proven
    terminal-native UI while we invest in redraw-based improvements to the
    existing experience.
    
    ## Migration / compatibility
    - If you previously set tui2-related options in config.toml, they are
    now ignored and Codex continues using the existing terminal-native TUI
    (no action required).
    
    ## Context
    - What worked: a transcript-owned viewport delivered excellent resize
    rewrap and high-fidelity copy (especially for code).
    - Why stop: making that experience feel fully native across the
    environment matrix (terminal emulator, OS, input modality, multiplexer,
    font/theme, alt-screen behavior) creates a combinatorial explosion of
    edge cases.
    - What next: we are focusing on redraw-based improvements to the
    existing terminal-native TUI so scrolling, selection, and copy remain
    native while resize/redraw correctness improves.
    
    ## Testing
    - just write-config-schema
    - just fmt
    - cargo clippy --fix --all-features --tests --allow-dirty --allow-no-vcs
    -p codex-core
    - cargo clippy --fix --all-features --tests --allow-dirty --allow-no-vcs
    -p codex-cli
    - cargo check
    - cargo test -p codex-core
    - cargo test -p codex-cli
  • Reduce burst testing flake (#9549)
    ## Summary
    
    - make paste-burst tests deterministic by injecting explicit timestamps
    instead of relying on wall clock timing
    - add time-aware helpers for input/submission paths so tests can drive
    the burst heuristic precisely
    - update burst-related tests to flush using computed timeouts while
    preserving behavior assertions
    - increase timeout slack in
    shell_tools_start_before_response_completed_when_stream_delayed to
    reduce flakiness
  • fix(tui) turn timing incremental (#9599)
    ## Summary
    When we send multiple assistant messages, reset the timer so "Worked for
    2m 36s" is the time since the last time we showed the message, rather
    than an ever-increasing number.
    
    We could instead change the copy so it's more clearly a running counter.
    
    ## Testing
    - [x] ran locally
    
    <img width="903" height="732" alt="Screenshot 2026-01-21 at 1 42 51 AM"
    src="https://github.com/user-attachments/assets/bb4d827b-3a0e-48ba-bd6a-d8cd65d8e892"
    />
  • feat: better sorting of shell commands (#9629)
    This PR changes the way we sort slash command by going in this order:
    1. Exact match
    2. Prefix
    3. Fuzzy
    
    As a result, we you type `/ps` the default command is not `/approvals`
  • Chore: update plan mode output in prompt (#9592)
    ### Summary
    * Update plan prompt output
    * Update requestUserInput response to be a single key value pair
    `answer: String`.