Commit Graph

1221 Commits

  • Add realtime audio device config (#12849)
    ## Summary
    - add top-level realtime audio config for microphone and speaker
    selection
    - apply configured devices when starting realtime capture and playback
    - keep missing-device behavior on the system default fallback path
    
    ## Validation
    - just write-config-schema
    - cargo test -p codex-core realtime_audio
    - cargo test -p codex-tui
    - just fix -p codex-core
    - just fix -p codex-tui
    - just fmt
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Allow clients not to send summary as an option (#12950)
    Summary is a required parameter on UserTurn. Ideally we'd like the core
    to decide the appropriate summary level.
    
    Make the summary optional and don't send it when not needed.
  • tui: use thread_id for resume/fork cwd resolution (#12727)
    ## Summary
    - make resume/fork targets explicit and typed as `SessionTarget { path,
    thread_id }` (non-optional `thread_id`)
    - resolve `thread_id` centrally via `resolve_session_thread_id(...)`:
    - use CLI input directly when it is a UUID (`--resume <uuid>` / `--fork
    <uuid>`)
    - otherwise read `thread_id` from rollout `SessionMeta` for path-based
    selections (picker, `--resume-last`, name-based resume/fork)
    - use `thread_id` to read cwd from SQLite first during resume/fork cwd
    resolution
    - keep rollout fallback for cwd resolution when SQLite is unavailable or
    does not return thread metadata (`TurnContext` tail, then `SessionMeta`)
    - keep the resume picker open when a selected row has unreadable session
    metadata, and show an inline recoverable error instead of aborting the
    TUI
    
    ## Why
    This removes ad-hoc rollout filename parsing and makes resume/fork
    target identity explicit. The resume/fork cwd check can use indexed
    SQLite lookup by `thread_id` in the common path, while preserving
    rollout-based fallback behavior. It also keeps malformed legacy rows
    recoverable in the picker instead of letting a selection failure unwind
    the app.
    
    ## Notes
    - minimal TUI-only change; no schema/protocol changes
    - includes TUI test coverage for SQLite cwd precedence when `thread_id`
    is available
    - includes TUI regression coverage for picker inline error rendering /
    non-fatal unreadable session rows
    
    ## Codex author
    `codex resume 019c9205-7f8b-7173-a2a2-f082d4df3de3`
  • Remove noisy log (#12929)
    This log message floods logs on windows
  • Use model catalog default for reasoning summary fallback (#12873)
    ## Summary
    - make `Config.model_reasoning_summary` optional so unset means use
    model default
    - resolve the optional config value to a concrete summary when building
    `TurnContext`
    - add protocol support for `default_reasoning_summary` in model metadata
    
    ## Validation
    - `cargo test -p codex-core --lib client::tests -- --nocapture`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • tui: restore visible line numbers for hidden file links (#12870)
    we recently changed file linking so the model uses markdown links when
    it wants something to be clickable.
    
    This works well across the GUI surfaces because they can render markdown
    cleanly and use the full absolute path in the anchor target.
    
    A previous pass hid the absolute path in the TUI (and only showed the
    label), but that also meant we could lose useful location info when the
    model put the line number or range in the anchor target instead of the
    label.
    
    This follow-up keeps the TUI behavior simple while making local file
    links feel closer to the old TUI file reference style.
    
    key changes:
    - Local markdown file links in the TUI keep the old file-ref feel: code
    styling, no underline, no visible absolute path.
    - If the hidden local anchor target includes a location suffix and the
    label does not already include one, we append that suffix to the visible
    label.
    - This works for single lines, line/column references, and ranges.
    - If the label already includes the location, we leave it alone.
    - normal web links keep the old TUI markdown-link behavior
    
    some examples:
    - `[foo.rs](/abs/path/foo.rs)` renders as `foo.rs`
    - `[foo.rs](/abs/path/foo.rs:45)` renders as `foo.rs:45`
    - `[foo.rs](/abs/path/foo.rs:45:3-48:9)` renders as `foo.rs:45:3-48:9`
    - `[foo.rs:45](/abs/path/foo.rs:45)` stays `foo.rs:45`
    - `[docs](https://example.com/docs)` still renders like a normal web
    link
    
    how it looks:
    <img width="732" height="813" alt="Screenshot 2026-02-26 at 9 27 55 AM"
    src="https://github.com/user-attachments/assets/d51bf236-653a-4e83-96e4-9427f0804471"
    />
  • Enforce user input length cap (#12823)
    Currently there is no bound on the length of a user message submitted in
    the TUI or through the app server interface. That means users can paste
    many megabytes of text, which can lead to bad performance, hangs, and
    crashes. In extreme cases, it can lead to a [kernel
    panic](https://github.com/openai/codex/issues/12323).
    
    This PR limits the length of a user input to 2**20 (about 1M)
    characters. This value was chosen because it fills the entire context
    window on the latest models, so accepting longer inputs wouldn't make
    sense anyway.
    
    Summary
    - add a shared `MAX_USER_INPUT_TEXT_CHARS` constant in codex-protocol
    and surface it in TUI and app server code
    - block oversized submissions in the TUI submit flow and emit error
    history cells when validation fails
    - reject heavy app-server requests with JSON-RPC `-32602` and structured
    `input_too_large` data, plus document the behavior
    
    Testing
    - ran the IDE extension with this change and verified that when I
    attempt to paste a user message that's several MB long, it correctly
    reports an error instead of crashing or making my computer hot.
  • Hide local file link destinations in TUI markdown (#12705)
    ## Summary
    - hide appended destinations for local path-style markdown links in the
    TUI renderer
    - keep web links rendering with their visible destination and style link
    labels consistently
    - add markdown renderer tests and a snapshot for the new file-link
    output
    
    ## Testing
    - just fmt
    - cargo test -p codex-tui
    <img width="1120" height="968" alt="image"
    src="https://github.com/user-attachments/assets/490e8eda-ae47-4231-89fa-b254a1f83eed"
    />
  • feat: include available decisions in command approval requests (#12758)
    Command-approval clients currently infer which choices to show from
    side-channel fields like `networkApprovalContext`,
    `proposedExecpolicyAmendment`, and `additionalPermissions`. That makes
    the request shape harder to evolve, and it forces each client to
    replicate the server's heuristics instead of receiving the exact
    decision list for the prompt.
    
    This PR introduces a mapping between `CommandExecutionApprovalDecision`
    and `codex_protocol::protocol::ReviewDecision`:
    
    ```rust
    impl From<CoreReviewDecision> for CommandExecutionApprovalDecision {
        fn from(value: CoreReviewDecision) -> Self {
            match value {
                CoreReviewDecision::Approved => Self::Accept,
                CoreReviewDecision::ApprovedExecpolicyAmendment {
                    proposed_execpolicy_amendment,
                } => Self::AcceptWithExecpolicyAmendment {
                    execpolicy_amendment: proposed_execpolicy_amendment.into(),
                },
                CoreReviewDecision::ApprovedForSession => Self::AcceptForSession,
                CoreReviewDecision::NetworkPolicyAmendment {
                    network_policy_amendment,
                } => Self::ApplyNetworkPolicyAmendment {
                    network_policy_amendment: network_policy_amendment.into(),
                },
                CoreReviewDecision::Abort => Self::Cancel,
                CoreReviewDecision::Denied => Self::Decline,
            }
        }
    }
    ```
    
    And updates `CommandExecutionRequestApprovalParams` to have a new field:
    
    ```rust
    available_decisions: Option<Vec<CommandExecutionApprovalDecision>>
    ```
    
    when, if specified, should make it easier for clients to display an
    appropriate list of options in the UI.
    
    This makes it possible for `CoreShellActionProvider::prompt()` in
    `unix_escalation.rs` to specify the `Vec<ReviewDecision>` directly,
    adding support for `ApprovedForSession` when approving a skill script,
    which was previously missing in the TUI.
    
    Note this results in a significant change to `exec_options()` in
    `approval_overlay.rs`, as the displayed options are now derived from
    `available_decisions: &[ReviewDecision]`.
    
    ## What Changed
    
    - Add `available_decisions` to
    [`ExecApprovalRequestEvent`](https://github.com/openai/codex/blob/de00e932dd9801de0a4faac0519162099753f331/codex-rs/protocol/src/approvals.rs#L111-L175),
    including helpers to derive the legacy default choices when older
    senders omit the field.
    - Map `codex_protocol::protocol::ReviewDecision` to app-server
    `CommandExecutionApprovalDecision` and expose the ordered list as
    experimental `availableDecisions` in
    [`CommandExecutionRequestApprovalParams`](https://github.com/openai/codex/blob/de00e932dd9801de0a4faac0519162099753f331/codex-rs/app-server-protocol/src/protocol/v2.rs#L3798-L3807).
    - Thread optional `available_decisions` through the core approval path
    so Unix shell escalation can explicitly request `ApprovedForSession` for
    session-scoped approvals instead of relying on client heuristics.
    [`unix_escalation.rs`](https://github.com/openai/codex/blob/de00e932dd9801de0a4faac0519162099753f331/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs#L194-L214)
    - Update the TUI approval overlay to build its buttons from the ordered
    decision list, while preserving the legacy fallback when
    `available_decisions` is missing.
    - Update the app-server README, test client output, and generated schema
    artifacts to document and surface the new field.
    
    ## Testing
    
    - Add `approval_overlay.rs` coverage for explicit decision lists,
    including the generic `ApprovedForSession` path and network approval
    options.
    - Update `chatwidget/tests.rs` and app-server protocol tests to populate
    the new optional field and keep older event shapes working.
    
    ## Developers Docs
    
    - If we document `item/commandExecution/requestApproval` on
    [developers.openai.com/codex](https://developers.openai.com/codex), add
    experimental `availableDecisions` as the preferred source of approval
    choices and note that older servers may omit it.
  • Revert "Add skill approval event/response (#12633)" (#12811)
    This reverts commit https://github.com/openai/codex/pull/12633. We no
    longer need this PR, because we favor sending normal exec command
    approval server request with `additional_permissions` of skill
    permissions instead
  • Remove steer feature flag (#12026)
    All code should go in the direction that steer is enabled
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Enable request_user_input in Default mode (#12735)
    ## Summary
    - allow `request_user_input` in Default collaboration mode as well as
    Plan
    - update the Default-mode instructions to prefer assumptions first and
    use `request_user_input` only when a question is unavoidable
    - update request_user_input and app-server tests to match the new
    Default-mode behavior
    - refactor collaboration-mode availability plumbing into
    `CollaborationModesConfig` for future mode-related flags
    
    ## Codex author
    `codex resume 019c9124-ed28-7c13-96c6-b916b1c97d49`
  • make 5.3-codex visible in cli for api users (#12808)
    5.3-codex released in api, mark it visible for API users via bundled
    `models.json`.
  • Update Codex docs success link (#12805)
    Fix a stale documentation link in the sign-in flow
  • feat(app-server): add ThreadItem::DynamicToolCall (#12732)
    Previously, clients would call `thread/start` with dynamic_tools set,
    and when a model invokes a dynamic tool, it would just make the
    server->client `item/tool/call` request and wait for the client's
    response to complete the tool call. This works, but it doesn't have an
    `item/started` or `item/completed` event.
    
    Now we are doing this:
    - [new] emit `item/started` with `DynamicToolCall` populated with the
    call arguments
    - send an `item/tool/call` server request
    - [new] once the client responds, emit `item/completed` with
    `DynamicToolCall` populated with the response.
    
    Also, with `persistExtendedHistory: true`, dynamic tool calls are now
    reconstructable in `thread/read` and `thread/resume` as
    `ThreadItem::DynamicToolCall`.
  • Promote js_repl to experimental with Node requirement (#12712)
    ## Summary
    
    - Promote `js_repl` to an experimental feature that users can enable
    from `/experimental`.
    - Add `js_repl` experimental metadata, including the Node prerequisite
    and activation guidance.
    - Add regression coverage for the feature metadata and the
    `/experimental` popup.
    
    ## What Changed
    
    - Changed `Feature::JsRepl` from `Stage::UnderDevelopment` to
    `Stage::Experimental`.
    - Added experimental metadata for `js_repl` in `core/src/features.rs`:
      - name: `JavaScript REPL`
    - description: calls out interactive website debugging, inline
    JavaScript execution, and the required Node version (`>= v24.13.1`)
    - announcement: tells users to enable it, then start a new chat or
    restart Codex
    - Added a core unit test that verifies:
      - `js_repl` is experimental
      - `js_repl` is disabled by default
    - the hardcoded Node version in the description matches
    `node-version.txt`
    - Added a TUI test that opens the `/experimental` popup and verifies the
    rendered `js_repl` entry includes the Node requirement text.
    
    ## Testing
    
    - `just fmt`
    - `cargo test -p codex-tui`
    - `cargo test -p codex-core` (unit-test phase passed; stopped during the
    long `tests/all.rs` integration suite)
  • Display pending child-thread approvals in TUI (#12767)
    Summary
    - propagate approval policy from parent to spawned agents and drop the
    Never override so sub-agents respect the caller’s request
    - refresh the pending-approval list whenever events arrive or the active
    thread changes and surface the list above the composer for inactive
    threads
    - add widgets, helpers, and tests covering the new pending-thread
    approval UI state
    
    ![Uploading Screenshot 2026-02-25 at 11.02.18.png…]()
  • feat: add search term to thread list (#12578)
    Add `searchTerm` to `thread/list` that will search for a match in the
    titles (the condition being `searchTerm` $$\in$$ `title`)
  • Surface skill permission profiles in zsh-fork exec approvals (#12753)
    ## Summary
    
    - Preserve each skill’s raw permissions block as a permission_profile on
    SkillMetadata during skill loading.
    - Keep compiling that same metadata into the existing runtime
    Permissions object, so current enforcement
        behavior stays intact.
    - When zsh-fork intercepts execution of a script that belongs to a
    skill, include the skill’s
        permission_profile in the exec approval request.
    - This lets approval UIs show the extra filesystem access the skill
    declared when prompting for approval.
  • feat(ui): add network approval persistence plumbing (#12358)
    ## Summary
    - add TUI approval options for persistent network host rules
    - add app-server v2 approval payload plumbing for network approval
    context + proposed network policy amendments
    - add app-server handling to translate `applyNetworkPolicyAmendment`
    decisions back into core review decisions
    - update docs/test client output and generated app-server schemas/types
  • fix: chatwidget was not honoring approval_id for an ExecApprovalRequestEvent (#12746)
    ## Why
    
    `ExecApprovalRequestEvent` can carry a distinct `approval_id` for
    subcommand approvals, including the `execve`-intercepted zsh-fork path.
    
    The session registers the pending approval callback under `approval_id`
    when one is present, but `ChatWidget` was stashing `call_id` in the
    approval modal state. When the user approved the command in the TUI, the
    response was sent back with the wrong identifier, so the pending
    approval could not be matched and the approval callback would not
    resolve.
    
    Note `approval_id` was introduced in
    https://github.com/openai/codex/pull/12051.
    
    ## What changed
    
    - In `tui/src/chatwidget.rs`, `ChatWidget` now uses
    `ExecApprovalRequestEvent::effective_approval_id()` when constructing
    `ApprovalRequest::Exec`.
    - That preserves the existing behavior for normal shell and
    `unified_exec` approvals, where `approval_id` is absent and the
    effective id still falls back to `call_id`.
    - For subcommand approvals that provide a distinct `approval_id`, the
    TUI now sends back the same key that
    `Session::request_command_approval()` registered.
    
    ## Verification
    
    - Traced the approval flow end to end to confirm the same effective
    approval id is now used on both sides of the round trip:
    - `Session::request_command_approval()` registers the pending callback
    under `approval_id.unwrap_or(call_id)`.
    - `ChatWidget` now emits `Op::ExecApproval` with that same effective id.
  • chore: migrate additional permissions to PermissionProfile (#12731)
    This PR replaces the old `additional_permissions.fs_read/fs_write` shape
    with a shared `PermissionProfile`
    model and wires it through the command approval, sandboxing, protocol,
    and TUI layers. The schema is adopted from the
    `SkillManifestPermissions`, which is also refactored to use this unified
    struct. This helps us easily expose permission profiles in app
    server/core as a follow-up.
  • feat: pass helper executable paths via Arg0DispatchPaths (#12719)
    ## Why
    
    `codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs` previously
    located `codex-execve-wrapper` by scanning `PATH` and sibling
    directories. That lookup is brittle and can select the wrong binary when
    the runtime environment differs from startup assumptions.
    
    We already pass `codex-linux-sandbox` from `codex-arg0`;
    `codex-execve-wrapper` should use the same startup-driven path plumbing.
    
    ## What changed
    
    - Introduced `Arg0DispatchPaths` in `codex-arg0` to carry both helper
    executable paths:
      - `codex_linux_sandbox_exe`
      - `main_execve_wrapper_exe`
    - Updated `arg0_dispatch_or_else()` to pass `Arg0DispatchPaths` to
    top-level binaries and preserve helper paths created in
    `prepend_path_entry_for_codex_aliases()`.
    - Threaded `Arg0DispatchPaths` through entrypoints in `cli`, `exec`,
    `tui`, `app-server`, and `mcp-server`.
    - Added `main_execve_wrapper_exe` to core configuration plumbing
    (`Config`, `ConfigOverrides`, and `SessionServices`).
    - Updated zsh-fork shell escalation to consume the configured
    `main_execve_wrapper_exe` and removed path-sniffing fallback logic.
    - Updated app-server config reload paths so reloaded configs keep the
    same startup-provided helper executable paths.
    
    ## References
    
    - [`Arg0DispatchPaths`
    definition](https://github.com/openai/codex/blob/e355b43d5c2a771f045296a6deae10d7c9c36ec6/codex-rs/arg0/src/lib.rs#L20-L24)
    - [`arg0_dispatch_or_else()` forwarding both
    paths](https://github.com/openai/codex/blob/e355b43d5c2a771f045296a6deae10d7c9c36ec6/codex-rs/arg0/src/lib.rs#L145-L176)
    - [zsh-fork escalation using configured wrapper
    path](https://github.com/openai/codex/blob/e355b43d5c2a771f045296a6deae10d7c9c36ec6/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs#L109-L150)
    
    ## Testing
    
    - `cargo check -p codex-arg0 -p codex-core -p codex-exec -p codex-tui -p
    codex-mcp-server -p codex-app-server`
    - `cargo test -p codex-arg0`
    - `cargo test -p codex-core tools::runtimes::shell::unix_escalation:: --
    --nocapture`
  • fix: clarify the value of SkillMetadata.path (#12729)
    Rename `SkillMetadata.path` to `SkillMetadata.path_to_skills_md` for
    clarity.
    
    Would ideally change the type to `AbsolutePathBuf`, but that can be done
    later.
  • feat(tui) - /copy (#12613)
    # /copy!
    
    /copy allows you to copy the latest **complete** message from Codex on
    the TUI.
  • Add TUI realtime conversation mode (#12687)
    - Add a hidden `realtime_conversation` feature flag and `/realtime`
    slash command for start/stop live voice sessions.
    - Reuse transcription composer/footer UI for live metering, stream mic
    audio, play assistant audio, render realtime user text events, and
    force-close on feature disable.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat(tui): add theme-aware diff backgrounds with capability-graded palettes (#12581)
    ## Problem
    
    Diff lines used only foreground colors (green/red) with no background
    tinting, making them hard to scan. The gutter (line numbers) also had no
    theme awareness — dimmed text was fine on dark terminals but unreadable
    on light ones.
    
    ## Mental model
    
    Each diff line now has four styled layers: **gutter** (line number),
    **sign** (`+`/`-`), **content** (text), and **line background** (full
    terminal width). A `DiffTheme` enum (`Dark` / `Light`) is selected once
    per render by probing the terminal's queried background via
    `default_bg()`. A companion `DiffColorLevel` enum (`TrueColor` /
    `Ansi256` / `Ansi16`) is derived from `stdout_color_level()` and gates
    which palette is used. All style helpers dispatch on `(theme,
    DiffLineType, color_level)` to pick the right colors.
    
    | Theme Picker Wide | Theme Picker Narrow |
    |---|---|
    | <img width="1552" height="1012" alt="image"
    src="https://github.com/user-attachments/assets/231b21b7-32d4-4727-80ed-7d01924954be"
    /> | <img width="795" height="1012" alt="image"
    src="https://github.com/user-attachments/assets/549cacdf-daec-43c9-ad64-2a28d16d140e"
    /> |
    
    | Dark BG - 16 colors | Dark BG - 256 colors | Dark BG - True Colors |
    |---|---|---|
    | <img width="1552" height="1012" alt="dark-16colors"
    src="https://github.com/user-attachments/assets/fba36de3-c101-47d4-9e63-88cdd00410d0"
    /> | <img width="1552" height="1012" alt="dark-256colors"
    src="https://github.com/user-attachments/assets/f39e4307-c6b0-49c4-b4fe-bd26d3d8e41c"
    /> | <img width="1552" height="1012" alt="dark-truecolor"
    src="https://github.com/user-attachments/assets/1af4ec57-04bf-4dfb-8a44-0ab5e5aaaf18"
    /> |
    
    | Light BG - 16 colors | Light BG - 256 colors | Light BG - True Colors
    |
    |---|---|---|
    | <img width="1552" height="1012" alt="light-16colors"
    src="https://github.com/user-attachments/assets/2b5423d1-74b4-4b1e-8123-7c2488ff436b"
    /> | <img width="1552" height="1012" alt="light-256colors"
    src="https://github.com/user-attachments/assets/c94cff9a-8d3e-42c9-bbe7-079da39953a8"
    /> | <img width="1552" height="1012" alt="light-truecolor"
    src="https://github.com/user-attachments/assets/f73da626-725f-4452-99ee-69ef706df2c6"
    /> |
    
    ## Non-goals
    
    - No runtime theme switching beyond what `default_bg()` already
    provides.
    - No change to syntax highlighting theme selection or the highlight
    module.
    
    ## Tradeoffs
    
    - Three fixed palettes (truecolor RGB, 256-color indexed, 16-color
    named) are maintained rather than using `best_color` nearest-match. This
    is deliberate: `supports_color::on_cached(Stream::Stdout)` can misreport
    capabilities once crossterm enters the alternate screen, so hand-picked
    palette entries give better visual results than automatic quantization.
    - Delete lines in the syntax-highlighted path get `Modifier::DIM` to
    visually recede compared to insert lines. This trades some readability
    of deleted code for scan-ability of additions.
    - The theme picker's diff preview sets `preserve_side_content_bg: true`
    on `ListSelectionView` so diff background tints survive into the side
    panel. Other popups keep the default (`false`) to preserve their
    reset-background look.
    
    ## Architecture
    
    - **Color constants** are module-level `const` items grouped by palette
    tier: `DARK_TC_*` / `LIGHT_TC_*` (truecolor RGB tuples), `DARK_256_*` /
    `LIGHT_256_*` (xterm indexed), with named `Color` variants used for the
    16-color tier.
    - **`DiffTheme`** is a private enum; `diff_theme()` probes the terminal
    and `diff_theme_for_bg()` is the testable pure-function version.
    - **`DiffColorLevel`** is a private enum derived from `StdoutColorLevel`
    via `diff_color_level()`.
    - **Palette helpers** (`add_line_bg`, `del_line_bg`, `light_gutter_fg`,
    `light_add_num_bg`, `light_del_num_bg`) each take `(DiffTheme,
    DiffColorLevel)` or just `DiffColorLevel` and return a `Color`.
    - **Style helpers** (`style_line_bg_for`, `style_gutter_for`,
    `style_sign_add`, `style_sign_del`, `style_add`, `style_del`) each take
    `(DiffLineType, DiffTheme, DiffColorLevel)` or `(DiffTheme,
    DiffColorLevel)` and return a `Style`.
    - **`push_wrapped_diff_line_inner_with_theme_and_color_level`** is the
    innermost renderer, accepting both theme and color level so tests can
    exercise any combination without depending on the terminal.
    - **Line-level background** is applied via
    `RtLine::from(...).style(line_bg)` so the tint extends across the full
    terminal width, not just the text content.
    - **Theme picker integration**: `ListSelectionView` gained a
    `preserve_side_content_bg` flag. When `true`, the side panel skips
    `force_bg_to_terminal_bg`, letting diff preview backgrounds render
    faithfully.
    
    ## Observability
    
    No new logging. Theme selection is deterministic from `default_bg()`,
    which is already queried and cached at TUI startup.
    
    ## Tests
    
    1. **`DiffTheme` is determined per `render_change` call** — if
    `default_bg()` changes mid-render (e.g. `requery_default_colors()`
    fires), different file chunks could render with different themes. Low
    risk in practice since re-query only happens on explicit user action.
    2. **16-color tier uses named `Color` variants** (`Color::Green`,
    `Color::Red`, etc.) which the terminal maps to its own palette. On
    unusual terminal themes these could clash with the background.
    Acceptable since 16-color terminals already have unpredictable color
    rendering.
    3. **Light-theme `style_add` / `style_del` set bg but no fg** — on light
    terminals, non-syntax-highlighted content uses the terminal's default
    foreground against a pastel background. If the terminal's default fg
    happens to be very light, contrast could suffer. This is an edge case
    since light-terminal users typically have dark default fg.
    4. **`preserve_side_content_bg` is a general-purpose flag but only used
    by the theme picker** — if other popups start using side content with
    intentional backgrounds they'll need to opt in explicitly. Not a real
    risk today, just a note for future callers.
  • Fix @mention token parsing in chat composer (#12643)
    Fixes #12175
    
    If a user types an npm package name with multiple `@` symbols like `npx
    -y @foo/bar@latest`, the TUI currently treats this as though it's
    attempting to invoke the file picker.
    
    ### What changed
    
    - **Generalized `@` token parsing**
    - `current_prefixed_token(...)` now treats `@` as a token start **only
    at a whitespace boundary** (or start-of-line).
    - If the cursor is on a nested `@` inside an existing
    whitespace-delimited token (for example `@scope/pkg@latest`), it keeps
    the surrounding token active instead of starting a new token at the
    second `@`.
    - It also avoids misclassifying mid-word usages like `foo@bar` as an `@`
    file token.
    
    - **Enter behavior with file popup**
    - If the file-search popup is open but has **no selected match**,
    pressing `Enter` now closes the popup and falls through to normal submit
    behavior.
    - This prevents pasted strings containing `@...` from blocking
    submission just because file-search was active with no actionable
    selection.
    
    ### Testing
    I manually built and tested the scenarios involved with the bug report
    and related use of `@` mentions to verify no regressions
  • feat: run zsh fork shell tool via shell-escalation (#12649)
    ## Why
    
    This PR switches the `shell_command` zsh-fork path over to
    `codex-shell-escalation` so the new shell tool can use the shared
    exec-wrapper/escalation protocol instead of the `zsh_exec_bridge`
    implementation that was introduced in
    https://github.com/openai/codex/pull/12052. `zsh_exec_bridge` relied on
    UNIX domain sockets, which is not as tamper-proof as the FD-based
    approach in `codex-shell-escalation`.
    
    ## What Changed
    
    - Added a Unix zsh-fork runtime adapter in `core`
    (`core/src/tools/runtimes/shell/unix_escalation.rs`) that:
    - runs zsh-fork commands through
    `codex_shell_escalation::run_escalate_server`
      - bridges exec-policy / approval decisions into `ShellActionProvider`
    - executes escalated commands via a `ShellCommandExecutor` that calls
    `process_exec_tool_call`
    - Updated `ShellRuntime` / `ShellCommandHandler` / tool spec wiring to
    select a `shell_command` backend (`classic` vs `zsh-fork`) while leaving
    the generic `shell` tool path unchanged.
    - Removed the `zsh_exec_bridge`-based session service and deleted
    `core/src/zsh_exec_bridge/mod.rs`.
    - Moved exec-wrapper entrypoint dispatch to `arg0` by handling the
    `codex-execve-wrapper` arg0 alias there, and removed the old
    `codex_core::maybe_run_zsh_exec_wrapper_mode()` hooks from `cli` and
    `app-server` mains.
    - Added the needed `codex-shell-escalation` dependencies for `core` and
    `arg0`.
    
    ## Tests
    
    - `cargo test -p codex-core
    shell_zsh_fork_prefers_shell_command_over_unified_exec`
    - `cargo test -p codex-app-server turn_start_shell_zsh_fork --
    --nocapture`
    - verifies zsh-fork command execution and approval flows through the new
    backend
    - includes subcommand approve/decline coverage using the shared zsh
    DotSlash fixture in `app-server/tests/suite/zsh`
    - To test manually, I added the following to `~/.codex/config.toml`:
    
    ```toml
    zsh_path = "/Users/mbolin/code/codex3/codex-rs/app-server/tests/suite/zsh"
    
    [features]
    shell_zsh_fork = true
    ```
    
    Then I ran `just c` to run the dev build of Codex with these changes and
    sent it the message:
    
    ```
    run `echo $0`
    ```
    
    And it replied with:
    
    ```
      echo $0 printed:
    
      /Users/mbolin/code/codex3/codex-rs/app-server/tests/suite/zsh
    
      In this tool context, $0 reflects the script path used to invoke the shell, not just zsh.
    ```
    
    so the tool appears to be wired up correctly.
    
    ## Notes
    
    - The zsh subcommand-decline integration test now uses `rm` under a
    `WorkspaceWrite` sandbox. The previous `/usr/bin/true` scenario is
    auto-allowed by the new `shell-escalation` policy path, which no longer
    produces subcommand approval prompts.
  • ctrl-L (clears terminal but does not start a new chat) (#12628)
    # ctrl-L
    
    - Clears your terminal window
    - Does not start a new chat
  • feat(core) Introduce Feature::RequestPermissions (#11871)
    ## Summary
    Introduces the initial implementation of Feature::RequestPermissions.
    RequestPermissions allows the model to request that a command be run
    inside the sandbox, with additional permissions, like writing to a
    specific folder. Eventually this will include other rules as well, and
    the ability to persist these permissions, but this PR is already quite
    large - let's get the core flow working and go from there!
    
    <img width="1279" height="541" alt="Screenshot 2026-02-15 at 2 26 22 PM"
    src="https://github.com/user-attachments/assets/0ee3ec0f-02ec-4509-91a2-809ac80be368"
    />
    
    ## Testing
    - [x] Added tests
    - [x] Tested locally
    - [x] Feature
  • fix: replay after /agent (#12663)
    Filter the events after a`/agent` replay to prevent replaying decision
    events
  • chore: rm hardcoded PRESETS list (#12650)
    rm `PRESETS` list harcoded in `model_presets` as we now have bundled
    `models.json` with equivalent info.
    
    update logic to rely on bundled models instead, update tests.
  • Add skill approval event/response (#12633)
    Set the stage for skill-level permission approval in addition to
    command-level.
    
    Behind a feature flag.
  • feat(core): persist network approvals in execpolicy (#12357)
    ## Summary
    Persist network approval allow/deny decisions as `network_rule(...)`
    entries in execpolicy (not proxy config)
    
    It adds `network_rule` parsing + append support in `codex-execpolicy`,
    including `decision="prompt"` (parse-only; not compiled into proxy
    allow/deny lists)
    - compile execpolicy network rules into proxy allow/deny lists and
    update the live proxy state on approval
    - preserve requirements execpolicy `network_rule(...)` entries when
    merging with file-based execpolicy
    - reject broad wildcard hosts (for example `*`) for persisted
    `network_rule(...)`
  • voice transcription (#3381)
    Adds voice transcription on press-and-hold of spacebar.
    
    
    https://github.com/user-attachments/assets/85039314-26f3-46d1-a83b-8c4a4a1ecc21
    
    ---------
    
    Co-authored-by: Codex <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
    Co-authored-by: David Zbarsky <zbarsky@openai.com>
  • fix: show command running in background terminal in details under status indicator (#12549)
    #### What
    Display in-progress background terminal command in `status.details`
    (right under header) rather than inline, as it gets cut off currently.
    
    ###### Before
    <img width="993" height="395" alt="image"
    src="https://github.com/user-attachments/assets/6792b666-8184-40f7-bf29-409bb06c21d5"
    />
    
    ###### After
    <img width="469" height="137" alt="image"
    src="https://github.com/user-attachments/assets/4d6a2481-bd19-4333-8c1a-92f521b09b3d"
    />
    
    #### Tests
    Added/updated tests
  • fix(tui): queue steer Enter while final answer is still streaming to prevent dead state (#12569)
    ## Summary
    This fixes a TUI race (https://github.com/openai/codex/issues/11008)
    where pressing Enter with Steer enabled while the assistant is still
    streaming the final answer could put Codex into a non-recoverable
    “running” state (no further prompts handled until exiting and resuming).
    
    ## Root Cause
    In steer mode, `InputResult::Submitted` could submit immediately even
    while a final-answer stream was active. That immediate submission races
    with turn completion and can strand turn state.
    
    ## Fix
    When handling `InputResult::Submitted`, we now queue instead of
    immediate-submit if a final-answer stream is active
    (`stream_controller.is_some()`).
    
    This keeps behavior deterministic:
    - Prompt is preserved in the queue.
    - `on_task_complete()` drains queued input through
    `maybe_send_next_queued_input()`.
    - Follow-up prompts continue in FIFO order after completion.
    
    ## Why this resolves the “dead mode”
    The problematic timing window is now converted into queueing, so prompts
    entered during final streaming are not lost and are processed after the
    current output ends. The model continues handling prompts normally
    without requiring `/quit` + `resume`.
    
    ## Tests
    Added regression coverage in `tui/src/chatwidget/tests.rs`:
    
    - `steer_enter_queues_while_final_answer_stream_is_active`
    - `steer_enter_during_final_stream_preserves_follow_up_prompts_in_order`
    
    Both fail on old behavior and pass with this fix.
  • fix(tui): recover on owned wrap mapping mismatch (#12609)
    ## Summary
    
    - Replace the `panic!` in `map_owned_wrapped_line_to_range` with a
    recoverable flow that skips synthetic leading characters, logs a warning
    on mid-line mismatch, and returns the mapped prefix range instead of
    crashing
    - Fixes a crash when `textwrap` produces owned lines with synthetic
    indent prefixes (e.g. non-space indents via
    `initial_indent`/`subsequent_indent`)
    
    ## Test plan
    
    - [x] Added unit test for direct mismatch recovery
    (`map_owned_wrapped_line_to_range_recovers_on_non_prefix_mismatch`)
    - [x] Added end-to-end `wrap_ranges` test with non-space indents that
    forces owned wrapped lines and validates full source reconstruction
    - [x] Verify no regressions in existing `wrapping.rs` tests (`cargo test
    -p codex-tui`)
  • fix: add ellipsis for truncated status indicator (#12540)
    #### What
    
    - Add ellipsis truncation of the status indicator, similar to equivalent
    truncation done in the footer.
    - Extract truncation helpers into separate file
    
    
    
    https://github.com/user-attachments/assets/a2d5f22f-8adc-456e-8059-97359194c25c
    
    
    #### Tests
    Updated relevant snapshot tests
  • tweaked /clear to support clear + new chat, also fix minor bug for macos terminal (#12520)
    # /clear feature! 
    
    Use /clear to start a new chat with Codex on a clean terminal!
  • remove feature flag collaboration modes (#12028)
    All code should go in the direction that steer is enabled
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Add C# syntax option to highlight selections (#12511)
    Summary
    - map csharp/c-sharp aliases to the existing C# syntax in the highlight
    matcher
    - ensure the extension list and tests include .cs and the new aliases so
    coverage stays accurate
    
    Testing
    
    <img width="543" height="266" alt="image"
    src="https://github.com/user-attachments/assets/e6c8a42f-649c-4c30-b574-421b4287534c"
    />
  • Sort themes case-insensitively in picker (#12509)
    ## Summary
    - order bundled and custom themes together by name while keeping entries
    stable across platforms
    - update the theme fixture names and tests to assert case-insensitive
    ordering
  • feat(tui): support Alt-d delete-forward-word (#12455)
    Alt-d should delete the next word. It didn’t. Now it does. Added a small
    test so it stays that way.
    
    Details:
    File updated:
    [codex-rs/tui/src/bottom_pane/textarea.rs](./codex-rs/tui/src/bottom_pane/textarea.rs)
    Test added: delete_forward_word_alt_d — verifies Alt-d deletes the next
    word and keeps the cursor position correct.
    
    Solves  Issue #12453
  • Handle orphan exec ends without clobbering active exploring cell (#12313)
    Summary
    - distinguish exec end handling targets (active tracking, active orphan
    history, new cell) so unified exec responses don’t clobber unrelated
    exploring cells
    - ensure orphan ends flush existing exploring history when complete,
    insert standalone history entries, and keep active cells correct
    - add regression tests plus a snapshot covering the new behavior and
    expose the ExecCell completion result for verification
    
    Fix for https://github.com/openai/codex/issues/12278
    
    ---------
    
    Co-authored-by: Josh McKinney <joshka@openai.com>
  • feat(tui) /clear (#12444)
    # /clear feature! 
    
    /clear will clear your terminal while preserving the context/state of
    the thread.