Commit Graph

1943 Commits

  • 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>
  • fix: introduce ConfigBuilder (#8235)
    Introduce `ConfigBuilder` as an alternative to our existing `Config`
    constructors.
    
    I noticed that the existing constructors,
    `Config::load_with_cli_overrides()` and
    `Config::load_with_cli_overrides_and_harness_overrides()`, did not take
    `codex_home` as a parameter, which can be a problem.
    
    Historically, when Codex was purely a CLI, we wanted to be extra sure
    that the creation of `codex_home` was always done via
    `find_codex_home()`, so we did not expose `codex_home` as a parameter
    when creating `Config` in business logic. But in integration tests,
    `codex_home` nearly always needs to be configured (as a temp directory),
    which is why callers would have to go through
    `Config::load_from_base_config_with_overrides()` instead.
    
    Now that the Codex harness also functions as an app server, which could
    conceivably load multiple threads where `codex_home` is parameterized
    differently in each one, I think it makes sense to make this
    configurable. Going to a builder pattern makes it more flexible to
    ensure an arbitrary permutation of options can be set when constructing
    a `Config` while using the appropriate defaults for the options that
    aren't set explicitly.
    
    Ultimately, I think this should make it possible for us to make
    `Config::load_from_base_config_with_overrides()` private because all
    integration tests should be able to leverage `ConfigBuilder` instead.
    Though there could be edge cases, so I'll pursue that migration after we
    get through the current config overhaul.
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/8235).
    * #8237
    * __->__ #8235
  • 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"
    />
  • Support SYSTEM skills. (#8220)
    1. Remove PUBLIC skills and introduce SYSTEM skills embedded in the
    binary and installed into $CODEX_HOME/skills/.system at startup.
    2. Skills are now always enabled (feature flag removed).
    3. Update skills/list to accept forceReload and plumb it through (not
    used by clients yet).
  • Show migration link (#8228)
    # External (non-OpenAI) Pull Request Requirements
    
    Before opening this Pull Request, please read the dedicated
    "Contributing" markdown file or your PR may be closed:
    https://github.com/openai/codex/blob/main/docs/contributing.md
    
    If your PR conforms to our contribution guidelines, replace this text
    with a detailed and high quality description of your changes.
    
    Include a link to a bug report or enhancement request.
  • chore: cleanup Config instantiation codepaths (#8226)
    This PR does various types of cleanup before I can proceed with more
    ambitious changes to config loading.
    
    First, I noticed duplicated code across these two methods:
    
    
    https://github.com/openai/codex/blob/774bd9e432fa2e0f4e059e97648cf92216912e19/codex-rs/core/src/config/mod.rs#L314-L324
    
    
    https://github.com/openai/codex/blob/774bd9e432fa2e0f4e059e97648cf92216912e19/codex-rs/core/src/config/mod.rs#L334-L344
    
    This has now been consolidated in
    `load_config_as_toml_with_cli_overrides()`.
    
    Further, I noticed that `Config::load_with_cli_overrides()` took two
    similar arguments:
    
    
    https://github.com/openai/codex/blob/774bd9e432fa2e0f4e059e97648cf92216912e19/codex-rs/core/src/config/mod.rs#L308-L311
    
    The difference between `cli_overrides` and `overrides` was not
    immediately obvious to me. At first glance, it appears that one should
    be able to be expressed in terms of the other, but it turns out that
    some fields of `ConfigOverrides` (such as `cwd` and
    `codex_linux_sandbox_exe`) are, by design, not configurable via a
    `.toml` file or a command-line `--config` flag.
    
    That said, I discovered that many callers of
    `Config::load_with_cli_overrides()` were passing
    `ConfigOverrides::default()` for `overrides`, so I created two separate
    methods:
    
    - `Config::load_with_cli_overrides(cli_overrides: Vec<(String,
    TomlValue)>)`
    - `Config::load_with_cli_overrides_and_harness_overrides(cli_overrides:
    Vec<(String, TomlValue)>, harness_overrides: ConfigOverrides)`
    
    The latter has a long name, as it is _not_ what should be used in the
    common case, so the extra typing is designed to draw attention to this
    fact. I tried to update the existing callsites to use the shorter name,
    where possible.
    
    Further, in the cases where `ConfigOverrides` is used, usually only a
    limited subset of fields are actually set, so I updated the declarations
    to leverage `..Default::default()` where possible.
  • feat: model picker (#8209)
    # External (non-OpenAI) Pull Request Requirements
    
    Before opening this Pull Request, please read the dedicated
    "Contributing" markdown file or your PR may be closed:
    https://github.com/openai/codex/blob/main/docs/contributing.md
    
    If your PR conforms to our contribution guidelines, replace this text
    with a detailed and high quality description of your changes.
    
    Include a link to a bug report or enhancement request.
  • speed and reliability improvements for setting reads ACLs (#8216)
    - Batch read ACL creation for online/offline sandbox user
    - creates a new ACL helper process that is long-lived and runs in the
    background
    - uses a mutex so that only one helper process is running at a time.
  • Load models from static file (#8153)
    - Load models from static file as a fallback
    - Make API users use this file directly
    - Add tests to make sure updates to the file always serialize
  • feat: make list_models non-blocking (#8198)
    ### Summary
    * Make `app_server.list_models` to be non-blocking and consumers (i.e.
    extension) can manage the flow themselves.
    * Force config to use remote models and therefore fetch codex-auto model
    list.
  • fix: PathBuf -> AbsolutePathBuf in ConfigToml struct (#8205)
    We should not have any `PathBuf` fields in `ConfigToml` or any of the
    transitive structs we include, as we should use `AbsolutePathBuf`
    instead so that we do not have to keep track of the file from which
    `ConfigToml` was loaded such that we need it to resolve relative paths
    later when the values of `ConfigToml` are used.
    
    I only found two instances of this: `experimental_instructions_file` and
    `experimental_compact_prompt_file`. Incidentally, when these were
    specified as relative paths, they were resolved against `cwd` rather
    than `config.toml`'s parent, which seems wrong to me. I changed the
    behavior so they are resolved against the parent folder of the
    `config.toml` being parsed, which we get "for free" due to the
    introduction of `AbsolutePathBufGuard ` in
    https://github.com/openai/codex/pull/7796.
    
    While it is not great to change the behavior of a released feature,
    these fields are prefixed with `experimental_`, which I interpret to
    mean we have the liberty to change the contract.
    
    For reference:
    
    - `experimental_instructions_file` was introduced in
    https://github.com/openai/codex/pull/1803
    - `experimental_compact_prompt_file` was introduced in
    https://github.com/openai/codex/pull/5959
  • fix: session downgrade (#8196)
    The problem is that the `tokio` task own an `Arc` reference of the
    session and that this task only exit with the broadcast channel get
    closed. But this never get closed if the session is not dropped. So it's
    a snake biting his tail basically
    
    The most notable result was that non of the `Drop` implementation were
    triggered (temporary files, shell snapshots, session cleaning etc etc)
    when closing the session (through a `/new` for example)
    
    The fix is just to weaken the `Arc` and upgrade it on the fly
  • chore: update listMcpServerStatus to be non-blocking (#8151)
    ### Summary
    * Update `listMcpServerStatus` to be non-blocking by wrapping it with
    tokio:spawn.
  • feat: unified exec footer (#8117)
    # With `unified_exec`
    Known tools are correctly casted
    <img width="1150" height="312" alt="Screenshot 2025-12-16 at 19 27 28"
    src="https://github.com/user-attachments/assets/24150ee5-e88d-461b-a459-483c24784196"
    />
    If a session exit the turn, we render it with the "Ran ..."
    <img width="1168" height="355" alt="Screenshot 2025-12-16 at 19 27 58"
    src="https://github.com/user-attachments/assets/3f00b60c-2d57-4f9d-a201-9cc8388957cb"
    />
    If a session does not exit during the turn, it is closed at the end of
    the turn but this is not rendered
    <img width="642" height="342" alt="Screenshot 2025-12-16 at 19 34 37"
    src="https://github.com/user-attachments/assets/c2bd9283-7017-4915-ba73-c52199b0b28e"
    />
    
    # Without `unified_exec`
    No changes
    <img width="740" height="603" alt="Screenshot 2025-12-16 at 19 31 21"
    src="https://github.com/user-attachments/assets/ca5d90fe-a9b2-42ba-bcd7-3e98c4ed22e8"
    />
  • feat: experimental menu (#8071)
    This will automatically render any `Stage::Beta` features.
    
    The change only gets applied to the *next session*. This started as a
    bug but actually this is a good thing to prevent out of distribution
    push
    
    <img width="986" height="288" alt="Screenshot 2025-12-15 at 15 38 35"
    src="https://github.com/user-attachments/assets/78b7a71d-0e43-4828-a118-91c5237909c7"
    />
    
    
    <img width="509" height="109" alt="Screenshot 2025-12-15 at 17 35 44"
    src="https://github.com/user-attachments/assets/6933de52-9b66-4abf-b58b-a5f26d5747e2"
    />
  • feat: Constrain values for approval_policy (#7778)
    Constrain `approval_policy` through new `admin_policy` config.
    
    This PR will:
    1. Add a `admin_policy` section to config, with a single field (for now)
    `allowed_approval_policies`. This list constrains the set of
    user-settable `approval_policy`s.
    2. Introduce a new `Constrained<T>` type, which combines a current value
    and a validator function. The validator function ensures disallowed
    values are not set.
    3. Change the type of `approval_policy` on `Config` and
    `SessionConfiguration` from `AskForApproval` to
    `Constrained<AskForApproval>`. The validator function is set by the
    values passed into `allowed_approval_policies`.
    4. `GenericDisplayRow`: add a `disabled_reason: Option<String>`. When
    set, it disables selection of the value and indicates as such in the
    menu. This also makes it unselectable with arrow keys or numbers. This
    is used in the `/approvals` menu.
    
    Follow ups are:
    1. Do the same thing to `sandbox_policy`.
    2. Propagate the allowed set of values through app-server for the
    extension (though already this should prevent app-server from setting
    this values, it's just that we want to disable UI elements that are
    unsettable).
    
    Happy to split this PR up if you prefer, into the logical numbered areas
    above. Especially if there are parts we want to gavel on separately
    (e.g. admin_policy).
    
    Disabled full access:
    <img width="1680" height="380" alt="image"
    src="https://github.com/user-attachments/assets/1fb61c8c-1fcb-4dc4-8355-2293edb52ba0"
    />
    
    Disabled `--yolo` on startup:
    <img width="749" height="76" alt="image"
    src="https://github.com/user-attachments/assets/0a1211a0-6eb1-40d6-a1d7-439c41e94ddb"
    />
    
    CODEX-4087
  • feat: change ConfigLayerName into a disjoint union rather than a simple enum (#8095)
    This attempts to tighten up the types related to "config layers."
    Currently, `ConfigLayerEntry` is defined as follows:
    
    
    https://github.com/openai/codex/blob/bef36f4ae765f471d7cd69372fcf1b92c8f0367a/codex-rs/core/src/config_loader/state.rs#L19-L25
    
    but the `source` field is a bit of a lie, as:
    
    - for `ConfigLayerName::Mdm`, it is
    `"com.openai.codex/config_toml_base64"`
    - for `ConfigLayerName::SessionFlags`, it is `"--config"`
    - for `ConfigLayerName::User`, it is `"config.toml"` (just the file
    name, not the path to the `config.toml` on disk that was read)
    - for `ConfigLayerName::System`, it seems like it is usually
    `/etc/codex/managed_config.toml` in practice, though on Windows, it is
    `%CODEX_HOME%/managed_config.toml`:
    
    
    https://github.com/openai/codex/blob/bef36f4ae765f471d7cd69372fcf1b92c8f0367a/codex-rs/core/src/config_loader/layer_io.rs#L84-L101
    
    All that is to say, in three out of the four `ConfigLayerName`, `source`
    is a `PathBuf` that is not an absolute path (or even a true path).
    
    This PR tries to uplevel things by eliminating `source` from
    `ConfigLayerEntry` and turning `ConfigLayerName` into a disjoint union
    named `ConfigLayerSource` that has the appropriate metadata for each
    variant, favoring the use of `AbsolutePathBuf` where appropriate:
    
    ```rust
    pub enum ConfigLayerSource {
        /// Managed preferences layer delivered by MDM (macOS only).
        #[serde(rename_all = "camelCase")]
        #[ts(rename_all = "camelCase")]
        Mdm { domain: String, key: String },
        /// Managed config layer from a file (usually `managed_config.toml`).
        #[serde(rename_all = "camelCase")]
        #[ts(rename_all = "camelCase")]
        System { file: AbsolutePathBuf },
        /// Session-layer overrides supplied via `-c`/`--config`.
        SessionFlags,
        /// User config layer from a file (usually `config.toml`).
        #[serde(rename_all = "camelCase")]
        #[ts(rename_all = "camelCase")]
        User { file: AbsolutePathBuf },
    }
    ```
  • 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
  • chore(apply-patch) unicode scenario (#8141)
    ## Summary
    Adds a unicode scenario, and fills in files on failing scenarios to
    ensure directory state is unchanged, for completeness
    
    ## Testing
    - [x] only changes tests
  • [app-server] add new RawResponseItem v2 event (#8152)
    ``codex/event/raw_response_item` (v1) -> `rawResponseItem/completed`
    (v1).
    
    test client log:
    ````
    < {
    <   "method": "codex/event/raw_response_item",
    <   "params": {
    <     "conversationId": "019b29f7-b089-7140-a535-3fe681562c15",
    <     "id": "0",
    <     "msg": {
    <       "item": {
    <         "arguments": "{\"command\":\"sed -n '1,160p' Cargo.toml\",\"workdir\":\"/Users/celia/code/codex/codex-rs\"}",
    <         "call_id": "call_DrqbdB2jPxezPWc19YVEEt3h",
    <         "name": "shell_command",
    <         "type": "function_call"
    <       },
    <       "type": "raw_response_item"
    <     }
    <   }
    < }
    < {
    <   "method": "rawResponseItem/completed",
    <   "params": {
    <     "item": {
    <       "arguments": "{\"command\":\"sed -n '1,160p' Cargo.toml\",\"workdir\":\"/Users/celia/code/codex/codex-rs\"}",
    <       "call_id": "call_DrqbdB2jPxezPWc19YVEEt3h",
    <       "name": "shell_command",
    <       "type": "function_call"
    <     },
    <     "threadId": "019b29f7-b089-7140-a535-3fe681562c15",
    <     "turnId": "0"
    <   }
    < }
    ```
  • chore: update listMcpServers to listMcpServerStatus (#8114)
    ### Summary
    * rename app server `listMcpServers` to `listMcpServerStatuses`.
  • docs: refine tui2 viewport roadmap (#8122)
    Update the tui2 viewport/history design doc with current status and a
    prioritized roadmap (scroll feel, selection/copy correctness, streaming
    wrap polish, terminal integration, and longer-term per-cell
    interactivity ideas).
  • chore(apply-patch) move invocation tests (#8111)
    ## Summary:
    This PR is a pure copy and paste of tests from lib.rs into
    invocation.rs, to colocate logic and tests.
    
    ## Testing
    - [x] Purely a test refactor
  • Revert "chore: review in read-only (#7593)" (#8127)
    This reverts commit 291b54a762.
    
    This commit was intended to prevent the model from making code changes
    during `/review`, which is sometimes does. Unfortunately, it has other
    unintended side effects that cause `/review` to fail in a variety of
    ways. See #8115 and #7815. We've therefore decided to revert this
    change.
  • 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>
  • chore(apply-patch) move invocation parsing (#8110)
    lib.rs has grown quite large, and mixes two responsibilities:
    1. executing patch operations
    2. parsing apply_patch invocations via a shell command
    
    This PR splits out (2) into its own file, so we can work with it more
    easily. We are explicitly NOT moving tests in this PR, to ensure
    behavior stays the same and we can avoid losing coverage via merge
    conflicts. Tests are moved in a subsequent PR.
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/8110).
    * #8111
    * __->__ #8110
  • bug fixes and perf improvements for elevated sandbox setup (#8094)
    a few fixes based on testing feedback:
    * ensure cap_sid file is always written by elevated setup.
    * always log to same file whether using elevated sandbox or not
    * process potentially slow ACE write operations in parallel
    * dedupe write roots so we don't double process any
    * don't try to create read/write ACEs on the same directories, due to
    race condition
  • nit: trace span for regular task (#8053)
    Logs are too spammy
    
    ---------
    
    Co-authored-by: Anton Panasenko <apanasenko@openai.com>
  • feat: unified exec footer (#8067)
    <img width="452" height="205" alt="Screenshot 2025-12-15 at 17 54 44"
    src="https://github.com/user-attachments/assets/9ece0b1c-8387-4dfc-b883-c6a68ea1b663"
    />
  • refactor TUI event loop to enable dropping + recreating crossterm event stream (#7961)
    Introduces an `EventBroker` between the crossterm `EventStream` source
    and the consumers in the TUI. This enables dropping + recreating the
    `crossterm_events` without invalidating the consumer.
    
    Dropping and recreating the crossterm event stream enables us to fully
    relinquish `stdin` while the app keeps running. If the stream is not
    dropped, it will continue to read from `stdin` even when it is not
    actively being polled, potentially stealing input from other processes.
    See
    [here](https://www.reddit.com/r/rust/comments/1f3o33u/myterious_crossterm_input_after_running_vim/?utm_source=chatgpt.com)
    and [here](https://ratatui.rs/recipes/apps/spawn-vim/) for details.
    
    ### Tests
    Added tests for new `EventBroker` setup, existing tests pass, tested
    locally.
  • feat: if .codex is a sub-folder of a writable root, then make it read-only to the sandbox (#8088)
    In preparation for in-repo configuration support, this updates
    `WritableRoot::get_writable_roots_with_cwd()` to include the `.codex`
    subfolder in `WritableRoot.read_only_subpaths`, if it exists, as we
    already do for `.git`.
    
    As noted, currently, like `.git`, `.codex` will only be read-only under
    macOS Seatbelt, but we plan to bring support to other OSes, as well.
    
    Updated the integration test in `seatbelt.rs` so that it actually
    attempts to run the generated Seatbelt commands, verifying that:
    
    - trying to write to `.codex/config.toml` in a writable root fails
    - trying to write to `.git/hooks/pre-commit` in a writable root fails
    - trying to write to the writable root containing the `.codex` and
    `.git` subfolders succeeds
  • 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.
  • chore(shell_command) fix freeform timeout output (#7791)
    ## Summary
    Adding an additional integration test for timeout_ms
    
    ## Testing
    - [x] these are tests
  • feat: merge remote models instead of destructing (#7997)
    - merge remote models instead of destructing
    - make config values have more precedent over remote values
  • 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.
  • chore(app-server): remove stubbed thread/compact API (#8086)
    We want to rely on server-side auto-compaction instead of having the
    client trigger context compaction manually. This API was stubbed as a
    placeholder and never implemented.