Commit Graph

1028 Commits

  • Add websocket telemetry metrics and labels (#10316)
    Summary
    - expose websocket telemetry hooks through the responses client so
    request durations and event processing can be reported
    - record websocket request/event metrics and emit runtime telemetry
    events that the history UI now surfaces
    - improve tests to cover websocket telemetry reporting and guard runtime
    summary updates
    
    
    <img width="824" height="79" alt="Screenshot 2026-01-31 at 5 28 12 PM"
    src="https://github.com/user-attachments/assets/ea9a7965-d8b4-4e3c-a984-ef4fdc44c81d"
    />
  • feat(core,tui,app-server) personality migration (#10307)
    ## Summary
    Keep existing users on Pragmatic, to preserve behavior while new users
    default to Friendly
    
    ## Testing
    - [x] Tested locally
    - [x] add integration tests
  • feat: show runtime metrics in console (#10278)
    Summary of changes:
    
    - Adds a new feature flag: runtime_metrics
      - Declared in core/src/features.rs
      - Added to core/config.schema.json
      - Wired into OTEL init in core/src/otel_init.rs
    
    - Enables on-demand runtime metric snapshots in OTEL
      - Adds runtime_metrics: bool to otel/src/config.rs
      - Enables experimental custom reader features in otel/Cargo.toml
      - Adds snapshot/reset/summary APIs in:
        - otel/src/lib.rs
        - otel/src/metrics/client.rs
        - otel/src/metrics/config.rs
        - otel/src/metrics/error.rs
    
    - Defines metric names and a runtime summary builder
      - New files:
        - otel/src/metrics/names.rs
        - otel/src/metrics/runtime_metrics.rs
      - Summarizes totals for:
        - Tool calls
        - API requests
        - SSE/streaming events
    
    - Instruments metrics collection in OTEL manager
      - otel/src/traces/otel_manager.rs now records:
        - API call counts + durations
        - SSE event counts + durations (success/failure)
        - Tool call metrics now use shared constants
    
    - Surfaces runtime metrics in the TUI
      - Resets runtime metrics at turn start in tui/src/chatwidget.rs
    - Displays metrics in the final separator line in
    tui/src/history_cell.rs
    
    - Adds tests
      - New OTEL tests:
        - otel/tests/suite/snapshot.rs
        - otel/tests/suite/runtime_summary.rs
      - New TUI test:
    - final_message_separator_includes_runtime_metrics in
    tui/src/history_cell.rs
    
    Scope:
    - 19 files changed
    - ~652 insertions, 38 deletions
    
    
    <img width="922" height="169" alt="Screenshot 2026-01-30 at 4 11 34 PM"
    src="https://github.com/user-attachments/assets/1efd754d-a16d-4564-83a5-f4442fd2f998"
    />
  • Fix minor typos in comments and documentation (#10287)
    ## Summary
    
    I have read the contribution guidelines.  
    All changes in this PR are limited to text corrections and do not modify
    any business logic, runtime behavior, or user-facing functionality.
    
    ## Details
    
    This PR fixes several minor typos, including:
    
    - `create` -> `crate`
    - `analagous` -> `analogous`
    - `apply-patch` -> `apply_patch`
    - `codecs` -> `codex`
    - ` '/" ` -> ` '/' `
    - `Respesent` -> `Represent`
  • add missing fields to WebSearchAction and update app-server types (#10276)
    - add `WebSearchAction` to app-server v2 types
    - add `queries` to `WebSearchAction::Search` type
    
    Updated tests.
  • Add enforce_residency to requirements (#10263)
    Add `enforce_residency` to requirements.toml and thread it through to a
    header on `default_client`.
  • fix: update file search directory when session CWD changes (#9279)
    ## Summary
    
    Fixes #9041
    
    - Adds update_search_dir() method to FileSearchManager to allow updating
    the search directory after initialization
    - Calls this method when the session CWD changes: new session, resume,
    or fork
    
    ## Problem
    
    The FileSearchManager was created once with the initial search_dir and
    never updated. When a user:
    
    1. Starts Codex in a non-git directory (e.g., /tmp/random)
    2. Resumes or forks a session from a different workspace
    3. The @filename lookup still searched the original directory
    
    This caused no matches to be returned even when files existed in the
    current workspace.
    
    ## Solution
    
    Update FileSearchManager.search_dir whenever the session working
    directory changes:
    - AppEvent::NewSession: Use current config CWD
    - SessionSelection::Resume: Use resumed session CWD
    - SessionSelection::Fork: Use forked session CWD
    
    ## Test plan
    
    - [ ] Start Codex in /tmp/test-dir (non-git)
    - [ ] Resume a session from a project with actual files
    - [ ] Verify @filename returns matches from the resumed session
    directory
    
    ---------
    
    Co-authored-by: Eric Traut <etraut@openai.com>
  • file-search: multi-root walk (#10240)
    Instead of a separate walker for each root in a multi-root walk, use a
    single walker.
  • Hide /approvals from the slash-command list (#10265)
    `/permissions` is the replacement. `/approvals` still available when
    typing.
  • Make plan highlight use popup grey background (#10253)
    ## Summary
    - align proposed plan background with popup surface color by reusing
    `user_message_bg`
    - remove the custom blue-tinted plan background
    
    <img width="1572" height="1568" alt="image"
    src="https://github.com/user-attachments/assets/63a5341e-4342-4c07-b6b0-c4350c3b2639"
    />
  • chore: rename ChatGpt -> Chatgpt in type names (#10244)
    When using ChatGPT in names of types, we should be consistent, so this
    renames some types with `ChatGpt` in the name to `Chatgpt`. From
    https://rust-lang.github.io/api-guidelines/naming.html:
    
    > In `UpperCamelCase`, acronyms and contractions of compound words count
    as one word: use `Uuid` rather than `UUID`, `Usize` rather than `USize`
    or `Stdin` rather than `StdIn`. In `snake_case`, acronyms and
    contractions are lower-cased: `is_xid_start`.
    
    This PR updates existing uses of `ChatGpt` and changes them to
    `Chatgpt`. Though in all cases where it could affect the wire format, I
    visually inspected that we don't change anything there. That said, this
    _will_ change the codegen because it will affect the spelling of type
    names.
    
    For example, this renames `AuthMode::ChatGPT` to `AuthMode::Chatgpt` in
    `app-server-protocol`, but the wire format is still `"chatgpt"`.
    
    This PR also updates a number of types in `codex-rs/core/src/auth.rs`.
  • Tui: hide Code mode footer label (#10063)
    Title
    Hide Code mode footer label/cycle hint; add Plan footer-collapse
    snapshots
    
    Summary
    - Keep Code mode internal naming but suppress the footer mode label +
    cycle hint when Code is active.
    - Only show the cycle hint when a non‑Code mode indicator is present.
    - Add Plan-mode footer collapse snapshot coverage (empty + queued,
    across widths) and update existing footer collapse snapshots for the new
    Code behavior.
    
    Notes
    - The test run currently fails in codex-cloud-requirements on
    origin/main due to a stale auth.mode field; no fix is included in this
    PR to keep the diff minimal.
    
    Codex author
    `codex resume 019c0296-cfd4-7193-9b0a-6949048e4546`
  • Plan mode: stream proposed plans, emit plan items, and render in TUI (#9786)
    ## Summary
    - Stream proposed plans in Plan Mode using `<proposed_plan>` tags parsed
    in core, emitting plan deltas plus a plan `ThreadItem`, while stripping
    tags from normal assistant output.
    - Persist plan items and rebuild them on resume so proposed plans show
    in thread history.
    - Wire plan items/deltas through app-server protocol v2 and render a
    dedicated proposed-plan view in the TUI, including the “Implement this
    plan?” prompt only when a plan item is present.
    
    ## Changes
    
    ### Core (`codex-rs/core`)
    - Added a generic, line-based tag parser that buffers each line until it
    can disprove a tag prefix; implements auto-close on `finish()` for
    unterminated tags. `codex-rs/core/src/tagged_block_parser.rs`
    - Refactored proposed plan parsing to wrap the generic parser.
    `codex-rs/core/src/proposed_plan_parser.rs`
    - In plan mode, stream assistant deltas as:
      - **Normal text** → `AgentMessageContentDelta`
      - **Plan text** → `PlanDelta` + `TurnItem::Plan` start/completion  
      (`codex-rs/core/src/codex.rs`)
    - Final plan item content is derived from the completed assistant
    message (authoritative), not necessarily the concatenated deltas.
    - Strips `<proposed_plan>` blocks from assistant text in plan mode so
    tags don’t appear in normal messages.
    (`codex-rs/core/src/stream_events_utils.rs`)
    - Persist `ItemCompleted` events only for plan items for rollout replay.
    (`codex-rs/core/src/rollout/policy.rs`)
    - Guard `update_plan` tool in Plan Mode with a clear error message.
    (`codex-rs/core/src/tools/handlers/plan.rs`)
    - Updated Plan Mode prompt to:  
      - keep `<proposed_plan>` out of non-final reasoning/preambles  
      - require exact tag formatting  
      - allow only one `<proposed_plan>` block per turn  
      (`codex-rs/core/templates/collaboration_mode/plan.md`)
    
    ### Protocol / App-server protocol
    - Added `TurnItem::Plan` and `PlanDeltaEvent` to core protocol items.
    (`codex-rs/protocol/src/items.rs`, `codex-rs/protocol/src/protocol.rs`)
    - Added v2 `ThreadItem::Plan` and `PlanDeltaNotification` with
    EXPERIMENTAL markers and note that deltas may not match the final plan
    item. (`codex-rs/app-server-protocol/src/protocol/v2.rs`)
    - Added plan delta route in app-server protocol common mapping.
    (`codex-rs/app-server-protocol/src/protocol/common.rs`)
    - Rebuild plan items from persisted `ItemCompleted` events on resume.
    (`codex-rs/app-server-protocol/src/protocol/thread_history.rs`)
    
    ### App-server
    - Forward plan deltas to v2 clients and map core plan items to v2 plan
    items. (`codex-rs/app-server/src/bespoke_event_handling.rs`,
    `codex-rs/app-server/src/codex_message_processor.rs`)
    - Added v2 plan item tests.
    (`codex-rs/app-server/tests/suite/v2/plan_item.rs`)
    
    ### TUI
    - Added a dedicated proposed plan history cell with special background
    and padding, and moved “• Proposed Plan” outside the highlighted block.
    (`codex-rs/tui/src/history_cell.rs`, `codex-rs/tui/src/style.rs`)
    - Only show “Implement this plan?” when a plan item exists.
    (`codex-rs/tui/src/chatwidget.rs`,
    `codex-rs/tui/src/chatwidget/tests.rs`)
    
    <img width="831" height="847" alt="Screenshot 2026-01-29 at 7 06 24 PM"
    src="https://github.com/user-attachments/assets/69794c8c-f96b-4d36-92ef-c1f5c3a8f286"
    />
    
    ### Docs / Misc
    - Updated protocol docs to mention plan deltas.
    (`codex-rs/docs/protocol_v1.md`)
    - Minor plumbing updates in exec/debug clients to tolerate plan deltas.
    (`codex-rs/debug-client/src/reader.rs`, `codex-rs/exec/...`)
    
    ## Tests
    - Added core integration tests:
      - Plan mode strips plan from agent messages.
      - Missing `</proposed_plan>` closes at end-of-message.  
      (`codex-rs/core/tests/suite/items.rs`)
    - Added unit tests for generic tag parser (prefix buffering, non-tag
    lines, auto-close). (`codex-rs/core/src/tagged_block_parser.rs`)
    - Existing app-server plan item tests in v2.
    (`codex-rs/app-server/tests/suite/v2/plan_item.rs`)
    
    ## Notes / Behavior
    - Plan output no longer appears in standard assistant text in Plan Mode;
    it streams via `PlanDelta` and completes as a `TurnItem::Plan`.
    - The final plan item content is authoritative and may diverge from
    streamed deltas (documented as experimental).
    - Reasoning summaries are not filtered; prompt instructs the model not
    to include `<proposed_plan>` outside the final plan message.
    
    ## Codex Author
    `codex fork 019bec2d-b09d-7450-b292-d7bcdddcdbfb`
  • feat: refactor CodexAuth so invalid state cannot be represented (#10208)
    Previously, `CodexAuth` was defined as follows:
    
    
    https://github.com/openai/codex/blob/d550fbf41afc09d7d7b5ac813aea38de07b2a73f/codex-rs/core/src/auth.rs#L39-L46
    
    But if you looked at its constructors, we had creation for
    `AuthMode::ApiKey` where `storage` was built using a nonsensical path
    (`PathBuf::new()`) and `auth_dot_json` was `None`:
    
    
    https://github.com/openai/codex/blob/d550fbf41afc09d7d7b5ac813aea38de07b2a73f/codex-rs/core/src/auth.rs#L212-L220
    
    By comparison, when `AuthMode::ChatGPT` was used, `api_key` was always
    `None`:
    
    
    https://github.com/openai/codex/blob/d550fbf41afc09d7d7b5ac813aea38de07b2a73f/codex-rs/core/src/auth.rs#L665-L671
    
    https://github.com/openai/codex/pull/10012 took things further because
    it introduced a new `ChatgptAuthTokens` variant to `AuthMode`, which is
    important in when invoking `account/login/start` via the app server, but
    most logic _internal_ to the app server should just reason about two
    `AuthMode` variants: `ApiKey` and `ChatGPT`.
    
    This PR tries to clean things up as follows:
    
    - `LoginAccountParams` and `AuthMode` in `codex-rs/app-server-protocol/`
    both continue to have the `ChatgptAuthTokens` variant, though it is used
    exclusively for the on-the-wire messaging.
    - `codex-rs/core/src/auth.rs` now has its own `AuthMode` enum, which
    only has two variants: `ApiKey` and `ChatGPT`.
    - `CodexAuth` has been changed from a struct to an enum. It is a
    disjoint union where each variant (`ApiKey`, `ChatGpt`, and
    `ChatGptAuthTokens`) have only the associated fields that make sense for
    that variant.
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10208).
    * #10224
    * __->__ #10208
  • Fetch Requirements from cloud (#10167)
    Load requirements from Codex Backend. It only does this for enterprise
    customers signed in with ChatGPT.
    
    Todo in follow-up PRs:
    * Add to app-server and exec too
    * Switch from fail-open to fail-closed on failure
  • Conversation naming (#8991)
    Session renaming:
    - `/rename my_session`
    - `/rename` without arg and passing an argument in `customViewPrompt`
    - AppExitInfo shows resume hint using the session name if set instead of
    uuid, defaults to uuid if not set
    - Names are stored in `CODEX_HOME/sessions.jsonl`
    
    Session resuming:
    - codex resume <name> lookup for `CODEX_HOME/sessions.jsonl` first entry
    matching the name and resumes the session
    
    ---------
    
    Co-authored-by: jif-oai <jif@openai.com>
  • Add community links to startup tooltips (#10177)
    ## Summary
    - add startup tooltip for OpenAI community Discord
    - add startup tooltip for Codex community forum
    
    ## Testing
    - not run (text-only tooltip change)
  • feat: add output to /ps (#10154)
    <img width="599" height="238" alt="Screenshot 2026-01-29 at 13 24 57"
    src="https://github.com/user-attachments/assets/1e9a5af2-f649-476c-b310-ae4938814538"
    />
  • feat(tui): route employee feedback follow-ups to internal link (#10198)
    ## Problem
    OpenAI employees were sent to the public GitHub issue flow after
    `/feedback`, which is the wrong follow-up path internally.
    
    ## Mental model
    After feedback upload completes, we render a follow-up link/message.
    That link should be audience-aware but must not change the upload
    pipeline itself.
    
    ## Non-goals
    - Changing how feedback is captured or uploaded
    - Changing external user behavior
    
    ## Tradeoffs
    We detect employees via the authenticated account email suffix
    (`@openai.com`). If the email is unavailable (e.g., API key auth), we
    default to the external behavior.
    
    ## Architecture
    - Introduce `FeedbackAudience` and thread it from `App` -> `ChatWidget`
    -> `FeedbackNoteView`
    - Gate internal messaging/links on `FeedbackAudience::OpenAiEmployee`
    - Internal follow-up link is now `http://go/codex-feedback-internal`
    - External GitHub URL remains byte-for-byte identical
    
    ## Observability
    No new telemetry; this only changes rendered follow-up instructions.
    
    ## Tests
    - `just fmt`
    - `cargo test -p codex-tui --lib`
  • tui: add feature-gated /plan slash command to switch to Plan mode (#10103)
    ## Summary
    Adds a simple `/plan` slash command in the TUI that switches the active
    collaboration mode to Plan mode. The command is only available when the
    `collaboration_modes` feature is enabled.
    
    ## Changes
    - Add `plan_mask` helper in `codex-rs/tui/src/collaboration_modes.rs`
    - Add `SlashCommand::Plan` metadata in
    `codex-rs/tui/src/slash_command.rs`
    - Implement and hard-gate `/plan` dispatch in
    `codex-rs/tui/src/chatwidget.rs`
    - Hide `/plan` when collaboration modes are disabled in
    `codex-rs/tui/src/bottom_pane/slash_commands.rs`
    - Update command popup tests in
    `codex-rs/tui/src/bottom_pane/command_popup.rs`
    - Add a focused unit test for `/plan` in
    `codex-rs/tui/src/chatwidget/tests.rs`
    
    ## Behavior notes
    - `/plan` is now a no-op if `Feature::CollaborationModes` is disabled.
    - When enabled, `/plan` switches directly to Plan mode without opening
    the picker.
    
    ## Codex author
    `codex resume 019c05da-d7c3-7322-ae2c-3ca38d0ef702`
  • feat(app-server): support external auth mode (#10012)
    This enables a new use case where `codex app-server` is embedded into a
    parent application that will directly own the user's ChatGPT auth
    lifecycle, which means it owns the user’s auth tokens and refreshes it
    when necessary. The parent application would just want a way to pass in
    the auth tokens for codex to use directly.
    
    The idea is that we are introducing a new "auth mode" currently only
    exposed via app server: **`chatgptAuthTokens`** which consist of the
    `id_token` (stores account metadata) and `access_token` (the bearer
    token used directly for backend API calls). These auth tokens are only
    stored in-memory. This new mode is in addition to the existing `apiKey`
    and `chatgpt` auth modes.
    
    This PR reuses the shape of our existing app-server account APIs as much
    as possible:
    - Update `account/login/start` with a new `chatgptAuthTokens` variant,
    which will allow the client to pass in the tokens and have codex
    app-server use them directly. Upon success, the server emits
    `account/login/completed` and `account/updated` notifications.
    - A new server->client request called
    `account/chatgptAuthTokens/refresh` which the server can use whenever
    the access token previously passed in has expired and it needs a new one
    from the parent application.
    
    I leveraged the core 401 retry loop which typically triggers auth token
    refreshes automatically, but made it pluggable:
    - **chatgpt** mode refreshes internally, as usual.
    - **chatgptAuthTokens** mode calls the client via
    `account/chatgptAuthTokens/refresh`, the client responds with updated
    tokens, codex updates its in-memory auth, then retries. This RPC has a
    10s timeout and handles JSON-RPC errors from the client.
    
    Also some additional things:
    - chatgpt logins are blocked while external auth is active (have to log
    out first. typically clients will pick one OR the other, not support
    both)
    - `account/logout` clears external auth in memory
    - Ensures that if `forced_chatgpt_workspace_id` is set via the user's
    config, we respect it in both:
    - `account/login/start` with `chatgptAuthTokens` (returns a JSON-RPC
    error back to the client)
    - `account/chatgptAuthTokens/refresh` (fails the turn, and on next
    request app-server will send another `account/chatgptAuthTokens/refresh`
    request to the client).
  • [Codex][CLI] Show model-capacity guidance on 429 (#10118)
    ###### Problem
    Users get generic 429s with no guidance when a model is at capacity.
    ###### Solution
    Detect model-cap headers, surface a clear “try a different model”
    message, and keep behavior non‑intrusive (no auto‑switch).
    ###### Scope
    CLI/TUI only; protocol + error mapping updated to carry model‑cap info.
    ###### Tests
          - just fmt
          - cargo test -p codex-tui
    - cargo test -p codex-core --lib
    shell_snapshot::tests::try_new_creates_and_deletes_snapshot_file --
    --nocapture (ran in isolated env)
          - validate local build with backend
         
    <img width="719" height="845" alt="image"
    src="https://github.com/user-attachments/assets/1470b33d-0974-4b1f-b8e6-d11f892f4b54"
    />
  • updating comment to better indicate intent of skipping quit in the main slash command menu (#10186)
    Updates comment indicating intent for skipping `quit` in the main slash
    command dropdown.
  • fix: /approvals -> /permissions (#10184)
    I believe we should be recommending `/permissions` in light of
    https://github.com/openai/codex/pull/9561.
  • Better handling skill depdenencies on ENV VAR. (#9017)
    An experimental flow for env var skill dependencies. Skills can now
    declare required env vars in SKILL.md; if missing, the CLI prompts the
    user to get the value, and Core will store it in memory (eventually to a
    local persistent store)
    <img width="790" height="169" alt="image"
    src="https://github.com/user-attachments/assets/cd928918-9403-43cb-a7e7-b8d59bcccd9a"
    />
  • feat: reduce span exposition (#10171)
    This only avoids the creation of duplicates spans
  • feat: add log db (#10086)
    Add a log DB. The goal is just to store our logs in a `.sqlite` DB to
    make it easier to crawl them and drop the oldest ones.
  • fix: ignore key release events during onboarding (#10131)
    ## Summary
    - guard onboarding key handling to ignore KeyEventKind::Release
    - handle key events at the onboarding screen boundary to avoid
    double-triggering widgets
    
    ## Related
    - https://github.com/ratatui/ratatui/issues/347
    
    ## Testing
    - cd codex-rs && just fmt
    - cd codex-rs && cargo test -p codex-tui
  • fix(tui) reorder personality command (#10134)
    ## Summary
    Reorder it down the list
    
    ## Testing 
    - [x] Tests pass
  • [connectors] Support connectors part 2 - slash command and tui (#9728)
    - [x] Support `/apps` slash command to browse the apps in tui.
    - [x] Support inserting apps to prompt using `$`.
    - [x] Lots of simplification/renaming from connectors to apps.
  • removing quit from dropdown menu, but not autocomplete [cli] (#10128)
    Currently we have both `\quit` and `\exit` which do the same thing. This
    removes `\quit` from the slash command menu but allows it to still be an
    autocomplete option & working for those used to that command.
    
    `/quit` autocomplete:
    <img width="232" height="108" alt="Screenshot 2026-01-28 at 4 32 53 PM"
    src="https://github.com/user-attachments/assets/d71e079f-77f6-4edc-9590-44a01e2a4ff5"
    />
    
    slash command menu:
    <img width="425" height="191" alt="Screenshot 2026-01-28 at 4 32 36 PM"
    src="https://github.com/user-attachments/assets/a9458cff-1784-4ce0-927d-43ad13d2a97c"
    />
  • chore(config) personality as a feature (#10116)
    ## Summary
    Sets up an explicit Feature flag for `/personality`, so users can now
    opt in to it via `/experimental`. #10114 also updates the config
    
    ## Testing
    - [x] Tested locally
  • Added tui.notifications_method config option (#10043)
    This PR adds a new `tui.notifications_method` config option that accepts
    values of "auto", "osc9" and "bel". It defaults to "auto", which
    attempts to auto-detect whether the terminal supports OSC 9 escape
    sequences and falls back to BEL if not.
    
    The PR also removes the inconsistent handling of notifications on
    Windows when WSL was used.
  • allow elevated sandbox to be enabled without base experimental flag (#10028)
    elevated flag = elevated sandbox
    experimental flag = non-elevated sandbox
    both = elevated
  • file-search: improve file query perf (#9939)
    switch nucleo-matcher for nucleo and use a "file search session" w/ live
    updating query instead of a single hermetic run per query.
  • Refine request_user_input TUI interactions and option UX (#10025)
    ## Summary
    Overhaul the ask‑user‑questions TUI to support “Other/None” answers,
    better notes handling, improved option selection
    UX, and a safer submission flow with confirmation for unanswered
    questions.
    
    Multiple choice (number keys for quick selection, up/down or jk for
    cycling through options):
    <img width="856" height="169" alt="Screenshot 2026-01-27 at 7 22 29 PM"
    src="https://github.com/user-attachments/assets/cabd1b0e-25e0-4859-bd8f-9941192ca274"
    />
    
    Tab to add notes:
    <img width="856" height="197" alt="Screenshot 2026-01-27 at 7 22 45 PM"
    src="https://github.com/user-attachments/assets/a807db5e-e966-412c-af91-6edc60062f35"
    />
    
    Freeform (also note enter tooltip is highlighted on last question to
    indicate questions UI will be exited upon submission):
    <img width="854" height="112" alt="Screenshot 2026-01-27 at 7 23 13 PM"
    src="https://github.com/user-attachments/assets/2e7b88bf-062b-4b9f-a9da-c9d8c8a59643"
    />
    
    Confirmation dialogue (submitting with unanswered questions):
    <img width="854" height="126" alt="Screenshot 2026-01-27 at 7 23 29 PM"
    src="https://github.com/user-attachments/assets/93965c8f-54ac-45bc-a660-9625bcd101f8"
    />
    
    ## Key Changes
    - **Options UI refresh**
    - Render options as numbered entries; allow number keys to select &
    submit.
    - Remove “Option X/Y” header and allow the question UI height to expand
    naturally.
    - Keep spacing between question, options, and notes even when notes are
    visible.
    - Hide the title line and render the question prompt in cyan **only when
    uncommitted**.
    
    - **“Other / None of the above” support**
      - Wire `isOther` to add “None of the above”.
      - Add guidance text: “Optionally, add details in notes (tab).”
    
    - **Notes composer UX**
    - Remove “Notes” heading; place composer directly under the selected
    option.
    - Preserve pending paste placeholders across question navigation and
    after submission.
      - Ctrl+C clears notes **only when the notes composer has focus**.
      - Ctrl+C now triggers an immediate redraw so the clear is visible.
    
    - **Committed vs uncommitted state**
      - Introduce a unified `answer_committed` flag per question.
    - Editing notes (including adding text or pastes) marks the answer
    uncommitted.
    - Changing the option highlight (j/k, up/down) marks the answer
    uncommitted.
      - Clearing options (Backspace/Delete) also clears pending notes.
      - Question prompt turns cyan only when the answer is uncommitted.
    
    - **Submission safety & confirmation**
      - Only submit notes/freeform text once explicitly committed.
    - Last-question submit with unanswered questions shows a confirmation
    dialog.
      - Confirmation options:
        1. **Proceed** (default)
        2. **Go back**
      - Description reflects count: “Submit with N unanswered question(s).”
      - Esc/Backspace in confirmation returns to first unanswered question.
      - Ctrl+C in confirmation interrupts and exits the overlay.
    
    - **Footer hints**
    - Cyan highlight restored for “enter to submit answer” / “enter to
    submit all”.
    
    ## Codex author
    `codex fork 019c00ed-323a-7000-bdb5-9f9c5a635bd9`
  • [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.