Commit Graph

1622 Commits

  • app-server: expose thread permission profiles (#18278)
    ## Why
    
    The `PermissionProfile` migration needs app-server clients to see the
    same constrained permission model that core is using at runtime. Before
    this PR, thread lifecycle responses only exposed the legacy
    `SandboxPolicy` shape, so clients still had to infer active permissions
    from sandbox fields. That makes downstream resume, fork, and override
    flows harder to make `PermissionProfile`-first.
    
    External sandbox policies are intentionally excluded from this canonical
    view. External enforcement cannot be round-tripped as a
    `PermissionProfile`, and exposing a lossy root-write profile would let
    clients accidentally change sandbox semantics if they echo the profile
    back later.
    
    ## What changed
    
    - Adds the app-server v2 `PermissionProfile` wire shape, including
    filesystem permissions and glob scan depth metadata.
    - Adds `PermissionProfileNetworkPermissions` so the profile response
    does not expose active network state through the older
    additional-permissions naming.
    - Returns `permissionProfile` from thread start, resume, and fork
    responses when the active sandbox can be represented as a
    `PermissionProfile`.
    - Keeps legacy `sandbox` in those responses for compatibility and
    documents `permissionProfile` as canonical when present.
    - Makes lifecycle `permissionProfile` nullable and returns `null` for
    `ExternalSandbox` to avoid exposing a lossy profile.
    - Regenerates the app-server JSON schema and TypeScript fixtures.
    
    ## Verification
    
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server
    thread_response_permission_profile_omits_external_sandbox --
    --nocapture`
    - `cargo check --tests -p codex-analytics -p codex-exec -p codex-tui`
    - `just fix -p codex-app-server-protocol -p codex-app-server -p
    codex-analytics -p codex-exec -p codex-tui`
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/18278).
    * #18279
    * __->__ #18278
  • feat: add explicit AgentIdentity auth mode (#18785)
    ## Summary
    
    This PR adds `CodexAuth::AgentIdentity` as an explicit auth mode.
    
    An AgentIdentity auth record is a standalone `auth.json` mode. When
    `AuthManager::auth().await` loads that mode, it registers one
    process-scoped task and stores it in runtime-only state on the auth
    value. Header creation stays synchronous after that because the task is
    initialized before callers receive the auth object.
    
    This PR also removes the old feature flag path. AgentIdentity is
    selected by explicit auth mode, not by a hidden flag or lazy mutation of
    ChatGPT auth records.
    
    Reference old stack: https://github.com/openai/codex/pull/17387/changes
    
    ## Design Decisions
    
    - AgentIdentity is a real auth enum variant because it can be the only
    credential in `auth.json`.
    - The process task is ephemeral runtime state. It is not serialized and
    is not stored in rollout/session data.
    - Account/user metadata needed by existing Codex backend checks lives on
    the AgentIdentity record for now.
    - `is_chatgpt_auth()` remains token-specific.
    - `uses_codex_backend()` is the broader predicate for ChatGPT-token auth
    and AgentIdentity auth.
    
    ## Stack
    
    1. https://github.com/openai/codex/pull/18757: full revert
    2. https://github.com/openai/codex/pull/18871: isolated Agent Identity
    crate
    3. This PR: explicit AgentIdentity auth mode and startup task allocation
    4. https://github.com/openai/codex/pull/18811: migrate Codex backend
    auth callsites through AuthProvider
    5. https://github.com/openai/codex/pull/18904: accept AgentIdentity JWTs
    and load `CODEX_AGENT_IDENTITY`
    
    ## Testing
    
    Tests: targeted Rust checks, cargo-shear, Bazel lock check, and CI.
  • chore(tui) debug-config guardian_policy_config (#18923)
    ## Summary
    List guardian_policy_config_source in `/debug-config` output
    
    ## Testing
     - [x] Ran locally
  • feat: Support remote plugin list/read. (#18452)
    Add a temporary internal remote_plugin feature flag that merges remote
    marketplaces into plugin/list and routes plugin/read through the remote
    APIs when needed, while keeping pure local marketplaces working as
    before.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Add turn-scoped environment selections (#18416)
    ## Summary
    - add experimental turn/start.environments params for per-turn
    environment id + cwd selections
    - pass selections through core protocol ops and resolve them with
    EnvironmentManager before TurnContext creation
    - treat omitted selections as default behavior, empty selections as no
    environment, and non-empty selections as first environment/cwd as the
    turn primary
    
    ## Testing
    - ran `just fmt`
    - ran `just write-app-server-schema`
    - not run: unit tests for this stacked PR
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • fix: windows snapshot for external_agent_config_migration::tests::prompt_snapshot did not match windows output (#18915)
    Fix a snapshot test that is failing on Windows, but is currently missed
    by Bazel due to https://github.com/openai/codex/pull/18913. We see this
    failing on Cargo builds on Windows, though.
    
    This Bazel vs. Cargo inconsistency explains why
    https://github.com/openai/codex/pull/18768 did not fix the Cargo Windows
    build.
  • Update /statusline and /title snapshots (#18909)
    Update `/statusline` and `/title` snapshots
  • [codex] Tighten external migration prompt tests (#18768)
    ## Summary
    - tighten the external migration prompt snapshot around stable synthetic
    fixture text
    - add focused display_description tests for relative path rewriting and
    plugin summaries
    - split the path-format assertions into smaller, easier-to-read unit
    tests
    
    ## Why
    The previous prompt snapshot was coupled to path text that came from
    detected migration items, which made it noisier and more brittle than
    necessary. This change keeps the snapshot focused on stable UI structure
    and moves dynamic path formatting checks into targeted unit tests.
    
    ## Validation
    - cargo test -p codex-tui external_agent_config_migration::tests::
    - cargo test -p codex-tui
    external_agent_config_migration::tests::display_description_
    - just fmt
    
    ## Notes
    Per the repo instructions, I did not rerun tests after the final `just
    fmt` pass.
  • Normalize /statusline & /title items (#18886)
    This change aligns the `/statusline` and `/title` UIs around the same
    normalized item model so both surfaces use consistent ids, labels, and
    preview semantics. It keeps the shared preview work from #18435 ,
    tightens the remaining mismatches by standardizing item naming, expands
    title/status item coverage where appropriate, and makes `/title` preview
    use the same title-specific formatting path as the real rendered
    terminal title.
    
    - Normalizes persisted item ids and keeps legacy aliases for
    compatibility
    - Aligns `status-line` and `terminal-title` items with the shared
    preview model
    - Routes `terminal-title` preview through title-specific formatting and
    truncation
    - Updates the affected status/title setup snapshots
    
    Added to `/statusline`:
    - status
    - task-progress
      
    Normalized in `/statusline`:
    - model-name -> model
    - project-root -> project-name
    
    Added to `/title`:
    - current-dir
    - context-remaining
    - context-used
    - five-hour-limit
    - weekly-limit
    - codex-version
    - used-tokens
    - total-input-tokens
    - total-output-tokens
    - session-id
    - fast-mode
    - model-with-reasoning
    
    Normalized in `/title`:
    - project -> project-name
    - thread -> thread-title
    - model-name -> model
  • Support multiple managed environments (#18401)
    ## Summary
    - refactor EnvironmentManager to own keyed environments with
    default/local lookup helpers
    - keep remote exec-server client creation lazy until exec/fs use
    - preserve disabled agent environment access separately from internal
    local environment access
    
    ## Validation
    - not run (per Codex worktree instruction to avoid tests/builds unless
    requested)
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • fix: fully revert agent identity runtime wiring (#18757)
    ## Summary
    
    This PR fully reverts the previously merged Agent Identity runtime
    integration from the old stack:
    https://github.com/openai/codex/pull/17387/changes
    
    It removes the Codex-side task lifecycle wiring, rollout/session
    persistence, feature flag plumbing, lazy `auth.json` mutation,
    background task auth paths, and request callsite changes introduced by
    that stack.
    
    This leaves the repo in a clean pre-AgentIdentity integration state so
    the follow-up PRs can reintroduce the pieces in smaller reviewable
    layers.
    
    ## Stack
    
    1. This PR: full revert
    2. https://github.com/openai/codex/pull/18871: move Agent Identity
    business logic into a crate
    3. https://github.com/openai/codex/pull/18785: add explicit
    AgentIdentity auth mode and startup task allocation
    4. https://github.com/openai/codex/pull/18811: migrate auth callsites
    through AuthProvider
    
    ## Testing
    
    Tests: targeted Rust checks, cargo-shear, Bazel lock check, and CI.
  • feat(tui): shortcuts to change reasoning level temporarily (#18866)
    ## Summary
    
    Adds main-chat shortcuts for changing reasoning effort one step at a
    time:
    
    - `Alt+,` lowers reasoning (has the `<` arrow on the key)
    - `Alt+.` raises reasoning (similarly, has the `>` arrow)
    
    The shortcut updates the active session only. It does not persist the
    selected reasoning level as the default for future sessions. In Plan
    mode, it applies temporarily to Plan mode without opening the
    global-vs-Plan scope prompt.
    
    ## Details
    
    The shortcut uses the active model preset to decide which reasoning
    levels are valid. If the current session has no explicit reasoning
    effort, it starts from the model default. Each keypress moves to the
    next supported level in the requested direction.
    
    The shortcut only runs from the main chat surface. If a popup or modal
    is open, input remains owned by that UI.
    
    In Plan mode, the shortcut updates the in-memory Plan reasoning override
    directly. The model/reasoning picker still keeps the existing scope
    prompt for explicit picker changes.
    
    ## Notes
    
    Ctrl-plus and Ctrl-minus were considered, but terminals do not deliver
    those combinations consistently, so this PR uses Alt shortcuts instead.
    
    If the current effort is unsupported by the selected model, the shortcut
    skips to the nearest supported level in the requested direction. If
    there is no valid step, it shows the existing boundary message.
    
    ## Tests
    
    - `cargo test -p codex-tui reasoning_shortcuts`
    - `cargo test -p codex-tui reasoning_effort`
    - `cargo test -p codex-tui reasoning_shortcut`
    - `cargo test -p codex-tui footer_snapshots`
    - `cargo test -p codex-tui`
    - `just fix -p codex-tui`
    - `./tools/argument-comment-lint/run.py -p codex-tui -- --tests`
    
    ---------
    
    Co-authored-by: Eric Traut <etraut@openai.com>
  • sandboxing: intersect permission profiles semantically (#18275)
    ## Why
    
    Permission approval responses must not be able to grant more access than
    the tool requested. Moving this flow to `PermissionProfile` means the
    comparison must be profile-shaped instead of `SandboxPolicy`-shaped, and
    cwd-relative special paths such as `:cwd` and `:project_roots` must stay
    anchored to the turn that produced the request.
    
    ## What changed
    
    This implements semantic `PermissionProfile` intersection in
    `codex-sandboxing` for file-system and network permissions. The
    intersection accepts narrower path grants, rejects broader grants,
    preserves deny-read carve-outs and glob scan depth, and materializes
    cwd-dependent special-path grants to absolute paths before they can be
    recorded for reuse.
    
    The request-permissions response paths now use that intersection
    consistently. App-server captures the request turn cwd before waiting
    for the client response, includes that cwd in the v2 approval params,
    and core stores the requested profile plus cwd for direct TUI/client
    responses and Guardian decisions before recording turn- or
    session-scoped grants. The TUI app-server bridge now preserves the
    app-server request cwd when converting permission approval params into
    core events.
    
    ## Verification
    
    - `cargo test -p codex-sandboxing intersect_permission_profiles --
    --nocapture`
    - `cargo test -p codex-app-server request_permissions_response --
    --nocapture`
    - `cargo test -p codex-core
    request_permissions_response_materializes_session_cwd_grants_before_recording
    -- --nocapture`
    - `cargo check -p codex-tui --tests`
    - `cargo check --tests`
    - `cargo test -p codex-tui
    app_server_request_permissions_preserves_file_system_permissions`
  • Move TUI app tests to modules they cover (#18799)
    ## Summary
    
    The TUI app refactor in #18753 moved the old `app.rs` tests into a
    single `app/tests.rs` file. That kept the split mechanically simple, but
    it left several focused unit tests far from the modules they exercise.
    
    This PR is a follow-up that moves tests next to the code they cover.
    
    It also adds `tui/src/app/test_support.rs` for shared fixture
    construction.
    
    This is just a mechanical refactoring (no functional changes) and does
    not affect any production code.
  • Queue follow-up input during user shell commands (#18820)
    Fixes #17954.
    
    ## Why
    When a manual shell command like `!sleep 10` is running, submitting
    plain text such as `hi` currently sends that text as a steer for the
    active shell turn. User shell turns are not steerable like model turns,
    so the TUI can remain stuck in `Working` after the shell command
    finishes.
    
    ## What Changed
    - Detect when the only active work is one or more
    `ExecCommandSource::UserShell` commands.
    - Queue plain submitted input in that state so it drains after the shell
    command and shell turn complete.
    - Preserve `!cmd` submissions during running work so explicit shell
    commands keep their existing behavior.
    - Add regression coverage for the `!sleep 10` plus `hi` flow in
    `chatwidget::tests::exec_flow::user_message_during_user_shell_command_is_queued_not_steered`.
    
    ## Verification
    - Manually confirmed hang before the fix and no hang after the fix
  • [codex] Add tmux-aware OSC 9 notifications (#17836)
    ## Summary
    - wrap OSC 9 notifications in tmux's DCS passthrough so terminal
    notifications make it through tmux
    - use codex-terminal-detection for OSC 9 auto-selection so tmux sessions
    inherit the underlying client terminal support
    - add focused notification backend tests for plain OSC 9 and
    tmux-wrapped output
    
    ## Stack
    - base PR: #18479
    - review order: #18479, then this PR
    
    ## Why
    Tmux does not forward OSC 9 notifications directly; the sequence has to
    be wrapped in tmux's DCS passthrough envelope. Codex also had local
    notification heuristics that could miss supported terminals when running
    under tmux, even though codex-terminal-detection already knows how to
    attribute tmux sessions to the client terminal.
    
    ## Validation
    - `just fmt`
    - `cargo test -p codex-tui` *(currently blocked by an unrelated existing
    compile error in `app-server/src/message_processor.rs:754` referencing
    `connection_id` out of scope; not caused by this change)*
    
    Co-authored-by: Codex <noreply@openai.com>
  • fix(tui): disable enhanced keys for VS Code WSL (#18741)
    Fixes https://github.com/openai/codex/issues/13638
    
    ## Why
    
    VS Code's integrated terminal can run a Linux shell through WSL without
    exposing `TERM_PROGRAM` to the Linux process, and with crossterm
    keyboard enhancement flags enabled that environment can turn dead-key
    composition into malformed key events instead of composed Unicode input.
    Codex already handles composed Unicode correctly, so the fix is to avoid
    enabling the terminal mode that breaks this path for the affected
    terminal combination.
    
    ## What Changed
    
    - Automatically skip crossterm keyboard enhancement flags when Codex
    detects WSL plus VS Code, including a Windows-side `TERM_PROGRAM` probe
    through WSL interop.
    - Add `CODEX_TUI_DISABLE_KEYBOARD_ENHANCEMENT` so users can
    force-disable or force-enable the keyboard enhancement policy for
    diagnosis.
    
    ## Verification
    
    - Added unit coverage for env parsing, VS Code detection, and the WSL/VS
    Code auto-disable policy.
    - `cargo check -p codex-tui` passed.
    - `./tools/argument-comment-lint/run.py -p codex-tui -- --tests` passed.
    - `cargo test -p codex-tui` was attempted locally, but the checkout
    failed during linking before tests executed because V8 symbols from
    `codex-code-mode` were unresolved for `arm64`.
  • show bash mode in the TUI (#18271)
    ## What
    
    - Explicitly show our "bash mode" by changing the color and adding a
    callout similar to how we do for `Plan mode (shift + tab to cycle)`
    - Also replace our `›` composer prefix with a bang `!`
    
    
    ![](https://github.com/user-attachments/assets/f5549c75-3a03-433d-aa57-e4c6d0682c49)
    
    ## Why
    
    - It was unclear that we had a Bash mode
    - This feels more responsive
    - It looks cool!
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [tool search] support namespaced deferred dynamic tools (#18413)
    Deferred dynamic tools need to round-trip a namespace so a tool returned
    by `tool_search` can be called through the same registry key that core
    uses for dispatch.
    
    This change adds namespace support for dynamic tool specs/calls,
    persists it through app-server thread state, and routes dynamic tool
    calls by full `ToolName` while still sending the app the leaf tool name.
    Deferred dynamic tools must provide a namespace; non-deferred dynamic
    tools may remain top-level.
    
    It also introduces `LoadableToolSpec` as the shared
    function-or-namespace Responses shape used by both `tool_search` output
    and dynamic tool registration, so dynamic tools use the same wrapping
    logic in both paths.
    
    Validation:
    - `cargo test -p codex-tools`
    - `cargo test -p codex-core tool_search`
    
    ---------
    
    Co-authored-by: Sayan Sisodiya <sayan@openai.com>
  • Add remote_sandbox_config to our config requirements (#18763)
    ## Why
    
    Customers need finer-grained control over allowed sandbox modes based on
    the host Codex is running on. For example, they may want stricter
    sandbox limits on devboxes while keeping a different default elsewhere.
    
    Our current cloud requirements can target user/account groups, but they
    cannot vary sandbox requirements by host. That makes remote development
    environments awkward because the same top-level `allowed_sandbox_modes`
    has to apply everywhere.
    
    ## What
    
    Adds a new `remote_sandbox_config` section to `requirements.toml`:
    
    ```toml
    allowed_sandbox_modes = ["read-only"]
    
    [[remote_sandbox_config]]
    hostname_patterns = ["*.org"]
    allowed_sandbox_modes = ["read-only", "workspace-write"]
    
    [[remote_sandbox_config]]
    hostname_patterns = ["*.sh", "runner-*.ci"]
    allowed_sandbox_modes = ["read-only", "danger-full-access"]
    ```
    
    During requirements resolution, Codex resolves the local host name once,
    preferring the machine FQDN when available and falling back to the
    cleaned kernel hostname. This host classification is best effort rather
    than authenticated device proof.
    
    Each requirements source applies its first matching
    `remote_sandbox_config` entry before it is merged with other sources.
    The shared merge helper keeps that `apply_remote_sandbox_config` step
    paired with requirements merging so new requirements sources do not have
    to remember the extra call.
    
    That preserves source precedence: a lower-precedence requirements file
    with a matching `remote_sandbox_config` cannot override a
    higher-precedence source that already set `allowed_sandbox_modes`.
    
    This also wires the hostname-aware resolution through app-server,
    CLI/TUI config loading, config API reads, and config layer metadata so
    they all evaluate remote sandbox requirements consistently.
    
    ## Verification
    
    - `cargo test -p codex-config remote_sandbox_config`
    - `cargo test -p codex-config host_name`
    - `cargo test -p codex-core
    load_config_layers_applies_matching_remote_sandbox_config`
    - `cargo test -p codex-core
    system_remote_sandbox_config_keeps_cloud_sandbox_modes`
    - `cargo test -p codex-config`
    - `cargo test -p codex-core` unit tests passed; `tests/all.rs`
    integration matrix was intentionally stopped after the relevant focused
    tests passed
    - `just fix -p codex-config`
    - `just fix -p codex-core`
    - `cargo check -p codex-app-server`
  • feat(auto-review) Handle request_permissions calls (#18393)
    ## Summary
    When auto-review is enabled, it should handle request_permissions tool.
    We'll need to clean up the UX but I'm planning to do that in a separate
    pass
    
    ## Testing
    - [x] Ran locally
    <img width="893" height="396" alt="Screenshot 2026-04-17 at 1 16 13 PM"
    src="https://github.com/user-attachments/assets/4c045c5f-1138-4c6c-ac6e-2cb6be4514d8"
    />
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Fallback display names for TUI skill mentions (#18786)
    This updates TUI skill mentions to show a fallback label when a skill
    does not define a display name, so unnamed skills remain understandable
    in the picker without changing behavior for skills that already have
    one.
    
    <img width="1028" height="198" alt="Screenshot 2026-04-20 at 6 25 15 PM"
    src="https://github.com/user-attachments/assets/84077b85-99d0-4db9-b533-37e1887b4506"
    />
  • protocol: preserve glob scan depth in permission profiles (#18713)
    ## Why
    
    #18274 made `PermissionProfile` the canonical file-system permissions
    shape, but the round-trip from `FileSystemSandboxPolicy` to
    `PermissionProfile` still dropped one piece of policy metadata:
    `glob_scan_max_depth`.
    
    That field is security-relevant for deny-read globs such as `**/*.env`.
    On Linux, bubblewrap sandbox construction uses it to bound unreadable
    glob expansion. If a profile copied from active runtime permissions
    loses this value and is submitted back as an override, the resulting
    `FileSystemSandboxPolicy` can behave differently even though the visible
    permission entries look equivalent.
    
    ## What changed
    
    - Add `glob_scan_max_depth` to protocol `FileSystemPermissions` and
    preserve it when converting to/from `FileSystemSandboxPolicy`.
    - Keep legacy `read`/`write` JSON for simple path-only permissions, but
    force canonical JSON when glob scan depth is present so the metadata is
    not silently dropped.
    - Carry `globScanMaxDepth` through app-server
    `AdditionalFileSystemPermissions`, generated JSON/TypeScript schemas,
    and app-server/TUI conversion call sites.
    - Preserve the metadata through sandboxing permission normalization,
    merging, and intersection.
    - Carry the merged scan depth into the effective
    `FileSystemSandboxPolicy` used for command execution, so bounded
    deny-read globs reach Linux bubblewrap materialization.
    
    ## Verification
    
    - `cargo test -p codex-sandboxing glob_scan -- --nocapture`
    - `cargo test -p codex-sandboxing policy_transforms -- --nocapture`
    - `just fix -p codex-sandboxing`
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/18713).
    * #18288
    * #18287
    * #18286
    * #18285
    * #18284
    * #18283
    * #18282
    * #18281
    * #18280
    * #18279
    * #18278
    * #18277
    * #18276
    * #18275
    * __->__ #18713
  • /statusline & /title - Shared preview values (#18435)
    This PR makes the `/statusline` and `/title` setup UIs share one
    preview-value source instead of each surface using its own examples.
    Both pickers now render consistent live values when available, and
    stable placeholders when they are not. It also resolves live preview
    values at the shared preview-item layer, so `/title` preview can use
    real runtime values for title-specific cases like status text, task
    progress, and project-name fallback behavior.
    
    - Adds a shared preview data model for status surfaces
    - Maps status-line items and terminal-title items onto that shared
    preview list
    - Feeds both setup views from the same chatwidget-derived preview data,
    with terminal-title-specific formatting applied before `/title` preview
    renders
    - Keeps project-root preview aligned with status-line behavior while
    project in /title keeps its title fallback/truncation behavior
    - Adds snapshot coverage for live-only, hardcoded-only, and mixed cases
    
    Test Steps
    - Open Codex TUI and launch `/statusline`.
    - Toggle and reorder items, then verify the preview uses current session
    values when possible, and placeholder values for missing values (ex: no
    thread ID).
    - Open `/title` and verify it shows the same normalized values,
    including live status/task-progress values when available.
  • Warn when trusting Git subdirectories (#18602)
    Addresses #18505
    
    ## Summary
    
    When Codex is launched from a subdirectory of a Git repository, the
    onboarding trust prompt says it is trusting the current directory even
    though the persisted trust target is the repository root. That can make
    the scope of the trust decision unclear.
    
    This updates the TUI trust prompt to show a yellow note only when the
    current directory differs from the resolved trust target, explaining
    that trust applies to the repository root and displaying that root. It
    also removes the stale onboarding TODO now that the warning is
    implemented.
  • Fix exec inheritance of root shared flags (#18630)
    Addresses #18113
    
    Problem: Shared flags provided before the exec subcommand were parsed by
    the root CLI but not inherited by the exec CLI, so exec sessions could
    run with stale or default sandbox and model configuration.
    
    Solution: Move shared TUI and exec flags into a common option block and
    merge root selections into exec before dispatch, while preserving exec's
    global subcommand flag behavior.
  • Refactor TUI app module into submodules (#18753)
    ## Why
    
    The TUI app module had grown past the 512K source-file cap enforced by
    CI/CD. This keeps the app entry point below that limit while preserving
    the existing runtime behavior and test surface.
    
    ## What changed
    
    - Kept the top-level `App` state and run-loop wiring in
    `tui/src/app.rs`.
    - Split app responsibilities into focused private submodules under
    `tui/src/app/`, covering event dispatch, thread routing, session
    lifecycle, config persistence, background requests, startup prompts,
    input, history UI, platform actions, and thread event buffering.
    - Moved the existing app-level tests into `tui/src/app/tests.rs` and
    reused the existing snapshot location rather than adding new tests or
    snapshots.
    - Added module header comments for `app.rs` and the new submodules.
    
    ## Follow-up
    
    A future cleanup can move narrow unit tests from `tui/src/app/tests.rs`
    into the specific app submodules they exercise. This PR keeps the
    existing app-level tests together so the refactor stays focused on the
    source-file split.
    
    ## Verification
    
    - `cargo test -p codex-tui --lib
    app::tests::agent_picker_item_name_snapshot`
    - `cargo test -p codex-tui --lib app::tests::clear_ui`
    - `cargo test -p codex-tui --lib
    app::tests::ctrl_l_clear_ui_after_long_transcript_reuses_clear_header_snapshot`
    - `just fix -p codex-tui`
    
    Full `cargo test -p codex-tui` still fails on model-catalog drift
    unrelated to this refactor, including stale
    `gpt-5.3-codex`/`gpt-5.1-codex` snapshot and migration expectations now
    resolving to `gpt-5.4`.
  • Add realtime silence tool (#18635)
    ## Summary
    
    Adds a second realtime v2 function tool, `remain_silent`, so the
    realtime model has an explicit non-speaking action when the
    collaboration mode or latest context says it should not answer aloud.
    This is stacked on #18597.
    
    ## Design
    
    - Advertise `remain_silent` alongside `background_agent` in realtime v2
    conversational sessions.
    - Parse `remain_silent` function calls into a typed
    `RealtimeEvent::NoopRequested` event.
    - Have core answer that function call with an empty
    `function_call_output` and deliberately avoid `response.create`, so no
    follow-up realtime response is requested.
    - Keep the event hidden from app-server/TUI surfaces; it is operational
    plumbing, not user-visible conversation content.
  • Use app server metadata for fork parent titles (#18632)
    ## Problem
    The TUI resolved fork parent titles from local CODEX_HOME metadata,
    which could show missing or stale titles when app-server metadata is
    authoritative.
    
    This is a lingering bug left over from the migration of the TUI to the
    app-server interface. I found it when I asked Codex to review all places
    where the TUI code was still directly accessing the local CODEX_HOME.
    
    ## Solution
    Route fork parent title metadata through the app-server session state
    and render only that supplied title, with focused snapshot coverage for
    stale local metadata.
    
    ## Testing
    I manually tested by renaming a thread then forking it and confirming
    that the "forked from" message indicated the parent thread's name.
  • fix(tui): keep /copy aligned with rollback (#18739)
    ## Why
    
    Fixes #18718.
    
    After rewinding a thread, `/copy` could still copy the latest assistant
    response from before the rewind. The transcript cells were rolled back,
    but the copy source was a single `last_agent_markdown` cache that was
    not synchronized with backtracking, so the visible conversation and
    copied content could diverge.
    
    ## What changed
    
    `ChatWidget` now keeps a bounded copy history for the most recent 32
    assistant responses, keyed by the visible user-turn count. When local
    rollback trims transcript cells, the copy cache is trimmed to the same
    surviving user-turn count so `/copy` uses the latest visible assistant
    response.
    
    If the user rewinds past the retained copy window, `/copy` now reports:
    
    ```text
    Cannot copy that response after rewinding. Only the most recent 32 responses are available to /copy.
    ```
    
    The change also adds coverage for copying the latest surviving response
    after rollback and for the over-limit rewind message.
    
    ## Verification
    
    - Manually resumed a synthetic 35-turn session, rewound within the
    retained window, and verified `/copy` copied the surviving response.
    - Manually rewound past the retained window and verified `/copy` showed
    the 32-response limit message.
    - `cargo test -p codex-tui slash_copy`
    - `just fix -p codex-tui`
    - `cargo insta pending-snapshots`
    
    Note: `cargo test -p codex-tui` currently fails on unrelated model
    catalog and snapshot drift around the default model changing to
    `gpt-5.4`; the focused `/copy` tests pass after fixing the new test
    setup.
  • Fix stale model test fixtures (#18719)
    Fixes stale test fixtures left after the active bundled model catalog
    updates in #18586 and #18388. Those changes made `gpt-5.4` the current
    default and removed several older hardcoded slugs, which left Windows
    Bazel shards failing TUI and config tests.
    
    What changed:
    - Refresh TUI model migration, availability NUX, plan-mode, status, and
    snapshot fixtures to use active bundled model slugs.
    - Update the config edit test expectation for the TOML-quoted
    `"gpt-5.2"` migration key.
    - Move the model catalog tests into
    `codex-rs/tui/src/app/tests/model_catalog.rs` so touching them does not
    trip the blob-size policy for `app.rs`.
    
    Verification:
    - CI Bazel/lint checks are expected to cover the affected test shards.
  • Surface TUI skills refresh failures (#18627)
    ## Why
    
    `skills/list` refreshes are best-effort metadata updates. If one fails
    during startup or thread switching, the TUI should keep running and show
    enough detail to diagnose the app-server failure instead of leaving the
    user with only a log entry.
    
    This addresses the recoverability and observability issue reported in
    #16914.
    
    ## What Changed
    
    - Preserve the full startup `skills/list` error chain before sending it
    back through the app event queue.
    - Surface failed skills refreshes as recoverable TUI error messages
    while still logging the warning.
    
    This is related to the recent bug fix from [PR
    #18370](https://github.com/openai/codex/pull/18370).
  • Remove simple TUI legacy_core reexports (#18631)
    ## Problem
    The TUI still imported path utilities and config-loader symbols through
    app-server-client's legacy_core facade even though those APIs already
    exist in utility/config crates. This is part of our ongoing effort to
    whittle away at these old dependencies.
    
    ## Solution
    Rewire imports to avoid the TUI directly importing from the core crate
    and instead import from common lower-level crates. This PR doesn't
    include any functional changes; it's just a simple rewiring.
  • Wire the PatchUpdated events through app_server (#18289)
    Wires patch_updated events through app_server. These events are parsed
    and streamed while apply_patch is being written by the model. Also adds 500ms of buffering to the patch_updated events in the diff_consumer.
    
    The eventual goal is to use this to display better progress indicators in
    the codex app.
  • Update models.json (#18586)
    - Replace the active models-manager catalog with the deleted core
    catalog contents.
    - Replace stale hardcoded test model slugs with current bundled model
    slugs.
    - Keep this as a stacked change on top of the cleanup PR.
  • protocol: canonicalize file system permissions (#18274)
    ## Why
    
    `PermissionProfile` needs stable, canonical file-system semantics before
    it can become the primary runtime permissions abstraction. Without a
    canonical form, callers have to keep re-deriving legacy sandbox maps and
    profile comparisons remain lossy or order-dependent.
    
    ## What changed
    
    This adds canonicalization helpers for `FileSystemPermissions` and
    `PermissionProfile`, expands special paths into explicit sandbox
    entries, and updates permission request/conversion paths to consume
    those canonical entries. It also tightens the legacy bridge so root-wide
    write profiles with narrower carveouts are not silently projected as
    full-disk legacy access.
    
    ## Verification
    
    - `cargo test -p codex-protocol
    root_write_with_read_only_child_is_not_full_disk_write -- --nocapture`
    - `cargo test -p codex-sandboxing permission -- --nocapture`
    - `cargo test -p codex-tui permissions -- --nocapture`
  • Surface parent thread status in side conversations (#18591)
    ## Summary
    
    Side conversations can hide important state changes from the parent
    conversation while the user is focused on the side thread. In
    particular, the parent may finish, fail, need user input, or require an
    approval while the side conversation remains visible. Users need a
    lightweight signal for those states, but parent approval overlays should
    not interrupt the side conversation itself.
    
    This change adds parent-conversation status to the side conversation
    context label and defers parent interactive overlays while side mode is
    active. When the user exits side mode, pending parent approvals and
    input requests are restored in the main thread. The pending approval
    footer avoids duplicating the same parent approval status, and replayed
    notice cells are filtered when restoring a pending interactive request
    so tips or warnings do not crowd out the approval prompt.
    
    The change is contained to the TUI side-conversation and thread replay
    paths.
    
    Example 1: Approval pending
    <img width="752" height="35" alt="Screenshot 2026-04-19 at 12 56 07 PM"
    src="https://github.com/user-attachments/assets/1cc0f1a3-9cab-4d60-aed2-96523ccafc20"
    />
    
    Example 2: Turn complete
    <img width="754" height="35" alt="Screenshot 2026-04-19 at 12 56 27 PM"
    src="https://github.com/user-attachments/assets/653521a5-e298-4366-ae1c-72b56eb88eeb"
    />
  • Use app server thread names in TUI picker (#18633)
    ## Problem
    
    The TUI resume/fork picker was backfilling thread names from local
    rollout indexes. This was left over from before the TUI was moved to the
    app server. It should be using app-server APIs because the TUI might be
    connected to a remote connection.
    
    This bug wasn't (yet) reported by a user. I found it by asking Codex to
    review places in the TUI code where it was still directly accessing the
    CODEX_HOME directory rather than going through app-server APIs.
    
    ## Solution
    
    The resume picker and session lookups should use app-server thread APIs
    only. Remove legacy rollout name/list backfills, and avoid local name
    reads in fork history.
    
    ## Testing
    
    I manually tested `codex resume` and `codex resume --all` to look for
    functional or performance regressions in the resume picker.
  • Add verbose diagnostics for /mcp (#18610)
    Fixes #18539.
    
    ## Summary
    The recent `/mcp` performance work kept the default command fast by
    avoiding resource and resource-template inventory probes, but it also
    removed useful diagnostics for users trying to confirm MCP server state.
    
    This keeps bare `/mcp` on the fast tools/auth path and adds `/mcp
    verbose` for the slower diagnostic view. Verbose mode requests full MCP
    server status from the app-server and restores status, resources, and
    resource templates in the TUI output.
    
    ## Testing
    In addition to running automation, I manually tested the feature to
    confirm that it works.
  • chore: drop review prompt from TUI UX (#18659)
    Due to the app-server rebase of the TUI, the review prompt was leaked
    into the transcript on the TUI
    This is not a security issue but it was bad UX. This PR fixes this
  • [codex] Add workspace owner usage nudge UI (#18221)
    ## Summary
    
    Third PR in the split from #17956. Stacked on #18220.
    
    - shows workspace-owner/member-specific rate-limit messages behind
    `workspace_owner_usage_nudge`
    - prompts workspace members to notify the owner or request a usage-limit
    increase
    - sends the confirmed nudge through the app-server API and renders
    completion feedback
    - adds focused TUI snapshot coverage for prompts and completion states
    - feature gate
    
    ## Validation
    
    - `cargo test -p codex-backend-client`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server rate_limits`
    - `cargo test -p codex-tui workspace_`
    - `cargo test -p codex-tui status_`
    - `just fmt`
    - `just fix -p codex-backend-client`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-app-server`
    - `just fix -p codex-tui`
  • TUI: remove simple legacy_core re-exports (#18605)
    ## Summary
    
    The TUI still imported several symbols through the transitional
    app-server-client `legacy_core` facade even though those symbols are
    already owned by smaller crates. This PR narrows that facade by rewiring
    those imports directly to their owner crates.
    
    ## Changes
    
    No functional changes, just import rewiring. This is part of our ongoing
    effort to whittle away at the `legacy_core` namespace, which represents
    all of the remaining symbols that the TUI imports from the core.
  • Use thread IDs in TUI resume hints (#18440)
    ## Summary
    
    Fixes #18313.
    
    Recent TUI resume breadcrumbs could print a thread title instead of the
    stable thread UUID. For sessions whose title was auto-derived from the
    first prompt, that made the suggested codex resume command look like it
    should resume a long prompt rather than the session ID.
    
    This updates the TUI and CLI post-exit resume hints, plus the in-session
    summary shown when switching/forking threads, to always use the stable
    thread ID for these recovery breadcrumbs. Explicit name-based resume
    support remains available elsewhere.
  • Soften Fast mode plan usage copy (#18601)
    Fast mode TUI copy currently names a specific plan-usage multiplier in
    two lightweight promo/help surfaces. This swaps that exact multiplier
    language for the broader increased plan usage wording we use elsewhere.
    
    There are no behavior changes here; the slash command and startup tip
    still point users at the same Fast mode flow.
  • Avoid redundant memory enable notice (#18580)
    ## Summary
    
    Fixes #18554.
    
    The `/experimental` menu can submit the full experimental feature state
    even when the user presses Enter without toggling anything. Previously,
    Codex showed `Memories will be enabled in the next session.` whenever
    the submitted updates included `Feature::MemoryTool = true`, so sessions
    where Memories were already enabled could show a redundant warning on a
    no-op save.
    
    This change records whether `Feature::MemoryTool` was enabled before
    applying feature updates and only emits the next-session notice when
    Memories actually transitions from disabled to enabled.
  • Add /side conversations (#18190)
    The TUI supports long-running turns and agent threads, but quick side
    questions have required interrupting the main flow or manually
    forking/navigating threads. This PR adds a guarded `/side` flow so users
    can ask brief side-conversation questions in an ephemeral fork while
    keeping the primary thread focused. This also helps address the feature
    request in #18125.
    
    The implementation creates one side conversation at a time, lets `/side`
    open either an empty side thread or immediately submit `/side
    <question>`, and returns to the parent with Esc or Ctrl+C. Side
    conversations get hidden developer guardrails that treat inherited
    history as reference-only and steer the model away from workspace
    mutations unless explicitly requested in the side conversation.
    
    The TUI hides most slash commands while side mode is active, leaving
    only `/copy`, `/diff`, `/mention`, and `/status` available there.
  • Queue slash and shell prompts in the TUI (#18542)
    ## Why
    
    Users have asked to queue follow-up slash commands while a task is
    running, including in #14081, #14588, #14286, and #13779. The previous
    TUI behavior validated slash commands immediately, so commands that are
    only meaningful once the current turn is idle could not be queued
    consistently.
    
    The queue should preserve what the user typed and defer command parsing
    until the item is actually dispatched. This also gives `/fast`, `/review
    ...`, `/rename ...`, `/model`, `/permissions`, and similar slash
    workflows the same FIFO behavior as plain queued prompts.
    
    ## What Changed
    
    - Added a queued-input action enum so queued items can be dispatched as
    plain prompts, slash commands, or user shell commands.
    - Changed `Tab` queueing to accept slash-led prompts without validating
    them up front, then parse and dispatch them when dequeued.
    - Added `!` shell-command queueing for `Tab` while a task is running,
    while preserving existing `Enter` behavior for immediate shell
    execution.
    - Moved queued slash dispatch through shared slash-command parsing so
    inline commands, unavailable commands, unknown commands, and local
    config commands report at dequeue time.
    - Continued queue draining after local-only actions and after slash menu
    cancellation or selection when no task is running.
    - Preserved slash-popup completion behavior so `/mo<Tab>` completes to
    `/model ` instead of queueing the prefix.
    - Updated pending-input preview snapshots to show queued follow-up
    inputs.
    
    ## Verification
    
    I did a bunch of manual validation (and found and fixed a few bugs along
    the way).
  • feat(tui): show context used in plan implementation prompt (#18573)
    # Summary
    
    When a user finishes planning, the TUI asks whether to implement in the
    current conversation or start fresh with the approved plan. The
    clear-context choice is easier to evaluate when the prompt shows how
    much context has already been used, because the user can see when
    carrying the full prior conversation is likely to be less useful than
    preserving only the plan.
    
    <img width="1612" height="1312" alt="image"
    src="https://github.com/user-attachments/assets/694bcf87-8be5-4e88-a412-e562af62d5f7"
    />
        
    This PR adds that context signal directly to the clear-context option
    while keeping the copy compact enough for the Plan-mode selection popup.
    
    # What Changed
    
    - Compute an optional context-usage label when opening the plan
    implementation prompt.
    - Show the label only on `Yes, clear context and implement`, where it
    informs the cleanup decision.
    - Prefer a percentage-used label when context-window information is
    available, with a compact token-used fallback when only token totals are
    known.
    - Preserve the original option description when usage is unknown or
    effectively zero.
    - Add rustdoc comments around the prompt-copy boundary so future changes
    keep the context label formatting and selection rendering
    responsibilities clear.
    
    # Testing
    
    - `cargo test -p codex-tui plan_implementation`
    
    # Notes
    
    The footer continues to show context remaining as ambient status. The
    implementation prompt intentionally shows context used because the user
    is choosing whether to clean up the current thread before
    implementation.
  • feat: Budget skill metadata and surface trimming as a warning (#18298)
    Cap the model-visible skills section to a small share of the context
    window, with a fallback character budget, and keep only as many implicit
    skills as fit within that budget.
    
    Emit a non-fatal warning when enabled skills are omitted, and add a new
    app-server warning notification
    
    Record thread-start skill metrics for total enabled skills, kept skills,
    and whether truncation happened
    
    ---------
    
    Co-authored-by: Matthew Zeng <mzeng@openai.com>
    Co-authored-by: Codex <noreply@openai.com>