Commit Graph

79 Commits

  • fix: windows can now paste non-ascii multiline text (#8774)
    ## Summary
    This PR builds _heavily_ on the work from @occurrent in #8021 - I've
    only added a small fix, added additional tests, and propagated the
    changes to tui2.
    
    From the original PR:
    
    > On Windows, Codex relies on PasteBurst for paste detection because
    bracketed paste is not reliably available via crossterm.
    > 
    > When pasted content starts with non-ASCII characters, input is routed
    through handle_non_ascii_char, which bypasses the normal paste burst
    logic. This change extends the paste burst window for that path, which
    should ensure that Enter is correctly grouped as part of the paste.
    
    
    ## Testing
    - [x] tested locally cross-platform
    - [x] added regression tests
    
    ---------
    
    Co-authored-by: occur <occurring@outlook.com>
  • add ability to disable input temporarily in the TUI. (#8876)
    We will disable input while the elevated sandbox setup is running.
  • add footer note to TUI (#8867)
    This will be used by the elevated sandbox NUX to give a hint on how to
    run the elevated sandbox when in the non-elevated mode.
  • 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"
    />
  • remove unnecessary todos (#8842)
    > // todo(aibrahim): why are we passing model here while it can change?
    
    we update it on each turn with `.with_model`
    
    > //TODO(aibrahim): run CI in release mode.
    
    although it's good to have, release builds take double the time tests
    take.
    
    > // todo(aibrahim): make this async function
    
    we figured out another way of doing this sync
  • Merge Modelfamily into modelinfo (#8763)
    - Merge ModelFamily into ModelInfo
    - Remove logic for adding instructions to apply patch
    - Add compaction limit and visible context window to `ModelInfo`
  • chore: unify conversation with thread name (#8830)
    Done and verified by Codex + refactor feature of RustRover
  • fix: handle /review arguments in TUI (#8823)
    Handle /review <instructions> in the TUI and TUI2 by routing it as a
    custom review command instead of plain text, wiring command dispatch and
    adding composer coverage so typing /review text starts a review directly
    rather than posting a message. User impact: /review with arguments now
    kicks off the review flow, previously it would just forward as a plain
    command and not actually start a review.
  • 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"
    />
  • feat(app-server): thread/rollback API (#8454)
    Add `thread/rollback` to app-server to support IDEs undo-ing the last N
    turns of a thread.
    
    For context, an IDE partner will be supporting an "undo" capability
    where the IDE (the app-server client) will be responsible for reverting
    the local changes made during the last turn. To support this well, we
    also need a way to drop the last turn (or more generally, the last N
    turns) from the agent's context. This is what `thread/rollback` does.
    
    **Core idea**: A Thread rollback is represented as a persisted event
    message (EventMsg::ThreadRollback) in the rollout JSONL file, not by
    rewriting history. On resume, both the model's context (core replay) and
    the UI turn list (app-server v2's thread history builder) apply these
    markers so the pruned history is consistent across live conversations
    and `thread/resume`.
    
    Implementation notes:
    - Rollback only affects agent context and appends to the rollout file;
    clients are responsible for reverting files on disk.
    - If a thread rollback is currently in progress, subsequent
    `thread/rollback` calls are rejected.
    - Because we use `CodexConversation::submit` and codex core tracks
    active turns, returning an error on concurrent rollbacks is communicated
    via an `EventMsg::Error` with a new variant
    `CodexErrorInfo::ThreadRollbackFailed`. app-server watches for that and
    sends the BAD_REQUEST RPC response.
    
    Tests cover thread rollbacks in both core and app-server, including when
    `num_turns` > existing turns (which clears all turns).
    
    **Note**: this explicitly does **not** behave like `/undo` which we just
    removed from the CLI, which does the opposite of what `thread/rollback`
    does. `/undo` reverts local changes via ghost commits/snapshots and does
    not modify the agent's context / conversation history.
  • Clear copy pill background and add snapshot test (#8777)
    ### Motivation
    - Fix a visual bug where transcript text could bleed through the
    on-screen copy "pill" overlay.
    - Ensure the copy affordance fully covers the underlying buffer so the
    pill background is solid and consistent with styling.
    - Document the approach in-code to make the background-clearing
    rationale explicit.
    
    ### Description
    - Clear the pill area before drawing by iterating `Rect::positions()`
    and calling `cell.set_symbol(" ")` and `cell.set_style(base_style)` in
    `render_copy_pill` in `transcript_copy_ui.rs`.
    - Added an explanatory comment for why the pill background is explicitly
    cleared.
    - Added a unit test `copy_pill_clears_background` and committed the
    corresponding snapshot file to validate the rendering behavior.
    
    ### Testing
    - Ran `just fmt` (formatting completed; non-blocking environment warning
    may appear).
    - Ran `just fix -p codex-tui2` to apply lints/fixes (completed). 
    - Ran `cargo test -p codex-tui2` and all tests passed (snapshot updated
    and tests succeeded).
    
    ------
    [Codex
    Task](https://chatgpt.com/codex/tasks/task_i_695c9b23e9b8832997d5a457c4d83410)
  • feat: forced tool tips (#8752)
    Force an announcement tooltip in the CLI. This query the gh repo on this
    [file](https://raw.githubusercontent.com/openai/codex/main/announcement_tip.toml)
    which contains announcements in TOML looking like this:
    ```
    # Example announcement tips for Codex TUI.
    # Each [[announcements]] entry is evaluated in order; the last matching one is shown.
    # Dates are UTC, formatted as YYYY-MM-DD. The from_date is inclusive and the to_date is exclusive.
    # version_regex matches against the CLI version (env!("CARGO_PKG_VERSION")); omit to apply to all versions.
    # target_app specify which app should display the announcement (cli, vsce, ...).
    
    [[announcements]]
    content = "Welcome to Codex! Check out the new onboarding flow."
    from_date = "2024-10-01"
    to_date = "2024-10-15"
    version_regex = "^0\\.0\\.0$"
    target_app = "cli"
    ``` 
    
    To make this efficient, the announcement is queried on a best effort
    basis at the launch of the CLI (no refresh made after this).
    This is done in an async way and we display the announcement (with 100%
    probability) iff the announcement is available, the cache is correctly
    warmed and there is a matching announcement (matching is recomputed for
    each new session).
  • fix: render cwd-relative paths in tui (#8771)
    Display paths relative to the cwd before checking git roots so view
    image tool calls keep project-local names in jj/no-.git workspaces.
  • tui2: stop baking streaming wraps; reflow agent markdown (#8761)
    Background
    Streaming assistant prose in tui2 was being rendered with viewport-width
    wrapping during streaming, then stored in history cells as already split
    `Line`s. Those width-derived breaks became indistinguishable from hard
    newlines, so the transcript could not "un-split" on resize. This also
    degraded copy/paste, since soft wraps looked like hard breaks.
    
    What changed
    - Introduce width-agnostic `MarkdownLogicalLine` output in
    `tui2/src/markdown_render.rs`, preserving markdown wrap semantics:
    initial/subsequent indents, per-line style, and a preformatted flag.
    - Update the streaming collector (`tui2/src/markdown_stream.rs`) to emit
    logical lines (newline-gated) and remove any captured viewport width.
    - Update streaming orchestration (`tui2/src/streaming/*`) to queue and
    emit logical lines, producing `AgentMessageCell::new_logical(...)`.
    - Make `AgentMessageCell` store logical lines and wrap at render time in
    `HistoryCell::transcript_lines_with_joiners(width)`, emitting joiners so
    copy/paste can join soft-wrap continuations correctly.
    
    Overlay deferral
    When an overlay is active, defer *cells* (not rendered `Vec<Line>`) and
    render them at overlay close time. This avoids baking width-derived
    wraps based on a stale width.
    
    Tests + docs
    - Add resize/reflow regression tests + snapshots for streamed agent
    output.
    - Expand module/API docs for the new logical-line streaming pipeline and
    clarify joiner semantics.
    - Align scrollback-related docs/comments with current tui2 behavior
    (main draw loop does not flush queued "history lines" to the terminal).
    
    More details
    See `codex-rs/tui2/docs/streaming_wrapping_design.md` for the full
    problem statement and solution approach, and
    `codex-rs/tui2/docs/tui_viewport_and_history.md` for viewport vs printed
    output behavior.
  • [device-auth] Update login instruction for headless environments. (#8753)
    We've seen reports that people who try to login on a remote/headless
    machine will open the login link on their own machine and got errors.
    Update the instructions to ask those users to use `codex login
    --device-auth` instead.
    
    <img width="1434" height="938" alt="CleanShot 2026-01-05 at 11 35 02@2x"
    src="https://github.com/user-attachments/assets/2b209953-6a42-4eb0-8b55-bb0733f2e373"
    />
  • feat: expose outputSchema to user_turn/turn_start app_server API (#8377)
    What changed
    - Added `outputSchema` support to the app-server APIs, mirroring `codex
    exec --output-schema` behavior.
    - V1 `sendUserTurn` now accepts `outputSchema` and constrains the final
    assistant message for that turn.
    - V2 `turn/start` now accepts `outputSchema` and constrains the final
    assistant message for that turn (explicitly per-turn only).
    
    Core behavior
    - `Op::UserTurn` already supported `final_output_json_schema`; now V1
    `sendUserTurn` forwards `outputSchema` into that field.
    - `Op::UserInput` now carries `final_output_json_schema` for per-turn
    settings updates; core maps it into
    `SessionSettingsUpdate.final_output_json_schema` so it applies to the
    created turn context.
    - V2 `turn/start` does NOT persist the schema via `OverrideTurnContext`
    (it’s applied only for the current turn). Other overrides
    (cwd/model/etc) keep their existing persistent behavior.
    
    API / docs
    - `codex-rs/app-server-protocol/src/protocol/v1.rs`: add `output_schema:
    Option<serde_json::Value>` to `SendUserTurnParams` (serialized as
    `outputSchema`).
    - `codex-rs/app-server-protocol/src/protocol/v2.rs`: add `output_schema:
    Option<JsonValue>` to `TurnStartParams` (serialized as `outputSchema`).
    - `codex-rs/app-server/README.md`: document `outputSchema` for
    `turn/start` and clarify it applies only to the current turn.
    - `codex-rs/docs/codex_mcp_interface.md`: document `outputSchema` for v1
    `sendUserTurn` and v2 `turn/start`.
    
    Tests added/updated
    - New app-server integration tests asserting `outputSchema` is forwarded
    into outbound `/responses` requests as `text.format`:
      - `codex-rs/app-server/tests/suite/output_schema.rs`
      - `codex-rs/app-server/tests/suite/v2/output_schema.rs`
    - Added per-turn semantics tests (schema does not leak to the next
    turn):
      - `send_user_turn_output_schema_is_per_turn_v1`
      - `turn_start_output_schema_is_per_turn_v2`
    - Added protocol wire-compat tests for the merged op:
      - serialize omits `final_output_json_schema` when `None`
      - deserialize works when field is missing
      - serialize includes `final_output_json_schema` when `Some(schema)`
    
    Call site updates (high level)
    - Updated all `Op::UserInput { .. }` constructions to include
    `final_output_json_schema`:
      - `codex-rs/app-server/src/codex_message_processor.rs`
      - `codex-rs/core/src/codex_delegate.rs`
      - `codex-rs/mcp-server/src/codex_tool_runner.rs`
      - `codex-rs/tui/src/chatwidget.rs`
      - `codex-rs/tui2/src/chatwidget.rs`
      - plus impacted core tests.
    
    Validation
    - `just fmt`
    - `cargo test -p codex-core`
    - `cargo test -p codex-app-server`
    - `cargo test -p codex-mcp-server`
    - `cargo test -p codex-tui`
    - `cargo test -p codex-tui2`
    - `cargo test -p codex-protocol`
    - `cargo clippy --all-features --tests --profile dev --fix -- -D
    warnings`
  • feat(tui2): transcript scrollbar (auto-hide + drag) (#8728)
    ## Summary
    - Add a transcript scrollbar in `tui2` using `tui-scrollbar`.
    - Reserve 2 columns on the right (1 empty gap + 1 scrollbar track) and
    plumb the reduced width through wrapping/selection/copy so rendering and
    interactions match.
    - Auto-hide the scrollbar when the transcript is pinned to the bottom
    (columns remain reserved).
    - Add mouse click/drag support for the scrollbar, with pointer-capture
    so drags don’t fall through into transcript selection.
    - Skip scrollbar hit-testing when auto-hidden to avoid an invisible
    interactive region.
    
    ## Notes
    - Styling is theme-aware: in light themes the thumb is darker than the
    track; in dark themes it reads as an “indented” element without going
    full-white.
    - Pre-Ratatui 0.30 (ratatui-core split) requires a small scratch-buffer
    bridge; this should simplify once we move to Ratatui 0.30.
    
    ## Testing
    - `just fmt`
    - `just fix -p codex-tui2 --allow-no-vcs`
    - `cargo test -p codex-tui2`
  • tui2: copy selection dismisses highlight (#8718)
    Clicking the transcript copy pill or pressing the copy shortcut now
    copies the selected transcript text and clears the highlight.
    
    Show transient footer feedback ("Copied"/"Copy failed") after a copy
    attempt, with logic in transcript_copy_action to keep app.rs smaller and
    closer to tui for long-term diffs.
    
    Update footer snapshots and add tiny unit tests for feedback expiry.
    
    
    https://github.com/user-attachments/assets/c36c8163-11c5-476b-b388-e6fbe0ff6034
  • fix(tui2): render copy pill at viewport bottom (#8716)
    When the selection ends on the last visible row, the copy affordance had
    no space below and never rendered. Fall back to placing it above (or on
    the same row for 1-row viewports) and add a regression test.
  • fix(tui2): avoid scroll stickiness at cell boundaries (#8695)
    Mouse/trackpad scrolling in tui2 applies deltas in visual lines, but the
    transcript scroll state was anchored only to CellLine entries.
    
    When a 1-line scroll landed on the synthetic inter-cell Spacer row
    (inserted between non-continuation cells),
    `TranscriptScroll::anchor_for` would skip that row and snap back to the
    adjacent cell line. That makes the resolved top offset unchanged for
    small/coalesced scroll deltas, so scrolling appears to get stuck right
    before certain cells (commonly user prompts and command output cells).
    
    Fix this by making spacer rows a first-class scroll anchor:
    - Add `TranscriptScroll::ScrolledSpacerBeforeCell` and resolve it back
    to the spacer row index when present.
    - Update `anchor_for`/`scrolled_by` to preserve spacers instead of
    skipping them.
    - Treat the new variant as "already anchored" in
    `lock_transcript_scroll_to_current_view`.
    
    Tests:
    - cargo test -p codex-tui2
  • fix: brighten transcript copy affordance (#8697)
    - set the pill text to white so it stays legible on light themes
    - render the icon in bold light cyan so it stands out
    - reran `just fmt`, `just fix -p codex-tui2`, and `cargo test -p
    codex-tui2` after the tweak
    
    
    https://github.com/user-attachments/assets/6c296b8d-84fb-45b9-bce7-57982e0d531b
  • perf(tui2): cache transcript view rendering (#8693)
    The transcript viewport draws every frame. Ratatui's Line::render_ref
    does grapheme segmentation and span layout, so repeated redraws can burn
    CPU during streaming even when the visible transcript hasn't changed.
    
    Introduce TranscriptViewCache to reduce per-frame work:
    - WrappedTranscriptCache memoizes flattened+wrapped transcript lines per
    width, appends incrementally as new cells arrive, and rebuilds on width
    change, truncation (backtrack), or transcript replacement.
    - TranscriptRasterCache caches rasterized rows (Vec<Cell>) per line
    index and user-row styling; redraws copy cells instead of rerendering
    spans.
    
    The caches are width-scoped and store base transcript content only;
    selection highlighting and copy affordances are applied after drawing.
    User rows include the row-wide base style in the cached raster.
    
    Refactor transcript_render to expose append_wrapped_transcript_cell for
    incremental building and add a test that incremental append matches the
    full build.
    
    Add docs/tui2/performance-testing.md as a playbook for macOS sample
    profiles and hotspot greps.
    
    Expand transcript_view_cache tests to cover rebuild conditions, raster
    equivalence vs direct rendering, user-row caching, and eviction.
    
    Test: cargo test -p codex-tui2
  • Attach more tags to feedback submissions (#8688)
    Attach more tags to sentry feedback so it's easier to classify and debug
    without having to scan through logs.
    
    Formatting isn't amazing but it's a start.
    <img width="1234" height="276" alt="image"
    src="https://github.com/user-attachments/assets/521a349d-f627-4051-b511-9811cd5cd933"
    />
  • perf(tui2): reduce unnecessary redraws (#8681)
    This reduces unnecessary frame scheduling in codex-tui2.
    
    Changes:
    - Gate redraw scheduling for streaming deltas when nothing visible
    changes.
    - Avoid a redraw feedback loop from footer transcript UI state updates.
    
    Why:
    - Streaming deltas can arrive at very high frequency; redrawing on every
    delta can drive a near-constant render loop.
    - BottomPane was requesting another frame after every Draw even when the
    derived transcript UI state was unchanged.
    
    Testing:
    - cargo test -p codex-tui2
    
    Manual sampling:
    - sample "$(pgrep -n codex-tui2)" 3 -file
    /tmp/tui2.idle.after.sample.txt
    - sample "$(pgrep -n codex-tui2)" 3 -file
    /tmp/tui2.streaming.after.sample.txt
  • 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"
    />
  • perf(tui): cap redraw scheduling to 60fps (#8499)
    Clamp frame draw notifications in the `FrameRequester` scheduler so we
    don't redraw more frequently than a user can perceive.
    
    This applies to both `codex-tui` and `codex-tui2`, and keeps the
    draw/dispatch loops simple by centralizing the rate limiting in a small
    helper module.
    
    - Add `FrameRateLimiter` (pure, unit-tested) to clamp draw deadlines
    - Apply the limiter in the scheduler before emitting `TuiEvent::Draw`
    - Use immediate redraw requests for scroll paths (scheduler now
    coalesces + clamps)
    - Add scheduler tests covering immediate/delayed interactions
  • Remove reasoning format (#8484)
    This isn't very useful parameter. 
    
    logic:
    ```
    if model puts `**` in their reasoning, trim it and visualize the header.
    if couldn't trim: don't render
    if model doesn't support: don't render
    ```
    
    We can simplify to:
    ```
    if could trim, visualize header.
    if not, don't render
    ```
  • fix: fix test that was writing temp file to cwd instead of TMPDIR (#8493)
    I am trying to support building with [Buck2](https://buck2.build), which
    reports which files have changed between invocations of `buck2 test` and
    `tmp_delete_example.txt` came up. This turned out to be the reason.
  • feat(tui2): add multi-click transcript selection (#8471)
    Support multi-click transcript selection using transcript/viewport
    coordinates
    (wrapped visual line index + content column), not terminal buffer
    positions.
    
    Gestures:
    - double click: select word-ish token under cursor
    - triple click: select entire wrapped line
    - quad click: select paragraph (contiguous non-empty wrapped lines)
    - quint+ click: select the entire history cell (all wrapped lines
    belonging to a
      single `HistoryCell`, including blank lines inside the cell)
    
    Selection expansion rebuilds the wrapped transcript view from
    `HistoryCell::display_lines(width)` so boundaries match on-screen
    wrapping during
    scroll/resize/streaming reflow. Click grouping is resilient to minor
    drag jitter
    (some terminals emit tiny Drag events during clicks) and becomes more
    tolerant as
    the sequence progresses so quad/quint clicks are practical.
    
    Tests cover expansion (word/line/paragraph/cell), sequence resets
    (timing, motion,
    line changes, real drags), drag jitter, and behavior on spacer lines
    between
    history cells (paragraph/cell selection prefers the cell above).
  • [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.
  • fix(tui2): start transcript selection on drag (#8466)
    Avoid distracting 1-cell highlights on simple click by tracking an
    anchor on mouse down and only creating a visible selection once the
    mouse is dragged (selection head set).
    
    When dragging while following the bottom during streaming, request a
    scroll lock so the viewport stops moving under the active selection.
    
    Move selection state transitions into transcript_selection helpers
    (returning change/lock outcomes for the caller) and add unit tests for
    the state machine.
  • fix(tui2): fix screen corruption (#8463)
    Summary
    
    Fixes intermittent screen corruption in tui2 (random stale characters)
    by
    addressing two terminal state desyncs: nested alt-screen transitions and
    the
    first-draw viewport clear.
    
    - Make alt-screen enter/leave re-entrant via a small nesting guard so
    closing
    - Ensure the first viewport draw clears after the viewport is sized,
    preventing
    old terminal contents from leaking through when diff-based rendering
    skips
      space cells.
    - Add docs + a small unit test for the alt-screen nesting behavior.
    
    Testing
    
    - cargo test -p codex-tui2
    - cargo clippy -p codex-tui2 --all-features --tests
    - Manual:
    - Opened the transcript overlay and dismissed it repeatedly; verified
    the
          normal view redraws cleanly with no leftover characters.
    - Ran tui2 in a new folder with no trust settings (and also cleared the
    trust setting from config to re-trigger the prompt); verified the
    initial
          trust/onboarding screen renders without artifacts.
  • feat(tui2): add copy selection shortcut + UI affordance (#8462)
    - Detect Ctrl+Shift+C vs VS Code Ctrl+Y and surface in footer hints
    - Render clickable “⧉ copy” pill near transcript selection (hidden while
    dragging)
    - Handle copy hotkey + click to copy selection
    - Document updated copy UX
    
    VSCode:
    <img width="1095" height="413" alt="image"
    src="https://github.com/user-attachments/assets/84be0c82-4762-4c3e-80a4-c751c078bdaa"
    />
    
    Ghosty:
    <img width="505" height="68" alt="image"
    src="https://github.com/user-attachments/assets/109cc1a1-f029-4f7e-a141-4c6ed2da7338"
    />
  • chore(tui): include tracing targets in file logs (#8418)
    with_target(true) is the default for tracing-subscriber, but we
    previously disabled it for file output.
    
    Keep it enabled so we can selectively enable specific targets/events at
    runtime via RUST_LOG=..., and then grep by target/module in the log file
    during troubleshooting.
    
    before and after:
    
    <img width="629" height="194" alt="image"
    src="https://github.com/user-attachments/assets/33f7df3f-0c5d-4d3f-b7b7-80b03d4acd21"
    />
  • fix(tui2): copy transcript selection outside viewport (#8449)
    Copy now operates on the full logical selection range (anchor..head),
    not just the visible viewport, so selections that include offscreen
    lines copy the expected text.
    
    Selection extraction is factored into `transcript_selection` to make the
    logic easier to test and reason about. It reconstructs the wrapped
    visual transcript, renders each wrapped line into a 1-row offscreen
    Buffer, and reads the selected cells. This keeps clipboard text aligned
    with what is rendered (gutter, indentation, wrapping).
    
    Additional behavior:
    - Skip continuation cells for wide glyphs (e.g. CJK) so copied text does
    not include spurious spaces like "コ X".
    - Avoid copying right-margin padding spaces.
    
    Manual tested performed:
    - "tell me a story" a few times
    - scroll up, select text, scroll down, copy text
    - confirm copied text is what you expect
  • fix(tui2): constrain transcript mouse selection bounds (#8419)
    Ignore mouse events outside the transcript region so composer/footer
    interactions do not start or mutate transcript selection state.
    
    A left-click outside the transcript also cancels any active selection.
    Selection changes schedule a redraw because mouse events don't
    inherently trigger a frame.
  • test(tui2): re-enable ANSI for VT100 tests (#8423)
    Codex Unified Exec injects NO_COLOR=1 (and TERM=dumb) into shell tool
    commands to keep output stable. Crossterm respects NO_COLOR and
    suppresses ANSI escapes, which breaks our VT100-backed tests that assert
    on parsed ANSI color output (they see vt100::Color::Default everywhere).
    
    Force ANSI color output back on in the VT100 test backend by overriding
    crossterm's memoized NO_COLOR setting in VT100Backend::new. This keeps
    Unified Exec behavior unchanged while making the VT100 tests meaningful
    and deterministic under Codex.
    
    > [!WARNING]  
    > it's possible that this might be a race condition problem for this and
    need to be solved a different way. Feel free to revert if it causes the
    opposite problem for other tests that assume NOCOLOR is set. If it does
    then we need to probably add some extra AGENTS.md lines for how to run
    tests when using unified exec.
    
    (this same change was made in tui, so it's probably safe).
  • feat(tui2): tune scrolling inpu based on (#8357)
    ## TUI2: Normalize Mouse Scroll Input Across Terminals (Wheel +
    Trackpad)
    
    This changes TUI2 scrolling to a stream-based model that normalizes
    terminal scroll event density into consistent wheel behavior (default:
    ~3 transcript lines per physical wheel notch) while keeping trackpad
    input higher fidelity via fractional accumulation.
    
    Primary code: `codex-rs/tui2/src/tui/scrolling/mouse.rs`
    
    Doc of record (model + probe-derived data):
    `codex-rs/tui2/docs/scroll_input_model.md`
    
    ### Why
    
    Terminals encode both mouse wheels and trackpads as discrete scroll
    up/down events with direction but no magnitude, and they vary widely in
    how many raw events they emit per physical wheel notch (commonly 1, 3,
    or 9+). Timing alone doesn’t reliably distinguish wheel vs trackpad, so
    cadence-based heuristics are unstable across terminals/hardware.
    
    This PR treats scroll input as short *streams* separated by silence or
    direction flips, normalizes raw event density into tick-equivalents,
    coalesces redraws for dense streams, and exposes explicit config
    overrides.
    
    ### What Changed
    
    #### Scroll Model (TUI2)
    
    - Stream detection
      - Start a stream on the first scroll event.
      - End a stream on an idle gap (`STREAM_GAP_MS`) or a direction flip.
    - Normalization
    - Convert raw events into tick-equivalents using per-terminal
    `tui.scroll_events_per_tick`.
    - Wheel-like vs trackpad-like behavior
    - Wheel-like: fixed “classic” lines per wheel notch; flush immediately
    for responsiveness.
    - Trackpad-like: fractional accumulation + carry across stream
    boundaries; coalesce flushes to ~60Hz to avoid floods and reduce “stop
    lag / overshoot”.
    - Trackpad divisor is intentionally capped: `min(scroll_events_per_tick,
    3)` so terminals with dense wheel ticks (e.g. 9 events per notch) don’t
    make trackpads feel artificially slow.
    - Auto mode (default)
      - Start conservatively as trackpad-like (avoid overshoot).
    - Promote to wheel-like if the first tick-worth of events arrives
    quickly.
    - Fallback for 1-event-per-tick terminals (no tick-completion timing
    signal).
    
    #### Trackpad Acceleration
    
    Some terminals produce relatively low vertical event density for
    trackpad gestures, which makes large/faster swipes feel sluggish even
    when small motions feel correct. To address that, trackpad-like streams
    apply a bounded multiplier based on event count:
    
    - `multiplier = clamp(1 + abs(events) / scroll_trackpad_accel_events,
    1..scroll_trackpad_accel_max)`
    
    The multiplier is applied to the trackpad stream’s computed line delta
    (including carried fractional remainder). Defaults are conservative and
    bounded.
    
    #### Config Knobs (TUI2)
    
    All keys live under `[tui]`:
    
    - `scroll_wheel_lines`: lines per physical wheel notch (default: 3).
    - `scroll_events_per_tick`: raw vertical scroll events per physical
    wheel notch (terminal-specific default; fallback: 3).
    - Wheel-like per-event contribution: `scroll_wheel_lines /
    scroll_events_per_tick`.
    - `scroll_trackpad_lines`: baseline trackpad sensitivity (default: 1).
    - Trackpad-like per-event contribution: `scroll_trackpad_lines /
    min(scroll_events_per_tick, 3)`.
    - `scroll_trackpad_accel_events` / `scroll_trackpad_accel_max`: bounded
    trackpad acceleration (defaults: 30 / 3).
    - `scroll_mode = auto|wheel|trackpad`: force behavior or use the
    heuristic (default: `auto`).
    - `scroll_wheel_tick_detect_max_ms`: auto-mode promotion threshold (ms).
    - `scroll_wheel_like_max_duration_ms`: auto-mode fallback for
    1-event-per-tick terminals (ms).
    - `scroll_invert`: invert scroll direction (applies to wheel +
    trackpad).
    
    Config docs: `docs/config.md` and field docs in
    `codex-rs/core/src/config/types.rs`.
    
    #### App Integration
    
    - The app schedules follow-up ticks to close idle streams (via
    `ScrollUpdate::next_tick_in` and `schedule_frame_in`) and finalizes
    streams on draw ticks.
      - `codex-rs/tui2/src/app.rs`
    
    #### Docs
    
    - Single doc of record describing the model + preserved probe
    findings/spec:
      - `codex-rs/tui2/docs/scroll_input_model.md`
    
    #### Other (jj-only friendliness)
    
    - `codex-rs/tui2/src/diff_render.rs`: prefer stable cwd-relative paths
    when the file is under the cwd even if there’s no `.git`.
    
    ### Terminal Defaults
    
    Per-terminal defaults are derived from scroll-probe logs (see doc).
    Notable:
    
    - Ghostty currently defaults to `scroll_events_per_tick = 3` even though
    logs measured ~9 in one setup. This is a deliberate stopgap; if your
    Ghostty build emits ~9 events per wheel notch, set:
    
      ```toml
      [tui]
      scroll_events_per_tick = 9
      ```
    
    ### Testing
    
    - `just fmt`
    - `just fix -p codex-core --allow-no-vcs`
    - `cargo test -p codex-core --lib` (pass)
    - `cargo test -p codex-tui2` (scroll tests pass; remaining failures are
    known flaky VT100 color tests in `insert_history`)
    
    ### Review Focus
    
    - Stream finalization + frame scheduling in `codex-rs/tui2/src/app.rs`.
    - Auto-mode promotion thresholds and the 1-event-per-tick fallback
    behavior.
    - Trackpad divisor cap (`min(events_per_tick, 3)`) and acceleration
    defaults.
    - Ghostty default tradeoff (3 vs ~9) and whether we should change it.
  • chore: enusre the logic that creates ConfigLayerStack has access to cwd (#8353)
    `load_config_layers_state()` should load config from a
    `.codex/config.toml` in any folder between the `cwd` for a thread and
    the project root. Though in order to do that,
    `load_config_layers_state()` needs to know what the `cwd` is, so this PR
    does the work to thread the `cwd` through for existing callsites.
    
    A notable exception is the `/config` endpoint in app server for which a
    `cwd` is not guaranteed to be associated with the query, so the `cwd`
    param is `Option<AbsolutePathBuf>` to account for this case.
    
    The logic to make use of the `cwd` will be done in a follow-up PR.
  • 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: support allowed_sandbox_modes in requirements.toml (#8298)
    This adds support for `allowed_sandbox_modes` in `requirements.toml` and
    provides legacy support for constraining sandbox modes in
    `managed_config.toml`. This is converted to `Constrained<SandboxPolicy>`
    in `ConfigRequirements` and applied to `Config` such that constraints
    are enforced throughout the harness.
    
    Note that, because `managed_config.toml` is deprecated, we do not add
    support for the new `external-sandbox` variant recently introduced in
    https://github.com/openai/codex/pull/8290. As noted, that variant is not
    supported in `config.toml` today, but can be configured programmatically
    via app server.
  • feat(tui2): coalesce transcript scroll redraws (#8295)
    Problem
    - Mouse wheel events were scheduling a redraw on every event, which
    could backlog and create lag during fast scrolling.
    
    Solution
    - Schedule transcript scroll redraws with a short delay (16ms) so the
    frame requester coalesces bursts into fewer draws.
    
    Why
    - Smooths rapid wheel scrolling while keeping the UI responsive.
    
    Testing
    - Manual: Scrolled in iTerm and Ghostty; no lag observed.
    - `cargo clippy --fix --all-features --tests --allow-dirty
    --allow-no-vcs -p codex-tui2`
  • 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.