Commit Graph

34 Commits

  • Rename tui_app_server to tui (#16104)
    This is a follow-up to https://github.com/openai/codex/pull/15922. That
    previous PR deleted the old `tui` directory and left the new
    `tui_app_server` directory in place. This PR renames `tui_app_server` to
    `tui` and fixes up all references.
  • Remove the legacy TUI split (#15922)
    This is the part 1 of 2 PRs that will delete the `tui` /
    `tui_app_server` split. This part simply deletes the existing `tui`
    directory and marks the `tui_app_server` feature flag as removed. I left
    the `tui_app_server` feature flag in place for now so its presence
    doesn't result in an error. It is simply ignored.
    
    Part 2 will rename the `tui_app_server` directory `tui`. I did this as
    two parts to reduce visible code churn.
  • Apply argument comment lint across codex-rs (#14652)
    ## Why
    
    Once the repo-local lint exists, `codex-rs` needs to follow the
    checked-in convention and CI needs to keep it from drifting. This commit
    applies the fallback `/*param*/` style consistently across existing
    positional literal call sites without changing those APIs.
    
    The longer-term preference is still to avoid APIs that require comments
    by choosing clearer parameter types and call shapes. This PR is
    intentionally the mechanical follow-through for the places where the
    existing signatures stay in place.
    
    After rebasing onto newer `main`, the rollout also had to cover newly
    introduced `tui_app_server` call sites. That made it clear the first cut
    of the CI job was too expensive for the common path: it was spending
    almost as much time installing `cargo-dylint` and re-testing the lint
    crate as a representative test job spends running product tests. The CI
    update keeps the full workspace enforcement but trims that extra
    overhead from ordinary `codex-rs` PRs.
    
    ## What changed
    
    - keep a dedicated `argument_comment_lint` job in `rust-ci`
    - mechanically annotate remaining opaque positional literals across
    `codex-rs` with exact `/*param*/` comments, including the rebased
    `tui_app_server` call sites that now fall under the lint
    - keep the checked-in style aligned with the lint policy by using
    `/*param*/` and leaving string and char literals uncommented
    - cache `cargo-dylint`, `dylint-link`, and the relevant Cargo
    registry/git metadata in the lint job
    - split changed-path detection so the lint crate's own `cargo test` step
    runs only when `tools/argument-comment-lint/*` or `rust-ci.yml` changes
    - continue to run the repo wrapper over the `codex-rs` workspace, so
    product-code enforcement is unchanged
    
    Most of the code changes in this commit are intentionally mechanical
    comment rewrites or insertions driven by the lint itself.
    
    ## Verification
    
    - `./tools/argument-comment-lint/run.sh --workspace`
    - `cargo test -p codex-tui-app-server -p codex-tui`
    - parsed `.github/workflows/rust-ci.yml` locally with PyYAML
    
    ---
    
    * -> #14652
    * #14651
  • Add Smart Approvals guardian review across core, app-server, and TUI (#13860)
    ## Summary
    - add `approvals_reviewer = "user" | "guardian_subagent"` as the runtime
    control for who reviews approval requests
    - route Smart Approvals guardian review through core for command
    execution, file changes, managed-network approvals, MCP approvals, and
    delegated/subagent approval flows
    - expose guardian review in app-server with temporary unstable
    `item/autoApprovalReview/{started,completed}` notifications carrying
    `targetItemId`, `review`, and `action`
    - update the TUI so Smart Approvals can be enabled from `/experimental`,
    aligned with the matching `/approvals` mode, and surfaced clearly while
    reviews are pending or resolved
    
    ## Runtime model
    This PR does not introduce a new `approval_policy`.
    
    Instead:
    - `approval_policy` still controls when approval is needed
    - `approvals_reviewer` controls who reviewable approval requests are
    routed to:
      - `user`
      - `guardian_subagent`
    
    `guardian_subagent` is a carefully prompted reviewer subagent that
    gathers relevant context and applies a risk-based decision framework
    before approving or denying the request.
    
    The `smart_approvals` feature flag is a rollout/UI gate. Core runtime
    behavior keys off `approvals_reviewer`.
    
    When Smart Approvals is enabled from the TUI, it also switches the
    current `/approvals` settings to the matching Smart Approvals mode so
    users immediately see guardian review in the active thread:
    - `approval_policy = on-request`
    - `approvals_reviewer = guardian_subagent`
    - `sandbox_mode = workspace-write`
    
    Users can still change `/approvals` afterward.
    
    Config-load behavior stays intentionally narrow:
    - plain `smart_approvals = true` in `config.toml` remains just the
    rollout/UI gate and does not auto-set `approvals_reviewer`
    - the deprecated `guardian_approval = true` alias migration does
    backfill `approvals_reviewer = "guardian_subagent"` in the same scope
    when that reviewer is not already configured there, so old configs
    preserve their original guardian-enabled behavior
    
    ARC remains a separate safety check. For MCP tool approvals, ARC
    escalations now flow into the configured reviewer instead of always
    bypassing guardian and forcing manual review.
    
    ## Config stability
    The runtime reviewer override is stable, but the config-backed
    app-server protocol shape is still settling.
    
    - `thread/start`, `thread/resume`, and `turn/start` keep stable
    `approvalsReviewer` overrides
    - the config-backed `approvals_reviewer` exposure returned via
    `config/read` (including profile-level config) is now marked
    `[UNSTABLE]` / experimental in the app-server protocol until we are more
    confident in that config surface
    
    ## App-server surface
    This PR intentionally keeps the guardian app-server shape narrow and
    temporary.
    
    It adds generic unstable lifecycle notifications:
    - `item/autoApprovalReview/started`
    - `item/autoApprovalReview/completed`
    
    with payloads of the form:
    - `{ threadId, turnId, targetItemId, review, action? }`
    
    `review` is currently:
    - `{ status, riskScore?, riskLevel?, rationale? }`
    - where `status` is one of `inProgress`, `approved`, `denied`, or
    `aborted`
    
    `action` carries the guardian action summary payload from core when
    available. This lets clients render temporary standalone pending-review
    UI, including parallel reviews, even when the underlying tool item has
    not been emitted yet.
    
    These notifications are explicitly documented as `[UNSTABLE]` and
    expected to change soon.
    
    This PR does **not** persist guardian review state onto `thread/read`
    tool items. The intended follow-up is to attach guardian review state to
    the reviewed tool item lifecycle instead, which would improve
    consistency with manual approvals and allow thread history / reconnect
    flows to replay guardian review state directly.
    
    ## TUI behavior
    - `/experimental` exposes the rollout gate as `Smart Approvals`
    - enabling it in the TUI enables the feature and switches the current
    session to the matching Smart Approvals `/approvals` mode
    - disabling it in the TUI clears the persisted `approvals_reviewer`
    override when appropriate and returns the session to default manual
    review when the effective reviewer changes
    - `/approvals` still exposes the reviewer choice directly
    - the TUI renders:
    - pending guardian review state in the live status footer, including
    parallel review aggregation
      - resolved approval/denial state in history
    
    ## Scope notes
    This PR includes the supporting core/runtime work needed to make Smart
    Approvals usable end-to-end:
    - shell / unified-exec / apply_patch / managed-network / MCP guardian
    review
    - delegated/subagent approval routing into guardian review
    - guardian review risk metadata and action summaries for app-server/TUI
    - config/profile/TUI handling for `smart_approvals`, `guardian_approval`
    alias migration, and `approvals_reviewer`
    - a small internal cleanup of delegated approval forwarding to dedupe
    fallback paths and simplify guardian-vs-parent approval waiting (no
    intended behavior change)
    
    Out of scope for this PR:
    - redesigning the existing manual approval protocol shapes
    - persisting guardian review state onto app-server `ThreadItem`s
    - delegated MCP elicitation auto-review (the current delegated MCP
    guardian shim only covers the legacy `RequestUserInput` path)
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • fix(tui): preserve URL clickability across all TUI views (#12067)
    ## Problem
    
    Long URLs containing `/` and `-` characters are split across multiple
    terminal lines by `textwrap`'s default hyphenation rules. This breaks
    terminal link detection: emulators can no longer identify the URL as
    clickable, and copy-paste yields a truncated fragment. The issue affects
    every view that renders user or agent text — exec output, history cells,
    markdown, the app-link setup screen, and the VT100 scrollback path.
    
    A secondary bug compounds the first: `desired_height()` calculations
    count logical lines rather than viewport rows. When a URL overflows its
    line and wraps visually, the height budget is too small, causing content
    to clip or leave gaps.
    
    Here is how the complete URL is interpreted by the terminal before
    (first line only) and after (complete URL):
    
    | Before | After |
    |---|---|
    | <img width="777" height="1002" alt="Screenshot 2026-02-17 at 7 59 11
    PM"
    src="https://github.com/user-attachments/assets/193a89a0-7e56-49c5-8b76-53499a76e7e3"
    /> | <img width="777" height="1002" alt="Screenshot 2026-02-17 at 7 58
    40 PM"
    src="https://github.com/user-attachments/assets/0b9b4c14-aafb-439f-9ffe-f6bba556f95e"
    /> |
    
    ## Mental model
    
    The TUI now treats URL-like tokens as atomic units that must never be
    split by the wrapping engine. Every call site that previously used
    `word_wrap_*` has been migrated to `adaptive_wrap_*`, which inspects
    each line for URL-like tokens and switches wrapping strategy
    accordingly:
    
    - **Non-URL lines** follow the existing `textwrap` path unchanged (word
    boundaries, optional indentation, hyphenation).
    - **URL-only lines** (with at most decorative markers like `│`, `-`,
    `1.`) are emitted unwrapped so terminal link detection works; ratatui's
    `Wrap { trim: false }` handles the final character wrap at render time.
    - **Mixed lines** (URL + substantive non-URL prose) flow through
    `adaptive_wrap_line` so prose wraps naturally at word boundaries while
    URL tokens remain unsplit.
    
    Height measurement everywhere now delegates to
    `Paragraph::line_count(width)`, which accounts for the visual row cost
    of overflowed lines. This single source of truth replaces ad-hoc line
    counting in individual cells.
    
    For terminal scrollback (the VT100 path that prints history when the TUI
    exits), URL-only lines are emitted unwrapped so the terminal's own link
    detector can find them. Mixed URL+prose lines use adaptive wrapping so
    surrounding text wraps naturally. Continuation rows are pre-cleared to
    avoid stale content artifacts.
    
    ## Non-goals
    
    - Full RFC 3986 URL parsing. The detector is a conservative heuristic
    that covers `scheme://host`, bare domains (`example.com/path`),
    `localhost:port`, and IPv4 hosts. IPv6 (`[::1]:8080`) and exotic schemes
    are intentionally excluded from v1.
    - Changing wrapping behavior for non-URL content.
    - Reflowing or reformatting existing terminal scrollback on resize.
    
    ## Tradeoffs
    
    | Decision | Upside | Downside |
    |----------|--------|----------|
    | Heuristic URL detection vs. full parser | Fast, zero-alloc on the hot
    path; conservative enough to reject file paths like `src/main.rs` |
    False negatives on obscure URL formats (they get split as before) |
    | Adaptive (three-path) wrapping | Non-URL lines are untouched — no
    behavior change, no perf cost; mixed lines wrap prose naturally while
    preserving URLs | Three wrapping strategies to reason about when
    debugging layout |
    | Row-based truncation with line-unit ellipsis | Accurate viewport
    budget; stable "N lines omitted" count across terminal widths |
    `truncate_lines_middle` is more complex (must compute per-line row cost)
    |
    | Unwrapped URL-only lines in scrollback | Terminal emulators detect
    clickable links; copy-paste gets the full URL | TUI and scrollback
    formatting diverge for URL-only lines |
    | Default `desired_height` via `Paragraph::line_count` | DRY — most
    cells inherit correct measurement | Cells with custom layout must
    remember to override |
    
    ## Architecture
    
    ```mermaid
    flowchart TD
        A["adaptive_wrap_*()"] --> B{"line_contains_url_like?"}
        B -- No URL tokens --> C["word_wrap_line<br/>(textwrap default)"]
        B -- Has URL tokens --> D{"mixed URL + prose?"}
        D -- "URL-only<br/>(+ decorative markers)" --> E["emit unwrapped<br/>(terminal char-wraps)"]
        D -- "Mixed<br/>(URL + substantive text)" --> F["adaptive_wrap_line<br/>(AsciiSpace + custom WordSplitter)"]
        C --> G["Paragraph::line_count(w)<br/>(single height truth)"]
        E --> G
        F --> G
    ```
    
    **Changed files:**
    
    | File | Role |
    |------|------|
    | `wrapping.rs` | URL detection heuristics, mixed-line detection,
    `adaptive_wrap_*` functions, custom `WordSplitter` |
    | `exec_cell/render.rs` | Row-aware `truncate_lines_middle`, adaptive
    wrapping for command/output display |
    | `history_cell.rs` | Migrate all cell types to `adaptive_wrap_*`;
    default `desired_height` via `Paragraph::line_count` |
    | `insert_history.rs` | Three-path scrollback wrapping (unwrapped
    URL-only, adaptive mixed, word-wrapped text); continuation row clearing
    |
    | `app_link_view.rs` | Adaptive wrapping for setup URL; `desired_height`
    via `Paragraph::line_count` |
    | `markdown_render.rs` | Adaptive wrapping in `finish_paragraph` |
    | `model_migration.rs` | Viewport-aware wrapping for narrow-pane
    markdown |
    | `pager_overlay.rs` | `Wrap { trim: false }` for transcript and
    streaming chunks |
    | `queued_user_messages.rs` | Migrate to `adaptive_wrap_lines` |
    | `status/card.rs` | Migrate to `adaptive_wrap_lines` |
    
    ## Observability
    
    - **Ellipsis message** in truncated exec output reports omitted count in
    logical lines (stable across resize) rather than viewport rows
    (fluctuates).
    - URL detection is deterministic and stateless — no hidden caching or
    memoization to go stale.
    - Height mismatch bugs surface immediately as visual clipping or gaps;
    the `Paragraph::line_count` path is the same code ratatui uses at render
    time, so measurement and rendering cannot diverge.
    
    ## Tests
    
    26 new unit tests across 7 files, covering:
    
    - **URL integrity**: assert a URL-like token appears on exactly one
    rendered line (not split across two).
    - **Height accuracy**: compare `desired_height()` against
    `Paragraph::line_count()` for URL-containing content.
    - **Row-aware truncation**: verify ellipsis counts logical lines and
    output fits within the row budget.
    - **Scrollback rendering**: VT100 backend tests confirm prefix and URL
    land on the same row; continuation rows are cleared; mixed URL+prose
    lines wrap prose while preserving URL tokens.
    - **Mixed URL+prose detection**: `line_has_mixed_url_and_non_url_tokens`
    correctly distinguishes lines with substantive non-URL text from lines
    with only decorative markers alongside a URL.
    - **Heuristic correctness**: positive matches (`https://...`,
    `example.com/path`, `localhost:3000/api`, `192.168.1.1:8080/health`) and
    negative matches (`src/main.rs`, `foo/bar`, `hello-world`).
    
    ## Risks and open items
    
    1. **URL-like tokens in code output** (e.g. `example.com/api` inside a
    JSON blob) will trigger URL-preserving wrap on that line. This is
    acceptable — the worst case is a slightly wider line, not broken output.
    2. **Very long non-URL tokens on a URL line** can only break at
    character boundaries (the custom splitter emits all char indices for
    non-URL words). On extremely narrow terminals this could overflow, but
    narrow terminals already degrade gracefully.
    3. **No IPv6 support** — `[::1]:8080/path` will be treated as a non-URL
    and may get split. Can be added later without API changes.
    
    Fixes #5457
  • chore: remove codex-core public protocol/shell re-exports (#12432)
    ## Why
    
    `codex-rs/core/src/lib.rs` re-exported a broad set of types and modules
    from `codex-protocol` and `codex-shell-command`. That made it easy for
    workspace crates to import those APIs through `codex-core`, which in
    turn hides dependency edges and makes it harder to reduce compile-time
    coupling over time.
    
    This change removes those public re-exports so call sites must import
    from the source crates directly. Even when a crate still depends on
    `codex-core` today, this makes dependency boundaries explicit and
    unblocks future work to drop `codex-core` dependencies where possible.
    
    ## What Changed
    
    - Removed public re-exports from `codex-rs/core/src/lib.rs` for:
    - `codex_protocol::protocol` and related protocol/model types (including
    `InitialHistory`)
      - `codex_protocol::config_types` (`protocol_config_types`)
    - `codex_shell_command::{bash, is_dangerous_command, is_safe_command,
    parse_command, powershell}`
    - Migrated workspace Rust call sites to import directly from:
      - `codex_protocol::protocol`
      - `codex_protocol::config_types`
      - `codex_protocol::models`
      - `codex_shell_command`
    - Added explicit `Cargo.toml` dependencies (`codex-protocol` /
    `codex-shell-command`) in crates that now import those crates directly.
    - Kept `codex-core` internal modules compiling by using `pub(crate)`
    aliases in `core/src/lib.rs` (internal-only, not part of the public
    API).
    - Updated the two utility crates that can already drop a `codex-core`
    dependency edge entirely:
      - `codex-utils-approval-presets`
      - `codex-utils-cli`
    
    ## Verification
    
    - `cargo test -p codex-utils-approval-presets`
    - `cargo test -p codex-utils-cli`
    - `cargo check --workspace --all-targets`
    - `just clippy`
  • tui: queue non-pending rollback trims in app-event order (#11373)
    ## Summary
    
    This PR fixes TUI transcript-sync behavior for
    `EventMsg::ThreadRolledBack` and makes rollback application order
    deterministic.
    
    Previously, rollback handling depended on `pending_rollback`:
    
    - if `pending_rollback` was set (local backtrack), TUI trimmed correctly
    - otherwise, replayed/external rollbacks were either ignored or could be
    applied at the wrong time relative to queued transcript inserts
    
    This change keeps the local backtrack path intact and routes non-pending
    rollbacks through the app event queue so rollback trims are applied in
    FIFO order with transcript cell inserts.
    
    ## What changed
    
    - Added/used `trim_transcript_cells_drop_last_n_user_turns(...)` for
    rollback-by-`num_turns` semantics.
    - Renamed rollback app event:
    - `AppEvent::ApplyReplayedThreadRollback` ->
    `AppEvent::ApplyThreadRollback`
    - Replay path (`ChatWidget`) now emits `ApplyThreadRollback`.
    - Live non-pending rollback path (`App::handle_backtrack_event`) now
    emits `ApplyThreadRollback` instead of trimming immediately.
    - App-level event handler applies `ApplyThreadRollback` after queued
    `InsertHistoryCell` events and schedules redraw only when a trim
    occurred.
    - When a trim occurs with an overlay open, TUI now syncs transcript
    overlay committed cells, clamps backtrack preview selection, and clears
    stale `deferred_history_lines` so closed overlays do not re-append
    rolled-back lines.
    - Clarified inline comments around the `pending_rollback` branch so
    future readers can reason about why there are two paths.
    
    ## Why queueing matters
    
    During resume/replay, transcript cells are populated via queued
    `InsertHistoryCell` app events. If a rollback is applied immediately
    outside that queue, it can run against an incomplete transcript and
    under-trim. Queueing non-pending rollbacks ensures consistent ordering
    and correct final transcript state.
    
    ## Behavior by rollback source
    
    - `pending_rollback = Some(...)` (local backtrack requested by this
    TUI):
      - use `finish_pending_backtrack()` and the stored selection boundary
    - `pending_rollback = None` (replay/external/non-local rollback):
    - enqueue `AppEvent::ApplyThreadRollback { num_turns }` and trim in
    app-event order
    
    ## Tests
    
    Added/updated tests covering ordering and semantics:
    
    -
    `app_backtrack::tests::trim_drop_last_n_user_turns_applies_rollback_semantics`
    - `app_backtrack::tests::trim_drop_last_n_user_turns_allows_overflow`
    - `app::tests::replayed_initial_messages_apply_rollback_in_queue_order`
    -
    `app::tests::live_rollback_during_replay_is_applied_in_app_event_order`
    -
    `app::tests::queued_rollback_syncs_overlay_and_clears_deferred_history`
    - `chatwidget::tests::replayed_thread_rollback_emits_ordered_app_event`
    
    Validation run:
    
    - `just fmt`
    - `cargo test -p codex-tui`
  • feat(tui): pace catch-up stream chunking with hysteresis (#10461)
    ## Summary
    - preserve baseline streaming behavior (smooth mode still commits one
    line per 50ms tick)
    - extract adaptive chunking policy and commit-tick orchestration from
    ChatWidget into `streaming/chunking.rs` and `streaming/commit_tick.rs`
    - add hysteresis-based catch-up behavior with bounded batch draining to
    reduce queue lag without bursty single-frame jumps
    - document policy behavior, tuning guidance, and debug flow in rustdoc +
    docs
    
    ## Testing
    - just fmt
    - cargo test -p codex-tui
  • tui: allow forward navigation in backtrack preview (#9059)
    Fixes #9058
    
    ## Summary
    When the transcript backtrack preview is armed (press `Esc`), allow
    navigating to newer user messages with the `→` arrow, in addition to
    navigating backwards with `Esc`/`←`, before confirming with `Enter`.
    
    ## Changes
    - Backtrack preview navigation: `Esc`/`←` steps to older user messages,
    `→` steps to newer ones, `Enter` edits the selected message (clamped at
    bounds, no wrap-around).
    - Transcript overlay footer hints updated to advertise `esc/←`, `→`, and
    `enter` when a message is highlighted.
    
    ## Related
    - WSL shortcut-overlay snapshot determinism: #9359
    
    ## Testing
    - `just fmt`
    - `just fix -p codex-tui`
    - `just fix -p codex-tui2`
    - `cargo test -p codex-tui app_backtrack::`
    - `cargo test -p codex-tui pager_overlay::`
    - `cargo test -p codex-tui2 app_backtrack::`
    - `cargo test -p codex-tui2 pager_overlay::`
    
    ---------
    
    Co-authored-by: Josh McKinney <joshka@openai.com>
  • 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.
  • Add vim navigation keys to transcript pager (#7550)
    ## Summary
    - add vim-style pager navigation for transcript overlays (j/k,
    ctrl+f/b/d/u) without removing existing keys
    - add shift-space to page up
    
    ------
    [Codex
    Task](https://chatgpt.com/codex/tasks/task_i_69309d26da508329908b2dc8ca40afb7)
  • Fix transcript pager page continuity (#7363)
    ## What
    
    Fix PageUp/PageDown behaviour in the Ctrl+T transcript overlay so that
    paging is continuous and reversible, and add tests to lock in the
    expected behaviour.
    
    ## Why
    
    Today, paging in the transcript overlay uses the raw viewport height
    instead of the effective content height after layout. Because the
    overlay reserves some rows for chrome (header/footer), this can cause:
    
    - PageDown to skip transcript lines between pages.
    - PageUp/PageDown not to “round-trip” cleanly (PageDown then PageUp does
    not always return to the same set of visible lines).
    
    This shows up when inspecting longer transcripts via Ctrl+T; see #7356
    for context.
    
    ## How
    
    - Add a dedicated `PagerView::page_step` helper that computes the page
    size from the last rendered content height and falls back to
    `content_area(viewport_area).height` when that is not yet available.
    - Use `page_step(...)` for both PageUp and PageDown (including SPACE) so
    the scroll step always matches the actual content area height, not the
    full viewport height.
    - Add a focused test
    `transcript_overlay_paging_is_continuous_and_round_trips` that:
      - Renders a synthetic transcript with numbered `line-NN` rows.
    - Asserts that successive PageDown operations show continuous line
    numbers (no gaps).
    - Asserts that PageDown+PageUp and PageUp+PageDown round-trip correctly
    from non-edge offsets.
    
    The change is limited to `codex-rs/tui/src/pager_overlay.rs` and only
    affects the transcript overlay paging semantics.
    
    ## Related issue
    
    - #7356
    
    ## Testing
    
    On Windows 11, using PowerShell 7 in the repo root:
    
    ```powershell
    cargo test
    cargo clippy --tests
    cargo fmt -- --config imports_granularity=Item
    ```
    
    - All tests passed.
    - `cargo clippy --tests` reported some pre-existing warnings that are
    unrelated to this change; no new lints were introduced in the modified
    code.
    
    ---------
    
    Signed-off-by: muyuanjin <24222808+muyuanjin@users.noreply.github.com>
    Co-authored-by: Eric Traut <etraut@openai.com>
  • fix: wrap long exec lines in transcript overlay (#7481)
    What
    -----
    - Fix the Ctrl+T transcript overlay so that very long exec output lines
    are soft‑wrapped to the viewport width instead of being rendered as a
    single truncated row.
    - Add a regression test to `TranscriptOverlay` to ensure long exec
    outputs are rendered on multiple lines in the overlay.
    
    Why
    ----
    - Previously, the transcript overlay rendered extremely long single exec
    lines as one on‑screen row and simply cut them off at the right edge,
    with no horizontal scrolling.
    - This made it impossible to inspect the full content of long tool/exec
    outputs in the transcript view, even though the main TUI view already
    wrapped those lines.
    - Fixes #7454.
    
    How
    ----
    - Update `ExecCell::transcript_lines` to wrap exec output lines using
    the existing `RtOptions`/`word_wrap_line` helpers so that transcript
    rendering is width‑aware.
    - Reuse the existing line utilities to expand the wrapped `Line` values
    into the transcript overlay, preserving styling while respecting the
    current viewport width.
    - Add `transcript_overlay_wraps_long_exec_output_lines` test in
    `pager_overlay.rs` that constructs a long single‑line exec output,
    renders the transcript overlay into a small buffer, and asserts that the
    long marker string spans multiple rendered lines.
  • Added feature switch to disable animations in TUI (#6870)
    This PR adds support for a new feature flag `tui.animations`. By
    default, the TUI uses animations in its welcome screen, "working"
    spinners, and "shimmer" effects. This animations can interfere with
    screen readers, so it's good to provide a way to disable them.
    
    This change is inspired by [a
    PR](https://github.com/openai/codex/pull/4014) contributed by @Orinks.
    That PR has faltered a bit, but I think the core idea is sound. This
    version incorporates feedback from @aibrahim-oai. In particular:
    1. It uses a feature flag (`tui.animations`) rather than the unqualified
    CLI key `no-animations`. Feature flags are the preferred way to expose
    boolean switches. They are also exposed via CLI command switches.
    2. It includes more complete documentation.
    3. It disables a few animations that the other PR omitted.
  • feat: better UI for unified_exec (#6515)
    <img width="376" height="132" alt="Screenshot 2025-11-12 at 17 36 22"
    src="https://github.com/user-attachments/assets/ce693f0d-5ca0-462e-b170-c20811dcc8d5"
    />
  • feature: Add "!cmd" user shell execution (#2471)
    feature: Add "!cmd" user shell execution
    
    This change lets users run local shell commands directly from the TUI by
    prefixing their input with ! (e.g. !ls). Output is truncated to keep the
    exec cell usable, and Ctrl-C cleanly
      interrupts long-running commands (e.g. !sleep 10000).
    
    **Summary of changes**
    
    - Route Op::RunUserShellCommand through a dedicated UserShellCommandTask
    (core/src/tasks/user_shell.rs), keeping the task logic out of codex.rs.
    - Reuse the existing tool router: the task constructs a ToolCall for the
    local_shell tool and relies on ShellHandler, so no manual MCP tool
    lookup is required.
    - Emit exec lifecycle events (ExecCommandBegin/ExecCommandEnd) so the
    TUI can show command metadata, live output, and exit status.
    
    **End-to-end flow**
    
      **TUI handling**
    
    1. ChatWidget::submit_user_message (TUI) intercepts messages starting
    with !.
    2. Non-empty commands dispatch Op::RunUserShellCommand { command };
    empty commands surface a help hint.
    3. No UserInput items are created, so nothing is enqueued for the model.
    
      **Core submission loop**
    4. The submission loop routes the op to handlers::run_user_shell_command
    (core/src/codex.rs).
    5. A fresh TurnContext is created and Session::spawn_user_shell_command
    enqueues UserShellCommandTask.
    
      **Task execution**
    6. UserShellCommandTask::run emits TaskStartedEvent, formats the
    command, and prepares a ToolCall targeting local_shell.
      7. ToolCallRuntime::handle_tool_call dispatches to ShellHandler.
    
      **Shell tool runtime**
    8. ShellHandler::run_exec_like launches the process via the unified exec
    runtime, honoring sandbox and shell policies, and emits
    ExecCommandBegin/End.
    9. Stdout/stderr are captured for the UI, but the task does not turn the
    resulting ToolOutput into a model response.
    
      **Completion**
    10. After ExecCommandEnd, the task finishes without an assistant
    message; the session marks it complete and the exec cell displays the
    final output.
    
      **Conversation context**
    
    - The command and its output never enter the conversation history or the
    model prompt; the flow is local-only.
      - Only exec/task events are emitted for UI rendering.
    
    **Demo video**
    
    
    https://github.com/user-attachments/assets/fcd114b0-4304-4448-a367-a04c43e0b996
  • tui: show aggregated output in display (#5539)
    This shows the aggregated (stdout + stderr) buffer regardless of exit
    code.
    
    Many commands output useful / relevant info on stdout when returning a
    non-zero exit code, or the same on stderr when returning an exit code of
    0. Often, useful info is present on both stdout AND stderr. Also, the
    model sees both. So it is confusing to see commands listed as "(no
    output)" that in fact do have output, just on the stream that doesn't
    match the exit status, or to see some sort of trivial output like "Tests
    failed" but lacking any information about the actual failure.
    
    As such, always display the aggregated output in the display. Transcript
    mode remains unchanged as it was already displaying the text that the
    model sees, which seems correct for transcript mode.
  • tui: /diff mode wraps long lines (#4891)
    fixes a regression that stopped long lines from being wrapped when
    viewing `/diff`.
  • tui: fix wrapping in user approval decisions (#5008)
    before:
    <img width="706" height="71" alt="Screenshot 2025-10-09 at 10 20 57 AM"
    src="https://github.com/user-attachments/assets/ff758b77-4e64-4736-b867-7ebf596e4e65"
    />
    
    after:
    <img width="706" height="71" alt="Screenshot 2025-10-09 at 10 20 35 AM"
    src="https://github.com/user-attachments/assets/6a44efc0-d9ee-40ce-a709-cce969d6e3b3"
    />
  • tui: bring the transcript closer to display mode (#4848)
    before
    <img width="1161" height="836" alt="Screenshot 2025-10-06 at 3 06 52 PM"
    src="https://github.com/user-attachments/assets/7622fd6b-9d37-402f-8651-61c2c55dcbc6"
    />
    
    after
    <img width="1161" height="858" alt="Screenshot 2025-10-06 at 3 07 02 PM"
    src="https://github.com/user-attachments/assets/1498f327-1d1a-4630-951f-7ca371ab0139"
    />
  • normalize key hints (#4586)
    render key hints the same everywhere.
    
    
    
    | Before | After |
    |--------|-------|
    | <img width="816" height="172" alt="Screenshot 2025-10-01 at 5 15 42
    PM"
    src="https://github.com/user-attachments/assets/f88d5db4-04bb-4e89-b571-568222c41e4b"
    /> | <img width="672" height="137" alt="Screenshot 2025-10-01 at 5 13 56
    PM"
    src="https://github.com/user-attachments/assets/1fee6a71-f313-4620-8d9a-10766dc4e195"
    /> |
    | <img width="816" height="172" alt="Screenshot 2025-10-01 at 5 17 01
    PM"
    src="https://github.com/user-attachments/assets/5170ab35-88b7-4131-b485-ecebea9f0835"
    /> | <img width="816" height="174" alt="Screenshot 2025-10-01 at 5 14 24
    PM"
    src="https://github.com/user-attachments/assets/6b6bc64c-25b9-4824-b2d7-56f60370870a"
    /> |
    | <img width="816" height="172" alt="Screenshot 2025-10-01 at 5 17 29
    PM"
    src="https://github.com/user-attachments/assets/2313b36a-e0a8-4cd2-82be-7d0fe7793c19"
    /> | <img width="816" height="134" alt="Screenshot 2025-10-01 at 5 14 37
    PM"
    src="https://github.com/user-attachments/assets/e18934e8-8e9d-4f46-9809-39c8cb6ee893"
    /> |
    | <img width="816" height="172" alt="Screenshot 2025-10-01 at 5 17 40
    PM"
    src="https://github.com/user-attachments/assets/0cc69e4e-8cce-420a-b3e4-be75a7e2c8f5"
    /> | <img width="816" height="134" alt="Screenshot 2025-10-01 at 5 14 56
    PM"
    src="https://github.com/user-attachments/assets/329a5121-ae4a-4829-86e5-4c813543770c"
    /> |
  • rework patch/exec approval UI (#4573)
    | Scenario | Screenshot |
    | ---------------------- |
    ----------------------------------------------------------------------------------------------------------------------------------------------------
    |
    | short patch | <img width="1096" height="533" alt="short patch"
    src="https://github.com/user-attachments/assets/8a883429-0965-4c0b-9002-217b3759b557"
    /> |
    | short command | <img width="1096" height="533" alt="short command"
    src="https://github.com/user-attachments/assets/901abde8-2494-4e86-b98a-7cabaf87ca9c"
    /> |
    | long patch | <img width="1129" height="892" alt="long patch"
    src="https://github.com/user-attachments/assets/fa799a29-a0d6-48e6-b2ef-10302a7916d3"
    /> |
    | long command | <img width="1096" height="892" alt="long command"
    src="https://github.com/user-attachments/assets/11ddf79b-98cb-4b60-ac22-49dfa7779343"
    /> |
    | viewing complete patch | <img width="1129" height="892" alt="viewing
    complete patch"
    src="https://github.com/user-attachments/assets/81666958-af94-420e-aa66-b60d0a42b9db"
    /> |
  • Show exec output on success with trimmed display (#4113)
    - Refactor Exec Cell into its own module
    - update exec command rendering to inline the first command line
    - limit continuation lines
    - always show trimmed output
  • Fix pager overlay clear between pages (#3952)
    should fix characters sometimes hanging around while scrolling the
    transcript.
  • chore: clippy on redundant closure (#4058)
    Add redundant closure clippy rules and let Codex fix it by minimising
    FQP
  • Revert "refactor transcript view to handle HistoryCells" (#3614)
    Reverts openai/codex#3538
    It panics on forking first message. It also calculates the index in a
    wrong way.
  • refactor transcript view to handle HistoryCells (#3538)
    No (intended) functional change.
    
    This refactors the transcript view to hold a list of HistoryCells
    instead of a list of Lines. This simplifies and makes much of the logic
    more robust, as well as laying the groundwork for future changes, e.g.
    live-updating history cells in the transcript.
    
    Similar to #2879 in goal. Fixes #2755.
  • syntax-highlight bash lines (#3142)
    i'm not yet convinced i have the best heuristics for what to highlight,
    but this feels like a useful step towards something a bit easier to
    read, esp. when the model is producing large commands.
    
    <img width="669" height="589" alt="Screenshot 2025-09-03 at 8 21 56 PM"
    src="https://github.com/user-attachments/assets/b9cbcc43-80e8-4d41-93c8-daa74b84b331"
    />
    
    also a fairly significant refactor of our line wrapping logic.
  • tui: pager pins scroll to bottom (#3167)
    when the pager is scrolled to the bottom of the buffer, keep it there.
    
    this should make transcript mode feel a bit more "alive". i've also seen
    some confusion about what transcript mode does/doesn't show that i think
    has been related to it not pinning scroll.
  • prefer ratatui Stylized for constructing lines/spans (#3068)
    no functional change, just simplifying ratatui styling and adding
    guidance in AGENTS.md for future.
  • Cache transcript wraps (#2739)
    Previously long transcripts would become unusable.
  • fix transcript lines being added to diff view (#2721)
    This fixes a bug where if you ran /diff while at turn was running,
    transcript lines would be added to the end of the diff view. Also,
    refactor to make this kind of issue less likely in future.