Commit Graph

1234 Commits

  • Fix CLI feedback link (#13086)
    Addresses #12967
    
    About a month ago, I updated the Github bug report templates to
    accommodate the (at the time) new Codex app. The `/feedback` code path
    in the CLI was referencing one of the old templates, and I didn't
    realize it at the time. This PR updates the link so users don't get an
    empty bug template when using `/feedback`.
  • Add model availability NUX tooltips (#13021)
    - override startup tooltips with model availability NUX and persist
    per-model show counts in config
    - stop showing each model after four exposures and fall back to normal
    tooltips
  • Keep large-paste placeholders intact during file completion (#13070)
    Addresses https://github.com/openai/codex/issues/13040
    
    Fixes a regression in 0.106.0 introduced in
    https://github.com/openai/codex/pull/9393
    
    Summary
    - replace only the active completion range so unrelated text elements
    (e.g., large-paste placeholders) stay atomic and can still expand
    - add a regression test verifying large paste placeholders persist
    through completions and submit
    - could not fetch issue details via GitHub API because network access is
    disabled in this sandboxed environment
  • fix(tui): theme-aware diff backgrounds with fallback behavior (#13037)
    ## Problem
    
    The TUI diff renderer uses hardcoded background palettes for
    insert/delete lines that don't respect the user's chosen syntax theme.
    When a theme defines `markup.inserted` / `markup.deleted` scope
    backgrounds (the convention used by GitHub, Solarized, Monokai, and most
    VS Code themes), those colors are ignored — the diff always renders with
    the same green/red tints regardless of theme selection.
    
    Separately, ANSI-16 terminals (and Windows Terminal sessions misreported
    as ANSI-16) rendered diff backgrounds as full-saturation blocks that
    obliterated syntax token colors, making highlighted diffs unreadable.
    
    ## Mental model
    
    Diff backgrounds are resolved in three layers:
    
    1. **Color level detection** — `diff_color_level_for_terminal()` maps
    the raw `supports-color` probe + Windows Terminal heuristics to a
    `DiffColorLevel` (TrueColor / Ansi256 / Ansi16). Windows Terminal gets
    promoted from Ansi16 to TrueColor when `WT_SESSION` is present.
    
    2. **Background resolution** — `resolve_diff_backgrounds()` queries the
    active syntax theme for `markup.inserted`/`markup.deleted` (falling back
    to `diff.inserted`/`diff.deleted`), then overlays those on top of the
    hardcoded palette. For ANSI-256, theme RGB values are quantized to the
    nearest xterm-256 index. For ANSI-16, backgrounds are `None`
    (foreground-only).
    
    3. **Style composition** — The resolved `ResolvedDiffBackgrounds` is
    threaded through every call to `style_add`, `style_del`, `style_sign_*`,
    and `style_line_bg_for`, which decide how to compose
    foreground+background for each line kind and theme variant.
    
    A new `RichDiffColorLevel` type (a subset of `DiffColorLevel` without
    Ansi16) encodes the invariant "we have enough depth for tinted
    backgrounds" at the type level, so background-producing functions have
    exhaustive matches without unreachable arms.
    
    ## Non-goals
    
    - No change to gutter (line number column) styling — gutter backgrounds
    still use the hardcoded palette.
    - No per-token scope background resolution — this is line-level
    background only; syntax token colors come from the existing
    `highlight_code_to_styled_spans` path.
    - No dark/light theme auto-switching from scope backgrounds —
    `DiffTheme` is still determined by querying the terminal's background
    color.
    
    ## Tradeoffs
    
    - **Theme trust vs. visual safety:** When a theme defines scope
    backgrounds, we trust them unconditionally for rich color levels. A
    badly authored theme could produce illegible combinations. The fallback
    for `None` backgrounds (foreground-only) is intentionally conservative.
    - **Quantization quality:** ANSI-256 quantization uses perceptual
    distance across indices 16–255, skipping system colors. The result is
    approximate — a subtle theme tint may land on a noticeably different
    xterm index.
    - **Single-query caching:** `resolve_diff_backgrounds` is called once
    per `render_change` invocation (i.e., once per file in a diff). If the
    theme changes mid-render (live preview), the next file picks up the new
    backgrounds.
    
    ## Architecture
    
    Files changed:
    
    | File | Role |
    |---|---|
    | `tui/src/render/highlight.rs` | New: `DiffScopeBackgroundRgbs`,
    `diff_scope_background_rgbs()`, scope extraction helpers |
    | `tui/src/diff_render.rs` | New: `RichDiffColorLevel`,
    `ResolvedDiffBackgrounds`, `resolve_diff_backgrounds*`,
    `quantize_rgb_to_ansi256`, Windows Terminal promotion; modified: all
    style helpers to accept/thread `ResolvedDiffBackgrounds` |
    
    The scope-extraction code lives in `highlight.rs` because it uses
    `syntect::highlighting::Highlighter` and the theme singleton. The
    resolution and quantization logic lives in `diff_render.rs` because it
    depends on diff-specific types (`DiffTheme`, `DiffColorLevel`, ratatui
    `Color`).
    
    ## Observability
    
    No runtime logging was added. The most useful debugging aid is the
    `diff_color_level_for_terminal` function, which is pure and fully
    unit-tested — to diagnose a color-depth mismatch, log its four inputs
    (`StdoutColorLevel`, `TerminalName`, `WT_SESSION` presence,
    `FORCE_COLOR` presence).
    
    Scope resolution can be tested by loading a custom `.tmTheme` with known
    `markup.inserted` / `markup.deleted` backgrounds and checking the diff
    output in a truecolor terminal.
    
    ## Tests
    
    - **Windows Terminal promotion:** 7 unit tests cover every branch of
    `diff_color_level_for_terminal` (ANSI-16 promotion, `WT_SESSION`
    unconditional promotion, `FORCE_COLOR` suppression, conservative
    `Unknown` level).
    - **ANSI-16 foreground-only:** Tests verify that `style_add`,
    `style_del`, `style_sign_*`, `style_line_bg_for`, and `style_gutter_for`
    all return `None` backgrounds on ANSI-16.
    - **Scope resolution:** Tests verify `markup.*` preference over
    `diff.*`, `None` when no scope matches, bundled theme resolution, and
    custom `.tmTheme` round-trip.
    - **Quantization:** Test verifies ANSI-256 quantization of a known RGB
    triple.
    - **Insta snapshots:** 2 new snapshot tests
    (`ansi16_insert_delete_no_background`,
    `theme_scope_background_resolution`) lock visual output.
  • Align TUI voice transcription audio with 4o ASR (#13030)
    ## Summary
    - switch TUI push-to-talk transcription requests to
    `gpt-4o-mini-transcribe`
    - prefer 24 kHz mono `i16` microphone configs and normalize voice input
    to 24 kHz mono before upload/send
    - add unit coverage for the new downmix/resample path
    
    ## Testing
    - `just fmt`
    - `cargo test -p codex-tui`
  • fix(tui): promote windows terminal diff ansi16 to truecolor (#13016)
    ## Summary
    
    - Promote ANSI-16 to truecolor for diff rendering when running inside
    Windows Terminal
    - Respect explicit `FORCE_COLOR` override, skipping promotion when set
    - Extract a pure `diff_color_level_for_terminal` function for
    testability
    - Strip background tints from ANSI-16 diff output, rendering add/delete
    lines with foreground color only
    - Introduce `RichDiffColorLevel` to type-safely restrict background
    fills to truecolor and ansi256
    
    ## Problem
    
    Windows Terminal fully supports 24-bit (truecolor) rendering but often
    does not provide the usual TERM metadata (`TERM`, `TERM_PROGRAM`,
    `COLORTERM`) in `cmd.exe`/PowerShell sessions. In those environments,
    `supports-color` can report only ANSI-16 support. The diff renderer
    therefore falls back to a 16-color palette, producing washed-out,
    hard-to-read diffs.
    
    The screenshots below demonstrate that both PowerShell and cmd.exe don't
    set any `*TERM*` environment variables.
    
    | PowerShell | cmd.exe |
    |---|---|
    | <img width="2032" height="1162" alt="SCR-20260226-nfvy"
    src="https://github.com/user-attachments/assets/59e968cc-4add-4c7b-a415-07163297e86a"
    /> | <img width="2032" height="1162" alt="SCR-20260226-nfyc"
    src="https://github.com/user-attachments/assets/d06b3e39-bf91-4ce3-9705-82bf9563a01b"
    /> |
    
    
    ## Mental model
    
    `StdoutColorLevel` (from `supports-color`) is the _detected_ capability.
    `DiffColorLevel` is the _intended_ capability for diff rendering. A new
    intermediary — `diff_color_level_for_terminal` — maps one to the other
    and is the single place where terminal-specific overrides live.
    
    Windows Terminal is detected two independent ways: the `TerminalName`
    parsed by `terminal_info()` and the raw presence of `WT_SESSION`. When
    `WT_SESSION` is present and `FORCE_COLOR` is not set, we promote
    unconditionally to truecolor. When `WT_SESSION` is absent but
    `TerminalName::WindowsTerminal` is detected, we promote only the ANSI-16
    level (not `Unknown`).
    
    A single override helper — `has_force_color_override()` — checks whether
    `FORCE_COLOR` is set. When it is, both the `WT_SESSION` fast-path and
    the `TerminalName`-based promotion are suppressed, preserving explicit
    user intent.
    
    | PowerShell | cmd.exe | WSL | Bash for Windows |
    |---|---|---|---|
    |
    ![SCR-20260226-msrh](https://github.com/user-attachments/assets/0f6297a6-4241-4dbf-b7ff-cf02da8941b0)
    |
    ![SCR-20260226-nbao](https://github.com/user-attachments/assets/bb5ff8a9-903c-4677-a2de-1f6e1f34b18e)
    |
    ![SCR-20260226-nbej](https://github.com/user-attachments/assets/26ecec2c-a7e9-410a-8702-f73995b490a6)
    |
    ![SCR-20260226-nbkz](https://github.com/user-attachments/assets/80c4bf9a-3b41-40e1-bc87-f5c565f96075)
    |
    
    ## Non-goals
    
    - This does not change color detection for anything outside the diff
    renderer (e.g. the chat widget, markdown rendering).
    - This does not add a user-facing config knob; `FORCE_COLOR` already
    serves that role.
    
    ## Tradeoffs
    
    - The `has_wt_session` signal is intentionally kept separate from
    `TerminalName::WindowsTerminal`. `terminal_info()` is derived with
    `TERM_PROGRAM` precedence, so it can differ from raw `WT_SESSION`.
    - Real-world validation in this issue: in both `cmd.exe` and PowerShell,
    `TERM`/`TERM_PROGRAM`/`COLORTERM` were absent, so TERM-based capability
    hints were unavailable in those sessions.
    - Checking `FORCE_COLOR` for presence rather than parsing its value is a
    simplification. In practice `supports-color` has already parsed it, so
    our check is a coarse "did the user set _anything_?" gate. The effective
    color level still comes from `supports-color`.
    - When `WT_SESSION` is present without `FORCE_COLOR`, we promote to
    truecolor regardless of `stdout_level` (including `Unknown`). This is
    aggressive but correct: `WT_SESSION` is a strong signal that we're in
    Windows Terminal.
    - ANSI-16 add/delete backgrounds (bright green/red) overpower
    syntax-highlighted token colors, making diffs harder to read.
    Foreground-only cues (colored text, gutter signs) preserve readability
    on low-color terminals.
    
    ## Architecture
    
    ```
    stdout_color_level()  ──┐
    terminal_info().name  ──┤
    WT_SESSION presence   ──┼──▶ diff_color_level_for_terminal() ──▶ DiffColorLevel
    FORCE_COLOR presence  ──┘                                            │
                                                                         ▼
                                                              RichDiffColorLevel::from_diff_color_level()
                                                                         │
                                                              ┌──────────┴──────────┐
                                                              │ Some(TrueColor|256) │ → bg tints
                                                              │ None (Ansi16)       │ → fg only
                                                              └─────────────────────┘
    ```
    
    `diff_color_level()` is the environment-reading entry point; it gathers
    the four runtime signals and delegates to the pure, testable
    `diff_color_level_for_terminal()`.
    
    ## Observability
    
    No new logs or metrics. Incorrect color selection is immediately visible
    as broken diff rendering; the test suite covers the decision matrix
    exhaustively.
    
    ## Tests
    
    Six new unit tests exercise every branch of
    `diff_color_level_for_terminal`:
    
    | Test | Inputs | Expected |
    |------|--------|----------|
    | `windows_terminal_promotes_ansi16_to_truecolor_for_diffs` | Ansi16 +
    WindowsTerminal name | TrueColor |
    | `wt_session_promotes_ansi16_to_truecolor_for_diffs` | Ansi16 +
    WT_SESSION only | TrueColor |
    | `non_windows_terminal_keeps_ansi16_diff_palette` | Ansi16 + WezTerm |
    Ansi16 |
    | `wt_session_promotes_unknown_color_level_to_truecolor` | Unknown +
    WT_SESSION | TrueColor |
    | `explicit_force_override_keeps_ansi16_on_windows_terminal` | Ansi16 +
    WindowsTerminal + FORCE_COLOR | Ansi16 |
    | `explicit_force_override_keeps_ansi256_on_windows_terminal` | Ansi256
    + WT_SESSION + FORCE_COLOR | Ansi256 |
    | `ansi16_add_style_uses_foreground_only` | Dark + Ansi16 | fg=Green,
    bg=None |
    | (and any other new snapshot/assertion tests from commits d757fee and
    d7c78b3) | | |
    
    ## Test plan
    
    - [x] Verify all new unit tests pass (`cargo test -p codex-tui --lib`)
    - [x] On Windows Terminal: confirm diffs render with truecolor
    backgrounds
    - [x] On Windows Terminal with `FORCE_COLOR` set: confirm promotion is
    disabled and output follows the forced `supports-color` level
    - [x] On macOS/Linux terminals: confirm no behavior change
    
    Fixes https://github.com/openai/codex/issues/12904 
    Fixes https://github.com/openai/codex/issues/12890
    Fixes https://github.com/openai/codex/issues/12912
    Fixes https://github.com/openai/codex/issues/12840
  • fix: use AbsolutePathBuf for permission profile file roots (#12970)
    ## Why
    `PermissionProfile` should describe filesystem roots as absolute paths
    at the type level. Using `PathBuf` in `FileSystemPermissions` made the
    shared type too permissive and blurred together three different
    deserialization cases:
    
    - skill metadata in `agents/openai.yaml`, where relative paths should
    resolve against the skill directory
    - app-server API payloads, where callers should have to send absolute
    paths
    - local tool-call payloads for commands like `shell_command` and
    `exec_command`, where `additional_permissions.file_system` may
    legitimately be relative to the command `workdir`
    
    This change tightens the shared model without regressing the existing
    local command flow.
    
    ## What Changed
    - changed `protocol::models::FileSystemPermissions` and the app-server
    `AdditionalFileSystemPermissions` mirror to use `AbsolutePathBuf`
    - wrapped skill metadata deserialization in `AbsolutePathBufGuard`, so
    relative permission roots in `agents/openai.yaml` resolve against the
    containing skill directory
    - kept app-server/API deserialization strict, so relative
    `additionalPermissions.fileSystem.*` paths are rejected at the boundary
    - restored cwd/workdir-relative deserialization for local tool-call
    payloads by parsing `shell`, `shell_command`, and `exec_command`
    arguments under an `AbsolutePathBufGuard` rooted at the resolved command
    working directory
    - simplified runtime additional-permission normalization so it only
    canonicalizes and deduplicates absolute roots instead of trying to
    recover relative ones later
    - updated the app-server schema fixtures, `app-server/README.md`, and
    the affected transport/TUI tests to match the final behavior
  • notify: include client in legacy hook payload (#12968)
    ## Why
    
    The `notify` hook payload did not identify which Codex client started
    the turn. That meant downstream notification hooks could not distinguish
    between completions coming from the TUI and completions coming from
    app-server clients such as VS Code or Xcode. Now that the Codex App
    provides its own desktop notifications, it would be nice to be able to
    filter those out.
    
    This change adds that context without changing the existing payload
    shape for callers that do not know the client name, and keeps the new
    end-to-end test cross-platform.
    
    ## What changed
    
    - added an optional top-level `client` field to the legacy `notify` JSON
    payload
    - threaded that value through `core` and `hooks`; the internal session
    and turn state now carries it as `app_server_client_name`
    - set the field to `codex-tui` for TUI turns
    - captured `initialize.clientInfo.name` in the app server and applied it
    to subsequent turns before dispatching hooks
    - replaced the notify integration test hook with a `python3` script so
    the test does not rely on Unix shell permissions or `bash`
    - documented the new field in `docs/config.md`
    
    ## Testing
    
    - `cargo test -p codex-hooks`
    - `cargo test -p codex-tui`
    - `cargo test -p codex-app-server
    suite::v2::initialize::turn_start_notify_payload_includes_initialize_client_name
    -- --exact --nocapture`
    - `cargo test -p codex-core` (`src/lib.rs` passed; `core/tests/all.rs`
    still has unrelated existing failures in this environment)
    
    ## Docs
    
    The public config reference on `developers.openai.com/codex` should
    mention that the legacy `notify` payload may include a top-level
    `client` field. The TUI reports `codex-tui`, and the app server reports
    `initialize.clientInfo.name` when it is available.
  • Add model availability NUX metadata (#12972)
    - replace show_nux with structured availability_nux model metadata
    - expose availability NUX data through the app-server model API
    - update shared fixtures and tests for the new field
  • Add oauth_resource handling for MCP login flows (#12866)
    Addresses bug https://github.com/openai/codex/issues/12589
    
    Builds on community PR #12763.
    
    This adds `oauth_resource` support for MCP `streamable_http` servers and
    wires it through the relevant config and login paths. It fixes the bug
    where the configured OAuth resource was not reliably included in the
    authorization request, causing MCP login to omit the expected
    `resource` parameter.
  • Add realtime audio device picker (#12850)
    ## Summary
    - add a dedicated /audio picker for realtime microphone and speaker
    selection
    - persist realtime audio choices and prompt to restart only local audio
    when voice is live
    - add snapshot coverage for the new picker surfaces
    
    ## Validation
    - cargo test -p codex-tui
    - cargo insta accept
    - just fix -p codex-tui
    - just fmt
  • feat: add local date/timezone to turn environment context (#12947)
    ## Summary
    
    This PR includes the session's local date and timezone in the
    model-visible environment context and persists that data in
    `TurnContextItem`.
    
      ## What changed
    - captures the current local date and IANA timezone when building a turn
    context, with a UTC fallback if the timezone lookup fails
    - includes current_date and timezone in the serialized
    <environment_context> payload
    - stores those fields on TurnContextItem so they survive rollout/history
    handling, subagent review threads, and resume flows
    - treats date/timezone changes as environment updates, so prompt caching
    and context refresh logic do not silently reuse stale time context
    - updates tests to validate the new environment fields without depending
    on a single hardcoded environment-context string
    
    ## test
    
    built a local build and saw it in the rollout file:
    ```
    {"timestamp":"2026-02-26T21:39:50.737Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"<environment_context>\n  <shell>zsh</shell>\n  <current_date>2026-02-26</current_date>\n  <timezone>America/Los_Angeles</timezone>\n</environment_context>"}]}}
    ```
  • 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>