Commit Graph

35 Commits

  • UI tweaks on skills popup. (#8250)
    Only display the skill name (not the folder), and truncate the skill
    description to a maximum of two lines.
  • feat: introduce ExternalSandbox policy (#8290)
    ## Description
    
    Introduced `ExternalSandbox` policy to cover use case when sandbox
    defined by outside environment, effectively it translates to
    `SandboxMode#DangerFullAccess` for file system (since sandbox configured
    on container level) and configurable `network_access` (either Restricted
    or Enabled by outside environment).
    
    as example you can configure `ExternalSandbox` policy as part of
    `sendUserTurn` v1 app_server API:
    
    ```
     {
                "conversationId": <id>,
                "cwd": <cwd>,
                "approvalPolicy": "never",
                "sandboxPolicy": {
                      "type": ""external-sandbox",
                      "network_access": "enabled"/"restricted"
                },
                "model": <model>,
                "effort": <effort>,
                ....
            }
    ```
  • 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.
  • Support skills shortDescription. (#8278)
    Allow SKILL.md to specify a more human-readable short description as
    skill metadata.
  • Terminal Detection Metadata for Per-Terminal Scroll Scaling (#8252)
    # Terminal Detection Metadata for Per-Terminal Scroll Scaling
    
    ## Summary
    Expand terminal detection into structured metadata (`TerminalInfo`) with
    multiplexer awareness, plus a testable environment shim and
    characterization tests.
    
    ## Context / Motivation
    - TUI2 owns its viewport and scrolling model (see
    `codex-rs/tui2/docs/tui_viewport_and_history.md`), so scroll behavior
    must be consistent across terminals and independent of terminal
    scrollback quirks.
    - Prior investigations show mouse wheel scroll deltas vary noticeably by
    terminal. To tune scroll scaling (line increments per wheel tick) we
    need reliable terminal identification, including when running inside
    tmux/zellij.
    - tmux is especially tricky because it can mask the underlying terminal;
    we now consult `tmux display-message` client termtype/name to attribute
    sessions to the actual terminal rather than tmux itself.
    - This remains backwards compatible with the existing OpenTelemetry
    user-agent token because `user_agent()` is still derived from the same
    environment signals (now via `TerminalInfo`).
    
    ## Changes
    - Introduce `TerminalInfo`, `TerminalName`, and `Multiplexer` with
    `TERM_PROGRAM`/`TERM`/multiplexer detection and user-agent formatting in
    `codex-rs/core/src/terminal.rs`.
    - Add an injectable `Environment` trait + `FakeEnvironment` for testing,
    and comprehensive characterization tests covering known terminals, tmux
    client termtype/name, and zellij.
    - Document module usage and detection order; update `terminal_info()` to
    be the primary interface for callers.
    
    ## Testing
    - `cargo test -p codex-core terminal::tests`
    - manually checked ghostty, iTerm2, Terminal.app, vscode, tmux, zellij,
    Warp, alacritty, kitty.
    ```
    2025-12-18T07:07:49.191421Z  INFO Detected terminal info terminal=TerminalInfo { name: Iterm2, term_program: Some("iTerm.app"), version: Some("3.6.6"), term: None, multiplexer: None }
    2025-12-18T07:07:57.991776Z  INFO Detected terminal info terminal=TerminalInfo { name: AppleTerminal, term_program: Some("Apple_Terminal"), version: Some("455.1"), term: None, multiplexer: None }
    2025-12-18T07:08:07.732095Z  INFO Detected terminal info terminal=TerminalInfo { name: WarpTerminal, term_program: Some("WarpTerminal"), version: Some("v0.2025.12.10.08.12.stable_03"), term: None, multiplexer: None }
    2025-12-18T07:08:24.860316Z  INFO Detected terminal info terminal=TerminalInfo { name: Kitty, term_program: None, version: None, term: None, multiplexer: None }
    2025-12-18T07:08:38.302761Z  INFO Detected terminal info terminal=TerminalInfo { name: Alacritty, term_program: None, version: None, term: None, multiplexer: None }
    2025-12-18T07:08:50.887748Z  INFO Detected terminal info terminal=TerminalInfo { name: VsCode, term_program: Some("vscode"), version: Some("1.107.1"), term: None, multiplexer: None }
    2025-12-18T07:10:01.309802Z  INFO Detected terminal info terminal=TerminalInfo { name: WezTerm, term_program: Some("WezTerm"), version: Some("20240203-110809-5046fc22"), term: None, multiplexer: None }
    2025-12-18T08:05:17.009271Z  INFO Detected terminal info terminal=TerminalInfo { name: Ghostty, term_program: Some("ghostty"), version: Some("1.2.3"), term: None, multiplexer: None }
    2025-12-18T08:05:23.819973Z  INFO Detected terminal info terminal=TerminalInfo { name: Ghostty, term_program: Some("ghostty"), version: Some("1.2.3"), term: Some("xterm-ghostty"), multiplexer: Some(Tmux { version: Some("3.6a") }) }
    2025-12-18T08:05:35.572853Z  INFO Detected terminal info terminal=TerminalInfo { name: Ghostty, term_program: Some("ghostty"), version: Some("1.2.3"), term: None, multiplexer: Some(Zellij) }
    ```
    
    ## Notes / Follow-ups
    - Next step is to wire `TerminalInfo` into TUI2’s scroll scaling
    configuration and add a per-terminal tuning table.
    - The log output in TUI2 helps validate real-world detection before
    applying behavior changes.
  • tui: improve rendering of search cell (#8273)
    before:
    
    <img width="795" height="150" alt="Screenshot 2025-12-18 at 10 48 01 AM"
    src="https://github.com/user-attachments/assets/6f4d8856-b4c2-4e2a-b60a-b86f82b956a0"
    />
    
    after:
    
    <img width="795" height="150" alt="Screenshot 2025-12-18 at 10 48 39 AM"
    src="https://github.com/user-attachments/assets/dd0d167a-5d09-4bb7-9d36-95a2eb1aaa83"
    />
  • splash screen (#8270)
    # 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.
  • 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.
  • Change "Team" to "Buisness" and add Education (#8221)
    This pull request updates the ChatGPT login description in the
    onboarding authentication widgets to clarify which plans include usage.
    The description now lists "Business" rather than "Team" and adds
    "Education" plans in addition to the previously mentioned plans.
    
    I have read the CLA Document and I hereby sign the CLAs.
    
    ---------
    
    Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
  • Make loading malformed skills fail-open (#8243)
    Instead of failing to start Codex, clearly call out that N skills did
    not load and provide warnings so that the user may fix them.
    
    <img width="3548" height="874" alt="image"
    src="https://github.com/user-attachments/assets/6ce041b2-1373-4007-a6dd-0194e58fafe4"
    />
  • Show migration link (#8228)
    # 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: cleanup Config instantiation codepaths (#8226)
    This PR does various types of cleanup before I can proceed with more
    ambitious changes to config loading.
    
    First, I noticed duplicated code across these two methods:
    
    
    https://github.com/openai/codex/blob/774bd9e432fa2e0f4e059e97648cf92216912e19/codex-rs/core/src/config/mod.rs#L314-L324
    
    
    https://github.com/openai/codex/blob/774bd9e432fa2e0f4e059e97648cf92216912e19/codex-rs/core/src/config/mod.rs#L334-L344
    
    This has now been consolidated in
    `load_config_as_toml_with_cli_overrides()`.
    
    Further, I noticed that `Config::load_with_cli_overrides()` took two
    similar arguments:
    
    
    https://github.com/openai/codex/blob/774bd9e432fa2e0f4e059e97648cf92216912e19/codex-rs/core/src/config/mod.rs#L308-L311
    
    The difference between `cli_overrides` and `overrides` was not
    immediately obvious to me. At first glance, it appears that one should
    be able to be expressed in terms of the other, but it turns out that
    some fields of `ConfigOverrides` (such as `cwd` and
    `codex_linux_sandbox_exe`) are, by design, not configurable via a
    `.toml` file or a command-line `--config` flag.
    
    That said, I discovered that many callers of
    `Config::load_with_cli_overrides()` were passing
    `ConfigOverrides::default()` for `overrides`, so I created two separate
    methods:
    
    - `Config::load_with_cli_overrides(cli_overrides: Vec<(String,
    TomlValue)>)`
    - `Config::load_with_cli_overrides_and_harness_overrides(cli_overrides:
    Vec<(String, TomlValue)>, harness_overrides: ConfigOverrides)`
    
    The latter has a long name, as it is _not_ what should be used in the
    common case, so the extra typing is designed to draw attention to this
    fact. I tried to update the existing callsites to use the shorter name,
    where possible.
    
    Further, in the cases where `ConfigOverrides` is used, usually only a
    limited subset of fields are actually set, so I updated the declarations
    to leverage `..Default::default()` where possible.
  • 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: 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
  • Add public skills + improve repo skill discovery and error UX (#8098)
    1. Adds SkillScope::Public end-to-end (core + protocol) and loads skills
    from the public cache directory
    2. Improves repo skill discovery by searching upward for the nearest
    .codex/skills within a git repo
    3. Deduplicates skills by name with deterministic ordering to avoid
    duplicates across sources
    4. Fixes garbled “Skill errors” overlay rendering by preventing pending
    history lines from being injected during the modal
    5. Updates the project docs “Skills” intro wording to avoid hardcoded
    paths
  • docs: refine tui2 viewport roadmap (#8122)
    Update the tui2 viewport/history design doc with current status and a
    prioritized roadmap (scroll feel, selection/copy correctness, streaming
    wrap polish, terminal integration, and longer-term per-cell
    interactivity ideas).
  • fix tui2 compile error (#8124)
    I'm not sure if this fix is ​​correct for the intended change in #7601,
    but at least the compilation error is fixed.
    
    regression: #7601
    
    ```
    error[E0004]: non-exhaustive patterns: `TuiEvent::Mouse(_)` not covered
       --> tui2/src/update_prompt.rs:57:19
        |
     57 |             match event {
        |                   ^^^^^ pattern `TuiEvent::Mouse(_)` not covered
        |
    note: `TuiEvent` defined here
       --> tui2/src/tui.rs:122:10
        |
    122 | pub enum TuiEvent {
        |          ^^^^^^^^
    ...
    126 |     Mouse(crossterm::event::MouseEvent),
        |     ----- not covered
        = note: the matched value is of type `TuiEvent`
    help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
        |
     64 ~                 },
     65 +                 TuiEvent::Mouse(_) => todo!()
        |
    ```
    
    Signed-off-by: Koichi Shiraishi <zchee.io@gmail.com>
  • refactor(tui2): make transcript line metadata explicit (#8089)
    This is a pure refactor only change.
    
    Replace the flattened transcript line metadata from `Option<(usize,
    usize)>` to an explicit
    `TranscriptLineMeta::{CellLine { cell_index, line_in_cell }, Spacer}`
    enum.
    
    This makes spacer rows unambiguous, removes “tuple semantics” from call
    sites, and keeps the
    scroll anchoring model clearer and aligned with the viewport/history
    design notes.
    
    Changes:
    - Introduce `TranscriptLineMeta` and update `TranscriptScroll` helpers
    to consume it.
    - Update `App::build_transcript_lines` and downstream consumers
    (scrolling, row classification, ANSI rendering).
    - Refresh scrolling module docs to describe anchors + spacer semantics
    in context.
    - Add tests and docs about the behavior
    
    Tests:
    - just fmt
    - cargo test -p codex-tui2 tui::scrolling
    
    Manual testing:
    - Scroll the inline transcript with mouse wheel + PgUp/PgDn/Home/End,
    then resize the terminal while staying scrolled up; verify the same
    anchored content stays in view and you don’t jump to bottom
    unexpectedly.
    - Create a gap case (multiple non-continuation cells) and scroll so a
    blank spacer row is at/near the top; verify scrolling doesn’t get stuck
    on spacers and still anchors to nearby real lines.
    - Start a selection while the assistant is streaming; verify the view
    stops auto-following, the selection stays on the intended content, and
    subsequent scrolling still behaves normally.
    - Exit the TUI and confirm scrollback rendering still styles user rows
    as blocks (background padding) and non-user rows as expected.
  • WIP: Rework TUI viewport, history printing, and selection/copy (#7601)
    > large behavior change to how the TUI owns its viewport, history, and
    suspend behavior.
    > Core model is in place; a few items are still being polished before
    this is ready to merge.
    
    We've moved this over to a new tui2 crate from being directly on the tui
    crate.
    To enable use --enable tui2 (or the equivalent in your config.toml). See
    https://developers.openai.com/codex/local-config#feature-flags
    
    Note that this serves as a baseline for the changes that we're making to
    be applied rapidly. Tui2 may not track later changes in the main tui.
    It's experimental and may not be where we land on things.
    
    ---
    
    ## Summary
    
    This PR moves the Codex TUI off of “cooperating” with the terminal’s
    scrollback and onto a model
    where the in‑memory transcript is the single source of truth. The TUI
    now owns scrolling, selection,
    copy, and suspend/exit printing based on that transcript, and only
    writes to terminal scrollback in
    append‑only fashion on suspend/exit. It also fixes streaming wrapping so
    streamed responses reflow
    with the viewport, and introduces configuration to control whether we
    print history on suspend or
    only on exit.
    
    High‑level goals:
    
    - Ensure history is complete, ordered, and never silently dropped.
    - Print each logical history cell at most once into scrollback, even
    with resizes and suspends.
    - Make scrolling, selection, and copy match the visible transcript, not
    the terminal’s notion of
      scrollback.
    - Keep suspend/alt‑screen behavior predictable across terminals.
    
    ---
    
    ## Core Design Changes
    
    ### Transcript & viewport ownership
    
    - Treat the transcript as a list of **cells** (user prompts, agent
    messages, system/info rows,
      streaming segments).
    - On each frame:
    - Compute a **transcript region** as “full terminal frame minus the
    bottom input area”.
    - Flatten all cells into visual lines plus metadata (which cell + which
    line within that cell).
    - Use scroll state to choose which visual line is at the top of the
    region.
      - Clear that region and draw just the visible slice of lines.
    - The terminal’s scrollback is no longer part of the live layout
    algorithm; it is only ever written
      to when we decide to print history.
    
    ### User message styling
    
    - User prompts now render as clear blocks with:
      - A blank padding line above and below.
    - A full‑width background for every line in the block (including the
    prompt line itself).
    - The same block styling is used when we print history into scrollback,
    so the transcript looks
    consistent whether you are in the TUI or scrolling back after
    exit/suspend.
    
    ---
    
    ## Scrolling, Mouse, Selection, and Copy
    
    ### Scrolling
    
    - Scrolling is defined in terms of the flattened transcript lines:
      - Mouse wheel scrolls up/down by fixed line increments.
      - PgUp/PgDn/Home/End operate on the same scroll model.
    - The footer shows:
      - Whether you are “following live output” vs “scrolled up”.
      - Current scroll position (line / total).
    - When there is no history yet, the bottom pane is **pegged high** and
    gradually moves down as the
      transcript fills, matching the existing UX.
    
    ### Selection
    
    - Click‑and‑drag defines a **linear selection** over transcript
    line/column coordinates, not raw
      screen rows.
    - Selection is **content‑anchored**:
    - When you scroll, the selection moves with the underlying lines instead
    of sticking to a fixed
        Y position.
    - This holds both when scrolling manually and when new content streams
    in, as long as you are in
        “follow” mode.
    - The selection only covers the “transcript text” area:
      - Left gutter/prefix (bullets, markers) is intentionally excluded.
    - This keeps copy/paste cleaner and avoids including structural margin
    characters.
    
    ### Copy (`Ctrl+Y`)
    
    - Introduce a small clipboard abstraction (`ClipboardManager`‑style) and
    use a cross‑platform
      clipboard crate under the hood.
    - When `Ctrl+Y` is pressed and a non‑empty selection exists:
    - Re‑render the transcript region off‑screen using the same wrapping as
    the visible viewport.
    - Walk the selected line/column range over that buffer to reconstruct
    the exact text:
        - Includes spaces between words.
        - Preserves empty lines within the selection.
      - Send the resulting text to the system clipboard.
    - Show a short status message in the footer indicating success/failure.
    - Copy is **best‑effort**:
    - Clipboard failures (headless environment, sandbox, remote sessions)
    are handled gracefully via
        status messages; they do not crash the TUI.
    - Copy does *not* insert a new history entry; it only affects the status
    bar.
    
    ---
    
    ## Streaming and Wrapping
    
    ### Previous behavior
    
    Previously, streamed markdown:
    
    - Was wrapped at a fixed width **at commit time** inside the streaming
    collector.
    - Those wrapped `Line<'static>` values were then wrapped again at
    display time.
    - As a result, streamed paragraphs could not “un‑wrap” when the terminal
    width increased; they were
      permanently split according to the width at the start of the stream.
    
    ### New behavior
    
    This PR implements the first step from
    `codex-rs/tui/streaming_wrapping_design.md`:
    
    - Streaming collector is constructed **without** a fixed width for
    wrapping.
      - It still:
        - Buffers the full markdown source for the current stream.
        - Commits only at newline boundaries.
        - Emits logical lines as new content becomes available.
    - Agent message cells now wrap streamed content only at **display
    time**, based on the current
      viewport width, just like non‑streaming messages.
    - Consequences:
      - Streamed responses reflow correctly when the terminal is resized.
    - Animation steps are per logical line instead of per “pre‑wrapped”
    visual line; this makes some
    commits slightly larger but keeps the behavior simple and predictable.
    
    Streaming responses are still represented as a sequence of logical
    history entries (first line +
    continuations) and integrate with the same scrolling, selection, and
    printing model.
    
    ---
    
    ## Printing History on Suspend and Exit
    
    ### High‑water mark and append‑only scrollback
    
    - Introduce a **cell‑based high‑water mark** (`printed_history_cells`)
    on the transcript:
    - Represents “how many cells at the front of the transcript have already
    been printed”.
      - Completely independent of wrapped line counts or terminal geometry.
    - Whenever we print history (suspend or exit):
    - Take the suffix of `transcript_cells` beyond `printed_history_cells`.
      - Render just that suffix into styled lines at the **current** width.
      - Write those lines to stdout.
      - Advance `printed_history_cells` to cover all cells we just printed.
    - Older cells are never re‑rendered for scrollback. They stay in
    whatever wrapping they had when
    printed, which is acceptable as long as the logical content is present
    once.
    
    ### Suspend (`Ctrl+Z`)
    
    - On suspend:
      - Leave alt screen if active and restore normal terminal modes.
    - Render the not‑yet‑printed suffix of the transcript and append it to
    normal scrollback.
      - Advance the high‑water mark.
      - Suspend the process.
    - On resume (`fg`):
      - Re‑enter the TUI mode (alt screen + input modes).
    - Clear the viewport region and fully redraw from in‑memory transcript
    and state.
    
    This gives predictable behavior across terminals without trying to
    maintain scrollback live.
    
    ### Exit
    
    - On exit:
      - Render any remaining unprinted cells once and write them to stdout.
    - Add an extra blank line after the final Codex history cell before
    printing token usage, so the
        transcript and usage info are visually separated.
    - If you never suspended, exit prints the entire transcript exactly
    once.
    - If you suspended one or more times, exit prints only the cells
    appended after the last suspend.
    
    ---
    
    ## Configuration: Suspend Printing
    
    This PR also adds configuration to control **when** we print history:
    
    - New TUI config option to gate printing on suspend:
      - At minimum:
    - `print_on_suspend = true` – current behavior: print new history at
    each suspend *and* on exit.
        - `print_on_suspend = false` – only print on exit.
    - Default is tuned to preserve current behavior, but this can be
    revisited based on feedback.
    - The config is respected in the suspend path:
    - If disabled, suspend only restores terminal modes and stops rendering
    but does not print new
        history.
      - Exit still prints the full not‑yet‑printed suffix once.
    
    This keeps the core viewport logic agnostic to preference, while letting
    users who care about
    quiet scrollback opt out of suspend printing.
    
    ---
    
    ## Tradeoffs
    
    What we gain:
    
    - A single authoritative history model (the in‑memory transcript).
    - Deterministic viewport rendering independent of terminal quirks.
    - Suspend/exit flows that:
      - Print each logical history cell exactly once.
      - Work across resizes and different terminals.
      - Interact cleanly with alt screen and raw‑mode toggling.
    - Consistent, content‑anchored scrolling, selection, and copy.
    - Streaming messages that reflow correctly with the viewport width.
    
    What we accept:
    
    - Scrollback may contain older cells wrapped differently than newer
    ones.
    - Streaming responses appear in scrollback as a sequence of blocks
    corresponding to their streaming
      structure, not as a single retroactively reflowed paragraph.
    - We do not attempt to rewrite or reflow already‑printed scrollback.
    
    For deeper rationale and diagrams, see
    `docs/tui_viewport_and_history.md` and
    `codex-rs/tui/streaming_wrapping_design.md`.
    
    ---
    
    ## Still to Do Before This PR Is Ready
    
    These are scoped to this PR (not long‑term future work):
    
    - [ ] **Streaming wrapping polish**
      - Double‑check all streaming paths use display‑time wrapping only.
      - Ensure tests cover resizing after streaming has started.
    
    - [ ] **Suspend printing config**
    - Finalize config shape and default (keep existing behavior vs opt‑out).
    - Wire config through TUI startup and document it in the appropriate
    config docs.
    
    - [x] **Bottom pane positioning**
    - Ensure the bottom pane is pegged high when there’s no history and
    smoothly moves down as the
    transcript fills, matching the current behavior across startup and
    resume.
    
    - [x] **Transcript mouse scrolling**
    - Re‑enable wheel‑based transcript scrolling on top of the new scroll
    model.
    - Make sure mouse scroll does not get confused with “alternate scroll”
    modes from terminals.
    
    - [x] **Mouse selection vs streaming**
    - When selection is active, stop auto‑scrolling on streaming so the
    selection remains stable on
        the selected content.
    - Ensure that when streaming continues after selection is cleared,
    “follow latest output” mode
        resumes correctly.
    
    - [ ] **Auto‑scroll during drag**
    - While the user is dragging a selection, auto‑scroll when the cursor is
    at/near the top or bottom
    of the transcript viewport to allow selecting beyond the current visible
    window.
    
    - [ ] **Feature flag / rollout**
    - Investigate gating the new viewport/history behavior behind a feature
    flag for initial rollout,
    so we can fall back to the old behavior if needed during early testing.
    
    - [ ] **Before/after videos**
      - Capture short clips showing:
        - Scrolling (mouse + keys).
        - Selection and copy.
        - Streaming behavior under resize.
        - Suspend/resume and exit printing.
      - Use these to validate UX and share context in the PR discussion.
  • 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.
  • Changed default wrap algorithm from OptimalFit to FirstFit (#7960)
    Codex identified this as the cause of a reported hang:
    https://github.com/openai/codex/issues/7822. Apparently, the wrapping
    algorithm we're using has known issues and bad worst-case behaviors when
    OptimalFit is used on certain strings. It recommended switching to
    FirstFit instead.
  • Sync tui2 with tui and keep dual-run glue (#7965)
    - Copy latest tui sources into tui2
    - Restore notifications, tests, and styles
    - Keep codex-tui interop conversions and snapshots
    
    The expected changes that are necessary to make this work are still in
    place:
    
    diff -ru codex-rs/tui codex-rs/tui2 --exclude='*.snap'
    --exclude='*.snap.new'
    
    ```diff
    diff -ru --ex codex-rs/tui/Cargo.toml codex-rs/tui2/Cargo.toml
    --- codex-rs/tui/Cargo.toml	2025-12-12 16:39:12
    +++ codex-rs/tui2/Cargo.toml	2025-12-12 17:31:01
    @@ -1,15 +1,15 @@
     [package]
    -name = "codex-tui"
    +name = "codex-tui2"
     version.workspace = true
     edition.workspace = true
     license.workspace = true
     
     [[bin]]
    -name = "codex-tui"
    +name = "codex-tui2"
     path = "src/main.rs"
     
     [lib]
    -name = "codex_tui"
    +name = "codex_tui2"
     path = "src/lib.rs"
     
     [features]
    @@ -42,6 +42,7 @@
     codex-login = { workspace = true }
     codex-protocol = { workspace = true }
     codex-utils-absolute-path = { workspace = true }
    +codex-tui = { workspace = true }
     color-eyre = { workspace = true }
     crossterm = { workspace = true, features = ["bracketed-paste", "event-stream"] }
     derive_more = { workspace = true, features = ["is_variant"] }
    diff -ru --ex codex-rs/tui/src/app.rs codex-rs/tui2/src/app.rs
    --- codex-rs/tui/src/app.rs	2025-12-12 16:39:05
    +++ codex-rs/tui2/src/app.rs	2025-12-12 17:30:36
    @@ -69,6 +69,16 @@
         pub update_action: Option<UpdateAction>,
     }
     
    +impl From<AppExitInfo> for codex_tui::AppExitInfo {
    +    fn from(info: AppExitInfo) -> Self {
    +        codex_tui::AppExitInfo {
    +            token_usage: info.token_usage,
    +            conversation_id: info.conversation_id,
    +            update_action: info.update_action.map(Into::into),
    +        }
    +    }
    +}
    +
     fn session_summary(
         token_usage: TokenUsage,
         conversation_id: Option<ConversationId>,
    Only in codex-rs/tui/src/bin: md-events.rs
    Only in codex-rs/tui2/src/bin: md-events2.rs
    diff -ru --ex codex-rs/tui/src/cli.rs codex-rs/tui2/src/cli.rs
    --- codex-rs/tui/src/cli.rs	2025-11-19 13:40:42
    +++ codex-rs/tui2/src/cli.rs	2025-12-12 17:30:43
    @@ -88,3 +88,28 @@
         #[clap(skip)]
         pub config_overrides: CliConfigOverrides,
     }
    +
    +impl From<codex_tui::Cli> for Cli {
    +    fn from(cli: codex_tui::Cli) -> Self {
    +        Self {
    +            prompt: cli.prompt,
    +            images: cli.images,
    +            resume_picker: cli.resume_picker,
    +            resume_last: cli.resume_last,
    +            resume_session_id: cli.resume_session_id,
    +            resume_show_all: cli.resume_show_all,
    +            model: cli.model,
    +            oss: cli.oss,
    +            oss_provider: cli.oss_provider,
    +            config_profile: cli.config_profile,
    +            sandbox_mode: cli.sandbox_mode,
    +            approval_policy: cli.approval_policy,
    +            full_auto: cli.full_auto,
    +            dangerously_bypass_approvals_and_sandbox: cli.dangerously_bypass_approvals_and_sandbox,
    +            cwd: cli.cwd,
    +            web_search: cli.web_search,
    +            add_dir: cli.add_dir,
    +            config_overrides: cli.config_overrides,
    +        }
    +    }
    +}
    diff -ru --ex codex-rs/tui/src/main.rs codex-rs/tui2/src/main.rs
    --- codex-rs/tui/src/main.rs	2025-12-12 16:39:05
    +++ codex-rs/tui2/src/main.rs	2025-12-12 16:39:06
    @@ -1,8 +1,8 @@
     use clap::Parser;
     use codex_arg0::arg0_dispatch_or_else;
     use codex_common::CliConfigOverrides;
    -use codex_tui::Cli;
    -use codex_tui::run_main;
    +use codex_tui2::Cli;
    +use codex_tui2::run_main;
     
     #[derive(Parser, Debug)]
     struct TopCli {
    diff -ru --ex codex-rs/tui/src/update_action.rs codex-rs/tui2/src/update_action.rs
    --- codex-rs/tui/src/update_action.rs	2025-11-19 11:11:47
    +++ codex-rs/tui2/src/update_action.rs	2025-12-12 17:30:48
    @@ -9,6 +9,20 @@
         BrewUpgrade,
     }
     
    +impl From<UpdateAction> for codex_tui::update_action::UpdateAction {
    +    fn from(action: UpdateAction) -> Self {
    +        match action {
    +            UpdateAction::NpmGlobalLatest => {
    +                codex_tui::update_action::UpdateAction::NpmGlobalLatest
    +            }
    +            UpdateAction::BunGlobalLatest => {
    +                codex_tui::update_action::UpdateAction::BunGlobalLatest
    +            }
    +            UpdateAction::BrewUpgrade => codex_tui::update_action::UpdateAction::BrewUpgrade,
    +        }
    +    }
    +}
    +
     impl UpdateAction {
         /// Returns the list of command-line arguments for invoking the update.
         pub fn command_args(self) -> (&'static str, &'static [&'static str]) {
    ```
  • 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(tui): show xhigh reasoning warning for gpt-5.2 (#7910)
    ## Notes
    - Extend reasoning-effort popup warning eligibility to gpt-5.2* models
    for the Extra High (xhigh) option.
    
    ## Revisions
    - R2: Remove unnecessary tests and snapshots
    - R1: initial
    
    ## Testing
    - `just fix`, `cargo test -p codex-tui`, and `cargo test -p codex-tui2`
    - Manual testing
    
    **Before**:
    <img width="864" height="162" alt="image"
    src="https://github.com/user-attachments/assets/d12a8f11-3ba5-4c31-9ae9-096a408b4971"
    />
    
    **After** (consistent with GPT 5.1 Codex Max):
    <img width="864" height="156" alt="image"
    src="https://github.com/user-attachments/assets/29c0ea7a-c68e-4fac-b10f-15a420ae5953"
    />
    
    <img width="684" height="154" alt="image"
    src="https://github.com/user-attachments/assets/b562b8b6-6e63-4dc2-8344-5c7f9a9b6263"
    />
  • 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>
  • feat(tui2): copy tui crate and normalize snapshots (#7833)
    Introduce a full codex-tui source snapshot under the new codex-tui2
    crate so viewport work can be replayed in isolation.
    
    This change copies the entire codex-rs/tui/src tree into
    codex-rs/tui2/src in one atomic step, rather than piecemeal, to keep
    future diffs vs the original viewport bookmark easy to reason about.
    
    The goal is for codex-tui2 to render identically to the existing TUI
    behind the `features.tui2` flag while we gradually port the
    viewport/history commits from the joshka/viewport bookmark onto this
    forked tree.
    
    While on this baseline change, we also ran the codex-tui2 snapshot test
    suite and accepted all insta snapshots for the new crate, so the
    snapshot files now use the codex-tui2 naming scheme and encode the
    unmodified legacy TUI behavior. This keeps later viewport commits
    focused on intentional behavior changes (and their snapshots) rather
    than on mechanical snapshot renames.
  • feat(tui2): add feature-flagged tui2 frontend (#7793)
    Introduce a new codex-tui2 crate that re-exports the existing
    interactive TUI surface and delegates run_main directly to codex-tui.
    This keeps behavior identical while giving tui2 its own crate for future
    viewport work.
    
    Wire the codex CLI to select the frontend via the tui2 feature flag.
    When the merged CLI overrides include features.tui2=true (e.g. via
    --enable tui2), interactive runs are routed through
    codex_tui2::run_main; otherwise they continue to use the original
    codex_tui::run_main.
    
    Register Feature::Tui2 in the core feature registry and add the tui2
    crate and dependency entries so the new frontend builds alongside the
    existing TUI.
    
    This is a stub that only wires up the feature flag for this.
    
    <img width="619" height="364" alt="image"
    src="https://github.com/user-attachments/assets/4893f030-932f-471e-a443-63fe6b5d8ed9"
    />