Commit Graph

1097 Commits

  • feat: mem slash commands (#11569)
    Add 2 slash commands for memories:
    * `/m_drop` delete all the memories
    * `/m_update` update the memories with phase 1 and 2
  • feat: make sandbox read access configurable with ReadOnlyAccess (#11387)
    `SandboxPolicy::ReadOnly` previously implied broad read access and could
    not express a narrower read surface.
    This change introduces an explicit read-access model so we can support
    user-configurable read restrictions in follow-up work, while preserving
    current behavior today.
    
    It also ensures unsupported backends fail closed for restricted-read
    policies instead of silently granting broader access than intended.
    
    ## What
    
    - Added `ReadOnlyAccess` in protocol with:
      - `Restricted { include_platform_defaults, readable_roots }`
      - `FullAccess`
    - Updated `SandboxPolicy` to carry read-access configuration:
      - `ReadOnly { access: ReadOnlyAccess }`
      - `WorkspaceWrite { ..., read_only_access: ReadOnlyAccess }`
    - Preserved existing behavior by defaulting current construction paths
    to `ReadOnlyAccess::FullAccess`.
    - Threaded the new fields through sandbox policy consumers and call
    sites across `core`, `tui`, `linux-sandbox`, `windows-sandbox`, and
    related tests.
    - Updated Seatbelt policy generation to honor restricted read roots by
    emitting scoped read rules when full read access is not granted.
    - Added fail-closed behavior on Linux and Windows backends when
    restricted read access is requested but not yet implemented there
    (`UnsupportedOperation`).
    - Regenerated app-server protocol schema and TypeScript artifacts,
    including `ReadOnlyAccess`.
    
    ## Compatibility / rollout
    
    - Runtime behavior remains unchanged by default (`FullAccess`).
    - API/schema changes are in place so future config wiring can enable
    restricted read access without another policy-shape migration.
  • Use slug in tui (#11519)
    Display name is for VSCE and App, TUI uses lowercase everywhere.
  • change model cap to server overload (#11388)
    # External (non-OpenAI) Pull Request Requirements
    
    Before opening this Pull Request, please read the dedicated
    "Contributing" markdown file or your PR may be closed:
    https://github.com/openai/codex/blob/main/docs/contributing.md
    
    If your PR conforms to our contribution guidelines, replace this text
    with a detailed and high quality description of your changes.
    
    Include a link to a bug report or enhancement request.
  • chore(tui) Simplify /status Permissions (#11290)
    ## Summary
    Consolidate `/status` Permissions lines into a simpler view. It should
    only show "Default," "Full Access," or "Custom" (with specifics)
    
    ## Testing
    - [x] many snapshots updated
  • Promote Windows Sandbox (#11341)
    1. Move Windows Sandbox NUX to right after trust directory screen
    2. Don't offer read-only as an option in Sandbox NUX.
    Elevated/Legacy/Quit
    3. Don't allow new untrusted directories. It's trust or quit
    4. move experimental sandbox features to `[windows]
    sandbox="elevated|unelevatd"`
    5. Copy tweaks = elevated -> default, non-elevated -> non-admin
  • Linkify feedback link (#11414)
    Make it clickable
  • fix(tui): increase paste burst char interval on Windows to 30ms (#9348)
    ## Summary
    
    - Increases `PASTE_BURST_CHAR_INTERVAL` from 8ms to 30ms on Windows to
    fix multi-line paste issues in VS Code integrated terminal
    - Follows existing pattern of platform-specific timing (like
    `PASTE_BURST_ACTIVE_IDLE_TIMEOUT`)
    
    ## Problem
    
    When pasting multi-line text in Codex CLI on Windows (especially VS Code
    integrated terminal), only the first portion is captured before
    auto-submit. The rest arrives as a separate message.
    
    **Root cause**: VS Code's terminal emulation adds latency (~10-15ms per
    character) between key events. The 8ms `PASTE_BURST_CHAR_INTERVAL`
    threshold is too tight - characters arrive slower than expected, so
    burst detection fails and Enter submits instead of inserting a newline.
    
    ## Solution
    
    Use Windows-specific timing (30ms) for `PASTE_BURST_CHAR_INTERVAL`,
    following the same pattern already used for
    `PASTE_BURST_ACTIVE_IDLE_TIMEOUT` (60ms on Windows vs 8ms on Unix).
    
    30ms is still fast enough to distinguish paste from typing (humans type
    ~200ms between keystrokes).
    
    ## Test plan
    
    - [x] All existing paste_burst tests pass
    - [ ] Test multi-line paste in VS Code integrated PowerShell on Windows
    - [ ] Test multi-line paste in standalone Windows PowerShell
    - [ ] Verify no regression on macOS/Linux
    
    Fixes #2137
    
    Co-authored-by: Josh McKinney <joshka@openai.com>
  • Extract codex-config from codex-core (#11389)
    `codex-core` had accumulated config loading, requirements parsing,
    constraint logic, and config-layer state handling in a single crate.
    This change extracts that subsystem into `codex-config` to reduce
    `codex-core` rebuild/test surface area and isolate future config work.
    
    ## What Changed
    
    ### Added `codex-config`
    
    - Added new workspace crate `codex-rs/config` (`codex-config`).
    - Added workspace/build wiring in:
      - `codex-rs/Cargo.toml`
      - `codex-rs/config/Cargo.toml`
      - `codex-rs/config/BUILD.bazel`
    - Updated lockfiles (`codex-rs/Cargo.lock`, `MODULE.bazel.lock`).
    - Added `codex-core` -> `codex-config` dependency in
    `codex-rs/core/Cargo.toml`.
    
    ### Moved config internals from `core` into `config`
    
    Moved modules to `codex-rs/config/src/`:
    
    - `core/src/config/constraint.rs` -> `config/src/constraint.rs`
    - `core/src/config_loader/cloud_requirements.rs` ->
    `config/src/cloud_requirements.rs`
    - `core/src/config_loader/config_requirements.rs` ->
    `config/src/config_requirements.rs`
    - `core/src/config_loader/fingerprint.rs` -> `config/src/fingerprint.rs`
    - `core/src/config_loader/merge.rs` -> `config/src/merge.rs`
    - `core/src/config_loader/overrides.rs` -> `config/src/overrides.rs`
    - `core/src/config_loader/requirements_exec_policy.rs` ->
    `config/src/requirements_exec_policy.rs`
    - `core/src/config_loader/state.rs` -> `config/src/state.rs`
    
    `codex-config` now re-exports this surface from `config/src/lib.rs` at
    the crate top level.
    
    ### Updated `core` to consume/re-export `codex-config`
    
    - `core/src/config_loader/mod.rs` now imports/re-exports config-loader
    types/functions from top-level `codex_config::*`.
    - Local moved modules were removed from `core/src/config_loader/`.
    - `core/src/config/mod.rs` now re-exports constraint types from
    `codex_config`.
  • Cache cloud requirements (#11305)
    We're loading these from the web on every startup. This puts them in a
    local file with a 1hr TTL.
    
    We sign the downloaded requirements with a key compiled into the Codex
    CLI to prevent unsophisticated tampering (determined circumvention is
    outside of our threat model: after all, one could just compile Codex
    without any of these checks).
    
    If any of the following are true, we ignore the local cache and re-fetch
    from Cloud:
    * The signature is invalid for the payload (== requirements, sign time,
    ttl, user identity)
    * The identity does not match the auth'd user's identity
    * The TTL has expired
    * We cannot parse requirements.toml from the payload
  • feat: split codex-common into smaller utils crates (#11422)
    We are removing feature-gated shared crates from the `codex-rs`
    workspace. `codex-common` grouped several unrelated utilities behind
    `[features]`, which made dependency boundaries harder to reason about
    and worked against the ongoing effort to eliminate feature flags from
    workspace crates.
    
    Splitting these utilities into dedicated crates under `utils/` aligns
    this area with existing workspace structure and keeps each dependency
    explicit at the crate boundary.
    
    ## What changed
    
    - Removed `codex-rs/common` (`codex-common`) from workspace members and
    workspace dependencies.
    - Added six new utility crates under `codex-rs/utils/`:
      - `codex-utils-cli`
      - `codex-utils-elapsed`
      - `codex-utils-sandbox-summary`
      - `codex-utils-approval-presets`
      - `codex-utils-oss`
      - `codex-utils-fuzzy-match`
    - Migrated the corresponding modules out of `codex-common` into these
    crates (with tests), and added matching `BUILD.bazel` targets.
    - Updated direct consumers to use the new crates instead of
    `codex-common`:
      - `codex-rs/cli`
      - `codex-rs/tui`
      - `codex-rs/exec`
      - `codex-rs/app-server`
      - `codex-rs/mcp-server`
      - `codex-rs/chatgpt`
      - `codex-rs/cloud-tasks`
    - Updated workspace lockfile entries to reflect the new dependency graph
    and removal of `codex-common`.
  • Remove test-support feature from codex-core and replace it with explicit test toggles (#11405)
    ## Why
    
    `codex-core` was being built in multiple feature-resolved permutations
    because test-only behavior was modeled as crate features. For a large
    crate, those permutations increase compile cost and reduce cache reuse.
    
    ## Net Change
    
    - Removed the `test-support` crate feature and related feature wiring so
    `codex-core` no longer needs separate feature shapes for test consumers.
    - Standardized cross-crate test-only access behind
    `codex_core::test_support`.
    - External test code now imports helpers from
    `codex_core::test_support`.
    - Underlying implementation hooks are kept internal (`pub(crate)`)
    instead of broadly public.
    
    ## Outcome
    
    - Fewer `codex-core` build permutations.
    - Better incremental cache reuse across test targets.
    - No intended production behavior change.
  • tui: show non-file layer content in /debug-config (#11412)
    The debug output listed non-file-backed layers such as session flags and
    MDM managed config, but it did not show their values. That made it
    difficult to explain unexpected effective settings because users could
    not inspect those layers on disk.
    
    Now `/debug-config` might include output like this:
    
    ```
    Config layer stack (lowest precedence first):
      1. system (/etc/codex/config.toml) (enabled)
      2. user (/Users/mbolin/.codex/config.toml) (enabled)
      3. legacy managed_config.toml (mdm) (enabled)
         MDM value:
           # Production Codex configuration file.
    
           [otel]
           log_user_prompt = true
           environment = "prod"
           exporter = { otlp-http = {
             endpoint = "https://example.com/otel",
             protocol = "binary"
           }}
    ```
  • feat: support multiple rate limits (#11260)
    Added multi-limit support end-to-end by carrying limit_name in
    rate-limit snapshots and handling multiple buckets instead of only
    codex.
    Extended /usage client parsing to consume additional_rate_limits
    Updated TUI /status and in-memory state to store/render per-limit
    snapshots
    Extended app-server rate-limit read response: kept rate_limits and added
    rate_limits_by_name.
    Adjusted usage-limit error messaging for non-default codex limit buckets
  • chore: persist turn_id in rollout session and make turn_id uuid based (#11246)
    Problem:
    1. turn id is constructed in-memory;
    2. on resuming threads, turn_id might not be unique;
    3. client cannot no the boundary of a turn from rollout files easily.
    
    This PR does three things:
    1. persist `task_started` and `task_complete` events;
    1. persist `turn_id` in rollout turn events;
    5. generate turn_id as unique uuids instead of incrementing it in
    memory.
    
    This helps us resolve the issue of clients wanting to have unique turn
    ids for resuming a thread, and knowing the boundry of each turn in
    rollout files.
    
    example debug logs
    ```
    2026-02-11T00:32:10.746876Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=8 turn=Turn { id: "019c4a07-d809-74c3-bc4b-fd9618487b4b", items: [UserMessage { id: "item-24", content: [Text { text: "hi", text_elements: [] }] }, AgentMessage { id: "item-25", text: "Hi. I’m in the workspace with your current changes loaded and ready. Send the next task and I’ll execute it end-to-end." }], status: Completed, error: None }
    2026-02-11T00:32:10.746888Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=9 turn=Turn { id: "019c4a18-1004-76c0-a0fb-a77610f6a9b8", items: [UserMessage { id: "item-26", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-27", text: "Hello. Ready for the next change in `codex-rs`; I can continue from the current in-progress diff or start a new task." }], status: Completed, error: None }
    2026-02-11T00:32:10.746899Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=10 turn=Turn { id: "019c4a19-41f0-7db0-ad78-74f1503baeb8", items: [UserMessage { id: "item-28", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-29", text: "Hello. Send the specific change you want in `codex-rs`, and I’ll implement it and run the required checks." }], status: Completed, error: None }
    ```
    
    backward compatibility:
    if you try to resume an old session without task_started and
    task_complete event populated, the following happens:
    - If you resume and do nothing: those reconstructed historical IDs can
    differ next time you resume.
    - If you resume and send a new turn: the new turn gets a fresh UUID from
    live submission flow and is persisted, so that new turn’s ID is stable
    on later resumes.
    I think this behavior is fine, because we only care about deterministic
    turn id once a turn is triggered.
  • tui: queue non-pending rollback trims in app-event order (#11373)
    ## Summary
    
    This PR fixes TUI transcript-sync behavior for
    `EventMsg::ThreadRolledBack` and makes rollback application order
    deterministic.
    
    Previously, rollback handling depended on `pending_rollback`:
    
    - if `pending_rollback` was set (local backtrack), TUI trimmed correctly
    - otherwise, replayed/external rollbacks were either ignored or could be
    applied at the wrong time relative to queued transcript inserts
    
    This change keeps the local backtrack path intact and routes non-pending
    rollbacks through the app event queue so rollback trims are applied in
    FIFO order with transcript cell inserts.
    
    ## What changed
    
    - Added/used `trim_transcript_cells_drop_last_n_user_turns(...)` for
    rollback-by-`num_turns` semantics.
    - Renamed rollback app event:
    - `AppEvent::ApplyReplayedThreadRollback` ->
    `AppEvent::ApplyThreadRollback`
    - Replay path (`ChatWidget`) now emits `ApplyThreadRollback`.
    - Live non-pending rollback path (`App::handle_backtrack_event`) now
    emits `ApplyThreadRollback` instead of trimming immediately.
    - App-level event handler applies `ApplyThreadRollback` after queued
    `InsertHistoryCell` events and schedules redraw only when a trim
    occurred.
    - When a trim occurs with an overlay open, TUI now syncs transcript
    overlay committed cells, clamps backtrack preview selection, and clears
    stale `deferred_history_lines` so closed overlays do not re-append
    rolled-back lines.
    - Clarified inline comments around the `pending_rollback` branch so
    future readers can reason about why there are two paths.
    
    ## Why queueing matters
    
    During resume/replay, transcript cells are populated via queued
    `InsertHistoryCell` app events. If a rollback is applied immediately
    outside that queue, it can run against an incomplete transcript and
    under-trim. Queueing non-pending rollbacks ensures consistent ordering
    and correct final transcript state.
    
    ## Behavior by rollback source
    
    - `pending_rollback = Some(...)` (local backtrack requested by this
    TUI):
      - use `finish_pending_backtrack()` and the stored selection boundary
    - `pending_rollback = None` (replay/external/non-local rollback):
    - enqueue `AppEvent::ApplyThreadRollback { num_turns }` and trim in
    app-event order
    
    ## Tests
    
    Added/updated tests covering ordering and semantics:
    
    -
    `app_backtrack::tests::trim_drop_last_n_user_turns_applies_rollback_semantics`
    - `app_backtrack::tests::trim_drop_last_n_user_turns_allows_overflow`
    - `app::tests::replayed_initial_messages_apply_rollback_in_queue_order`
    -
    `app::tests::live_rollback_during_replay_is_applied_in_app_event_order`
    -
    `app::tests::queued_rollback_syncs_overlay_and_clears_deferred_history`
    - `chatwidget::tests::replayed_thread_rollback_emits_ordered_app_event`
    
    Validation run:
    
    - `just fmt`
    - `cargo test -p codex-tui`
  • Enable SOCKS defaults for common local network proxy use cases (#11362)
    ## Summary
    - enable local-use defaults in network proxy settings: SOCKS5 on, SOCKS5
    UDP on, upstream proxying on, and local binding on
    - add a regression test that asserts the full
    `NetworkProxySettings::default()` baseline
    - Fixed managed listener reservation behavior.
    Before: we always reserved a loopback SOCKS listener, even when
    enable_socks5 = false.
    Now: SOCKS listener is only reserved when SOCKS is enabled.
    - Fixed /debug-config env output for SOCKS-disabled sessions.
    ALL_PROXY now shows the HTTP proxy URL when SOCKS is disabled (instead
    of incorrectly showing socks5h://...).
    
    
    ## Validation
    - just fmt
    - cargo test -p codex-network-proxy
    - cargo clippy -p codex-network-proxy --all-targets
  • [apps] Improve app installation flow. (#11249)
    - [x] Add buttons to start the installation flow and verify installation
    completes.
    - [x] Hard refresh apps list when the /apps view opens.
  • Fix: update parallel tool call exec approval to approve on request id (#11162)
    ### Summary
    
    In parallel tool call, exec command approvals were not approved at
    request level but at a turn level. i.e. when a single request is
    approved, the system currently treats all requests in turn as approved.
    
    ### Before
    
    https://github.com/user-attachments/assets/d50ed129-b3d2-4b2f-97fa-8601eb11f6a8
    
    ### After
    
    https://github.com/user-attachments/assets/36528a43-a4aa-4775-9e12-f13287ef19fc
  • tui: keep history recall cursor at line end (#11295)
    ## Summary
    - keep cursor at end-of-line after Up/Down history recall
    - allow continued history navigation when recalled text cursor is at
    start or end boundary
    - add regression tests and document the history cursor contract in
    composer docs
    
    ## Testing
    - just fmt
    - cargo test -p codex-tui --lib
    history_navigation_leaves_cursor_at_end_of_line
    - cargo test -p codex-tui --lib
    should_handle_navigation_when_cursor_is_at_line_boundaries
    - cargo test -p codex-tui *(fails in existing integration test
    `suite::no_panic_on_startup::malformed_rules_should_not_panic` because
    `target/debug/codex` is not present in this environment)*
  • feat: retain NetworkProxy, when appropriate (#11207)
    As of this PR, `SessionServices` retains a
    `Option<StartedNetworkProxy>`, if appropriate.
    
    Now the `network` field on `Config` is `Option<NetworkProxySpec>`
    instead of `Option<NetworkProxy>`.
    
    Over in `Session::new()`, we invoke `NetworkProxySpec::start_proxy()` to
    create the `StartedNetworkProxy`, which is a new struct that retains the
    `NetworkProxy` as well as the `NetworkProxyHandle`. (Note that `Drop` is
    implemented for `NetworkProxyHandle` to ensure the proxies are shutdown
    when it is dropped.)
    
    The `NetworkProxy` from the `StartedNetworkProxy` is threaded through to
    the appropriate places.
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/11207).
    * #11285
    * __->__ #11207
  • chore(tui) cleanup /approvals (#10215)
    ## Summary
    Consolidate on the new `/permissions` flow
    
    ## Testing
    - [x] updated snapshots
  • feat: add SkillPolicy to skill metadata and support allow_implicit_invocation (#11244)
    Tested by setting the policy in agents/openai.yaml to true, false, and
    leaving it unset (default).
    ```
    policy:
      allow_implicit_invocation: false
    ```
    <img width="847" height="289" alt="Screenshot 2026-02-09 at 3 42 41 PM"
    src="https://github.com/user-attachments/assets/d3476264-3355-47cf-894a-4ffba53e3481"
    />
  • fix(tui): tab submits when no task running in steer mode (#10035)
    When steer mode is enabled, Tab used to only queue while a task was
    running and otherwise did nothing. Treat Tab as an immediate submit when
    no task is running so input isn't dropped when the inflight turn ends
    mid-typing.
    
    Adds a regression test and updates docs/tooltips.
  • adding image support for gif and webp (#11237)
    Adds image support for gif and webp images. Tested using webp and gif
    (both single and multi image gif files)
  • fix(tui): keep unified exec summary on working line (#10962)
    ## Problem
    When unified-exec background sessions appear while the status indicator
    is visible, the bottom pane can grow by one row to show a dedicated
    footer line. That row insertion/removal makes the composer jump
    vertically and produces visible jitter/flicker during streaming turns.
    
    ## Mental model
    The bottom pane should expose one canonical background-exec summary
    string, but it should surface that string in only one place at a time:
    - if the status indicator row is visible, show the summary inline on
    that row;
    - if the status indicator row is hidden, show the summary as the
    standalone unified-exec footer row.
    
    This keeps status information visible while preserving a stable pane
    height.
    
    ## Non-goals
    This change does not alter unified-exec lifecycle, process tracking, or
    `/ps` behavior. It does not redesign status text copy, spinner timing,
    or interrupt handling semantics.
    
    ## Tradeoffs
    Inlining the summary preserves layout stability and keeps interrupt
    affordances in a fixed location, but it reduces horizontal space for
    long status/detail text in narrow terminals. We accept that truncation
    risk in exchange for removing vertical jitter and keeping the composer
    anchored.
    
    ## Architecture
    `UnifiedExecFooter` remains the source of truth for background-process
    summary copy via `summary_text()`. `BottomPane` mirrors that text into
    `StatusIndicatorWidget::update_inline_message()` whenever process state
    changes or a status widget is created. Rendering enforces single-surface
    output: the standalone footer row is skipped while status is present,
    and the status row appends the summary after the elapsed/interrupt
    segment.
    
    ## Documentation pass
    Added non-functional docs/comments that make the new invariant explicit:
    - status row owns inline summary when present;
    - unified-exec footer row renders only when status row is absent;
    - summary ordering keeps elapsed/interrupt affordance in a stable
    position.
    
    ## Observability
    No new telemetry or logs are introduced. The behavior is traceable
    through:
    - `BottomPane::set_unified_exec_processes()` for state updates,
    - `BottomPane::sync_status_inline_message()` for status-row
    synchronization,
    - `StatusIndicatorWidget::render()` for final inline ordering.
    
    ## Tests
    - Added
    `bottom_pane::tests::unified_exec_summary_does_not_increase_height_when_status_visible`
    to lock the no-height-growth invariant.
    - Updated the unified-exec status restoration snapshot to match inline
    rendering order.
    - Validated with:
      - `just fmt`
      - `cargo test -p codex-tui --lib`
    
    ---------
    
    Co-authored-by: Sayan Sisodiya <sayan@openai.com>
  • TUI: fix request_user_input wrapping for long option labels (#11123)
    ## Summary
    
    This PR fixes long-text rendering in the `request_user_input` TUI
    overlay while preserving a clear two-column option layout. (Issue
    https://github.com/openai/codex/issues/11093)
    
    Before:
    - very long option labels could push description text into a narrow
    right-edge strip
    - option labels were effectively single-line when descriptions were
    present, causing truncation/poor readability
    - label and description wrapping interacted in one combined wrapped line
    
    <img width="504" height="409" alt="Screenshot 2026-02-08 at 2 27 25 PM"
    src="https://github.com/user-attachments/assets/a9afd108-d792-4522-bce1-e43b3cce882b"
    />
    
    After:
    - option labels wrap inside the left column
    - descriptions wrap independently inside the right column
    - row measurement and row rendering use the same wrapping path, so
    layout stays stable
    
    <img width="582" height="211" alt="Screenshot 2026-02-09 at 10 28 02 AM"
    src="https://github.com/user-attachments/assets/47885a1c-07e5-4b0f-b992-032b149f1b0d"
    />
    
    ## Problem
    
    `request_user_input` needs to handle verbose prompts/options. With
    oversized labels:
    - descriptions could collapse into a thin, hard-to-read column
    - important label context was lost
    
    ## Root Cause
    
    In shared row rendering (`selection_popup_common`):
    - rows were wrapped as a single combined line
    - auto column sizing could still place `desc_col` too far right for long
    labels
    - `request_user_input` rows did not provide wrap metadata to align
    continuation lines after the option prefix
    
    ## What Changed
    
    ### 1) `request_user_input` rows opt into wrapped labels
    File: `codex-rs/tui/src/bottom_pane/request_user_input/mod.rs`
    
    - In `option_rows()`, compute the rendered option prefix (`› 1. ` / ` 2.
    `) and set `wrap_indent` from its display width.
    - Apply the same behavior to the synthetic “None of the above” row.
    - Add long-text snapshot test coverage
    (`question_with_very_long_option_text` +
    `request_user_input_long_option_text_snapshot`).
    
    ### 2) Shared renderer now has an opt-in two-column wrapping path
    File: `codex-rs/tui/src/bottom_pane/selection_popup_common.rs`
    
    - Add focused helpers:
      - `should_wrap_name_in_column`
      - `wrap_two_column_row`
      - `wrap_standard_row`
      - `wrap_row_lines`
      - `apply_row_state_style`
    - For opted-in rows (plain option rows with `wrap_indent` +
    description), wrap label and description independently in their own
    columns.
    - Keep the legacy standard wrapping path for non-opted rows.
    - Use the same `wrap_row_lines` function in both rendering and height
    measurement to keep them in sync.
    
    ### 3) Keep column sizing simple and derived from existing fixed split
    constants
    File: `codex-rs/tui/src/bottom_pane/selection_popup_common.rs`
    
    - Keep fixed mode at `3/10` left column (`30/70` split).
    - In auto modes, cap label width using those same fixed constants (max
    70% label, min 30% description), instead of extra special-case
    constants/branches.
    - Add/keep narrow-width safety guard in `wrap_two_column_row` so
    extremely small widths do not panic.
    
    ### 4) Snapshot coverage
    File: `codex-rs/tui/src/bottom_pane/request_user_input/snapshots/
    
    codex_tui__bottom_pane__request_user_input__tests__request_user_input_long_option_text.snap`
    
    - Add snapshot for long-label/long-description two-column rendering
    behavior.
  • Load requirements on windows (#10770)
    We support requirements on Unix, loading from
    `/etc/codex/requirements.toml`. On MacOS, we also support MDM.
    
    Now, on Windows, we'll load requirements from
    `%ProgramData%\OpenAI\Codex\requirements.toml`
  • feat: do not close unified exec processes across turns (#10799)
    With this PR we do not close the unified exec processes (i.e. background
    terminals) at the end of a turn unless:
    * The user interrupt the turn
    * The user decide to clean the processes through `app-server` or
    `/clean`
    
    I made sure that `codex exec` correctly kill all the processes
  • tui: avoid no-op status-line redraws (#11155)
    Rate-limit snapshots are polled every 60s, which causes unconditional
    redraws.
    This causes spurious "tab changed" indicators in terminal apps.
  • [apps] Improve app loading. (#10994)
    There are two concepts of apps that we load in the harness:
    
    - Directory apps, which is all the apps that the user can install.
    - Accessible apps, which is what the user actually installed and can be
    $ inserted and be used by the model. These are extracted from the tools
    that are loaded through the gateway MCP.
    
    Previously we wait for both sets of apps before returning the full apps
    list. Which causes many issues because accessible apps won't be
    available to the UI or the model if directory apps aren't loaded or
    failed to load.
    
    In this PR we are separating them so that accessible apps can be loaded
    separately and are instantly available to be shown in the UI and to be
    provided in model context. We also added an app-server event so that
    clients can subscribe to also get accessible apps without being blocked
    on the full app list.
    
    - [x] Separate accessible apps and directory apps loading.
    - [x] `app/list` request will also emit `app/list/updated` notifications
    that app-server clients can subscribe. Which allows clients to get
    accessible apps list to render in the $ menu without being blocked by
    directory apps.
    - [x] Cache both accessible and directory apps with 1 hour TTL to avoid
    reloading them when creating new threads.
    - [x] TUI improvements to redraw $ menu and /apps menu when app list is
    updated.
  • Defer persistence of rollout file (#11028)
    - Defer rollout persistence for fresh threads (`InitialHistory::New`):
    keep rollout events in memory and only materialize rollout file + state
    DB row on first `EventMsg::UserMessage`.
    - Keep precomputed rollout path available before materialization.
    - Change `thread/start` to build thread response from live config
    snapshot and optional precomputed path.
    - Improve pre-materialization behavior in app-server/TUI: clearer
    invalid-request errors for file-backed ops and a friendlier `/fork` “not
    ready yet” UX.
    - Update tests to match deferred semantics across
    start/read/archive/unarchive/fork/resume/review flows.
    - Improved resilience of user_shell test, which should be unrelated to
    this change but must be affected by timing changes
    
    For Reviewers:
    * The primary change is in recorder.rs
    * Most of the other changes were to fix up broken assumptions in
    existing tests
    
    Testing:
    * Manually tested CLI
    * Exercised app server paths by manually running IDE Extension with
    rebuilt CLI binary
    * Only user-visible change is that `/fork` in TUI generates visible
    error if used prior to first turn
  • fix(tui): rehydrate drafts and restore image placeholders (#9040)
    Fixes #9050
    
    When a draft is stashed with Ctrl+C, we now persist the full draft state
    (text elements, local image paths, and pending paste payloads) in local
    history. Up/Down recall rehydrates placeholder elements and attachments
    so styling remains correct and large pastes still expand on submit.
    Persistent (cross‑session) history remains text‑only.
    
    Backtrack prefills now reuse the selected user message’s text elements
    and local image paths, so image placeholders/attachments rehydrate when
    rolling back.
    
    External editor replacements keep only attachments whose placeholders
    remain and then normalize image placeholders to `[Image #1]..[Image #N]`
    to keep the attachment mapping consistent.
    
    Docs:
    - docs/tui-chat-composer.md
    
    Testing:
    - just fix -p codex-tui
    - cargo test -p codex-tui
    
    Co-authored-by: Eric Traut <etraut@openai.com>
  • feat: include state of [experimental_network] in /debug-config output (#11039)
    #10958 introduced experimental support for a network config in
    `/etc/codex/requirements.toml`, so this extends `/debug-config` to
    surface this information, if set, which should make it easier to debug.
  • feat(core): add network constraints schema to requirements.toml (#10958)
    ## Summary
    
    Add `requirements.toml` schema support for admin-defined network
    constraints in the requirements layer
    
    example config:
    
    ```
    [experimental_network]
    enabled = true
    allowed_domains = ["api.openai.com"]
    denied_domains = ["example.com"]
    ```
  • Add resume_agent collab tool (#10903)
    Summary
    - add the new resume_agent collab tool path through core, protocol, and
    the app server API, including the resume events
    - update the schema/TypeScript definitions plus docs so resume_agent
    appears in generated artifacts and README
    - note that resumed agents rehydrate rollout history without overwriting
    their base instructions
    
    Testing
    - Not run (not requested)
  • Show left/right arrows to navigate in tui request_user_input (#10921)
    <img width="785" height="185" alt="Screenshot 2026-02-06 at 10 25 13 AM"
    src="https://github.com/user-attachments/assets/402a6e79-4626-4df9-b3da-bc2f28e64611"
    />
    
    <img width="784" height="213" alt="Screenshot 2026-02-06 at 10 26 37 AM"
    src="https://github.com/user-attachments/assets/cf9614b2-aa1e-4c61-8579-1d2c7e1c7dc1"
    />
    
    "left/right to navigate questions" in request_user_input footer
  • Do not poll for usage when using API Key auth (#10973)
    Fixes #10869
    
    - Gate TUI rate-limit polling on ChatGPT-auth providers only.
    - `prefetch_rate_limits()` now checks `should_prefetch_rate_limits()`.
    - New gate requires:
      - `config.model_provider.requires_openai_auth`
      - cached auth is ChatGPT (`CodexAuth::is_chatgpt_auth`)
    - Prevents `/wham/usage` polling in API/custom-endpoint profiles.
  • feat: add support for allowed_web_search_modes in requirements.toml (#10964)
    This PR makes it possible to disable live web search via an enterprise
    config even if the user is running in `--yolo` mode (though cached web
    search will still be available). To do this, create
    `/etc/codex/requirements.toml` as follows:
    
    ```toml
    # "live" is not allowed; "disabled" is allowed even though not listed explicitly.
    allowed_web_search_modes = ["cached"]
    ```
    
    Or set `requirements_toml_base64` MDM as explained on
    https://developers.openai.com/codex/security/#locations.
    
    ### Why
    - Enforce admin/MDM/`requirements.toml` constraints on web-search
    behavior, independent of user config and per-turn sandbox defaults.
    - Ensure per-turn config resolution and review-mode overrides never
    crash when constraints are present.
    
    ### What
    - Add `allowed_web_search_modes` to requirements parsing and surface it
    in app-server v2 `ConfigRequirements` (`allowedWebSearchModes`), with
    fixtures updated.
    - Define a requirements allowlist type (`WebSearchModeRequirement`) and
    normalize semantics:
      - `disabled` is always implicitly allowed (even if not listed).
      - An empty list is treated as `["disabled"]`.
    - Make `Config.web_search_mode` a `Constrained<WebSearchMode>` and apply
    requirements via `ConstrainedWithSource<WebSearchMode>`.
    - Update per-turn resolution (`resolve_web_search_mode_for_turn`) to:
    - Prefer `Live → Cached → Disabled` when
    `SandboxPolicy::DangerFullAccess` is active (subject to requirements),
    unless the user preference is explicitly `Disabled`.
    - Otherwise, honor the user’s preferred mode, falling back to an allowed
    mode when necessary.
    - Update TUI `/debug-config` and app-server mapping to display
    normalized `allowed_web_search_modes` (including implicit `disabled`).
    - Fix web-search integration tests to assert cached behavior under
    `SandboxPolicy::ReadOnly` (since `DangerFullAccess` legitimately prefers
    `live` when allowed).
  • fix(tui): conditionally restore status indicator using message phase (#10947)
    TLDR: use new message phase field emitted by preamble-supported models
    to determine whether an AgentMessage is mid-turn commentary. if so,
    restore the status indicator afterwards to indicate the turn has not
    completed.
    
    ### Problem
    `commit_tick` hides the status indicator while streaming assistant text.
    For preamble-capable models, that text can be commentary mid-turn, so
    hiding was correct during streaming but restore timing mattered:
    - restoring too aggressively caused jitter/flashing
    - not restoring caused indicator to stay hidden before subsequent work
    (tool calls, web search, etc.)
    
    ### Fix
    - Add optional `phase` to `AgentMessageItem` and propagate it from
    `ResponseItem::Message`
    - Keep indicator hidden during streamed commit ticks, restore only when:
      - assistant item completes as `phase=commentary`, and
      - stream queues are idle + task is still running.
    - Treat `phase=None` as final-answer behavior (no restore) to keep
    existing behavior for non-preamble models
    
    ### Tests
    Add/update tests for:
    - no idle-tick restore without commentary completion
    - commentary completion restoring status before tool begin
    - snapshot coverage for preamble/status behavior
    
    ---------
    
    Co-authored-by: Josh McKinney <joshka@openai.com>
  • TUI/Core: preserve duplicate skill/app mention selection across submit + resume (#10855)
    ## What changed
    
    - In `codex-rs/core/src/skills/injection.rs`, we now honor explicit
    `UserInput::Skill { name, path }` first, then fall back to text mentions
    only when safe.
    - In `codex-rs/tui/src/bottom_pane/chat_composer.rs`, mention selection
    is now token-bound (selected mention is tied to the specific inserted
    `$token`), and we snapshot bindings at submit time so selection is not
    lost.
    - In `codex-rs/tui/src/chatwidget.rs` and
    `codex-rs/tui/src/bottom_pane/mod.rs`, submit/queue paths now consume
    the submit-time mention snapshot (instead of rereading cleared composer
    state).
    - In `codex-rs/tui/src/mention_codec.rs` and
    `codex-rs/tui/src/bottom_pane/chat_composer_history.rs`, history now
    round-trips mention targets so resume restores the same selected
    duplicate.
    - In `codex-rs/tui/src/bottom_pane/skill_popup.rs` and
    `codex-rs/tui/src/bottom_pane/chat_composer.rs`, duplicate labels are
    normalized to `[Repo]` / `[App]`, app rows no longer show `Connected -`,
    and description space is a bit wider.
    
    <img width="550" height="163" alt="Screenshot 2026-02-05 at 9 56 56 PM"
    src="https://github.com/user-attachments/assets/346a7eb2-a342-4a49-aec8-68dfec0c7d89"
    />
    <img width="550" height="163" alt="Screenshot 2026-02-05 at 9 57 09 PM"
    src="https://github.com/user-attachments/assets/5e04d9af-cccf-4932-98b3-c37183e445ed"
    />
    
    
    ## Before vs now
    
    - Before: selecting a duplicate could still submit the default/repo
    match, and resume could lose which duplicate was originally selected.
    - Now: the exact selected target (skill path or app id) is preserved
    through submit, queue/restore, and resume.
    
    ## Manual test
    
    1. Build and run this branch locally:
       - `cd /Users/daniels/code/codex/codex-rs`
       - `cargo build -p codex-cli --bin codex`
       - `./target/debug/codex`
    2. Open mention picker with `$` and pick a duplicate entry (not the
    first one).
    3. Confirm duplicate UI:
       - repo duplicate rows show `[Repo]`
       - app duplicate rows show `[App]`
       - app description does **not** start with `Connected -`
    4. Submit the prompt, then press Up to restore draft and submit again.  
       Expected: it keeps the same selected duplicate target.
    5. Use `/resume` to reopen the session and send again.  
    Expected: restored mention still resolves to the same duplicate target.
  • Queue nudges while plan generating (#10457)
    ## Summary
    
    This PR fixes a UI/streaming race when nudged or steer-enabled messages
    are queued during an active Plan stream.
    
    Previously, `submit_user_message_with_mode` switched collaboration mode
    immediately (via `set_collaboration_mask`) even when the message was
    queued. If that happened mid-Plan stream, `active_mode_kind` could flip
    away from Plan before the turn finished, causing subsequent
    `on_plan_delta` updates to be ignored in the UI.
    
    Now, mode switching is deferred until the queued message is actually
    submitted.
    
    ## What changed
    
    - Added a per-message deferred mode override on `UserMessage`:
      - `collaboration_mode_override: Option<CollaborationModeMask>`
    - Updated `submit_user_message_with_mode` to:
      - create a `UserMessage` carrying the mode override
    - queue or submit that message without mutating global mode immediately
    - Updated `submit_user_message` to:
    - apply `collaboration_mode_override` just before constructing/sending
    `Op::UserTurn`
    - Kept queueing condition scoped to active Plan stream rendering:
    - queue only while plan output is actively streaming in TUI
    (`plan_stream_controller.is_some()`)
    
    ## Why
    
    This preserves Plan mode for the remainder of the in-flight Plan turn,
    so streamed plan deltas continue rendering correctly, while still
    ensuring the follow-up queued message is sent with the intended
    collaboration mode.
    
    ## Behavior after this change
    
    - If a nudged/steer submission happens while Plan output is actively
    streaming:
      - message is queued
      - UI stays in Plan mode for the running turn
    - once dequeued/submitted, mode override is applied and the message is
    sent in the intended mode
    - If no Plan stream is active:
    - submission proceeds immediately and mode override is applied as before
    
    ## Tests
    
    Added/updated coverage in `tui/src/chatwidget/tests.rs`:
    
    - `submit_user_message_with_mode_queues_while_plan_stream_is_active`
      - asserts mode remains Plan while queued
    - asserts mode switches to Code when queued message is actually
    submitted
    - `submit_user_message_with_mode_submits_when_plan_stream_is_not_active`
    - `steer_enter_queues_while_plan_stream_is_active`
    - `steer_enter_submits_when_plan_stream_is_not_active`
    
    Also updated existing `UserMessage { ... }` test fixtures to include the
    new field.
    
    ## Codex author
    `codex fork 019c1047-d5d5-7c92-a357-6009604dc7e8`
  • Removed "exec_policy" feature flag (#10851)
    This is no longer needed because it's on by default
  • Handle required MCP startup failures across components (#10902)
    Summary
    - add a `required` flag for MCP servers everywhere config/CLI data is
    touched so mandatory helpers can be round-tripped
    - have `codex exec` and `codex app-server` thread start/resume fail fast
    when required MCPs fail to initialize
  • Personality setting is no longer available in experimental menu (#10852)
    This PR removes the inaccurate "Disable in /experimental." statement now
    that the "personality" feature flag is no longer experimental.
    
    This addresses #10850