Commit Graph

1111 Commits

  • fix: render cwd-relative paths in tui (#8771)
    Display paths relative to the cwd before checking git roots so view
    image tool calls keep project-local names in jj/no-.git workspaces.
  • [device-auth] Update login instruction for headless environments. (#8753)
    We've seen reports that people who try to login on a remote/headless
    machine will open the login link on their own machine and got errors.
    Update the instructions to ask those users to use `codex login
    --device-auth` instead.
    
    <img width="1434" height="938" alt="CleanShot 2026-01-05 at 11 35 02@2x"
    src="https://github.com/user-attachments/assets/2b209953-6a42-4eb0-8b55-bb0733f2e373"
    />
  • feat: expose outputSchema to user_turn/turn_start app_server API (#8377)
    What changed
    - Added `outputSchema` support to the app-server APIs, mirroring `codex
    exec --output-schema` behavior.
    - V1 `sendUserTurn` now accepts `outputSchema` and constrains the final
    assistant message for that turn.
    - V2 `turn/start` now accepts `outputSchema` and constrains the final
    assistant message for that turn (explicitly per-turn only).
    
    Core behavior
    - `Op::UserTurn` already supported `final_output_json_schema`; now V1
    `sendUserTurn` forwards `outputSchema` into that field.
    - `Op::UserInput` now carries `final_output_json_schema` for per-turn
    settings updates; core maps it into
    `SessionSettingsUpdate.final_output_json_schema` so it applies to the
    created turn context.
    - V2 `turn/start` does NOT persist the schema via `OverrideTurnContext`
    (it’s applied only for the current turn). Other overrides
    (cwd/model/etc) keep their existing persistent behavior.
    
    API / docs
    - `codex-rs/app-server-protocol/src/protocol/v1.rs`: add `output_schema:
    Option<serde_json::Value>` to `SendUserTurnParams` (serialized as
    `outputSchema`).
    - `codex-rs/app-server-protocol/src/protocol/v2.rs`: add `output_schema:
    Option<JsonValue>` to `TurnStartParams` (serialized as `outputSchema`).
    - `codex-rs/app-server/README.md`: document `outputSchema` for
    `turn/start` and clarify it applies only to the current turn.
    - `codex-rs/docs/codex_mcp_interface.md`: document `outputSchema` for v1
    `sendUserTurn` and v2 `turn/start`.
    
    Tests added/updated
    - New app-server integration tests asserting `outputSchema` is forwarded
    into outbound `/responses` requests as `text.format`:
      - `codex-rs/app-server/tests/suite/output_schema.rs`
      - `codex-rs/app-server/tests/suite/v2/output_schema.rs`
    - Added per-turn semantics tests (schema does not leak to the next
    turn):
      - `send_user_turn_output_schema_is_per_turn_v1`
      - `turn_start_output_schema_is_per_turn_v2`
    - Added protocol wire-compat tests for the merged op:
      - serialize omits `final_output_json_schema` when `None`
      - deserialize works when field is missing
      - serialize includes `final_output_json_schema` when `Some(schema)`
    
    Call site updates (high level)
    - Updated all `Op::UserInput { .. }` constructions to include
    `final_output_json_schema`:
      - `codex-rs/app-server/src/codex_message_processor.rs`
      - `codex-rs/core/src/codex_delegate.rs`
      - `codex-rs/mcp-server/src/codex_tool_runner.rs`
      - `codex-rs/tui/src/chatwidget.rs`
      - `codex-rs/tui2/src/chatwidget.rs`
      - plus impacted core tests.
    
    Validation
    - `just fmt`
    - `cargo test -p codex-core`
    - `cargo test -p codex-app-server`
    - `cargo test -p codex-mcp-server`
    - `cargo test -p codex-tui`
    - `cargo test -p codex-tui2`
    - `cargo test -p codex-protocol`
    - `cargo clippy --all-features --tests --profile dev --fix -- -D
    warnings`
  • perf(tui2): cache transcript view rendering (#8693)
    The transcript viewport draws every frame. Ratatui's Line::render_ref
    does grapheme segmentation and span layout, so repeated redraws can burn
    CPU during streaming even when the visible transcript hasn't changed.
    
    Introduce TranscriptViewCache to reduce per-frame work:
    - WrappedTranscriptCache memoizes flattened+wrapped transcript lines per
    width, appends incrementally as new cells arrive, and rebuilds on width
    change, truncation (backtrack), or transcript replacement.
    - TranscriptRasterCache caches rasterized rows (Vec<Cell>) per line
    index and user-row styling; redraws copy cells instead of rerendering
    spans.
    
    The caches are width-scoped and store base transcript content only;
    selection highlighting and copy affordances are applied after drawing.
    User rows include the row-wide base style in the cached raster.
    
    Refactor transcript_render to expose append_wrapped_transcript_cell for
    incremental building and add a test that incremental append matches the
    full build.
    
    Add docs/tui2/performance-testing.md as a playbook for macOS sample
    profiles and hotspot greps.
    
    Expand transcript_view_cache tests to cover rebuild conditions, raster
    equivalence vs direct rendering, user-row caching, and eviction.
    
    Test: cargo test -p codex-tui2
  • Attach more tags to feedback submissions (#8688)
    Attach more tags to sentry feedback so it's easier to classify and debug
    without having to scan through logs.
    
    Formatting isn't amazing but it's a start.
    <img width="1234" height="276" alt="image"
    src="https://github.com/user-attachments/assets/521a349d-f627-4051-b511-9811cd5cd933"
    />
  • Remove model family from tui (#8488)
    - Remove model family from tui
  • [chore] add additional_details to StreamErrorEvent + wire through (#8307)
    ### What
    
    Builds on #8293.
    
    Add `additional_details`, which contains the upstream error message, to
    relevant structures used to pass along retryable `StreamError`s.
    
    Uses the new TUI status indicator's `details` field (shows under the
    status header) to display the `additional_details` error to the user on
    retryable `Reconnecting...` errors. This adds clarity for users for
    retryable errors.
    
    Will make corresponding change to VSCode extension to show
    `additional_details` as expandable from the `Reconnecting...` cell.
    
    Examples:
    <img width="1012" height="326" alt="image"
    src="https://github.com/user-attachments/assets/f35e7e6a-8f5e-4a2f-a764-358101776996"
    />
    
    <img width="1526" height="358" alt="image"
    src="https://github.com/user-attachments/assets/0029cbc0-f062-4233-8650-cc216c7808f0"
    />
  • perf(tui): cap redraw scheduling to 60fps (#8499)
    Clamp frame draw notifications in the `FrameRequester` scheduler so we
    don't redraw more frequently than a user can perceive.
    
    This applies to both `codex-tui` and `codex-tui2`, and keeps the
    draw/dispatch loops simple by centralizing the rate limiting in a small
    helper module.
    
    - Add `FrameRateLimiter` (pure, unit-tested) to clamp draw deadlines
    - Apply the limiter in the scheduler before emitting `TuiEvent::Draw`
    - Use immediate redraw requests for scroll paths (scheduler now
    coalesces + clamps)
    - Add scheduler tests covering immediate/delayed interactions
  • Remove reasoning format (#8484)
    This isn't very useful parameter. 
    
    logic:
    ```
    if model puts `**` in their reasoning, trim it and visualize the header.
    if couldn't trim: don't render
    if model doesn't support: don't render
    ```
    
    We can simplify to:
    ```
    if could trim, visualize header.
    if not, don't render
    ```
  • fix: fix test that was writing temp file to cwd instead of TMPDIR (#8493)
    I am trying to support building with [Buck2](https://buck2.build), which
    reports which files have changed between invocations of `buck2 test` and
    `tmp_delete_example.txt` came up. This turned out to be the reason.
  • [tui] add optional details to TUI status header (#8293)
    ### What
    
    Add optional `details` field to TUI's status indicator header. `details`
    is shown under the header with text wrapping and a max height of 3
    lines.
    
    Duplicated changes to `tui2`.
    
    ### Why
    
    Groundwork for displaying error details under `Reconnecting...` for
    clarity with retryable errors.
    
    Basic examples
    <img width="1012" height="326" alt="image"
    src="https://github.com/user-attachments/assets/dd751ceb-b179-4fb2-8fd1-e4784d6366fb"
    />
    
    <img width="1526" height="358" alt="image"
    src="https://github.com/user-attachments/assets/bbe466fc-faff-4a78-af7f-3073ccdd8e34"
    />
    
    Truncation example
    <img width="936" height="189" alt="image"
    src="https://github.com/user-attachments/assets/f3f1b5dd-9050-438b-bb07-bd833c03e889"
    />
    
    ### Tests
    Tested locally, added tests for truncation.
  • chore(tui): include tracing targets in file logs (#8418)
    with_target(true) is the default for tracing-subscriber, but we
    previously disabled it for file output.
    
    Keep it enabled so we can selectively enable specific targets/events at
    runtime via RUST_LOG=..., and then grep by target/module in the log file
    during troubleshooting.
    
    before and after:
    
    <img width="629" height="194" alt="image"
    src="https://github.com/user-attachments/assets/33f7df3f-0c5d-4d3f-b7b7-80b03d4acd21"
    />
  • feat: open prompt in configured external editor (#7606)
    Add `ctrl+g` shortcut to enable opening current prompt in configured
    editor (`$VISUAL` or `$EDITOR`).
    
    
    - Prompt is updated with editor's content upon editor close.
    - Paste placeholders are automatically expanded when opening the
    external editor, and are not "recompressed" on close
    - They could be preserved in the editor, but it would be hard to prevent
    the user from modifying the placeholder text directly, which would drop
    the mapping to the `pending_paste` value
    - Image placeholders stay as-is
    - `ctrl+g` explanation added to shortcuts menu, snapshot tests updated
    
    
    
    https://github.com/user-attachments/assets/4ee05c81-fa49-4e99-8b07-fc9eef0bbfce
  • chore: enusre the logic that creates ConfigLayerStack has access to cwd (#8353)
    `load_config_layers_state()` should load config from a
    `.codex/config.toml` in any folder between the `cwd` for a thread and
    the project root. Though in order to do that,
    `load_config_layers_state()` needs to know what the `cwd` is, so this PR
    does the work to thread the `cwd` through for existing callsites.
    
    A notable exception is the `/config` endpoint in app server for which a
    `cwd` is not guaranteed to be associated with the query, so the `cwd`
    param is `Option<AbsolutePathBuf>` to account for this case.
    
    The logic to make use of the `cwd` will be done in a follow-up PR.
  • Rename OpenAI models to models manager (#8346)
    # External (non-OpenAI) Pull Request Requirements
    
    Before opening this Pull Request, please read the dedicated
    "Contributing" markdown file or your PR may be closed:
    https://github.com/openai/codex/blob/main/docs/contributing.md
    
    If your PR conforms to our contribution guidelines, replace this text
    with a detailed and high quality description of your changes.
    
    Include a link to a bug report or enhancement request.
  • feat: support allowed_sandbox_modes in requirements.toml (#8298)
    This adds support for `allowed_sandbox_modes` in `requirements.toml` and
    provides legacy support for constraining sandbox modes in
    `managed_config.toml`. This is converted to `Constrained<SandboxPolicy>`
    in `ConfigRequirements` and applied to `Config` such that constraints
    are enforced throughout the harness.
    
    Note that, because `managed_config.toml` is deprecated, we do not add
    support for the new `external-sandbox` variant recently introduced in
    https://github.com/openai/codex/pull/8290. As noted, that variant is not
    supported in `config.toml` today, but can be configured programmatically
    via app server.
  • feat: make ConstraintError an enum (#8330)
    This will make it easier to test for expected errors in unit tests since
    we can compare based on the field values rather than the message (which
    might change over time). See https://github.com/openai/codex/pull/8298
    for an example.
    
    It also ensures more consistency in the way a `ConstraintError` is
    constructed.
  • Fix tests (#8299)
    Fix broken tests.
  • UI tweaks on skills popup. (#8250)
    Only display the skill name (not the folder), and truncate the skill
    description to a maximum of two lines.
  • feat: collapse "waiting" of unified_exec (#8257)
    Screenshots here but check the snapshot files to see it better
    <img width="712" height="408" alt="Screenshot 2025-12-18 at 11 58 02"
    src="https://github.com/user-attachments/assets/84a2c410-0767-4870-84d1-ae1c0d4c445e"
    />
    <img width="523" height="352" alt="Screenshot 2025-12-18 at 11 17 41"
    src="https://github.com/user-attachments/assets/d029c7ea-0feb-4493-9dca-af43a0c70c52"
    />
  • feat: introduce ExternalSandbox policy (#8290)
    ## Description
    
    Introduced `ExternalSandbox` policy to cover use case when sandbox
    defined by outside environment, effectively it translates to
    `SandboxMode#DangerFullAccess` for file system (since sandbox configured
    on container level) and configurable `network_access` (either Restricted
    or Enabled by outside environment).
    
    as example you can configure `ExternalSandbox` policy as part of
    `sendUserTurn` v1 app_server API:
    
    ```
     {
                "conversationId": <id>,
                "cwd": <cwd>,
                "approvalPolicy": "never",
                "sandboxPolicy": {
                      "type": ""external-sandbox",
                      "network_access": "enabled"/"restricted"
                },
                "model": <model>,
                "effort": <effort>,
                ....
            }
    ```
  • chore: migrate from Config::load_from_base_config_with_overrides to ConfigBuilder (#8276)
    https://github.com/openai/codex/pull/8235 introduced `ConfigBuilder` and
    this PR updates all call non-test call sites to use it instead of
    `Config::load_from_base_config_with_overrides()`.
    
    This is important because `load_from_base_config_with_overrides()` uses
    an empty `ConfigRequirements`, which is a reasonable default for testing
    so the tests are not influenced by the settings on the host. This method
    is now guarded by `#[cfg(test)]` so it cannot be used by business logic.
    
    Because `ConfigBuilder::build()` is `async`, many of the test methods
    had to be migrated to be `async`, as well. On the bright side, this made
    it possible to eliminate a bunch of `block_on_future()` stuff.
  • Support skills shortDescription. (#8278)
    Allow SKILL.md to specify a more human-readable short description as
    skill metadata.
  • feat: add /ps (#8279)
    See snapshots for view of edge cases
    This is still named `UnifiedExecSessions` for consistency across the
    code but should be renamed to `BackgroundTerminals` in a follow-up
    
    Example:
    <img width="945" height="687" alt="Screenshot 2025-12-18 at 20 12 53"
    src="https://github.com/user-attachments/assets/92f39ff2-243c-4006-b402-e3fa9e93c952"
    />
  • tui: improve rendering of search cell (#8273)
    before:
    
    <img width="795" height="150" alt="Screenshot 2025-12-18 at 10 48 01 AM"
    src="https://github.com/user-attachments/assets/6f4d8856-b4c2-4e2a-b60a-b86f82b956a0"
    />
    
    after:
    
    <img width="795" height="150" alt="Screenshot 2025-12-18 at 10 48 39 AM"
    src="https://github.com/user-attachments/assets/dd0d167a-5d09-4bb7-9d36-95a2eb1aaa83"
    />
  • splash screen (#8270)
    # External (non-OpenAI) Pull Request Requirements
    
    Before opening this Pull Request, please read the dedicated
    "Contributing" markdown file or your PR may be closed:
    https://github.com/openai/codex/blob/main/docs/contributing.md
    
    If your PR conforms to our contribution guidelines, replace this text
    with a detailed and high quality description of your changes.
    
    Include a link to a bug report or enhancement request.
  • feat: add name to beta features (#8266)
    Add a name to Beta features
    
    <img width="906" height="153" alt="Screenshot 2025-12-18 at 16 42 49"
    src="https://github.com/user-attachments/assets/d56f3519-0613-4d9a-ad4d-38b1a7eb125a"
    />
  • caribou (#8265)
    Welcome caribou
    
    <img width="1536" height="1024" alt="image"
    src="https://github.com/user-attachments/assets/2a67b21f-40cf-4518-aee4-691af331ab50"
    />
  • chores: clean picker (#8232)
    # External (non-OpenAI) Pull Request Requirements
    
    Before opening this Pull Request, please read the dedicated
    "Contributing" markdown file or your PR may be closed:
    https://github.com/openai/codex/blob/main/docs/contributing.md
    
    If your PR conforms to our contribution guidelines, replace this text
    with a detailed and high quality description of your changes.
    
    Include a link to a bug report or enhancement request.
  • Change "Team" to "Buisness" and add Education (#8221)
    This pull request updates the ChatGPT login description in the
    onboarding authentication widgets to clarify which plans include usage.
    The description now lists "Business" rather than "Team" and adds
    "Education" plans in addition to the previously mentioned plans.
    
    I have read the CLA Document and I hereby sign the CLAs.
    
    ---------
    
    Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
  • Make loading malformed skills fail-open (#8243)
    Instead of failing to start Codex, clearly call out that N skills did
    not load and provide warnings so that the user may fix them.
    
    <img width="3548" height="874" alt="image"
    src="https://github.com/user-attachments/assets/6ce041b2-1373-4007-a6dd-0194e58fafe4"
    />
  • Show migration link (#8228)
    # External (non-OpenAI) Pull Request Requirements
    
    Before opening this Pull Request, please read the dedicated
    "Contributing" markdown file or your PR may be closed:
    https://github.com/openai/codex/blob/main/docs/contributing.md
    
    If your PR conforms to our contribution guidelines, replace this text
    with a detailed and high quality description of your changes.
    
    Include a link to a bug report or enhancement request.
  • chore: cleanup Config instantiation codepaths (#8226)
    This PR does various types of cleanup before I can proceed with more
    ambitious changes to config loading.
    
    First, I noticed duplicated code across these two methods:
    
    
    https://github.com/openai/codex/blob/774bd9e432fa2e0f4e059e97648cf92216912e19/codex-rs/core/src/config/mod.rs#L314-L324
    
    
    https://github.com/openai/codex/blob/774bd9e432fa2e0f4e059e97648cf92216912e19/codex-rs/core/src/config/mod.rs#L334-L344
    
    This has now been consolidated in
    `load_config_as_toml_with_cli_overrides()`.
    
    Further, I noticed that `Config::load_with_cli_overrides()` took two
    similar arguments:
    
    
    https://github.com/openai/codex/blob/774bd9e432fa2e0f4e059e97648cf92216912e19/codex-rs/core/src/config/mod.rs#L308-L311
    
    The difference between `cli_overrides` and `overrides` was not
    immediately obvious to me. At first glance, it appears that one should
    be able to be expressed in terms of the other, but it turns out that
    some fields of `ConfigOverrides` (such as `cwd` and
    `codex_linux_sandbox_exe`) are, by design, not configurable via a
    `.toml` file or a command-line `--config` flag.
    
    That said, I discovered that many callers of
    `Config::load_with_cli_overrides()` were passing
    `ConfigOverrides::default()` for `overrides`, so I created two separate
    methods:
    
    - `Config::load_with_cli_overrides(cli_overrides: Vec<(String,
    TomlValue)>)`
    - `Config::load_with_cli_overrides_and_harness_overrides(cli_overrides:
    Vec<(String, TomlValue)>, harness_overrides: ConfigOverrides)`
    
    The latter has a long name, as it is _not_ what should be used in the
    common case, so the extra typing is designed to draw attention to this
    fact. I tried to update the existing callsites to use the shorter name,
    where possible.
    
    Further, in the cases where `ConfigOverrides` is used, usually only a
    limited subset of fields are actually set, so I updated the declarations
    to leverage `..Default::default()` where possible.
  • feat: model picker (#8209)
    # External (non-OpenAI) Pull Request Requirements
    
    Before opening this Pull Request, please read the dedicated
    "Contributing" markdown file or your PR may be closed:
    https://github.com/openai/codex/blob/main/docs/contributing.md
    
    If your PR conforms to our contribution guidelines, replace this text
    with a detailed and high quality description of your changes.
    
    Include a link to a bug report or enhancement request.
  • 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: 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
  • 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
  • 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.
  • fix: Don't trigger keybindings view on input burst (#7980)
    Human TL;DR - in some situations, pasting/rapidly inputting text will
    currently cause `?` characters to be stripped from the input message
    content, and display the key bindings helper. For instance, writing
    "Where is X defined? Can we do Y?" results in "Where is X defined Can we
    do Y" being added to the message draft area. This is mildly annoying.
    
    The fix was a simple one line addition. Added a test, ran linters, and
    all looks good to me. I didn't create an issue to link to in this PR - I
    had submitted this bug as a report a while ago but can't seem to find it
    now. Let me know if it's an absolute must for the PR to be accepted.
    
    I have read the CLA Document and I hereby sign the CLA
    
    Below is Codex's summary.
    
    ---
    
    # `?` characters toggling shortcuts / being dropped
    
    ## Symptom
    
    On Termux (and potentially other terminal environments), composing text
    in the native input field and sending it to the TTY can cause:
    
    - The shortcuts overlay to appear (as if `?` was pressed on an empty
    prompt), and
    - All of the literal `?` characters in the text to be **missing** from
    the composer input,
      even when `?` is not the first character.
    
    This typically happens when the composer was previously empty and the
    terminal delivers the text as a rapid sequence of key events rather than
    a single bracketed paste event.
    
    ## Root cause
    
    The TUI has two relevant behaviors:
    
    1. **Shortcut toggle on `?` when empty**
    - `ChatComposer::handle_shortcut_overlay_key` treats a plain `?` press
    as a toggle between the shortcut summary and the full shortcut overlay,
    but only when the composer is empty.
    - When it toggles, it consumes the key event (so `?` is *not* inserted
    into the text input).
    
    2. **“Paste burst” buffering for fast key streams**
    - The TUI uses a heuristic to detect “paste-like” input bursts even when
    the terminal doesn’t send an explicit paste event.
    - During that burst detection, characters can be buffered (and the text
    area can remain empty temporarily) while the system decides whether to
    treat the stream as paste-like input.
    
    In Termux’s “send composed text all at once” mode, the input often
    arrives as a very fast stream of `KeyCode::Char(...)` events. While that
    stream is being buffered as a burst, the visible textarea can still be
    empty. If a `?` arrives during this window, it matches “empty composer”
    and is interpreted as “toggle shortcuts” instead of “insert literal
    `?`”, so the `?` is dropped.
    
    ## Fix
    
    Make the `?` toggle conditional on not being in any paste-burst
    transient state.
    
    Implementation:
    
    - `ChatComposer::handle_shortcut_overlay_key` now checks
    `!self.is_in_paste_burst()` in addition to `self.is_empty()` before
    toggling.
    - This ensures that when input is arriving as a fast burst (including
    the “pending first char” case), `?` is treated as normal text input
    rather than a UI toggle.
    
    ## Test coverage
    
    Added a test that simulates a Termux-like fast stream:
    
    - Sends `h i ? t h e r e` as immediate successive `KeyEvent::Char`
    events (no delays).
    - Asserts that a paste burst is active and the textarea is still empty
    while buffering.
    - Flushes the burst and verifies:
      - The final text contains the literal `?` (`"hi?there"`), and
      - The footer mode is not `ShortcutOverlay`.
    
    ## Notes
    
    This fix intentionally keeps the existing UX:
    
    - `?` still toggles shortcuts when the composer is genuinely empty and
    the user is not in the middle of entering text.
    - `?` typed while composing content (including IME/native-input fast
    streams) remains literal.
  • Reimplement skills loading using SkillsManager + skills/list op. (#7914)
    refactor the way we load and manage skills:
    1. Move skill discovery/caching into SkillsManager and reuse it across
    sessions.
    2. Add the skills/list API (Op::ListSkills/SkillsListResponse) to fetch
    skills for one or more cwds. Also update app-server for VSCE/App;
    3. Trigger skills/list during session startup so UIs preload skills and
    handle errors immediately.
  • Changed default wrap algorithm from OptimalFit to FirstFit (#7960)
    Codex identified this as the cause of a reported hang:
    https://github.com/openai/codex/issues/7822. Apparently, the wrapping
    algorithm we're using has known issues and bad worst-case behaviors when
    OptimalFit is used on certain strings. It recommended switching to
    FirstFit instead.