Commit Graph

244 Commits

  • fix: report an appropriate error in the TUI for malformed rules (#9011)
    The underlying issue is that when we encountered an error starting a
    conversation (any sort of error, though making `$CODEX_HOME/rules` a
    file rather than folder was the example in #8803), then we were writing
    the message to stderr, but this could be printed over by our UI
    framework so the user would not see it. In general, we disallow the use
    of `eprintln!()` in this part of the code for exactly this reason,
    though this was suppressed by an `#[allow(clippy::print_stderr)]`.
    
    This attempts to clean things up by changing `handle_event()` and
    `handle_tui_event()` to return a `Result<AppRunControl>` instead of a
    `Result<bool>`, which is a new type introduced in this PR (and depends
    on `ExitReason`, also a new type):
    
    ```rust
    #[derive(Debug)]
    pub(crate) enum AppRunControl {
        Continue,
        Exit(ExitReason),
    }
    
    #[derive(Debug, Clone)]
    pub enum ExitReason {
        UserRequested,
        Fatal(String),
    }
    ```
    
    This makes it possible to exit the primary control flow of the TUI with
    richer information. This PR adds `ExitReason` to the existing
    `AppExitInfo` struct and updates `handle_app_exit()` to print the error
    and exit code `1` in the event of `ExitReason::Fatal`.
    
    I tried to create an integration test for this, but it was a bit
    involved, so I published it as a separate PR:
    https://github.com/openai/codex/pull/9166. For this PR, please have
    faith in my manual testing!
    
    Fixes https://github.com/openai/codex/issues/8803.
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/9011).
    * #9166
    * __->__ #9011
  • Fix spinner/Esc interrupt when MCP startup completes mid-turn (#8661)
    ## **Problem**
    
    Codex’s TUI uses a single “task running” indicator (spinner + Esc interrupt hint)
    to communicate “the UI is busy”. In practice, “busy” can mean two different
    things: an agent turn is running, or MCP servers are still starting up. Without a
    clear contract, those lifecycles can interfere: startup completion can clear the
    spinner while a turn is still in progress, or the UI can appear idle while MCP is
    still booting. This is user-visible confusion during the most important moments
    (startup and the first turn), so it was worth making the contract explicit and
    guarding it.
    
    ## **Mental model**
    
    `ChatWidget` is the UI-side adapter for the `codex_core::protocol` event stream.
    It receives `EventMsg` events and updates two major UI surfaces: the transcript
    (history/streaming cells) and the bottom pane (composer + status indicator).
    
    The key concept after this change is that the bottom pane’s “task running”
    indicator is treated as **derived UI-busy state**, not “agent is running”. It is
    considered active while either:
    - an agent turn is in progress (`TurnStarted` → completion/abort), or
    - MCP startup is in progress (`McpStartupUpdate` → `McpStartupComplete`).
    
    Those lifecycles are tracked independently, and the bottom-pane indicator is
    defined as their union.
    
    ## **Non-goals**
    
    - This does not introduce separate UI indicators for “turn busy” vs “MCP busy”.
    - This does not change MCP startup behavior, ordering guarantees, or core
      protocol semantics.
    - This does not rework unrelated status/header rendering or transcript layout.
    
    ## **Tradeoffs**
    
    - The “one flag represents multiple lifecycles” approach remains lossy: it
      preserves correct “busy vs idle” semantics but cannot express *which* kind of
      busy is happening without further UI changes.
    - The design keeps complexity low by keeping a single derived boolean, rather
      than adding a more expressive bottom-pane state machine. That’s chosen because
      it matches existing UX and minimizes churn while fixing the confusion.
    
    ## **Architecture**
    
    - `codex-core` owns the actual lifecycles and emits `codex_core::protocol`
      events.
    - `ChatWidget` owns the UI interpretation of those lifecycles. It is responsible
      for keeping the bottom pane’s derived “busy” state consistent with the event
      stream, and for updating the status header when MCP progress updates arrive.
    - The bottom pane remains a dumb renderer of the single “task running” flag; it
      does not learn about MCP or agent turns directly.
    
    ## **Observability**
    
    - When working: the spinner/Esc hint stays visible during MCP startup and does
      not disappear mid-turn when `McpStartupComplete` arrives; startup status
      headers can update without clearing “busy” for an active turn.
    - When broken: you’ll see the spinner/hint flicker off while output is still
      streaming, or the UI appears idle while MCP startup status is still changing.
    
    ## **Tests**
    
    - Adds/strengthens a regression test that asserts MCP startup completion does
      not clear the “task running” indicator for an active turn (in both `tui` and
      `tui2` variants).
    - These tests prove the **contract** (“busy is the union of turn + startup”) at
      the UI boundary; they do not attempt to validate MCP startup ordering,
      real-world startup timing, or backend integration behavior.
    
    Fixes #7017
    
    Signed-off-by: 2mawi2 <2mawi2@users.noreply.github.com>
    Co-authored-by: 2mawi2 <2mawi2@users.noreply.github.com>
    Co-authored-by: Josh McKinney <joshka@openai.com>
  • Fix queued messages during /review (#9122)
    Sending a message during /review interrupts the review, whereas during
    normal operation, sending a message while the agent is running will
    queue the message. This is unexpected behavior, and since /review
    usually takes a while, it takes away a potentially useful operation.
    
    Summary
    - Treat review mode as an active task for message queuing so inputs
    don’t inject into the running review turn.
    - Prevents user submissions from rendering immediately in the transcript
    while the review continues streaming.
    - Keeps review UX consistent with normal “task running” behavior and
    avoids accidental interrupt/replacement.
    
    Notes
    - This change only affects UI queuing logic; core review flow and task
    lifecycle remain unchanged.
  • Show tab queue hint in footer (#9138)
    - show the Tab queue hint in the footer when a task is running with
    Steer enabled
    - drop the history queue hint and add footer snapshots
  • fix(tui): show in-flight coalesced tool calls in transcript overlay (#8246)
    ### Problem
    Ctrl+T transcript overlay can omit in-flight coalesced tool calls because it
    renders only committed transcript cells while the main viewport can render the
    current in-flight ChatWidget.active_cell immediately.
    
    ### Mental model
    The UI has both committed transcript cells (finalized HistoryCell entries) and
    an in-flight active cell that can mutate in place while streaming, often
    representing a coalesced exec/tool group. The transcript overlay renders
    committed cells plus a render-only live tail derived from the current active
    cell. The live tail is cached and only recomputed when its cache key changes,
    which is derived from terminal width (wrapping), active-cell revision
    (in-place mutations), stream continuation (spacing), and animation tick
    (time-based visuals).
    
    ### Non-goals
    This does not change coalescing rules, flush boundaries, or when active cells
    become committed. It does not change tool-call semantics or transcript
    persistence; it is a rendering-only improvement for the overlay.
    
    ### Tradeoffs
    This adds cache invalidation complexity: correctness depends on bumping an
    active-cell revision (and/or providing an animation tick) when the active cell
    mutates in place. The mechanism is implemented in both codex-tui and codex-tui2,
    which keeps behavior consistent but risks drift if future changes are not
    applied in lockstep.
    
    ### Architecture
    App special-cases transcript overlay draws to sync a live tail from ChatWidget
    into TranscriptOverlay. TranscriptOverlay remains the owner of committed
    transcript cells; the live tail is an optional appended renderable.
    HistoryCell::transcript_animation_tick() allows time-dependent transcript output
    (spinner/shimmer) to invalidate the cached tail without requiring data mutation.
    
    ### Observability
    Manual verification is to open Ctrl+T while an exploring/coalesced active cell
    is still in-flight and confirm the overlay includes the same in-flight tool-call
    group the main viewport shows. The overlay is kept in sync by App passing an
    active-cell key and transcript lines into TranscriptOverlay::sync_live_tail; the
    key must change when the active cell mutates or animates.
    
    ### Tests
    Snapshot tests validate that the transcript overlay renders a live tail appended
    after committed cells and that identical keys short-circuit recomputation. Unit
    tests validate that active-cell revision bumps occur on specific in-place
    mutations (e.g. unified exec wait cell command display becoming known late) so
    cached tails are invalidated.
    
    ## Documentation patches (module, type, function)
    
    ### Module-level docs (invariants + mechanisms)
    - codex-rs/tui/src/app_backtrack.rs:1
    - codex-rs/tui/src/chatwidget.rs:1
    - codex-rs/tui/src/pager_overlay.rs:1
    - codex-rs/tui/src/history_cell.rs:1
    - codex-rs/tui2/src/app_backtrack.rs:1
    - codex-rs/tui2/src/chatwidget.rs:1
    - codex-rs/tui2/src/pager_overlay.rs:1
    - codex-rs/tui2/src/history_cell.rs:1
    
    ### Type-level docs (cache key + invariants)
    - codex-rs/tui/src/chatwidget.rs (ChatWidget.active_cell_revision, ActiveCellTranscriptKey)
    - codex-rs/tui/src/pager_overlay.rs (TranscriptOverlay live tail storage model)
    - codex-rs/tui/src/history_cell.rs (HistoryCell::transcript_animation_tick, UnifiedExecWaitCell::update_command_display)
    - Mirrored in codex-rs/tui2/src/chatwidget.rs, codex-rs/tui2/src/pager_overlay.rs, codex-rs/tui2/src/history_cell.rs
    
    ### Function-level docs (why/when/guarantees/pitfalls)
    - codex-rs/tui/src/app_backtrack.rs (overlay_forward_event)
    - codex-rs/tui/src/chatwidget.rs (active_cell_transcript_key, active_cell_transcript_lines)
    - codex-rs/tui/src/pager_overlay.rs (sync_live_tail, take_live_tail_renderable)
    - codex-rs/tui/src/history_cell.rs (transcript_animation_tick, UnifiedExecWaitCell::update_command_display)
    - Mirrored in codex-rs/tui2 equivalents where present
    
    ### Validation performed
    - cd codex-rs && just fmt
    - cd codex-rs && cargo test -p codex-tui
    - cd codex-rs && cargo test -p codex-tui2
    
    ## Design inconsistencies / risks
    
    - Cache invalidation is a distributed responsibility: any future in-place active
      cell transcript mutation that forgets to bump active_cell_revision (or expose
      an animation tick) can leave the transcript overlay live tail out of sync with
      the main viewport.
    - TranscriptOverlay tail handling assumes a structural invariant that the live
      tail, when present, is exactly one trailing renderable after the committed cell
      renderables; if renderable construction changes in a way that violates that
      assumption, tail insertion/removal logic becomes incorrect.
    - codex-tui and codex-tui2 duplicate the live-tail mechanism; the documentation
      is aligned, but the implementation can still drift unless changes continue to
      be applied in lockstep.
  • feat: wire fork to codex cli (#8994)
    ## Summary
    - add `codex fork` subcommand and `/fork` slash command mirroring resume
    - extend session picker to support fork/resume actions with dynamic
    labels in tui/tui2
    - wire fork selection flow through tui bootstraps and add fork-related
    tests
  • Label attached images so agent can understand in-message labels (#8950)
    Agent wouldn't "see" attached images and would instead try to use the
    view_file tool:
    <img width="1516" height="504" alt="image"
    src="https://github.com/user-attachments/assets/68a705bb-f962-4fc1-9087-e932a6859b12"
    />
    
    In this PR, we wrap image content items in XML tags with the name of
    each image (now just a numbered name like `[Image #1]`), so that the
    model can understand inline image references (based on name). We also
    put the image content items above the user message which the model seems
    to prefer (maybe it's more used to definitions being before references).
    
    We also tweak the view_file tool description which seemed to help a bit
    
    Results on a simple eval set of images:
    
    Before
    <img width="980" height="310" alt="image"
    src="https://github.com/user-attachments/assets/ba838651-2565-4684-a12e-81a36641bf86"
    />
    
    After
    <img width="918" height="322" alt="image"
    src="https://github.com/user-attachments/assets/10a81951-7ee6-415e-a27e-e7a3fd0aee6f"
    />
    
    ```json
    [
      {
        "id": "single_describe",
        "prompt": "Describe the attached image in one sentence.",
        "images": ["image_a.png"]
      },
      {
        "id": "single_color",
        "prompt": "What is the dominant color in the image? Answer with a single color word.",
        "images": ["image_b.png"]
      },
      {
        "id": "orientation_check",
        "prompt": "Is the image portrait or landscape? Answer in one sentence.",
        "images": ["image_c.png"]
      },
      {
        "id": "detail_request",
        "prompt": "Look closely at the image and call out any small details you notice.",
        "images": ["image_d.png"]
      },
      {
        "id": "two_images_compare",
        "prompt": "I attached two images. Are they the same or different? Briefly explain.",
        "images": ["image_a.png", "image_b.png"]
      },
      {
        "id": "two_images_captions",
        "prompt": "Provide a short caption for each image (Image 1, Image 2).",
        "images": ["image_c.png", "image_d.png"]
      },
      {
        "id": "multi_image_rank",
        "prompt": "Rank the attached images from most colorful to least colorful.",
        "images": ["image_a.png", "image_b.png", "image_c.png"]
      },
      {
        "id": "multi_image_choice",
        "prompt": "Which image looks more vibrant? Answer with 'Image 1' or 'Image 2'.",
        "images": ["image_b.png", "image_d.png"]
      }
    ]
    ```
  • Elevated sandbox NUX (#8789)
    Elevated Sandbox NUX:
    
    * prompt for elevated sandbox setup when agent mode is selected (via
    /approvals or at startup)
    * prompt for degraded sandbox if elevated setup is declined or fails
    * introduce /elevate-sandbox command to upgrade from degraded
    experience.
  • config requirements: improve requirement error messages (#8843)
    **Before:**
    ```
    Error loading configuration: value `Never` is not in the allowed set [OnRequest]
    ```
    
    **After:**
    ```
    Error loading configuration: invalid value for `approval_policy`: `Never` is not in the
    allowed set [OnRequest] (set by MDM com.openai.codex:requirements_toml_base64)
    ```
    
    Done by introducing a new struct `ConfigRequirementsWithSources` onto
    which we `merge_unset_fields` now. Also introduces a pair of requirement
    value and its `RequirementSource` (inspired by `ConfigLayerSource`):
    
    ```rust
    pub struct Sourced<T> {
        pub value: T,
        pub source: RequirementSource,
    }
    ```
  • fix: implement 'Allow this session' for apply_patch approvals (#8451)
    **Summary**
    This PR makes “ApprovalDecision::AcceptForSession / don’t ask again this
    session” actually work for `apply_patch` approvals by caching approvals
    based on absolute file paths in codex-core, properly wiring it through
    app-server v2, and exposing the choice in both TUI and TUI2.
    - This brings `apply_patch` calls to be at feature-parity with general
    shell commands, which also have a "Yes, and don't ask again" option.
    - This also fixes VSCE's "Allow this session" button to actually work.
    
    While we're at it, also split the app-server v2 protocol's
    `ApprovalDecision` enum so execpolicy amendments are only available for
    command execution approvals.
    
    **Key changes**
    - Core: per-session patch approval allowlist keyed by absolute file
    paths
    - Handles multi-file patches and renames/moves by recording both source
    and destination paths for `Update { move_path: Some(...) }`.
    - Extend the `Approvable` trait and `ApplyPatchRuntime` to work with
    multiple keys, because an `apply_patch` tool call can modify multiple
    files. For a request to be auto-approved, we will need to check that all
    file paths have been approved previously.
    - App-server v2: honor AcceptForSession for file changes
    - File-change approval responses now map AcceptForSession to
    ReviewDecision::ApprovedForSession (no longer downgraded to plain
    Approved).
    - Replace `ApprovalDecision` with two enums:
    `CommandExecutionApprovalDecision` and `FileChangeApprovalDecision`
    - TUI / TUI2: expose “don’t ask again for these files this session”
    - Patch approval overlays now include a third option (“Yes, and don’t
    ask again for these files this session (s)”).
        - Snapshot updates for the approval modal.
    
    **Tests added/updated**
    - Core:
    - Integration test that proves ApprovedForSession on a patch skips the
    next patch prompt for the same file
    - App-server:
    - v2 integration test verifying
    FileChangeApprovalDecision::AcceptForSession works properly
    
    **User-visible behavior**
    - When the user approves a patch “for session”, future patches touching
    only those previously approved file(s) will no longer prompt gain during
    that session (both via app-server v2 and TUI/TUI2).
    
    **Manual testing**
    Tested both TUI and TUI2 - see screenshots below.
    
    TUI:
    <img width="1082" height="355" alt="image"
    src="https://github.com/user-attachments/assets/adcf45ad-d428-498d-92fc-1a0a420878d9"
    />
    
    
    TUI2:
    <img width="1089" height="438" alt="image"
    src="https://github.com/user-attachments/assets/dd768b1a-2f5f-4bd6-98fd-e52c1d3abd9e"
    />
  • chore: unify conversation with thread name (#8830)
    Done and verified by Codex + refactor feature of RustRover
  • Enable model upgrade popup even when selected model is no longer in picker (#8802)
    With `config.toml`:
    ```
    model = "gpt-5.1-codex"
    ```
    (where `gpt-5.1-codex` has `show_in_picker: false` in
    [`model_presets.rs`](https://github.com/openai/codex/blob/main/codex-rs/core/src/models_manager/model_presets.rs);
    this happens if the user hasn't used codex in a while so they didn't see
    the popup before their model was changed to `show_in_picker: false`)
    
    The upgrade picker used to not show (because `gpt-5.1-codex` was
    filtered out of the model list in code). Now, the filtering is done
    downstream in tui and app-server, so the model upgrade popup shows:
    
    <img width="1503" height="227" alt="Screenshot 2026-01-06 at 5 04 37 PM"
    src="https://github.com/user-attachments/assets/26144cc2-0b3f-4674-ac17-e476781ec548"
    />
  • fix: truncate long approval prefixes when rendering (#8734)
    Fixes inscrutable multiline approval requests:
    <img width="686" height="844" alt="image"
    src="https://github.com/user-attachments/assets/cf9493dc-79e6-4168-8020-0ef0fe676d5e"
    />
  • Remove model family from tui (#8488)
    - Remove model family from tui
  • [chore] add additional_details to StreamErrorEvent + wire through (#8307)
    ### What
    
    Builds on #8293.
    
    Add `additional_details`, which contains the upstream error message, to
    relevant structures used to pass along retryable `StreamError`s.
    
    Uses the new TUI status indicator's `details` field (shows under the
    status header) to display the `additional_details` error to the user on
    retryable `Reconnecting...` errors. This adds clarity for users for
    retryable errors.
    
    Will make corresponding change to VSCode extension to show
    `additional_details` as expandable from the `Reconnecting...` cell.
    
    Examples:
    <img width="1012" height="326" alt="image"
    src="https://github.com/user-attachments/assets/f35e7e6a-8f5e-4a2f-a764-358101776996"
    />
    
    <img width="1526" height="358" alt="image"
    src="https://github.com/user-attachments/assets/0029cbc0-f062-4233-8650-cc216c7808f0"
    />
  • [tui] add optional details to TUI status header (#8293)
    ### What
    
    Add optional `details` field to TUI's status indicator header. `details`
    is shown under the header with text wrapping and a max height of 3
    lines.
    
    Duplicated changes to `tui2`.
    
    ### Why
    
    Groundwork for displaying error details under `Reconnecting...` for
    clarity with retryable errors.
    
    Basic examples
    <img width="1012" height="326" alt="image"
    src="https://github.com/user-attachments/assets/dd751ceb-b179-4fb2-8fd1-e4784d6366fb"
    />
    
    <img width="1526" height="358" alt="image"
    src="https://github.com/user-attachments/assets/bbe466fc-faff-4a78-af7f-3073ccdd8e34"
    />
    
    Truncation example
    <img width="936" height="189" alt="image"
    src="https://github.com/user-attachments/assets/f3f1b5dd-9050-438b-bb07-bd833c03e889"
    />
    
    ### Tests
    Tested locally, added tests for truncation.
  • feat: open prompt in configured external editor (#7606)
    Add `ctrl+g` shortcut to enable opening current prompt in configured
    editor (`$VISUAL` or `$EDITOR`).
    
    
    - Prompt is updated with editor's content upon editor close.
    - Paste placeholders are automatically expanded when opening the
    external editor, and are not "recompressed" on close
    - They could be preserved in the editor, but it would be hard to prevent
    the user from modifying the placeholder text directly, which would drop
    the mapping to the `pending_paste` value
    - Image placeholders stay as-is
    - `ctrl+g` explanation added to shortcuts menu, snapshot tests updated
    
    
    
    https://github.com/user-attachments/assets/4ee05c81-fa49-4e99-8b07-fc9eef0bbfce
  • Rename OpenAI models to models manager (#8346)
    # 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.
  • feat: make ConstraintError an enum (#8330)
    This will make it easier to test for expected errors in unit tests since
    we can compare based on the field values rather than the message (which
    might change over time). See https://github.com/openai/codex/pull/8298
    for an example.
    
    It also ensures more consistency in the way a `ConstraintError` is
    constructed.
  • Fix tests (#8299)
    Fix broken tests.
  • feat: collapse "waiting" of unified_exec (#8257)
    Screenshots here but check the snapshot files to see it better
    <img width="712" height="408" alt="Screenshot 2025-12-18 at 11 58 02"
    src="https://github.com/user-attachments/assets/84a2c410-0767-4870-84d1-ae1c0d4c445e"
    />
    <img width="523" height="352" alt="Screenshot 2025-12-18 at 11 17 41"
    src="https://github.com/user-attachments/assets/d029c7ea-0feb-4493-9dca-af43a0c70c52"
    />
  • chore: migrate from Config::load_from_base_config_with_overrides to ConfigBuilder (#8276)
    https://github.com/openai/codex/pull/8235 introduced `ConfigBuilder` and
    this PR updates all call non-test call sites to use it instead of
    `Config::load_from_base_config_with_overrides()`.
    
    This is important because `load_from_base_config_with_overrides()` uses
    an empty `ConfigRequirements`, which is a reasonable default for testing
    so the tests are not influenced by the settings on the host. This method
    is now guarded by `#[cfg(test)]` so it cannot be used by business logic.
    
    Because `ConfigBuilder::build()` is `async`, many of the test methods
    had to be migrated to be `async`, as well. On the bright side, this made
    it possible to eliminate a bunch of `block_on_future()` stuff.
  • caribou (#8265)
    Welcome caribou
    
    <img width="1536" height="1024" alt="image"
    src="https://github.com/user-attachments/assets/2a67b21f-40cf-4518-aee4-691af331ab50"
    />
  • chores: clean picker (#8232)
    # 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.
  • feat: model picker (#8209)
    # 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.
  • feat: unified exec footer (#8117)
    # With `unified_exec`
    Known tools are correctly casted
    <img width="1150" height="312" alt="Screenshot 2025-12-16 at 19 27 28"
    src="https://github.com/user-attachments/assets/24150ee5-e88d-461b-a459-483c24784196"
    />
    If a session exit the turn, we render it with the "Ran ..."
    <img width="1168" height="355" alt="Screenshot 2025-12-16 at 19 27 58"
    src="https://github.com/user-attachments/assets/3f00b60c-2d57-4f9d-a201-9cc8388957cb"
    />
    If a session does not exit during the turn, it is closed at the end of
    the turn but this is not rendered
    <img width="642" height="342" alt="Screenshot 2025-12-16 at 19 34 37"
    src="https://github.com/user-attachments/assets/c2bd9283-7017-4915-ba73-c52199b0b28e"
    />
    
    # Without `unified_exec`
    No changes
    <img width="740" height="603" alt="Screenshot 2025-12-16 at 19 31 21"
    src="https://github.com/user-attachments/assets/ca5d90fe-a9b2-42ba-bcd7-3e98c4ed22e8"
    />
  • feat: experimental menu (#8071)
    This will automatically render any `Stage::Beta` features.
    
    The change only gets applied to the *next session*. This started as a
    bug but actually this is a good thing to prevent out of distribution
    push
    
    <img width="986" height="288" alt="Screenshot 2025-12-15 at 15 38 35"
    src="https://github.com/user-attachments/assets/78b7a71d-0e43-4828-a118-91c5237909c7"
    />
    
    
    <img width="509" height="109" alt="Screenshot 2025-12-15 at 17 35 44"
    src="https://github.com/user-attachments/assets/6933de52-9b66-4abf-b58b-a5f26d5747e2"
    />
  • feat: Constrain values for approval_policy (#7778)
    Constrain `approval_policy` through new `admin_policy` config.
    
    This PR will:
    1. Add a `admin_policy` section to config, with a single field (for now)
    `allowed_approval_policies`. This list constrains the set of
    user-settable `approval_policy`s.
    2. Introduce a new `Constrained<T>` type, which combines a current value
    and a validator function. The validator function ensures disallowed
    values are not set.
    3. Change the type of `approval_policy` on `Config` and
    `SessionConfiguration` from `AskForApproval` to
    `Constrained<AskForApproval>`. The validator function is set by the
    values passed into `allowed_approval_policies`.
    4. `GenericDisplayRow`: add a `disabled_reason: Option<String>`. When
    set, it disables selection of the value and indicates as such in the
    menu. This also makes it unselectable with arrow keys or numbers. This
    is used in the `/approvals` menu.
    
    Follow ups are:
    1. Do the same thing to `sandbox_policy`.
    2. Propagate the allowed set of values through app-server for the
    extension (though already this should prevent app-server from setting
    this values, it's just that we want to disable UI elements that are
    unsettable).
    
    Happy to split this PR up if you prefer, into the logical numbered areas
    above. Especially if there are parts we want to gavel on separately
    (e.g. admin_policy).
    
    Disabled full access:
    <img width="1680" height="380" alt="image"
    src="https://github.com/user-attachments/assets/1fb61c8c-1fcb-4dc4-8355-2293edb52ba0"
    />
    
    Disabled `--yolo` on startup:
    <img width="749" height="76" alt="image"
    src="https://github.com/user-attachments/assets/0a1211a0-6eb1-40d6-a1d7-439c41e94ddb"
    />
    
    CODEX-4087
  • feat: unified exec footer (#8067)
    <img width="452" height="205" alt="Screenshot 2025-12-15 at 17 54 44"
    src="https://github.com/user-attachments/assets/9ece0b1c-8387-4dfc-b883-c6a68ea1b663"
    />
  • Reimplement skills loading using SkillsManager + skills/list op. (#7914)
    refactor the way we load and manage skills:
    1. Move skill discovery/caching into SkillsManager and reuse it across
    sessions.
    2. Add the skills/list API (Op::ListSkills/SkillsListResponse) to fetch
    skills for one or more cwds. Also update app-server for VSCE/App;
    3. Trigger skills/list during session startup so UIs preload skills and
    handle errors immediately.
  • fix: introduce AbsolutePathBuf as part of sandbox config (#7856)
    Changes the `writable_roots` field of the `WorkspaceWrite` variant of
    the `SandboxPolicy` enum from `Vec<PathBuf>` to `Vec<AbsolutePathBuf>`.
    This is helpful because now callers can be sure the value is an absolute
    path rather than a relative one. (Though when using an absolute path in
    a Seatbelt config policy, we still have to _canonicalize_ it first.)
    
    Because `writable_roots` can be read from a config file, it is important
    that we are able to resolve relative paths properly using the parent
    folder of the config file as the base path.
  • fix: restore MCP startup progress messages in TUI (fixes #7827) (#7828)
    ## Problem
    
    The introduction of `notify_sandbox_state_change()` in #7112 caused a
    regression where the blocking call in `Session::new()` waits for all MCP
    servers to fully initialize before returning. This prevents the TUI
    event loop from starting, resulting in `McpStartupUpdateEvent` messages
    being emitted but never consumed or displayed. As a result, the app
    appears to hang during startup, and users do not see the expected
    "Booting MCP server: {name}" status line.
    
    Issue: [#7827](https://github.com/openai/codex/issues/7827)
    
    ## Solution
    This change moves sandbox state notification into each MCP server's
    background initialization task. The notification is sent immediately
    after the server transitions to the Ready state. This approach:
    - Avoids blocking `Session::new()`, allowing the TUI event loop to start
    promptly.
    - Ensures each MCP server receives its sandbox state before handling any
    tool calls.
    - Restores the display of "Booting MCP server" status lines during
    startup.
    
    ## Key Changes
    - Added `ManagedClient::notify_sandbox_state()` method.
    - Passed sandbox_state to `McpConnectionManager::initialize()`.
    - Sends sandbox state notification in the background task after the
    server reaches Ready status.
    - Removed blocking notify_sandbox_state_change() methods.
    - Added a chatwidget snapshot test for the "Booting MCP server" status
    line.
    
    ## Regression Details
    
    Regression was bisected to #7112, which introduced the blocking
    behavior.
    
    ---------
    
    Co-authored-by: Michael Bolin <bolinfest@gmail.com>
    Co-authored-by: Michael Bolin <mbolin@openai.com>
  • Fix misleading 'maximize' high effort description on xhigh models (#7874)
    ## Notes
    - switch misleading High reasoning effort descriptions from "Maximizes
    reasoning depth" to "Higher reasoning depth" across models with xhigh
    reasoning. Affects GPT-5.1 Codex Max and Robin
    - refresh model list fixtures and chatwidget snapshots to match new copy
    
    ## Revision
    - R2: Change 'Higher' to 'Greater'
    - R1: Initial
    
    ## Testing
    
    <img width="583" height="142" alt="image"
    src="https://github.com/user-attachments/assets/1ddd8971-7841-4cb3-b9ba-91095a7435d2"
    />
    
    <img width="838" height="142" alt="image"
    src="https://github.com/user-attachments/assets/79aaedbf-7624-4695-b822-93dea7d6a800"
    />
  • feat: robin (#7882)
    <img width="554" height="554" alt="image"
    src="https://github.com/user-attachments/assets/aa86f4c8-fb34-4b0e-8b03-3a9980dfdb08"
    />
    
    ---------
    
    Co-authored-by: Dylan Hurd <dylan.hurd@openai.com>
  • fix(stuff) (#7855)
    Co-authored-by: Ahmed Ibrahim <aibrahim@openai.com>
  • Inject SKILL.md when it's explicitly mentioned. (#7763)
    1. Skills load once in core at session start; the cached outcome is
    reused across core and surfaced to TUI via SessionConfigured.
    2. TUI detects explicit skill selections, and core injects the matching
    SKILL.md content into the turn when a selected skill is present.
  • make model optional in config (#7769)
    - Make Config.model optional and centralize default-selection logic in
    ModelsManager, including a default_model helper (with
    codex-auto-balanced when available) so sessions now carry an explicit
    chosen model separate from the base config.
    - Resolve `model` once in `core` and `tui` from config. Then store the
    state of it on other structs.
    - Move refreshing models to be before resolving the default model
  • Removed experimental "command risk assessment" feature (#7799)
    This experimental feature received lukewarm reception during internal
    testing. Removing from the code base.