mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
4e6d6cd7982d2be3a0adb007585f93f06e23bc63
40 Commits
-
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.
Josh McKinney ·
2025-12-22 00:11:36 -08:00 -
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).
Josh McKinney ·
2025-12-22 00:00:32 -08:00 -
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.
Josh McKinney ·
2025-12-20 12:48:12 -08:00 -
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.
Michael Bolin ·
2025-12-19 20:11:27 -08:00 -
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.
Ahmed Ibrahim ·
2025-12-19 16:20:05 -08:00 -
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.
Michael Bolin ·
2025-12-19 21:09:20 +00:00 -
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`
Josh McKinney ·
2025-12-19 12:19:01 -08:00 -
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.
xl-openai ·
2025-12-19 01:16:51 +00:00 -
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>, .... } ```Anton Panasenko ·
2025-12-18 17:02:03 -08:00 -
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.
Michael Bolin ·
2025-12-18 16:12:52 -08:00 -
Support skills shortDescription. (#8278)
Allow SKILL.md to specify a more human-readable short description as skill metadata.
xl-openai ·
2025-12-18 23:13:18 +00:00 -
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.Josh McKinney ·
2025-12-18 12:50:00 -08:00 -
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" />
Jeremy Rose ·
2025-12-18 11:05:26 -08:00 -
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.
Ahmed Ibrahim ·
2025-12-18 10:59:53 -08:00 -
caribou (#8265)
Welcome caribou <img width="1536" height="1024" alt="image" src="https://github.com/user-attachments/assets/2a67b21f-40cf-4518-aee4-691af331ab50" />
Ahmed Ibrahim ·
2025-12-18 08:58:44 -08:00 -
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.
Ahmed Ibrahim ·
2025-12-18 08:41:34 -08:00 -
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>
Ethan Phillips ·
2025-12-18 08:35:22 +00:00 -
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" />
Gav Verma ·
2025-12-17 23:41:04 -08:00 -
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.
Ahmed Ibrahim ·
2025-12-18 02:03:40 +00:00 -
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.
Michael Bolin ·
2025-12-17 18:01:17 -08:00 -
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.
Ahmed Ibrahim ·
2025-12-17 16:12:35 -08:00 -
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
gt-oai ·
2025-12-17 16:19:27 +00:00 -
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
xl-openai ·
2025-12-17 01:35:49 -08:00 -
jif-oai ·
2025-12-17 09:10:51 +00:00 -
Fixed resume matching to respect case insensitivity when using WSL mount points (#8000)
This fixes #7995
Eric Traut ·
2025-12-16 16:27:38 -08:00 -
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>
Koichi Shiraishi ·
2025-12-16 11:31:55 -08:00 -
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.Josh McKinney ·
2025-12-16 05:27:47 +00:00 -
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.
Josh McKinney ·
2025-12-15 17:20:53 -08:00 -
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.
xl-openai ·
2025-12-14 09:58:17 -08:00 -
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.
Eric Traut ·
2025-12-12 21:47:37 -08:00 -
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]) { ```Josh McKinney ·
2025-12-12 20:46:18 -08:00 -
Anton Panasenko ·
2025-12-12 17:07:17 -08:00 -
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.
Michael Bolin ·
2025-12-12 15:25:22 -08:00 -
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" />
Victor Vannara ·
2025-12-12 06:30:56 +00:00 -
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" />
Victor Vannara ·
2025-12-11 16:38:52 -08:00 -
Ahmed Ibrahim ·
2025-12-11 14:06:07 -08:00 -
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>
Ahmed Ibrahim ·
2025-12-11 09:04:08 -08:00 -
fix(stuff) (#7855)
Co-authored-by: Ahmed Ibrahim <aibrahim@openai.com>
Dylan Hurd ·
2025-12-11 00:39:47 -08:00 -
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.
Josh McKinney ·
2025-12-10 22:53:46 +00:00 -
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" />
Josh McKinney ·
2025-12-09 16:23:53 -08:00