Commit Graph

418 Commits

  • [codex] reduce module visibility (#16978)
    ## Summary
    - reduce public module visibility across Rust crates, preferring private
    or crate-private modules with explicit crate-root public exports
    - update external call sites and tests to use the intended public crate
    APIs instead of reaching through module trees
    - add the module visibility guideline to AGENTS.md
    
    ## Validation
    - `cargo check --workspace --all-targets --message-format=short` passed
    before the final fix/format pass
    - `just fix` completed successfully
    - `just fmt` completed successfully
    - `git diff --check` passed
  • Fix CJK word navigation in the TUI composer (#16829)
    Addresses #16584
    
    Problem: TUI word-wise cursor movement treated entire CJK runs as a
    single word, so Option/Alt+Left and Right skipped too far when editing
    East Asian text.
    
    Solution: Use Unicode word-boundary segments within each non-whitespace
    run so CJK text advances one segment at a time while preserving
    separator and delete-word behavior, and add regression coverage for CJK
    and mixed-script navigation.
    
    Testing: Manually tested solution by pasting text that includes CJK
    characters into the composer and confirmed that keyboard navigation
    worked correctly (after confirming it didn't prior to the change).
  • fix(tui): sort skill mentions by display name first (#16710)
    ## Summary
    
    The skill list opened by '$' shows `interface.display_name` preferably
    if available but the sorting order of the search results use the
    `skill.name` for sorting the results regardless.
    
    This can be clearly seen in this example below: I expected with "pr" as
    the search term to have "PR Babysitter" be the first item, but instead
    it's way down the list.
    
    The reason is because "PR Babysitter" skill name is "babysit-pr" and
    therefore it doesn't rank as high as "pr-review-triage".
    
    This PR fixes this behavior.
    
    | Before | After |
    | --- | --- |
    | <img width="659" height="376" alt="image"
    src="https://github.com/user-attachments/assets/51a71491-62ec-4163-a6f3-943ddf55856d"
    /> | <img width="618" height="429" alt="image"
    src="https://github.com/user-attachments/assets/f5ec4f4a-c539-4a5d-bdc5-c3e3e630f530"
    /> |
    
    
    ## Testing
    
    - `just fmt`
    - `cargo test -p codex-tui
    bottom_pane::skill_popup::tests::display_name_match_sorting_beats_worse_secondary_search_term_matches
    --lib -- --exact`
    - `cargo test -p codex-tui`
  • fix(tui): handle zellij redraw and composer rendering (#16578)
    ## TL;DR
    
    Fixes the issues when using Codex CLI with Zellij multiplexer. Before
    this PR there would be no scrollback when using it inside a zellij
    terminal.
    
    ## Problem
    
    Addresses #2558
    
    Zellij does not support ANSI scroll-region manipulation (`DECSTBM` /
    Reverse Index) or the alternate screen buffer in the way traditional
    terminals do. When codex's TUI runs inside Zellij, two things break: (1)
    inline history insertion corrupts the display because the scroll-region
    escape sequences are silently dropped or mishandled, and (2) the
    composer textarea renders with inherited background/foreground styles
    that produce unreadable text against Zellij's pane chrome.
    
    ## Mental model
    
    The fix introduces a **Zellij mode** — a runtime boolean detected once
    at startup via `codex_terminal_detection::terminal_info().is_zellij()` —
    that gates two subsystems onto Zellij-safe terminal strategies:
    
    - **History insertion** (`insert_history.rs`): Instead of using
    `DECSTBM` scroll regions and Reverse Index (`ESC M`) to slide content
    above the viewport, Zellij mode scrolls the screen by emitting `\n` at
    the bottom row and then writes history lines at absolute positions. This
    avoids every escape sequence Zellij mishandles.
    - **Viewport expansion** (`tui.rs`): When the viewport grows taller than
    available space, the standard path uses `scroll_region_up` on the
    backend. Zellij mode instead emits newlines at the screen bottom to push
    content up, then invalidates the ratatui diff buffer so the next draw is
    a full repaint.
    - **Composer rendering** (`chat_composer.rs`, `textarea.rs`): All text
    rendering in the input area uses an explicit `base_style` with
    `Color::Reset` foreground, preventing Zellij's pane styling from
    bleeding into the textarea. The prompt chevron (`›`) and placeholder
    text use explicit color constants instead of relying on `.bold()` /
    `.dim()` modifiers that render inconsistently under Zellij.
    
    ## Non-goals
    
    - This change does not fix or improve Zellij's terminal emulation
    itself.
    - It does not rearchitect the inline viewport model; it adds a parallel
    code path gated on detection.
    - It does not touch the alternate-screen disable logic (that already
    existed and continues to use `is_zellij` via the same detection).
    
    ## Tradeoffs
    
    - **Code duplication in `insert_history.rs`**: The Zellij and Standard
    branches share the line-rendering loop (color setup, span merging,
    `write_spans`) but differ in the scrolling preamble. The duplication is
    intentional — merging them would force a complex conditional state
    machine that's harder to reason about than two flat sequences.
    - **`invalidate_viewport` after every Zellij history flush or viewport
    expansion**: This forces a full repaint on every draw cycle in Zellij,
    which is more expensive than ratatui's normal diff-based rendering. This
    is necessary because Zellij's lack of scroll-region support means the
    diff buffer's assumptions about what's on screen are invalid after we
    manually move content.
    - **Explicit colors vs semantic modifiers**: Replacing `.bold()` /
    `.dim()` with `Color::Cyan` / `Color::DarkGray` / `Color::White` in the
    Zellij branch sacrifices theme-awareness for correctness. If the project
    ever adopts a theming system, Zellij styling will need to participate.
    
    ## Architecture
    
    The Zellij detection flag flows through three layers:
    
    1. **`codex_terminal_detection`** — `TerminalInfo::is_zellij()` (new
    convenience method) reads the already-detected `Multiplexer` variant.
    2. **`Tui` struct** — caches `is_zellij` at construction; passes it into
    `update_inline_viewport`, `flush_pending_history_lines`, and
    `insert_history_lines_with_mode`.
    3. **`ChatComposer` struct** — independently caches `is_zellij` at
    construction; uses it in `render_textarea` for style decisions.
    
    The two caches (`Tui.is_zellij` and `ChatComposer.is_zellij`) are read
    from the same global `OnceLock<TerminalInfo>`, so they always agree.
    
    ## Observability
    
    No new logging, metrics, or tracing is introduced. Diagnosis depends on:
    - Whether `ZELLIJ` or `ZELLIJ_SESSION_NAME` env vars are set (the
    detection heuristic).
    - Visual inspection of the rendered TUI inside Zellij vs a standard
    terminal.
    - The insta snapshot `zellij_empty_composer` captures the Zellij-mode
    render path.
    
    ## Tests
    
    - `terminal_info_reports_is_zellij` — unit test in `terminal-detection`
    confirming the convenience method.
    - `zellij_empty_composer_snapshot` — insta snapshot in `chat_composer`
    validating the Zellij render path for an empty composer.
    - `vt100_zellij_mode_inserts_history_and_updates_viewport` — integration
    test in `insert_history` verifying that Zellij-mode history insertion
    writes content and shifts the viewport.
    
    ---------
    
    Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
  • Fix paste-driven bottom pane completion teardown (#16202)
    Fix paste-driven bottom-pane completion teardown (#16192)
    
    `BottomPane::handle_paste()` could leave a completed modal flow mounted
    while re-enabling the composer, putting the TUI in an inconsistent state
    where stale views could still affect rendering and input routing. Align
    the paste path with the existing key-driven completion logic by tearing
    down the active modal flow before restoring composer input, and add a
    regression test covering the stacked-view case that exposed the bug.
    
    Big thanks to @iqdoctor for identifying the root cause for this issue.
  • tui: remove debug/test-only crate features (#16457)
    ## Why
    
    The remaining `vt100-tests` and `debug-logs` features in `codex-tui`
    were only gating test-only and debug-only behavior. Those feature
    toggles add Cargo and Bazel permutations without buying anything, and
    they make it easier for more crate features to linger in the workspace.
    
    ## What changed
    
    - delete `vt100-tests` and `debug-logs` from `codex-tui`
    - always compile the VT100 integration tests in the TUI test target
    instead of hiding them behind a Cargo feature
    - remove the unused textarea debug logging branch instead of replacing
    it with another gate
    - add the required argument-comment annotations in the VT100 tests now
    that Bazel sees those callsites during linting
    - shrink the manifest verifier allowlist again so only the remaining
    real feature exceptions stay permitted
    
    ## How tested
    
    - `cargo test -p codex-tui`
    - `just argument-comment-lint -p codex-tui`
  • Route TUI /feedback submission through the app server (#16184)
    The TUI’s `/feedback` flow was still uploading directly through the
    local feedback crate, which bypassed app-server behavior such as
    auth-derived feedback tags like chatgpt_user_id and made TUI feedback
    handling diverge from other clients. It also meant that remove TUI
    sessions failed to upload the correct feedback logs and session details.
    
    Testing: Manually tested `/feedback` flow and confirmed that it didn't
    regress.
  • Remove TUI voice transcription feature (#16114)
    Removes the partially-completed TUI composer voice transcription flow,
    including its feature flag, app events, and hold-to-talk state machine.
  • Fix skills picker scrolling in tui app server (#16109)
    Fixes #16091.
    
    The app-server TUI was truncating the filtered mention candidate list to
    `MAX_POPUP_ROWS`, so the `$` skills picker only exposed the first 8
    matches. That made it look like many skills were missing and prevented
    keyboard navigation beyond the first page, even though direct
    `$skill-name` insertion still worked.
    
    Testing: I manually verified the regression and confirmed the fix.
  • Remove remaining custom prompt support (#16115)
    ## Summary
    - remove protocol and core support for discovering and listing custom
    prompts
    - simplify the TUI slash-command flow and command popup to built-in
    commands only
    - delete obsolete custom prompt tests, helpers, and docs references
    - clean up downstream event handling for the removed protocol events
  • Rename tui_app_server to tui (#16104)
    This is a follow-up to https://github.com/openai/codex/pull/15922. That
    previous PR deleted the old `tui` directory and left the new
    `tui_app_server` directory in place. This PR renames `tui_app_server` to
    `tui` and fixes up all references.
  • Remove the legacy TUI split (#15922)
    This is the part 1 of 2 PRs that will delete the `tui` /
    `tui_app_server` split. This part simply deletes the existing `tui`
    directory and marks the `tui_app_server` feature flag as removed. I left
    the `tui_app_server` feature flag in place for now so its presence
    doesn't result in an error. It is simply ignored.
    
    Part 2 will rename the `tui_app_server` directory `tui`. I did this as
    two parts to reduce visible code churn.
  • permissions: remove macOS seatbelt extension profiles (#15918)
    ## Why
    
    `PermissionProfile` should only describe the per-command permissions we
    still want to grant dynamically. Keeping
    `MacOsSeatbeltProfileExtensions` in that surface forced extra macOS-only
    approval, protocol, schema, and TUI branches for a capability we no
    longer want to expose.
    
    ## What changed
    
    - Removed the macOS-specific permission-profile types from
    `codex-protocol`, the app-server v2 API, and the generated
    schema/TypeScript artifacts.
    - Deleted the core and sandboxing plumbing that threaded
    `MacOsSeatbeltProfileExtensions` through execution requests and seatbelt
    construction.
    - Simplified macOS seatbelt generation so it always includes the fixed
    read-only preferences allowlist instead of carrying a configurable
    profile extension.
    - Removed the macOS additional-permissions UI/docs/test coverage and
    deleted the obsolete macOS permission modules.
    - Tightened `request_permissions` intersection handling so explicitly
    empty requested read lists are preserved only when that field was
    actually granted, avoiding zero-grant responses being stored as active
    permissions.
  • skills: remove unused skill permission metadata (#15900)
    ## Why
    
    Skill metadata accepted a `permissions` block and stored the result on
    `SkillMetadata`, but that data was never consumed by runtime behavior.
    Leaving the dead parsing path in place makes it look like skills can
    widen or otherwise influence execution permissions when, in practice,
    declared skill permissions are ignored.
    
    This change removes that misleading surface area so the skill metadata
    model matches what the system actually uses.
    
    ## What changed
    
    - removed `permission_profile` and `managed_network_override` from
    `core-skills::SkillMetadata`
    - stopped parsing `permissions` from skill metadata in
    `core-skills/src/loader.rs`
    - deleted the loader tests that only exercised the removed permissions
    parsing path
    - cleaned up dependent `SkillMetadata` constructors in tests and TUI
    code that were only carrying `None` for those fields
    
    ## Testing
    
    - `cargo test -p codex-core-skills`
    - `cargo test -p codex-tui
    submission_prefers_selected_duplicate_skill_path`
    - `just argument-comment-lint`
  • TUI plugin menu polish (#15802)
    - Add "OpenAI Curated" display name for `openai-curated` marketplace
    - Hide /apps menu
    - Change app install phase display text
  • Remove provenance filtering in $mentions for apps and skills from plugins (#15700)
    - Removes provenance filtering in the mentions feature for apps and
    skills that were installed as part of a plugin.
    - All skills and apps for a plugin are mentionable with this change.
  • Hide numeric prefixes on disabled TUI list rows (#15660)
    - Remove numeric prefixes for disabled rows in shared list rendering.
    These numbers are shortcuts, Ex: Pressing "2" selects option `#2`.
    Disabled items can not be selected, so keeping numbers on these items is
    misleading.
    - Apply the same behavior in both tui and tui_app_server.
    - Update affected snapshots for apps/plugins loading and plugin detail
    rows.
    
    _**This is a global change.**_
    
    Before:
    <img width="1680" height="488" alt="image"
    src="https://github.com/user-attachments/assets/4bcf94ad-285f-48d3-a235-a85b58ee58e2"
    />
    
    After:
    <img width="1706" height="484" alt="image"
    src="https://github.com/user-attachments/assets/76bb6107-a562-42fe-ae94-29440447ca77"
    />
  • Increase voice space hold timeout to 1s (#15579)
    Increase the space-hold delay to 1 second before voice capture starts,
    and mirror the change in tui_app_server.
  • Pretty plugin labels, preserve plugin app provenance during MCP tool refresh (#15606)
    - Prefer plugin manifest `interface.displayName` for plugin labels.
    - Preserve plugin provenance when handling `list_mcp_tools` so connector
    `plugin_display_names` are not clobbered.
    - Add a TUI test to ensure plugin-owned app mentions are deduped
    correctly.
  • Plugins TUI install/uninstall (#15342)
    - Add install/uninstall actions to the TUI plugins menu
    - Wire plugin install/uninstall through both TUI and `tui_app_server`
    - Refresh config/plugin state after changes so the UI updates
    immediately
    - Add a post-install app setup flow for plugins that require additional
    app auth
    
    <img width="1567" height="300" alt="Screenshot 2026-03-20 at 4 08 44 PM"
    src="https://github.com/user-attachments/assets/366bd31b-2ffd-4e80-b4a3-3a9a9c674a5f"
    />
    <img width="445" height="240" alt="Screenshot 2026-03-20 at 4 08 54 PM"
    src="https://github.com/user-attachments/assets/613999ab-269a-4758-ab59-7c057a1742dc"
    />
    <img width="797" height="219" alt="Screenshot 2026-03-20 at 4 09 07 PM"
    src="https://github.com/user-attachments/assets/b9679e60-40f5-49bb-ade0-2e40449c3fbf"
    />
    <img width="499" height="235" alt="Screenshot 2026-03-20 at 4 09 24 PM"
    src="https://github.com/user-attachments/assets/261ce2fe-f356-4e99-8ac9-f29ed850bc75"
    />
    
    
    
    
    Note/known issue: The /plugin install flow fails in `tui_app_server`
    because after a successful install it tries to trigger a
    ReloadUserConfig operation, but `tui_app_server` has not yet implemented
    transport for that operation, so it falls through to the generic “Not
    available in app-server TUI yet” stub.
  • tui: queue follow-ups during manual /compact (#15259)
    ## Summary
    - queue input after the user submits `/compact` until that manual
    compact turn ends
    - mirror the same behavior in the app-server TUI
    - add regressions for input queued before compact starts and while it is
    running
    
    Co-authored-by: Codex <noreply@openai.com>
  • Label plugins as plugins, and hide skills/apps for given plugin (#15279)
    - Duplicate app mentions are now suppressed when they’re plugin-backed
    with the same display name.
    - Remaining connector mentions now label category as [Plugin] when
    plugin metadata is present, otherwise [App].
    - Mention result lists are now capped to 8 rows after filtering.
    - Updates both tui and tui_app_server with the same changes.
  • Use Shift+Left to edit queued messages in tmux (#15480)
    ## Summary
    - use Shift+Left to edit the most recent queued message when running
    under tmux
    - mirror the same binding change in the app-server TUI
    - add tmux-specific tests and snapshot coverage for the rendered
    queued-message hint
    
    ## Testing
    - just fmt
    - cargo test -p codex-tui
    - cargo test -p codex-tui-app-server
    - just argument-comment-lint -p codex-tui -p codex-tui-app-server
    
    Co-authored-by: Codex <noreply@openai.com>
  • Gate tui /plugins menu behind flag (#15285)
    Gate /plugins menu behind `--enable plugins` flag
  • Split features into codex-features crate (#15253)
    - Split the feature system into a new `codex-features` crate.
    - Cut `codex-core` and workspace consumers over to the new config and
    warning APIs.
    
    Co-authored-by: Ahmed Ibrahim <219906144+aibrahim-oai@users.noreply.github.com>
    Co-authored-by: Codex <noreply@openai.com>
  • feat(tui): add /title terminal title configuration (#12334)
    ## Problem
    
    When multiple Codex sessions are open at once, terminal tabs and windows
    are hard to distinguish from each other. The existing status line only
    helps once the TUI is already focused, so it does not solve the "which
    tab is this?" problem.
    
    This PR adds a first-class `/title` command so the terminal window or
    tab title can carry a short, configurable summary of the current
    session.
    
    ## Screenshot
    
    <img width="849" height="320" alt="image"
    src="https://github.com/user-attachments/assets/8b112927-7890-45ed-bb1e-adf2f584663d"
    />
    
    ## Mental model
    
    `/statusline` and `/title` are separate status surfaces with different
    constraints. The status line is an in-app footer that can be denser and
    more detailed. The terminal title is external terminal metadata, so it
    needs short, stable segments that still make multiple sessions easy to
    tell apart.
    
    The `/title` configuration is an ordered list of compact items. By
    default it renders `spinner,project`, so active sessions show
    lightweight progress first while idle sessions still stay easy to
    disambiguate. Each configured item is omitted when its value is not
    currently available rather than forcing a placeholder.
    
    ## Non-goals
    
    This does not merge `/title` into `/statusline`, and it does not add an
    arbitrary free-form title string. The feature is intentionally limited
    to a small set of structured items so the title stays short and
    reviewable.
    
    This also does not attempt to restore whatever title the terminal or
    shell had before Codex started. When Codex clears the title, it clears
    the title Codex last wrote.
    
    ## Tradeoffs
    
    A separate `/title` command adds some conceptual overlap with
    `/statusline`, but it keeps title-specific constraints explicit instead
    of forcing the status line model to cover two different surfaces.
    
    Title refresh can happen frequently, so the implementation now shares
    parsing and git-branch orchestration between the status line and title
    paths, and caches the derived project-root name by cwd. That keeps the
    hot path cheap without introducing background polling.
    
    ## Architecture
    
    The TUI gets a new `/title` slash command and a dedicated picker UI for
    selecting and ordering terminal-title items. The chosen ids are
    persisted in `tui.terminal_title`, with `spinner` and `project` as the
    default when the config is unset. `status` remains available as a
    separate text item, so configurations like `spinner,status` render
    compact progress like `⠋ Working`.
    
    `ChatWidget` now refreshes both status surfaces through a shared
    `refresh_status_surfaces()` path. That shared path parses configured
    items once, warns on invalid ids once, synchronizes shared cached state
    such as git-branch lookup, then renders the footer status line and
    terminal title from the same snapshot.
    
    Low-level OSC title writes live in `codex-rs/tui/src/terminal_title.rs`,
    which owns the terminal write path and last-mile sanitization before
    emitting OSC 0.
    
    ## Security
    
    Terminal-title text is treated as untrusted display content before Codex
    emits it. The write path strips control characters, removes invisible
    and bidi formatting characters that can make the title visually
    misleading, normalizes whitespace, and caps the emitted length.
    
    References used while implementing this:
    
    - [xterm control
    sequences](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html)
    - [WezTerm escape sequences](https://wezterm.org/escape-sequences.html)
    - [CWE-150: Improper Neutralization of Escape, Meta, or Control
    Sequences](https://cwe.mitre.org/data/definitions/150.html)
    - [CERT VU#999008 (Trojan Source)](https://kb.cert.org/vuls/id/999008)
    - [Trojan Source disclosure site](https://trojansource.codes/)
    - [Unicode Bidirectional Algorithm (UAX
    #9)](https://www.unicode.org/reports/tr9/)
    - [Unicode Security Considerations (UTR
    #36)](https://www.unicode.org/reports/tr36/)
    
    ## Observability
    
    Unknown configured title item ids are warned about once instead of
    repeatedly spamming the transcript. Live preview applies immediately
    while the `/title` picker is open, and cancel rolls the in-memory title
    selection back to the pre-picker value.
    
    If terminal title writes fail, the TUI emits debug logs around set and
    clear attempts. The rendered status label intentionally collapses richer
    internal states into compact title text such as `Starting...`, `Ready`,
    `Thinking...`, `Working...`, `Waiting...`, and `Undoing...` when
    `status` is configured.
    
    ## Tests
    
    Ran:
    
    - `just fmt`
    - `cargo test -p codex-tui`
    
    At the moment, the red Windows `rust-ci` failures are due to existing
    `codex-core` `apply_patch_cli` stack-overflow tests that also reproduce
    on `main`. The `/title`-specific `codex-tui` suite is green.
  • Simple directory mentions (#14970)
    - Adds simple support for directory mentions in the TUI.
    - Codex App/VS Code will require minor change to recognize a directory
    mention as such and change the link behavior.
    - Directory mentions have a trailing slash to differentiate from
    extensionless files
    
    
    <img width="972" height="382" alt="image"
    src="https://github.com/user-attachments/assets/8035b1eb-0978-465b-8d7a-4db2e5feca39"
    />
    <img width="978" height="228" alt="image"
    src="https://github.com/user-attachments/assets/af22cf0b-dd10-4440-9bee-a09915f6ba52"
    />
  • [plugins] Support plugin installation elicitation. (#14896)
    It now supports:
    
    - Connectors that are from installed and enabled plugins that are not
    installed yet
    - Plugins that are on the allowlist that are not installed yet.
  • Apply argument comment lint across codex-rs (#14652)
    ## Why
    
    Once the repo-local lint exists, `codex-rs` needs to follow the
    checked-in convention and CI needs to keep it from drifting. This commit
    applies the fallback `/*param*/` style consistently across existing
    positional literal call sites without changing those APIs.
    
    The longer-term preference is still to avoid APIs that require comments
    by choosing clearer parameter types and call shapes. This PR is
    intentionally the mechanical follow-through for the places where the
    existing signatures stay in place.
    
    After rebasing onto newer `main`, the rollout also had to cover newly
    introduced `tui_app_server` call sites. That made it clear the first cut
    of the CI job was too expensive for the common path: it was spending
    almost as much time installing `cargo-dylint` and re-testing the lint
    crate as a representative test job spends running product tests. The CI
    update keeps the full workspace enforcement but trims that extra
    overhead from ordinary `codex-rs` PRs.
    
    ## What changed
    
    - keep a dedicated `argument_comment_lint` job in `rust-ci`
    - mechanically annotate remaining opaque positional literals across
    `codex-rs` with exact `/*param*/` comments, including the rebased
    `tui_app_server` call sites that now fall under the lint
    - keep the checked-in style aligned with the lint policy by using
    `/*param*/` and leaving string and char literals uncommented
    - cache `cargo-dylint`, `dylint-link`, and the relevant Cargo
    registry/git metadata in the lint job
    - split changed-path detection so the lint crate's own `cargo test` step
    runs only when `tools/argument-comment-lint/*` or `rust-ci.yml` changes
    - continue to run the repo wrapper over the `codex-rs` workspace, so
    product-code enforcement is unchanged
    
    Most of the code changes in this commit are intentionally mechanical
    comment rewrites or insertions driven by the lint itself.
    
    ## Verification
    
    - `./tools/argument-comment-lint/run.sh --workspace`
    - `cargo test -p codex-tui-app-server -p codex-tui`
    - parsed `.github/workflows/rust-ci.yml` locally with PyYAML
    
    ---
    
    * -> #14652
    * #14651
  • Preserve background terminals on interrupt and rename cleanup command to /stop (#14602)
    ### Motivation
    - Interrupting a running turn (Ctrl+C / Esc) currently also terminates
    long‑running background shells, which is surprising for workflows like
    local dev servers or file watchers.
    - The existing cleanup command name was confusing; callers expect an
    explicit command to stop background terminals rather than a UI clear
    action.
    - Make background‑shell termination explicit and surface a clearer
    command name while preserving backward compatibility.
    
    ### Description
    - Renamed the background‑terminal cleanup slash command from `Clean`
    (`/clean`) to `Stop` (`/stop`) and kept `clean` as an alias in the
    command parsing/visibility layer, updated the user descriptions and
    command popup wiring accordingly.
    - Updated the unified‑exec footer text and snapshots to point to `/stop`
    (and trimmed corresponding snapshot output to match the new label).
    - Changed interrupt behavior so `Op::Interrupt` (Ctrl+C / Esc interrupt)
    no longer closes or clears tracked unified exec / background terminal
    processes in the TUI or core cleanup path; background shells are now
    preserved after an interrupt.
    - Updated protocol/docs to clarify that `turn/interrupt` (or
    `Op::Interrupt`) interrupts the active turn but does not terminate
    background terminals, and that `thread/backgroundTerminals/clean` is the
    explicit API to stop those shells.
    - Updated unit/integration tests and insta snapshots in the TUI and core
    unified‑exec suites to reflect the new semantics and command name.
    
    ### Testing
    - Ran formatting with `just fmt` in `codex-rs` (succeeded). 
    - Ran `cargo test -p codex-protocol` (succeeded). 
    - Attempted `cargo test -p codex-tui` but the build could not complete
    in this environment due to a native build dependency that requires
    `libcap` development headers (the `codex-linux-sandbox` vendored build
    step); install `libcap-dev` / make `libcap.pc` available in
    `PKG_CONFIG_PATH` to run the TUI test suite locally.
    - Updated and accepted the affected `insta` snapshots for the TUI
    changes so visual diffs reflect the new `/stop` wording and preserved
    interrupt behavior.
    
    ------
    [Codex
    Task](https://chatgpt.com/codex/tasks/task_i_69b39c44b6dc8323bd133ae206310fae)
  • Add Smart Approvals guardian review across core, app-server, and TUI (#13860)
    ## Summary
    - add `approvals_reviewer = "user" | "guardian_subagent"` as the runtime
    control for who reviews approval requests
    - route Smart Approvals guardian review through core for command
    execution, file changes, managed-network approvals, MCP approvals, and
    delegated/subagent approval flows
    - expose guardian review in app-server with temporary unstable
    `item/autoApprovalReview/{started,completed}` notifications carrying
    `targetItemId`, `review`, and `action`
    - update the TUI so Smart Approvals can be enabled from `/experimental`,
    aligned with the matching `/approvals` mode, and surfaced clearly while
    reviews are pending or resolved
    
    ## Runtime model
    This PR does not introduce a new `approval_policy`.
    
    Instead:
    - `approval_policy` still controls when approval is needed
    - `approvals_reviewer` controls who reviewable approval requests are
    routed to:
      - `user`
      - `guardian_subagent`
    
    `guardian_subagent` is a carefully prompted reviewer subagent that
    gathers relevant context and applies a risk-based decision framework
    before approving or denying the request.
    
    The `smart_approvals` feature flag is a rollout/UI gate. Core runtime
    behavior keys off `approvals_reviewer`.
    
    When Smart Approvals is enabled from the TUI, it also switches the
    current `/approvals` settings to the matching Smart Approvals mode so
    users immediately see guardian review in the active thread:
    - `approval_policy = on-request`
    - `approvals_reviewer = guardian_subagent`
    - `sandbox_mode = workspace-write`
    
    Users can still change `/approvals` afterward.
    
    Config-load behavior stays intentionally narrow:
    - plain `smart_approvals = true` in `config.toml` remains just the
    rollout/UI gate and does not auto-set `approvals_reviewer`
    - the deprecated `guardian_approval = true` alias migration does
    backfill `approvals_reviewer = "guardian_subagent"` in the same scope
    when that reviewer is not already configured there, so old configs
    preserve their original guardian-enabled behavior
    
    ARC remains a separate safety check. For MCP tool approvals, ARC
    escalations now flow into the configured reviewer instead of always
    bypassing guardian and forcing manual review.
    
    ## Config stability
    The runtime reviewer override is stable, but the config-backed
    app-server protocol shape is still settling.
    
    - `thread/start`, `thread/resume`, and `turn/start` keep stable
    `approvalsReviewer` overrides
    - the config-backed `approvals_reviewer` exposure returned via
    `config/read` (including profile-level config) is now marked
    `[UNSTABLE]` / experimental in the app-server protocol until we are more
    confident in that config surface
    
    ## App-server surface
    This PR intentionally keeps the guardian app-server shape narrow and
    temporary.
    
    It adds generic unstable lifecycle notifications:
    - `item/autoApprovalReview/started`
    - `item/autoApprovalReview/completed`
    
    with payloads of the form:
    - `{ threadId, turnId, targetItemId, review, action? }`
    
    `review` is currently:
    - `{ status, riskScore?, riskLevel?, rationale? }`
    - where `status` is one of `inProgress`, `approved`, `denied`, or
    `aborted`
    
    `action` carries the guardian action summary payload from core when
    available. This lets clients render temporary standalone pending-review
    UI, including parallel reviews, even when the underlying tool item has
    not been emitted yet.
    
    These notifications are explicitly documented as `[UNSTABLE]` and
    expected to change soon.
    
    This PR does **not** persist guardian review state onto `thread/read`
    tool items. The intended follow-up is to attach guardian review state to
    the reviewed tool item lifecycle instead, which would improve
    consistency with manual approvals and allow thread history / reconnect
    flows to replay guardian review state directly.
    
    ## TUI behavior
    - `/experimental` exposes the rollout gate as `Smart Approvals`
    - enabling it in the TUI enables the feature and switches the current
    session to the matching Smart Approvals `/approvals` mode
    - disabling it in the TUI clears the persisted `approvals_reviewer`
    override when appropriate and returns the session to default manual
    review when the effective reviewer changes
    - `/approvals` still exposes the reviewer choice directly
    - the TUI renders:
    - pending guardian review state in the live status footer, including
    parallel review aggregation
      - resolved approval/denial state in history
    
    ## Scope notes
    This PR includes the supporting core/runtime work needed to make Smart
    Approvals usable end-to-end:
    - shell / unified-exec / apply_patch / managed-network / MCP guardian
    review
    - delegated/subagent approval routing into guardian review
    - guardian review risk metadata and action summaries for app-server/TUI
    - config/profile/TUI handling for `smart_approvals`, `guardian_approval`
    alias migration, and `approvals_reviewer`
    - a small internal cleanup of delegated approval forwarding to dedupe
    fallback paths and simplify guardian-vs-parent approval waiting (no
    intended behavior change)
    
    Out of scope for this PR:
    - redesigning the existing manual approval protocol shapes
    - persisting guardian review state onto app-server `ThreadItem`s
    - delegated MCP elicitation auto-review (the current delegated MCP
    guardian shim only covers the legacy `RequestUserInput` path)
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Override local apps settings with requirements.toml settings (#14304)
    This PR changes app and connector enablement when `requirements.toml` is
    present locally or via remote configuration.
    
    For apps.* entries:
    - `enabled = false` in `requirements.toml` overrides the user’s local
    `config.toml` and forces the app to be disabled.
    - `enabled = true` in `requirements.toml` does not re-enable an app the
    user has disabled in config.toml.
    
    This behavior applies whether or not the user has an explicit entry for
    that app in `config.toml`. It also applies to cloud-managed policies and
    configurations when the admin sets the override through
    `requirements.toml`.
    
    Scenarios tested and verified:
    - Remote managed, user config (present) override
    - Admin-defined policies & configurations include a connector override:
      `[apps.<appID>]
    enabled = false`
    - User's config.toml has the same connector configured with `enabled =
    true`
      - TUI/App should show connector as disabled
      - Connector should be unavailable for use in the composer
      
    - Remote managed, user config (absent) override
    - Admin-defined policies & configurations include a connector override:
      `[apps.<appID>]
    enabled = false`
      - User's config.toml has no entry for the the same connector
      - TUI/App should show connector as disabled
      - Connector should be unavailable for use in the composer
      
    - Locally managed, user config (present) override
      - Local requirements.toml includes a connector override:
      `[apps.<appID>]
    enabled = false`
    - User's config.toml has the same connector configured with `enabled =
    true`
      - TUI/App should show connector as disabled
      - Connector should be unavailable for use in the composer
    
    - Locally managed, user config (absent) override
      - Local requirements.toml includes a connector override:
      `[apps.<appID>]
    enabled = false`
      - User's config.toml has no entry for the the same connector
      - TUI/App should show connector as disabled
      - Connector should be unavailable for use in the composer
    
    
    
    
    <img width="1446" height="753" alt="image"
    src="https://github.com/user-attachments/assets/61c714ca-dcca-4952-8ad2-0afc16ff3835"
    />
    <img width="595" height="233" alt="image"
    src="https://github.com/user-attachments/assets/7c8ab147-8fd7-429a-89fb-591c21c15621"
    />
  • Use subagents naming in the TUI (#14618)
    - rename user-facing TUI multi-agent wording to subagents
    - rename the surfaced slash command to `subagents` and update
    tests/snapshots
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: support skill-scoped managed network domain overrides in skill config (#14522)
    ## Summary
    
    This lets skill loading split `permissions.network` into two distinct
    pieces:
    
    - `permissions.network.enabled` still feeds the skill
    `PermissionProfile` and remains the coarse gate for whether the skill
    can use network access at all.
    - `permissions.network.allowed_domains` and
    `permissions.network.denied_domains` are lifted into a new
    `SkillManagedNetworkOverride` so managed-network sessions can start
    per-skill scoped proxies with the right domain overrides.
    
    The change also updates `SkillMetadata` construction sites and adds
    loader tests covering YAML parsing plus normalization of the network
    gate vs. domain override fields.
    
    ## Follow-up
    A PR that uses the network_override to spin up a skill-specific proxy if
    network_override is not none.
  • [elicitation] User-friendly tool call messages. (#14403)
    - [x] Add a curated set of tool call messages and human-readable tool
    param names.
  • [apps] Add tool_suggest tool. (#14287)
    - [x] Add tool_suggest tool.
    - [x] Move chatgpt/src/connectors.rs and core/src/connectors.rs into a
    dedicated mod so that we have all the logic and global cache in one
    place.
    - [x] Update TUI app link view to support rendering the installation
    view for mcp elicitation.
    
    ---------
    
    Co-authored-by: Shaqayeq <shaqayeq@openai.com>
    Co-authored-by: Eric Traut <etraut@openai.com>
    Co-authored-by: pakrym-oai <pakrym@openai.com>
    Co-authored-by: Ahmed Ibrahim <aibrahim@openai.com>
    Co-authored-by: guinness-oai <guinness@openai.com>
    Co-authored-by: Eugene Brevdo <ebrevdo@users.noreply.github.com>
    Co-authored-by: Charlie Guo <cguo@openai.com>
    Co-authored-by: Fouad Matin <fouad@openai.com>
    Co-authored-by: Fouad Matin <169186268+fouad-openai@users.noreply.github.com>
    Co-authored-by: xl-openai <xl@openai.com>
    Co-authored-by: alexsong-oai <alexsong@openai.com>
    Co-authored-by: Owen Lin <owenlin0@gmail.com>
    Co-authored-by: sdcoffey <stevendcoffey@gmail.com>
    Co-authored-by: Codex <noreply@openai.com>
    Co-authored-by: Won Park <won@openai.com>
    Co-authored-by: Dylan Hurd <dylan.hurd@openai.com>
    Co-authored-by: celia-oai <celia@openai.com>
    Co-authored-by: gabec-openai <gabec@openai.com>
    Co-authored-by: joeytrasatti-openai <joey.trasatti@openai.com>
    Co-authored-by: Leo Shimonaka <leoshimo@openai.com>
    Co-authored-by: Rasmus Rygaard <rasmus@openai.com>
    Co-authored-by: maja-openai <163171781+maja-openai@users.noreply.github.com>
    Co-authored-by: pash-openai <pash@openai.com>
    Co-authored-by: Josh McKinney <joshka@openai.com>
  • feat: Add additional macOS Sandbox Permissions for Launch Services, Contacts, Reminders (#14155)
    Add additional macOS Sandbox Permissions levers for the following:
    
    - Launch Services
    - Contacts
    - Reminders
  • add(core): arc_monitor (#13936)
    ## Summary
    - add ARC monitor support for MCP tool calls by serializing MCP approval
    requests into the ARC action shape and sending the relevant
    conversation/policy context to the `/api/codex/safety/arc` endpoint
    - route ARC outcomes back into MCP approval flow so `ask-user` falls
    back to a user prompt and `steer-model` blocks the tool call, with
    guardian/ARC tests covering the new request shape
    - update the TUI approval copy from “Approve Once” to “Allow” / “Allow
    for this session” and refresh the related
      snapshots
    
    ---------
    
    Co-authored-by: Fouad Matin <fouad@openai.com>
    Co-authored-by: Fouad Matin <169186268+fouad-openai@users.noreply.github.com>
  • [apps] Fix apps enablement condition. (#14011)
    - [x] Fix apps enablement condition to check both the feature flag and
    that the user is not an API key user.
  • sort plugins first in menu (#14163)
    we want plugin mentions to show up before others, like apps and skills.
    
    updated tests.
  • make dollar-mention always clarify item category (skill, app, plugin) (#14147)
    #### What
    
    ###### Context + Problem
    
    With the introduction of plugins, we now have one more type of
    `$`-mentionable item in the TUI's popup menu on `$`. Apps, skills, and
    plugins can all have the same user-facing name, and we attempt to
    distinguish with a category tag suffix, like `[App]`. This has a few
    problems:
    
    - We decide to show tags by the text that will be inserted into the
    conversation, not the actual user-visible text, so two visibly-identical
    entries can have no clarifying category tag suffix
    - The category tag is a suffix and commonly gets cut off by long
    descriptions
    - The skill category tag is currently only displayed on repo skills as
    `[Repo]`, which is confusing to most users
    - The plugin category tag is currently `[<marketplace-name>]`, which is
    also confusing to most users
    
    ###### Solution
    - **Always** show a **prefix** category tag that is `[Skill]`, `[App]`,
    or `[Plugin]`. No conditional rendering or copy.
    
    Before:
    <img width="801" height="153" alt="image"
    src="https://github.com/user-attachments/assets/448e06e7-2af8-4c14-9804-ed1ca17cf514"
    />
    
    After:
    <img width="800" height="118" alt="image"
    src="https://github.com/user-attachments/assets/57895b41-06fe-4d92-887b-68704c5a15fd"
    />
    
    I also feel this clarifies the results at-a-glance while you scroll:
    
    
    https://github.com/user-attachments/assets/cbdd5840-53d9-4656-812c-6e816755e1fd
    
    ### Tests
    Added + updated tests (including snapshots), tested locally
  • feat(core) Persist request_permission data across turns (#14009)
    ## Summary
    request_permissions flows should support persisting results for the
    session.
    
    Open Question: Still deciding if we need within-turn approvals - this
    adds complexity but I could see it being useful
    
    ## Testing
    - [x] Updated unit tests
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat(tui) render request_permissions calls (#14004)
    ## Summary
    Adds support for tui rendering of request_permission calls
    
    <img width="724" height="245" alt="Screenshot 2026-03-08 at 9 04 07 PM"
    src="https://github.com/user-attachments/assets/e1997825-a496-4bfb-bbda-43d0006460a5"
    />
    
    
    ## Testing
    - [x] Added snapshot test
  • tui: clarify pending steer follow-ups (#13841)
    ## Summary
    - split the pending input preview into labeled pending-steer and queued
    follow-up sections
    - explain that pending steers submit after the next tool call and that
    Esc can interrupt and send them immediately
    - treat Esc as an interrupt-plus-resubmit path when pending steers
    exist, with updated TUI snapshots and tests
    
    Queues and steers:
    <img width="1038" height="263" alt="Screenshot 2026-03-07 at 10 17
    17 PM"
    src="https://github.com/user-attachments/assets/4ef433ef-27a3-4b7c-ad69-2046f6eb89e6"
    />
    
    After pressing Esc:
    <img width="1046" height="320" alt="Screenshot 2026-03-07 at 10 17
    21 PM"
    src="https://github.com/user-attachments/assets/0f4d89e0-b6b9-486a-9f04-b6021f169ba7"
    />
    
    ## Codex author
    `codex resume 019cc6f4-2cca-7803-b717-8264526dbd97`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [elicitations] Support always allow option for mcp tool calls. (#13807)
    - [x] Support always allow option for mcp tool calls, writes to
    config.toml.
    - [x] Fix config hot-reload after starting a new thread for TUI.
  • Add Fast mode status-line indicator (#13670)
    Addresses feature request #13660
    
    Adds new option to `/statusline` so the status line can display "fast
    on" or "fast off"
    
    Summary
    - introduce a `FastMode` status-line item so `/statusline` can render
    explicit `Fast on`/`Fast off` text for the service tier
    - wire the item into the picker metadata and resolve its string from
    `ChatWidget` without adding any unrelated `thread-name` logic or storage
    changes
    - ensure the refresh paths keep the cached footer in sync when the
    service tier (fast mode) changes
    
    Testing
    - Manually tested
    
    Here's what it looks like when enabled:
    
    <img width="366" height="75" alt="image"
    src="https://github.com/user-attachments/assets/7f992d2b-6dab-49ed-aa43-ad496f56f193"
    />
  • feat: structured plugin parsing (#13711)
    #### What
    
    Add structured `@plugin` parsing and TUI support for plugin mentions.
    
    - Core: switch from plain-text `@display_name` parsing to structured
    `plugin://...` mentions via `UserInput::Mention` and
    `[$...](plugin://...)` links in text, same pattern as apps/skills.
    - TUI: add plugin mention popup, autocomplete, and chips when typing
    `$`. Load plugin capability summaries and feed them into the composer;
    plugin mentions appear alongside skills and apps.
    - Generalize mention parsing to a sigil parameter, still defaults to `$`
    
    <img width="797" height="119" alt="image"
    src="https://github.com/user-attachments/assets/f0fe2658-d908-4927-9139-73f850805ceb"
    />
    
    Builds on #13510. Currently clients have to build their own `id` via
    `plugin@marketplace` and filter plugins to show by `enabled`, but we
    will add `id` and `available` as fields returned from `plugin/list`
    soon.
    
    ####Tests
    
    Added tests, verified locally.