Commit Graph

1849 Commits

  • tui: keep cleared Fast tier from reappearing after side-thread resume (#23121)
    ## Why
    
    After turning Fast mode off in the TUI, returning from a side thread
    could make `Fast` appear again in the main chat widget. The opt-out
    itself was still persisted; the display was being rebuilt from stale
    cached `ThreadSessionState` data, which made it look like Fast had been
    re-enabled.
    
    Fixes #23104.
    
    ## What changed
    
    - Keep the active thread's cached `service_tier` in sync whenever the
    user persists a service-tier selection.
    - Update both the primary-thread snapshot and the thread event store so
    restored TUI state reflects the current tier.
    - Add a focused regression test for clearing a cached Fast tier.
    
    ## Manual repro
    
    1. Start a TUI session where `Fast` is enabled by default.
    2. Run `/fast` and turn Fast mode off. Confirm `Fast` disappears from
    the chat widget display.
    3. Re-enter thread navigation via either path:
       - Run `/side test`, then return to the main thread.
       - Run `/agent`, enter a child thread, then return to the main thread.
    4. Before this fix, `Fast` reappears in the main chat widget display
    even though the opt-out was already persisted.
    5. After this fix, `Fast` stays cleared.
    
    ## Verification
    
    - `cargo test -p codex-tui
    app::thread_session_state::tests::service_tier_sync_updates_active_cached_session
    -- --exact`
  • [1 of 4] tui: route primary settings writes through app server (#22913)
    ## Why
    The TUI can run against a remote app server, but several high-traffic
    settings still persisted by editing the local config file. That sends
    remote sessions' preference writes to the wrong machine and lets local
    disk state drift from the app-server-owned config.
    
    This is **[1 of 4]** in a stacked series that moves TUI-owned config
    mutations onto app-server APIs.
    
    ## What changed
    - Added a small TUI helper for typed app-server config writes.
    - Routed primary interactive preference writes through
    `config/batchWrite`.
    - Preserved existing profile scoping for settings that already support
    `profiles.<profile>.*` overrides.
    
    ## Config keys affected
    - `model`
    - `model_reasoning_effort`
    - `personality`
    - `service_tier`
    - `plan_mode_reasoning_effort`
    - `approvals_reviewer`
    - `notice.fast_default_opt_out`
    - Profile-scoped equivalents under `profiles.<profile>.*`
    
    ## Suggested manual validation
    - Connect the TUI to a remote app server, change `model` and
    `model_reasoning_effort`, reconnect, and confirm the remote config
    retained both values while the local `config.toml` did not change.
    - Change `personality`, `plan_mode_reasoning_effort`, and the explicit
    auto-review selection, then reconnect and confirm those choices persist
    through the app server.
    - Clear the service tier back to default and confirm `service_tier` is
    cleared while `notice.fast_default_opt_out = true` is persisted
    remotely.
    - Repeat one setting change with an active profile and confirm the write
    lands under `profiles.<profile>.*`.
    
    ## Stack
    1. [#22913](https://github.com/openai/codex/pull/22913) `[1 of 4]`
    primary settings writes
    2. [#22914](https://github.com/openai/codex/pull/22914) `[2 of 4]` app
    and skill enablement
    3. [#22915](https://github.com/openai/codex/pull/22915) `[3 of 4]`
    feature and memory toggles
    4. [#22916](https://github.com/openai/codex/pull/22916) `[4 of 4]`
    startup and onboarding bookkeeping
  • core: set permission profiles from snapshots (#22920)
    ## Why
    
    #22891 moved the TUI turn-command path to pass `ActivePermissionProfile`
    instead of the full `PermissionProfile`, but the remaining
    config/session bridge still accepted the concrete `PermissionProfile`
    and active profile id as separate arguments. That shape made it too easy
    for future callers to update the concrete profile and active profile id
    out of sync.
    
    This PR makes the trusted session snapshot path pass one coherent value
    into `Permissions`, while keeping `requirements.toml` enforcement owned
    by the existing constrained permission state.
    
    ## What Changed
    
    - Added `PermissionProfileSnapshot` as the public snapshot value for
    trusted session/config synchronization.
    - Changed `Permissions::set_permission_profile_from_session_snapshot()`
    and `replace_permission_profile_from_session_snapshot()` to take a
    `PermissionProfileSnapshot`.
    - Updated the replacement path to derive its constrained
    `PermissionProfile` from the snapshot, so callers cannot pass a separate
    profile that disagrees with the snapshot.
    - Removed the internal tuple-style
    `PermissionProfileState::set_active_permission_profile()` mutation path.
    - Updated core session projection and TUI call sites to construct
    explicit legacy or active snapshots.
    - Documented the snapshot constructors so legacy use and id/profile
    mismatch hazards are called out at the API boundary.
    - Added a focused config test that verifies snapshot updates still
    respect existing permission constraints.
    
    ## How To Review
    
    1. Start with `codex-rs/core/src/config/resolved_permission_profile.rs`;
    `PermissionProfileSnapshot` is the public wrapper, while
    `ResolvedPermissionProfile` stays internal.
    2. Check `codex-rs/core/src/config/mod.rs` to confirm both
    session-snapshot setters validate through `PermissionProfileState` and
    no longer accept loose profile/id pairs.
    3. Skim `codex-rs/core/src/session/session.rs` for the session
    projection path; it now builds the snapshot before installing it.
    4. Skim the TUI changes as call-site migration from loose argument pairs
    to explicit snapshot construction.
    
    ## Verification
    
    - `cargo test -p codex-core
    permission_snapshot_setter_preserves_permission_constraints`
    - `cargo test -p codex-tui status_permissions_`
    - `cargo test -p codex-tui
    session_configured_preserves_profile_workspace_roots`
    - `just fix -p codex-core -p codex-tui`
  • app-server-protocol: remove PermissionProfile from API (#22924)
    ## Why
    
    The app server API should expose permission profile identity, not the
    lower-level runtime permission model. `PermissionProfile` is the
    compiled sandbox/network representation that the server uses internally;
    exposing it through app-server-protocol forces clients to understand
    details that should remain implementation-level.
    
    The API boundary should prefer `ActivePermissionProfile`: a stable
    profile id, plus future parent-profile metadata, that clients can pass
    back when they want to select the same active permissions. This also
    avoids schema generation collisions between the app-server v2 API type
    space and the core protocol model.
    
    Incidentally, while PR makes a number of changes to `command/exec`, note
    that we are hoping to deprecate this API in favor of `process/spawn`, so
    we don't need to be too finicky about these changes.
    
    ## What Changed
    
    - Removed `PermissionProfile` from the app-server-protocol API surface,
    including generated schema and TypeScript exports.
    - Changed `CommandExecParams.permissionProfile` to
    `ActivePermissionProfile`.
    - Resolve command exec profile ids through `ConfigManager` for the
    command cwd, matching turn override selection semantics.
    - Updated downstream TUI tests/helpers to use core permission types
    directly instead of app-server-protocol `PermissionProfile` shims.
  • tui: pass active permission profiles through app commands (#22891)
    ## Why
    
    This continues the permissions migration by keeping the TUI command
    boundary aligned with the app-server protocol direction from #22795:
    callers should select a permission profile by id instead of passing a
    concrete `PermissionProfile` value around as the turn configuration.
    
    `AppCommand` is internal to the TUI, but it is the path that eventually
    becomes `thread/turn/start`, so carrying concrete profile details there
    made it too easy for UI code to keep relying on the old whole-profile
    replacement model.
    
    ## What changed
    
    - `AppCommand::UserTurn` and `AppCommand::OverrideTurnContext` now carry
    `Option<ActivePermissionProfile>` instead of `PermissionProfile`.
    - Composer submissions copy the active permission profile id from the
    current session snapshot; legacy snapshots intentionally submit no
    active profile id.
    - Permission preset UI events now carry only the active built-in profile
    id. The app derives the concrete built-in `PermissionProfile` internally
    only when updating its local config/status snapshot.
    - Permission presets expose their built-in active profile id, and preset
    selection preserves that id in both the immediate turn override and the
    local TUI config snapshot.
    - Turn routing sends `TurnPermissionsOverride::ActiveProfile` when an
    active id is present, and only falls back to the legacy sandbox
    projection for the remaining runtime override path.
    
    ## How to review
    
    Start with `codex-rs/tui/src/app_command.rs` to verify the command shape
    no longer exposes `PermissionProfile`.
    
    Then read `codex-rs/tui/src/app/thread_routing.rs` to verify the
    app-server turn-start conversion: active ids go through as ids, while
    the legacy sandbox fallback is still constrained to the existing runtime
    override case.
    
    Finally, check `codex-rs/tui/src/chatwidget/permission_popups.rs`,
    `codex-rs/tui/src/app/event_dispatch.rs`,
    `codex-rs/tui/src/app/config_persistence.rs`, and
    `codex-rs/utils/approval-presets/src/lib.rs` to see how preset
    selections stay id-only across TUI events while the local display/config
    mirror still gets a concrete built-in profile.
    
    ## Verification
    
    Latest local verification after the id-only `AppEvent` cleanup:
    
    - `cargo check -p codex-tui --tests`
    - `cargo test -p codex-tui
    permissions_selection_sends_approvals_reviewer_in_override_turn_context`
    - `cargo test -p codex-tui update_feature_flags_enabling_guardian`
    - `cargo test -p codex-utils-approval-presets`
    - `just fmt`
    - `just fix -p codex-tui -p codex-utils-approval-presets`
    
    Earlier in the same PR, before the final event-shape cleanup:
    
    - `cargo test -p codex-tui turn_permissions_`
    - `cargo test -p codex-tui submission_`
    - `cargo test -p codex-tui
    session_configured_syncs_widget_config_permissions_and_cwd`
    - `RUST_MIN_STACK=16777216 cargo test -p codex-tui`
  • Preserve image detail in app-server inputs (#20693)
    ## Summary
    
    - Add optional image detail to user image inputs across core, app-server
    v2, thread history/event mapping, and the generated app-server
    schemas/types.
    - Preserve requested detail when serializing Responses image inputs:
    omitted detail stays on the existing `high` default, while explicit
    `original` keeps local images on the original-resolution path.
    - Support `high`/`original` consistently for tool image outputs,
    including MCP `codex/imageDetail`, code-mode image helpers, and
    `view_image`.
  • app-server: stop returning thread permission profiles (#22792)
    ## Why
    
    The app-server thread lifecycle API should no longer expose the full
    `PermissionProfile` value. After the permissions-profile migration,
    clients should round-trip only the active profile identity through
    `activePermissionProfile` and `permissions` when that identity is known.
    
    The full profile is server-side config. Treating a response-derived
    legacy sandbox projection as a new local profile can lose named-profile
    restrictions and accidentally widen permissions on the next turn. The
    legacy `sandbox` response field remains only as the
    compatibility/display fallback.
    
    ## What Changed
    
    - Removed `permissionProfile` from `ThreadStartResponse`,
    `ThreadResumeResponse`, and `ThreadForkResponse`.
    - Stopped populating that field in app-server thread start/resume/fork
    responses.
    - Updated embedded exec/TUI response mapping to derive display
    permission state from local config or the legacy sandbox fallback
    instead of a response profile value.
    - Added a TUI turn override shape that distinguishes preserving server
    permissions, selecting an active profile id, and sending a legacy
    sandbox for an explicit local override.
    - Preserved remote app-server permissions across turns by sending
    `permissions` only when an `activePermissionProfile` id is known, and
    otherwise sending no sandbox override unless the user selected a local
    override.
    - Kept embedded `thread/resume` hydration server-authored when
    `activePermissionProfile` is absent, which matches the live-thread
    attach path where the server ignores requested overrides.
    - Updated the app-server README to remove the obsolete lifecycle
    response `permissionProfile` reference. The remaining
    `permissionProfile` README references are request-side permission
    overrides.
    - Regenerated app-server JSON schema and TypeScript fixtures.
    - Kept the generated typed response enum exempt from
    `large_enum_variant`, matching the existing payload enum exemption after
    the lifecycle response variants shrank.
    
    ## How To Review
    
    Start with `codex-rs/app-server-protocol/src/protocol/v2/thread.rs` to
    confirm the response shape, then check the response construction in
    `codex-rs/app-server/src/request_processors`. The generated schema and
    TypeScript fixture changes are mechanical follow-through from the
    protocol removal.
    
    The TUI behavior is the delicate part: review
    `codex-rs/tui/src/app_server_session.rs` for response hydration and
    turn-start override projection, then
    `codex-rs/tui/src/app/thread_routing.rs` for the decision about whether
    the next turn should preserve the server snapshot, send an active
    profile id, or send a legacy sandbox for an explicit local override.
    
    ## Verification
    
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol
    thread_lifecycle_responses_default_missing_optional_fields`
    - `cargo test -p codex-exec
    session_configured_from_thread_response_uses_permission_profile_from_config`
    - `cargo test -p codex-tui --lib thread_response`
    - `cargo test -p codex-tui turn_permissions_`
    - `cargo test -p codex-tui
    resume_response_restores_turns_from_thread_items`
    - `cargo test -p codex-analytics
    track_response_only_enqueues_analytics_relevant_responses`
    - `just fix -p codex-analytics`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-tui`
    - `just argument-comment-lint`
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22792).
    * #22795
    * __->__ #22792
  • tui: split remaining composer draft and footer state (#22656)
    ## Why
    
    [#22581](https://github.com/openai/codex/pull/22581) started separating
    the chat composer’s responsibilities, but `ChatComposer` still owned the
    remaining editable draft state alongside footer/status presentation
    state. This follow-up makes those ownership lines explicit so future
    composer changes have a smaller blast radius and `BottomPane` does not
    need to keep exposing scattered draft getters.
    
    This is just a refactor. No functional or behavioral changes are
    intended.
    
    ## What changed
    
    - Move the remaining editable composer state into
    `bottom_pane/chat_composer/draft_state.rs`.
    - Move footer and status-row presentation state into
    `bottom_pane/chat_composer/footer_state.rs`.
    - Add an internal `ComposerDraftSnapshot` for restore flows, replacing
    several ad hoc `BottomPane` pass-through reads.
    - Rewire the related history-search and thread-input restore paths to
    use the extracted state.
    
    ## Verification
    
    - `RUST_MIN_STACK=8388608 cargo test -p codex-tui`
    - `cargo insta pending-snapshots`
  • tui/exec: show effective workspace roots in summaries (#22612)
    ## Why
    
    This PR builds on [#22611](https://github.com/openai/codex/pull/22611).
    
    After `runtimeWorkspaceRoots` moved onto thread state, the user-facing
    summaries were still inconsistent about which roots they showed. In
    particular, `/status` and the exec startup summary could under-report
    extra workspace roots from `--add-dir` or from profile-defined
    `workspace_roots`, which made the new model look incorrect even when the
    permissions themselves were right.
    
    ## What Changed
    
    - switched the TUI status surfaces to summarize against
    `Config::effective_workspace_roots()`
    - updated the exec human-output summary to render from the effective
    permission profile instead of the raw constrained profile
    - added focused regressions for both the TUI and exec code paths so
    extra workspace roots stay visible in user-facing summaries
    
    ## Verification
    
    Targeted coverage for this follow-up lives in:
    - `codex-rs/tui/src/status/tests.rs`
    - `codex-rs/exec/src/event_processor_with_human_output_tests.rs`
    
    The added regressions verify that:
    - status output includes profile-defined workspace roots in the
    effective permissions summary
    - exec startup output includes runtime workspace roots instead of
    collapsing back to `cwd` only
  • app-server: use permission ids and runtime workspace roots (#22611)
    ## Why
    
    This PR builds on [#22610](https://github.com/openai/codex/pull/22610)
    and is the app-server side of the migration from mutable per-turn
    `SandboxPolicy` replacement toward selecting immutable permission
    profiles by id plus mutable runtime workspace roots.
    
    Once permission profiles can carry their own immutable
    `workspace_roots`, app-server no longer needs to mutate the selected
    `PermissionProfile` just to represent thread-specific filesystem
    context. The mutable part now lives on the thread as explicit
    `runtimeWorkspaceRoots`, while `:workspace_roots` remains symbolic until
    the sandbox is realized for a turn.
    
    ## What Changed
    
    - Replaced the v2 permission-selection wrapper surface with plain
    profile ids for `thread/start`, `thread/resume`, `thread/fork`, and
    `turn/start`.
    - Removed the API surface for profile modifications
    (`PermissionProfileSelectionParams`,
    `PermissionProfileModificationParams`,
    `ActivePermissionProfileModification`).
    - Added experimental `runtimeWorkspaceRoots` fields to the thread
    lifecycle and turn-start APIs.
    - Threaded runtime workspace roots through core session/thread
    snapshots, turn overrides, app-server request handling, and command
    execution permission resolution.
    - Kept session permission state symbolic so later runtime root updates
    and cwd-only implicit-root retargeting rebind `:workspace_roots`
    correctly.
    - Updated the embedded clients just enough to send and restore the new
    thread state.
    - Refreshed the generated schema/TypeScript artifacts and the app-server
    README to match the new contract.
    
    ## Verification
    
    Targeted coverage for this layer lives in:
    
    - `codex-rs/app-server-protocol/src/protocol/v2/tests.rs`
    - `codex-rs/app-server/tests/suite/v2/thread_start.rs`
    - `codex-rs/app-server/tests/suite/v2/thread_resume.rs`
    - `codex-rs/app-server/tests/suite/v2/turn_start.rs`
    - `codex-rs/core/src/session/tests.rs`
    
    The key regression checks exercise that:
    
    - `runtimeWorkspaceRoots` resolve against the effective cwd on thread
    start.
    - Profile-declared workspace roots are excluded from the runtime
    workspace roots returned by app-server.
    - A turn-level runtime workspace-root update persists onto the thread
    and is returned by `thread/resume`.
    - A named permission profile selected on one turn remains symbolic so a
    later runtime-root-only turn update changes the actual sandbox writes.
    - A cwd-only turn update retargets the implicit runtime cwd root while
    preserving additional runtime roots.
    - The protocol fixtures and generated client artifacts stay in sync with
    the string-based permission selection contract.
    
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22611).
    * #22612
    * __->__ #22611
  • TUI: split history cells into focused modules (#22704)
    ## Why
    
    `codex-rs/tui/src/history_cell.rs` had become the dumping ground for
    transcript rendering: the shared trait, common helpers, and the concrete
    cells for messages, plans, MCP/search, notices, patches, approvals,
    session chrome, and separators all lived together. That made small
    transcript changes require reopening a very large file and made
    ownership less obvious.
    
    ## What changed
    
    - Replaced the monolithic `history_cell.rs` with a `history_cell/`
    module tree organized by concern.
    - Kept the existing `crate::history_cell::*` surface stable through
    re-exports in `history_cell/mod.rs`.
    - Moved the existing render coverage into `history_cell/tests.rs`.
    
    ## Reviewer notes
    
    - This PR is intentionally mechanical in mature — existing code and
    tests moving into files that match their concern.
    - The snapshot files under `codex-rs/tui/src/history_cell/snapshots/`
    moved with the extracted test module. `insta` resolves these unnamed
    snapshots relative to the source file that declares them, so this is
    path churn only; snapshot contents were not updated.
    - The small non-mechanical seam edits are limited to split fallout:
    sibling-module visibility for shared cell containers, moving
    approval-specific exec-snippet helpers beside approvals, fixing the
    separator module path, and keeping a couple of existing test helpers
    reachable after extraction.
  • Prevent Esc from dismissing or rewinding /side (#22710)
    Addresses #22599
    
    ## Why
    `/side` currently lets `Esc` return to the parent thread. Multiple users
    reported that this collides with queued-steer UI that also advertises
    `Esc`, so a timing-sensitive keypress can dismiss an ephemeral side chat
    instead of sending the queued prompt.
    
    After removing that dismissal shortcut, the same `Esc` path could fall
    through to main-thread backtrack/edit-previous handling, which is not
    valid for ephemeral side conversations. This keeps `/side` out of both
    global `Esc` behaviors.
    
    ## What changed
    - Remove `Esc` from the `/side` return shortcut matcher while keeping
    the existing `Ctrl+C` and `Ctrl+D` behavior.
    - Update side-conversation hints and blocked-command copy to advertise
    `Ctrl+C` as the return shortcut.
    - Rename the reserved `Esc` keymap label to describe backtracking only.
    - Block backtrack/edit-previous handling while a side conversation is
    active and report `Editing previous prompts is unavailable in side
    conversations.` when that path would have fired.
    - Keep composer-owned `Esc` behavior, such as Vim insert-mode escape,
    routed locally.
    - Refresh focused shortcut assertions and TUI snapshots for the updated
    footer and new side-conversation error message.
    
    ## Verification
    Manually tested `/side` use cases and `Esc`, `Ctrl+C`, `Ctrl+D`.
  • tui: recover local state db startup failures (#22734)
    ## Why
    
    #22580 made app-server startup fail when the local SQLite state database
    cannot be initialized. Embedded/local TUI startup still continued on the
    permissive path, which left the CLI inconsistent and could hide a real
    startup problem behind unrelated UI. This brings local TUI startup onto
    the same fail-closed behavior while keeping recovery humane for the two
    failure modes we are seeing in practice: damaged database files and
    startup stalls caused by another process holding the database write
    lock.
    
    ## What changed
    
    - Embedded TUI startup now uses `state_db::try_init(...)` and returns a
    typed `LocalStateDbStartupError` that preserves the affected database
    path plus the underlying failure detail.
    - CLI startup handles that failure before entering the interactive TUI:
    - lock-contention failures tell users to quit other Codex processes and
    try again
    - failures consistent with a broken local database offer a safe repair
    that backs up Codex-owned SQLite files, rebuilds local database files,
    and retries startup once
    - declined or unsuccessful repairs print concise guidance plus technical
    details
    - Shared startup error plumbing lives in `tui/src/startup_error.rs`,
    while CLI recovery policy and focused recovery tests live in
    `cli/src/state_db_recovery.rs`.
    
    ## Verification
    
    - `cargo test -p codex-tui
    embedded_state_db_failure_is_typed_for_cli_recovery`
    - `cargo test -p codex-cli state_db_recovery`
    - Manually held an exclusive SQLite lock on `state_5.sqlite` and
    confirmed the CLI shows lock-specific guidance without offering repair.
    - Manually exercised the repair path with a deliberately invalid
    `sqlite_home` and confirmed it backs up the blocking path and resumes
    startup.
  • permissions: resolve profile identity with constraints (#22683)
    ## Why
    
    This PR is the invariant-cleanup layer that follows the workspace-roots
    base merged in [#22610](https://github.com/openai/codex/pull/22610).
    
    #22610 adds `[permissions.<id>.workspace_roots]` and keeps runtime
    workspace roots separate from the raw permission profile, but its
    in-memory representation is intentionally transitional: `Permissions`
    still carries the selected profile identity next to a constrained
    `PermissionProfile`. That makes APIs such as
    `set_constrained_permission_profile_with_active_profile()` fragile
    because the id and value only mean the right thing when every caller
    keeps them in sync.
    
    This PR introduces a single resolved profile state so profile identity,
    `extends`, the profile value, and profile-declared workspace roots
    travel together. The next PR,
    [#22611](https://github.com/openai/codex/pull/22611), builds on this by
    changing the app-server turn API to select permission profiles by id
    plus runtime workspace roots.
    
    ## Stack Context
    
    - #22610, now merged: adds profile-declared `workspace_roots`, runtime
    workspace roots, and `:workspace_roots` materialization.
    - This PR: replaces the parallel active-profile/profile-value fields
    with `PermissionProfileState`.
    - #22611: switches app-server turn updates toward profile ids plus
    runtime workspace roots.
    - #22612: updates TUI/exec summaries to show the effective workspace
    roots.
    
    Keeping this separate from #22611 is deliberate: reviewers can validate
    the internal state invariant before reviewing the app-server protocol
    migration.
    
    ## What Changed
    
    - Added `ResolvedPermissionProfile::{Legacy, BuiltIn, Named}` and
    `PermissionProfileState`.
    - Typed built-in profile ids with `BuiltInPermissionProfileId`.
    - Moved selected profile identity and profile-declared workspace roots
    into the resolved state.
    - Replaced `Permissions` parallel profile fields with one
    `permission_profile_state`.
    - Removed `set_constrained_permission_profile_with_active_profile()`
    from session sync paths.
    - Kept trusted session replay/`SessionConfigured` compatibility through
    explicit session snapshot helpers.
    - Updated session configuration, MCP initialization, app-server, exec,
    TUI, and guardian call sites to consume `&PermissionProfile` directly.
    
    ## Review Guide
    
    Start with `codex-rs/core/src/config/resolved_permission_profile.rs`; it
    is the new invariant boundary. Then review
    `codex-rs/core/src/config/mod.rs` to see how config loading records
    active profile identity and profile workspace roots. The remaining
    call-site changes are mostly mechanical fallout from
    `Permissions::permission_profile()` returning `&PermissionProfile`
    instead of `&Constrained<PermissionProfile>`.
    
    ## Verification
    
    The existing config/session coverage now constructs and asserts through
    `PermissionProfileState`. The workspace-root config test also asserts
    that profile-declared roots are preserved in the resolved state, which
    is the behavior #22611 relies on when runtime roots become mutable
    through the app-server API.
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22683).
    * #22612
    * #22611
    * __->__ #22683
  • permissions: support workspace roots in profiles (#22610)
    ## Why
    
    This is the configuration/model half of the alternative permissions
    migration we discussed as a comparison point for
    [#22401](https://github.com/openai/codex/pull/22401) and
    [#22402](https://github.com/openai/codex/pull/22402).
    
    The old `workspace-write` model mixes three concerns that we want to
    keep separate:
    - reusable profile rules that should stay immutable once selected
    - user/runtime workspace roots from `cwd`, `--add-dir`, and legacy
    workspace-write config
    - internal Codex writable roots such as memories, which should not be
    shown as user workspace roots
    
    This PR gives permission profiles first-class `workspace_roots` so users
    can opt multiple repositories into the same `:workspace_roots` rules
    without using broad absolute-path write grants. It also starts
    separating the raw selected profile from the effective runtime profile
    by making `Permissions` expose explicit accessors instead of public
    mutable fields.
    
    A representative `config.toml` looks like this:
    
    ```toml
    default_permissions = "dev"
    
    [permissions.dev.workspace_roots]
    "~/code/openai" = true
    "~/code/developers-website" = true
    
    [permissions.dev.filesystem.":workspace_roots"]
    "." = "write"
    ".codex" = "read"
    ".git" = "read"
    ".vscode" = "read"
    ```
    
    If Codex starts in `~/code/codex` with that profile selected, the
    effective workspace-root set becomes:
    - `~/code/codex` from the runtime `cwd`
    - `~/code/openai` from the profile
    - `~/code/developers-website` from the profile
    
    The `:workspace_roots` rules are materialized across each root, so
    `.git`, `.codex`, and `.vscode` stay scoped the same way everywhere.
    Runtime additions such as `--add-dir` can still layer on later stack
    entries without mutating the selected profile.
    
    ## Stack Shape
    
    This PR intentionally stops before the profile-identity cleanup in
    [#22683](https://github.com/openai/codex/pull/22683) so the base review
    stays focused on config loading, workspace-root materialization, and
    compatibility with legacy `workspace-write`.
    
    The representation in this PR is therefore transitional: `Permissions`
    carries enough state to distinguish the raw constrained profile from the
    effective runtime profile, and there are still call sites that must keep
    the active profile identity and constrained profile value in sync. The
    follow-up PR replaces that with a single resolved profile state
    (`ResolvedPermissionProfile` / `PermissionProfileState`) that keeps the
    profile id, immutable `PermissionProfile`, and profile-declared
    workspace roots together. That follow-up removes APIs such as
    `set_constrained_permission_profile_with_active_profile()` where
    separate arguments could drift out of sync.
    
    Downstream PRs then build on this base to switch app-server turn updates
    to profile ids plus runtime workspace roots and to finish the
    user-visible summary behavior. Reviewers should judge this PR as the
    workspace-roots foundation, not as the final in-memory shape of selected
    permission profiles.
    
    ## Review Guide
    
    Suggested review order:
    
    1. Start with `codex-rs/core/src/config/mod.rs`.
    This is the main shape change in the base slice. `Permissions` now
    stores a private raw `Constrained<PermissionProfile>` plus runtime
    `workspace_roots`. Callers use `permission_profile()` when they need the
    raw constrained value and `effective_permission_profile()` when they
    need a materialized runtime profile. As noted above,
    [#22683](https://github.com/openai/codex/pull/22683) replaces this
    transitional shape with a resolved profile state that keeps identity and
    profile data together.
    
    2. Review `codex-rs/config/src/permissions_toml.rs` and
    `codex-rs/core/src/config/permissions.rs`.
    These add `[permissions.<id>.workspace_roots]`, resolve enabled entries
    relative to the policy cwd, and keep `:workspace_roots` deny-read glob
    patterns symbolic until the actual roots are known.
    
    3. Review `codex-rs/protocol/src/permissions.rs` and
    `codex-rs/protocol/src/models.rs`.
    These add the policy/profile materialization helpers that expand exact
    `:workspace_roots` entries and scoped deny-read globs over every
    workspace root. This is also where `ActivePermissionProfileModification`
    is removed from the core model.
    
    4. Review the legacy bridge in
    `Config::load_from_base_config_with_overrides` and
    `Config::set_legacy_sandbox_policy`.
    This is where legacy `workspace-write` roots become runtime workspace
    roots, while Codex internal writable roots stay internal and do not
    appear as user-facing workspace roots.
    
    5. Then skim downstream call sites.
    The interesting pattern is raw-vs-effective access: state/proxy/bwrap
    paths keep the raw constrained profile, while execution, summaries, and
    user-visible status use the effective profile and workspace-root list.
    
    ## What Changed
    
    - added `[permissions.<id>.workspace_roots]` to the config model and
    schema
    - added runtime `workspace_roots` state to `Config`/`Permissions` and
    `ConfigOverrides`
    - made `Permissions` profile fields private and replaced direct mutation
    with accessors/setters
    - added `PermissionProfile` and `FileSystemSandboxPolicy` helpers for
    materializing `:workspace_roots` exact paths and deny-read globs across
    all roots
    - moved legacy additional writable roots into runtime workspace-root
    state instead of active profile modifications
    - removed `ActivePermissionProfileModification` and its app-server
    protocol/schema export
    - updated sandbox/status summary paths so internal writable roots are
    not reported as user workspace roots
    
    ## Verification Strategy
    
    The targeted tests cover the behavior at the layers where regressions
    are most likely:
    - `codex-rs/core/src/config/config_tests.rs` verifies config loading,
    legacy workspace-root seeding, effective profile materialization, and
    memory-root handling.
    - `codex-rs/core/src/config/permissions_tests.rs` verifies profile
    `workspace_roots` parsing and `:workspace_roots` scoped/glob
    compilation.
    - `codex-rs/protocol/src/permissions.rs` unit tests verify exact and
    glob materialization over multiple workspace roots.
    - `codex-rs/tui/src/status/tests.rs` and
    `codex-rs/utils/sandbox-summary/src/sandbox_summary.rs` verify the
    user-facing summaries show effective workspace roots and hide internal
    writes.
    
    I also ran `cargo check --tests` locally after the latest stack refresh
    to catch cross-crate API breakage from the private-field/accessor
    changes.
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22610).
    * #22612
    * #22611
    * #22683
    * __->__ #22610
  • Fix /review mode MCP startup render issue (#21624)
    This change fixes the case where the UI can sit on _"Starting MCP
    servers"_ even though the review work is already running or has already
    completed.
    
    - MCP startup status header is visible when a `/review` turn starts with
    enabled MCP server startups
    - Restore the underlying _Working..._ status after MCP startup completes
    or fails
    - Add regression coverage for overlapping startup/turn flows and status
    restoration
    
    _De-scoped from a broader thread-scoped MCP status change that would
    have made it easier to route MCP startup statuses to the appropriate
    thread (parent vs. review). These changes address the UI regression
    without requiring more significant changes across app-server & core._
    
    Fixes #18792.
  • Trim TUI legacy core helper usage (#22695)
    ## Why
    
    The TUI still had a few low-risk dependencies flowing through the
    transitional `legacy_core` namespace after the app-server migration.
    These helpers either already have clearer non-core owners or are
    presentation logic that does not belong in `codex-core`, so moving them
    out reduces the compatibility surface without changing product behavior.
    
    ## What changed
    
    This is a low-risk change, almost completely mechanical in nature.
    
    - Route TUI Codex-home lookup through `codex-utils-home-dir`, use
    `Config::log_dir` directly, and call
    `codex-sandboxing::system_bwrap_warning` without going through
    `legacy_core`.
    - Move shared `codex resume` hint formatting from `codex-core` into
    `codex-utils-cli`.
    - Update CLI and TUI call sites to use the shared CLI utility, and keep
    the resume-command behavior covered by tests in its new home.
    
    ## Verification
    
    - `cargo test -p codex-utils-cli`
    - `cargo test -p codex-utils-cli resume_command`
  • [codex] Support multiple forced ChatGPT workspaces (#18161)
    ## Summary
    
    This change lets `forced_chatgpt_workspace_id` accept multiple workspace
    IDs instead of a single value.
    
    It keeps the existing config key name, adds backward-compatible parsing
    for a single string in `config.toml`, and normalizes the setting into an
    allowed workspace list across login enforcement, app-server config
    surfaces, and local ChatGPT auth helpers.
    
    ## Why
    
    Workspace-restricted deployments may need to allow more than one ChatGPT
    workspace without dropping the guardrail entirely.
    
    ## Server-side impact
    
    Codex's local server and app-server protocol needed changes because they
    previously assumed a single workspace ID. The local login flow now
    matches the auth backend interface by sending the allowed workspace list
    as a single comma-separated `allowed_workspace_id` query parameter.
    
    ## Validation
    
    This was tested with:
    
    - A single workspace config
    - With multi-workspace configs
    - With multiple workspaces in the config
    - The user only being a part of a subset of them
    
    All were successful.
    
    Automated coverage:
    
    - `cargo test -p codex-login`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-tui local_chatgpt_auth`
    - `cargo test --locked -p codex-app-server
    login_account_chatgpt_includes_forced_workspace_allowlist_query_param`
  • fix(tui): render network approval history by target (#22229)
    ## Why
    
    Network approval prompts are rendered without a command string on the
    app-server path. After the user approves one of those prompts, the TUI
    history cell previously fell back to command-oriented copy and produced
    malformed lines such as:
    
    ```text
    You approved codex to run  every time this session
    ```
    
    That hid the network target the user actually approved and left a
    visibly broken transcript entry.
    
    ## What changed
    
    - Preserve the approval subject as either a command or a network target
    when recording TUI approval decisions.
    - Render target-aware history copy for network approval outcomes:
      - approve once
      - approve for the current session
      - cancel
    - Include the approval protocol and preserve the managed-proxy
    `network-access` target when present, including non-default ports such
    as `https://example.com:8443`.
    - Fall back to formatting the network approval context as
    `protocol://host` when no generated target command is available.
    - Keep ordinary command approval history, Guardian approval history, and
    persisted network-rule history behavior unchanged.
    - Add focused regression coverage and snapshots for the three
    network-history cases.
    
    ## How to Test
    
    1. Start Codex in a flow that triggers a network approval prompt.
    2. Approve network access only for the current conversation.
    3. Confirm the transcript records the approved network target, for
    example:
    - `You approved codex network access to https://example.com:8443 every
    time this session`
    4. Trigger the prompt again and verify the one-time approval and cancel
    paths also record target-specific history text instead of an empty
    command gap.
    
    Targeted automated coverage:
    - `cargo test -p codex-tui network_exec_approval_history`
    
    ## Additional verification
    
    - `cargo insta pending-snapshots`
    - `git diff --check`
    - `just fix -p codex-tui`
    - `just argument-comment-lint`
    
    ## Known unrelated local test noise
    
    A full `cargo test -p codex-tui` run still hits a pre-existing stack
    overflow outside this change:
    - `tests::fork_last_filters_latest_session_by_cwd_unless_show_all`
    aborts with a stack overflow
  • tui: split composer attachment and popup state (#22581)
    ## Why
    
    `ChatComposer` currently owns text editing alongside attachment
    bookkeeping and popup lifecycle state, while `BottomPane` still triggers
    a couple of popup resyncs after composer methods that already do that
    work internally. That blurs the ownership boundary and makes the
    composer harder to simplify safely.
    
    This PR is part 1 of a two-part cleanup. It peels off the composer state
    that can move cleanly on its own, so the follow-up can tackle the
    heavier draft/editing boundary without mixing every concern into one
    diff.
    
    ## What changed
    
    - Move local and remote image bookkeeping, placeholder relabeling, and
    remote-image keyboard selection into `AttachmentState`.
    - Move active-popup and popup-dismissal/query bookkeeping into
    `PopupState`.
    - Update composer and history-search paths to use those state owners
    directly.
    - Remove redundant `BottomPane` popup synchronization after paste
    handling and `insert_str`.
    
    ## Part 2
    
    The follow-up PR will finish the cleanup around the remaining composer
    boundary: split out the draft/editing-oriented state and footer/status
    presentation concerns that still live in `ChatComposer`, then revisit
    the leftover `BottomPane` pass-throughs once those ownership lines are
    explicit. The goal is for `ChatComposer` to coordinate a few focused
    collaborators instead of continuing to be the landing zone for every
    input-path concern.
    
    ## Verification
    
    Did manual smoke tests.
  • permissions: canonicalize workspace_roots and danger-full-access names (#22624)
    ## Why
    
    This is a small precursor to the larger permissions-migration work. Both
    the comparison stack in
    [#22401](https://github.com/openai/codex/pull/22401) /
    [#22402](https://github.com/openai/codex/pull/22402) and the alternate
    stack in [#22610](https://github.com/openai/codex/pull/22610) /
    [#22611](https://github.com/openai/codex/pull/22611) /
    [#22612](https://github.com/openai/codex/pull/22612) are easier to
    review if the terminology is already settled underneath them.
    
    Because `:project_roots` and `:danger-no-sandbox` have not shipped as
    stable user-facing surface area, carrying them forward as aliases would
    just add more migration logic to the later stacks. This PR removes that
    ambiguity now so the follow-on work can rely on one spelling for each
    built-in concept.
    
    ## What Changed
    
    - renamed the config-facing special filesystem key from `:project_roots`
    to `:workspace_roots`
    - dropped unpublished `:project_roots` parsing support in
    `core/src/config/permissions.rs`, so new config only recognizes
    `:workspace_roots`
    - renamed the built-in full-access permission profile id from
    `:danger-no-sandbox` to `:danger-full-access`
    - dropped unpublished `:danger-no-sandbox` support entirely, including
    the old active-profile canonicalization path, and added explicit
    rejection coverage for the legacy id
    - introduced shared built-in permission-profile id constants in
    `codex-rs/protocol/src/models.rs`
    - updated `core`, `app-server`, and `tui` call sites that special-case
    built-in profiles to use the shared constants and canonical ids
    - updated tests and the Linux sandbox README to use `:workspace_roots` /
    `:danger-full-access`
    
    ## Verification
    
    I focused verification on the three places this rename can regress:
    config parsing, active-profile identity surfaced back out of `core`, and
    user/server call sites that special-case built-in profiles.
    
    Targeted checks:
    
    -
    `config::tests::default_permissions_can_select_builtin_profile_without_permissions_table`
    -
    `config::tests::default_permissions_read_only_applies_additional_writable_roots_as_modifications`
    -
    `config::tests::default_permissions_can_select_builtin_full_access_profile`
    - `config::tests::legacy_danger_no_sandbox_is_rejected`
    - `workspace_root` filtered `codex-core` tests
    -
    `request_processors::thread_processor::thread_processor_tests::thread_processor_behavior_tests::requested_permissions_trust_project_uses_permission_profile_intent`
    -
    `suite::v2::turn_start::turn_start_rejects_invalid_permission_selection_before_starting_turn`
    - `status::tests::status_snapshot_shows_auto_review_permissions`
    -
    `status::tests::status_permissions_full_disk_managed_with_network_is_danger_full_access`
    -
    `app_server_session::tests::embedded_turn_permissions_use_active_profile_selection`
  • feat: add layered --profile-v2 config files (#17141)
    ## Why
    
    `--profile-v2 <name>` gives launchers and runtime entry points a named
    profile config without making each profile duplicate the base user
    config. The base `$CODEX_HOME/config.toml` still loads first, then
    `$CODEX_HOME/<name>.config.toml` layers above it and becomes the active
    writable user config for that session.
    
    That keeps shared defaults, plugin/MCP setup, and managed/user
    constraints in one place while letting a named profile override only the
    pieces that need to differ.
    
    ## What Changed
    
    - Added the shared `--profile-v2 <name>` runtime option with validated
    plain names, now represented by `ProfileV2Name`.
    - Extended config layer state so the base user config and selected
    profile config are both `User` layers; APIs expose the active user layer
    and merged effective user config.
    - Threaded profile selection through runtime entry points: `codex`,
    `codex exec`, `codex review`, `codex resume`, `codex fork`, and `codex
    debug prompt-input`.
    - Made user-facing config writes go to the selected profile file when
    active, including TUI/settings persistence, app-server config writes,
    and MCP/app tool approval persistence.
    - Made plugin, marketplace, MCP, hooks, and config reload paths read
    from the merged user config so base and profile layers both participate.
    - Updated app-server config layer schemas to mark profile-backed user
    layers.
    
    ## Limits
    
    `--profile-v2` is still rejected for config-management subcommands such
    as feature, MCP, and marketplace edits. Those paths remain tied to the
    base `config.toml` until they have explicit profile-selection semantics.
    
    Some adjacent background writes may still update base or global state
    rather than the selected profile:
    
    - marketplace auto-upgrade metadata
    - automatic MCP dependency installs from skills
    - remote plugin sync or uninstall config edits
    - personality migration marker/default writes
    
    ## Verification
    
    Added targeted coverage for profile name validation, layer
    ordering/merging, selected-profile writes, app-server config writes,
    session hot reload, plugin config merging, hooks/config fixture updates,
    and MCP/app approval persistence.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Defer startup NUX impressions until startup succeeds (#22587)
    ## Why
    
    This is a follow-up to #22573. This problem was surfaced in a code
    review comment that I missed before merging the previous PR.
    
    Fresh-session startup could prepare a model-availability NUX before
    `app_server.start_thread(&config)` completed. If thread startup then
    failed, the TUI never rendered the tooltip, but
    `prepare_startup_tooltip_override(...)` had already persisted one of the
    limited impressions.
    
    ## What Changed
    
    - Move startup tooltip preparation inside the fresh-thread startup
    branch, after `start_thread(...)` succeeds.
    - Keep resume/fork paths unchanged.
    - Remove the now-redundant
    `should_prepare_startup_tooltip_override(...)` helper and its gate test.
  • Simplify TUI startup test coverage (#22573)
    ## Why
    
    The TUI startup test surface had drifted into expensive, brittle
    coverage:
    
    - `tui/tests/suite/no_panic_on_startup.rs` was already ignored as flaky
    while still spawning a PTY to exercise malformed exec-policy rules.
    - `tui/tests/suite/model_availability_nux.rs` used a seeded session,
    cursor-query spoofing, and repeated interrupts to verify a narrow
    resume-path invariant.
    - `app/tests.rs` had started accumulating unrelated startup and summary
    coverage in one flat module even after the surrounding app code was
    split into feature modules.
    
    This keeps those behaviors covered while making the tests cheaper to
    understand and less likely to rot. It also preserves the malformed-rules
    regression from #8803 without requiring a terminal orchestration test.
    
    ## What changed
    
    - Replaced the malformed `rules` startup PTY case with a direct
    exec-policy loader regression:
    
    [`rules_path_file_returns_read_dir_error`](https://github.com/openai/codex/blob/21b6b5622f18b8cac0ea41fd083b3106778d9ffc/codex-rs/core/src/exec_policy_tests.rs#L264-L284)
    - Made the existing fresh-session-only startup tooltip behavior explicit
    with
    
    [`should_prepare_startup_tooltip_override`](https://github.com/openai/codex/blob/21b6b5622f18b8cac0ea41fd083b3106778d9ffc/codex-rs/tui/src/app/thread_routing.rs#L1272-L1279),
    then added focused coverage for the resume/fork gate and the persisted
    NUX counter.
    - Split startup and session-summary coverage out of
    `tui/src/app/tests.rs` into dedicated modules so the test layout better
    mirrors the current app architecture.
    - Converted one single-message goal validation snapshot into semantic
    assertions where layout was not the behavior under test.
    - Removed the two PTY-heavy suite files that the narrower tests now
    supersede.
    
    ## Verification
    
    - `cargo test -p codex-core rules_path_file_returns_read_dir_error`
    - `cargo test -p codex-tui startup_`
    - `cargo test -p codex-tui session_summary_`
    - `cargo test -p codex-tui
    goal_slash_command_rejects_oversized_objective`
  • Refactor chatwidget orchestration into modules (phase 5) (#22537)
    ## Why
    
    `chatwidget.rs` is still carrying too many unrelated responsibilities in
    one file. #22269 started a five-phase cleanup to move coherent behavior
    domains into focused modules while keeping `chatwidget.rs` as the
    composition layer. #22407 completed phase 2 by extracting input and
    submission flow, #22433 completed phase 3 by extracting protocol,
    replay, streaming, and tool lifecycle handling, and #22518 completed
    phase 4 by extracting settings, popups, and status surfaces.
    
    This PR is phase 5. It cleans up the remaining constructor and
    orchestration code now that the larger behavior domains have moved out,
    leaving `chatwidget.rs` much closer to the composition layer the cleanup
    was aiming for. This is once again a mechanical movement of existing
    functions. No functional changes.
    
    ## What Changed
    
    - Added focused modules for widget construction and initial wiring,
    session configuration flow, key/composer interaction routing, review
    popup orchestration, desktop notification coalescing, and render
    composition.
    - Moved the remaining constructor, session setup, interaction,
    notification, review picker, and rendering helpers out of
    `codex-rs/tui/src/chatwidget.rs`.
    - Preserved the existing startup/session behavior, keyboard handling,
    review picker flow, notification priority behavior, and render
    composition while shrinking the central widget module substantially.
    - Left `codex-rs/tui/src/chatwidget.rs` as the registration and
    composition surface for the extracted behavior modules.
    
    ## Cleanup Phases
    
    The five-phase cleanup plan from #22269 is:
    
    1. Phase 1: mechanical helper and state moves. Completed in #22269.
    2. Phase 2: extract input and submission flow, including queued user
    messages, shell prompt submission, pending steer restoration, and thread
    input snapshot/restore behavior. Completed in #22407.
    3. Phase 3: extract protocol, replay, streaming, and tool lifecycle
    handling, while preserving active-cell grouping, transcript
    invalidation, interrupt deferral, and final-message separator behavior.
    Completed in #22433.
    4. Phase 4: extract settings, popups, and status surfaces, including
    model/reasoning/collaboration/personality popups, permission prompts,
    rate-limit UI, and connectors helpers. Completed in #22518.
    5. Phase 5: clean up the remaining constructor and orchestration code
    once the larger behavior domains have moved out, leaving `chatwidget.rs`
    as the composition layer. This PR.
    
    ## Verification
    
    - `cargo check -p codex-tui`
    - `cargo test -p codex-tui chatwidget::tests::popups_and_settings`
    - `cargo test -p codex-tui chatwidget::tests::plan_mode`
    - `cargo test -p codex-tui chatwidget::tests::review_mode`
    - `cargo test -p codex-tui chatwidget::tests::status_and_layout`
    
    `cargo test -p codex-tui` also compiles and begins running, but aborts
    in the unchanged app-side test
    `app::tests::discard_side_thread_keeps_local_state_when_server_close_fails`
    with the same reproducible stack overflow noted in phase 4.
  • Remove resurrected /collab slash command (#22535)
    ## Summary
    `/collab` was intentionally removed in
    [#12012](https://github.com/openai/codex/pull/12012), but the
    TUI/app-server migration accidentally brought that slash-command path
    back. This restores the earlier product decision so the TUI no longer
    advertises or dispatches `/collab`. This command was redundant because
    it did the same thing as `/plan` but in a less-intuitive way.
    
    ## What Changed
    - Remove `SlashCommand::Collab` from the TUI slash-command surface.
    - Delete the picker and app-event plumbing that only existed to service
    `/collab`.
    - Remove obsolete TUI test coverage for the deleted picker flow.
  • feat(cli): add codex doctor diagnostics (#22336)
    ## Why
    
    Users and support need a single command that captures the local Codex
    runtime, configuration, auth, terminal, network, and state shape without
    asking the user to know which diagnostic depth to choose first. `codex
    doctor` now runs the useful checks by default and makes the detailed
    human output the default because the command is usually run when someone
    already needs context.
    
    The command also targets concrete support failure modes we have seen
    while iterating on the design:
    
    - update-target mismatches like #21956, where the installed package
    manager target can differ from the running executable
    - terminal and multiplexer issues that depend on `TERM`, tmux/zellij
    state, color handling, and TTY metadata
    - provider-specific HTTP/WebSocket connectivity, including ChatGPT
    WebSocket handshakes and API-key/provider endpoint reachability
    - local state/log SQLite integrity problems and large rollout
    directories
    - feedback reports that need an attached, redacted diagnostic snapshot
    without asking the user to run a second command
    
    ## What Changed
    
    - Adds `codex doctor` as a grouped CLI diagnostic report with default
    detailed output and `--summary` for the compact view.
    - Adds stable report sections for Environment, Configuration, Updates,
    Connectivity, and Background Server, plus a top Notes block that
    promotes anomalies such as available updates, large rollout directories,
    optional MCP issues, and mixed auth signals.
    - Adds runtime provenance, install consistency, bundled/system search
    readiness, terminal/multiplexer metadata, `config.toml` parse status,
    auth mode details, sandbox details, feature flag summaries, update
    cache/latest-version state, app-server daemon state, SQLite integrity
    checks, rollout statistics, and provider-aware network diagnostics.
    - Adds ChatGPT WebSocket diagnostics that report the negotiated HTTP
    upgrade as `HTTP 101 Switching Protocols` and include timeout, DNS,
    auth, and provider context in detailed output.
    - Makes reachability provider-aware: API-key OpenAI setups check the API
    endpoint, ChatGPT auth checks the ChatGPT path, and custom/AWS/local
    providers check configured HTTP endpoints when available.
    - Adds structured, redacted JSON output where `checks` is keyed by check
    id and `details` is a key/value object for support tooling.
    - Integrates doctor with feedback uploads by attaching a best-effort
    `codex-doctor-report.json` report and adding derived Sentry tags for
    overall status and failing/warning checks.
    - Updates the TUI feedback consent copy so users can see that the doctor
    report is included when logs/diagnostics are uploaded.
    - Updates the CLI bug issue template to ask reporters for `codex doctor
    --json` and render pasted reports as JSON.
    
    ## Example Output
    
    The examples below are sanitized from local smoke runs with `--no-color`
    so the structure is reviewable in plain text.
    
    ### `codex doctor`
    
    ```text
    Codex Doctor v0.0.0 · macos-aarch64
    
    Notes
       ↑ updates      0.130.0 available (current 0.0.0, dismissed 0.128.0)
       ⚠ rollouts     1,526 active files · 2.53 GB on disk
       ⚠ mcp          MCP configuration has optional issues
       ⚠ auth         mixed auth signals: ChatGPT login plus API key env var; HTTP reachability uses API-key mode
    ─────────────────────────────────────────────────────────────
    
    Environment
      ✓ runtime      local debug build
          version                  0.0.0
          install method           other
          commit                   unknown
          executable               ~/code/codex.fcoury-doct…x-rs/target/debug/codex
      ✓ install      consistent
          context                  other
          managed by               npm: no · bun: no · package root —
          PATH entries (2)         ~/.local/share/mise/installs/node/24/bin/codex
                                   ~/.local/share/mise/shims/codex
      ✓ search       ripgrep 15.1.0 (system, `rg`)
      ✓ terminal     Ghostty 1.3.2-main-+b0f827665 · tmux 3.6a · TERM=xterm-256color
          terminal                 Ghostty
          TERM_PROGRAM             ghostty
          terminal version         1.3.2-main-+b0f827665
          TERM                     xterm-256color
          multiplexer              tmux 3.6a
          tmux extended-keys       on
          tmux allow-passthrough   on
          tmux set-clipboard       on
      ✓ state        databases healthy
          CODEX_HOME               ~/.codex (dir)
          state DB                 ~/.codex/state_5.sqlite (file) · integrity ok
          log DB                   ~/.codex/logs_2.sqlite (file) · integrity ok
          active rollouts          1,526 files · 2.53 GB (avg 1.70 MB)
          archived rollouts        8 files · 3.84 MB (avg 491.11 KB)
    
    Configuration
      ✓ config       loaded
          model                    gpt-5.5 · openai
          cwd                      ~/code/codex.fcoury-doctor/codex-rs
          config.toml              ~/.codex/config.toml
          config.toml parse        ok
          MCP servers              1
          feature flags            36 enabled · 7 overridden (full list with --all)
          overrides                code_mode, code_mode_only, memories, chronicle, goals, remote_control, prevent_idle_sleep
      ✓ auth         auth is configured
          auth storage mode        File
          auth file                ~/.codex/auth.json
          auth env vars present    OPENAI_API_KEY
          stored auth mode         chatgpt
          stored API key           false
          stored ChatGPT tokens    true
          stored agent identity    false
      ⚠ mcp          MCP configuration has optional issues — Set the missing MCP env vars or disable the affected server.
          configured servers       1
          disabled servers         0
          streamable_http servers  1
          optional reachability    openaiDeveloperDocs: https://developers.openai.com/mcp (HEAD connect failed; GET connect failed)
      ✓ sandbox      restricted fs + restricted network · approval OnRequest
          approval policy          OnRequest
          filesystem sandbox       restricted
          network sandbox          restricted
    
    Connectivity
      ✓ network      network-related environment looks readable
      ✓ websocket    connected (HTTP 101 Switching Protocols) · 15s timeout
          model provider           openai
          provider name            OpenAI
          wire API                 responses
          supports websockets      true
          connect timeout          15000 ms
          auth mode                chatgpt
          endpoint                 wss://chatgpt.com/backend-api/<redacted>
          DNS                      2 IPv4, 2 IPv6, first IPv6
          handshake result         HTTP 101 Switching Protocols
      ✗ reachability one or more required provider endpoints are unreachable over HTTP — Check proxy, VPN, firewall, DNS, and custom CA configuration.
          reachability mode        API key auth
          openai API               https://api.openai.com/v1 connect failed (required)
    
    Background Server
      ○ app-server   not running (ephemeral mode)
    
    ─────────────────────────────────────────────────────────────
    11 ok · 1 idle · 4 notes · 1 warn · 1 fail failed
    
    --summary compact output           --all expand truncated lists
    --json redacted report
    ```
    
    ### `codex doctor --summary`
    
    ```text
    Codex Doctor v0.0.0 · macos-aarch64
    
    Notes
       ↑ updates      0.130.0 available (current 0.0.0, dismissed 0.128.0)
       ⚠ rollouts     1,526 active files · 2.53 GB on disk
       ⚠ mcp          MCP configuration has optional issues
       ⚠ auth         mixed auth signals: ChatGPT login plus API key env var; HTTP reachability uses API-key mode
    ─────────────────────────────────────────────────────────────
    
    Environment
      ✓ runtime      local debug build
      ✓ install      consistent
      ✓ search       ripgrep 15.1.0 (system, `rg`)
      ✓ terminal     Ghostty 1.3.2-main-+b0f827665 · tmux 3.6a · TERM=xterm-256color
      ✓ state        databases healthy
    
    Configuration
      ✓ config       loaded
      ✓ auth         auth is configured
      ⚠ mcp          MCP configuration has optional issues — Set the missing MCP env vars or disable the affected server.
      ✓ sandbox      restricted fs + restricted network · approval OnRequest
    
    Updates
      ✓ updates      update configuration is locally consistent
    
    Connectivity
      ✓ network      network-related environment looks readable
      ✓ websocket    connected (HTTP 101 Switching Protocols) · 15s timeout
      ✗ reachability one or more required provider endpoints are unreachable over HTTP — Check proxy, VPN, firewall, DNS, and custom CA configuration.
    
    Background Server
      ○ app-server   not running (ephemeral mode)
    
    ─────────────────────────────────────────────────────────────
    11 ok · 1 idle · 4 notes · 1 warn · 1 fail failed
    
    Run codex doctor without --summary for detailed diagnostics.
    --all expand truncated lists       --json redacted report
    ```
    
    ### `codex doctor --json` shape
    
    ```json
    {
      "schema_version": 1,
      "overall_status": "fail",
      "checks": {
        "runtime.provenance": {
          "id": "runtime.provenance",
          "category": "Environment",
          "status": "ok",
          "summary": "local debug build",
          "details": {
            "version": "0.0.0",
            "install method": "other",
            "commit": "unknown"
          }
        },
        "sandbox.helpers": {
          "id": "sandbox.helpers",
          "category": "Configuration",
          "status": "ok",
          "summary": "restricted fs + restricted network · approval OnRequest",
          "details": {
            "approval policy": "OnRequest",
            "filesystem sandbox": "restricted",
            "network sandbox": "restricted"
          }
        }
      }
    }
    ```
    
    ### `/feedback` new sentry attachment
    
    <img width="938" height="798" alt="CleanShot 2026-05-13 at 15 36 14"
    src="https://github.com/user-attachments/assets/715e62e0-d7b4-4fea-a35a-fd5d5d33c4c0"
    />
    
    ### New section in CLI issue template
    
    <img width="1164" height="435" alt="CleanShot 2026-05-13 at 15 47 24"
    src="https://github.com/user-attachments/assets/9081dc25-a28c-4afa-8ba1-e299c2b4031d"
    />
    
    ## How to Test
    
    1. Run `cargo run --bin codex -- doctor --no-color`.
    2. Confirm the detailed report is the default and includes promoted
    Notes, grouped sections, terminal details, state DB integrity, rollout
    stats, provider reachability, WebSocket diagnostics, and app-server
    status.
    3. Run `cargo run --bin codex -- doctor --summary --no-color`.
    4. Confirm the compact view keeps the same sections and summary counts
    but omits detailed key/value rows.
    5. Run `cargo run --bin codex -- doctor --json`.
    6. Confirm the output is redacted JSON, `checks` is an object keyed by
    check id, and each check's `details` is a key/value object.
    7. Preview the CLI bug issue template and confirm the `Codex doctor
    report` field appears after the terminal field, asks for `codex doctor
    --json`, and renders pasted output as JSON.
    8. Start a feedback flow that includes logs.
    9. Confirm the upload consent copy lists `codex-doctor-report.json`
    alongside the log attachments.
    
    Targeted tests:
    
    - `cargo test -p codex-cli doctor`
    - `cargo test -p codex-app-server
    doctor_report_tags_summarize_status_counts`
    - `cargo test -p codex-feedback`
    - `cargo test -p codex-tui feedback_view`
    - `just argument-comment-lint`
    - `git diff --check`
  • [codex] Fix TUI wrapping for external borrowed slices (#21235)
    Fixes #20587, reported by @noeljackson.
    
    This prevents the TUI wrapping code from panicking when `textwrap`
    returns a borrowed slice that does not point into the original source
    text. The fix follows the direction proposed by @misrtjakub in the issue
    comment: validate the borrowed slice pointer range first, and fall back
    to the existing owned-line mapper when the slice is external.
    
    - Guards borrowed wrapped slices before converting pointer offsets into
    byte ranges.
    - Reuses the existing owned-line range recovery path for external
    borrowed slices.
    - Adds coverage for rejecting borrowed slices outside the source text.
    
    End-user testing steps:
    - Start Codex in TUI mode under a PTY wrapper that can inject stdin
    after startup.
    - Inject `\x1b[200~test message\x1b[201~\r` after the TUI is ready.
    - Confirm Codex does not panic and the pasted text is handled normally.
    
    Local validation:
    - `cargo test -p codex-tui wrapping::tests::`
    - `cargo test -p codex-tui -- --skip
    status::tests::status_permissions_full_disk_managed_with_network_is_danger_full_access
    --skip
    status::tests::status_permissions_full_disk_managed_without_network_is_external_sandbox`
  • Use plugin/list to get list of plugins for mentions (#22375)
    This switches TUI plugin mentions to use app-server `plugin/list` for
    plugin inventory and metadata instead of `PluginManager`, while keeping
    the same mention-eligibility filters as before.
    
    Same filters as before:
    - Only plugins in the current config / cwd scope.
    - Only installed and enabled plugins.
    - Only plugins that actually expose a capability, meaning at least one
    skill, MCP server, or app connector.
    - Uses `plugin/list` for the mention names/descriptions
  • Refactor chatwidget settings surfaces into modules (phase 4) (#22518)
    ## Why
    
    `chatwidget.rs` is still carrying too many unrelated responsibilities in
    one file. #22269 started a five-phase cleanup to move coherent behavior
    domains into focused modules while keeping `chatwidget.rs` as the
    composition layer. #22407 completed phase 2 by extracting input and
    submission flow, and #22433 completed phase 3 by extracting protocol,
    replay, streaming, and tool lifecycle handling.
    
    This PR is phase 4. It keeps moving high-churn UI coordination out of
    the central widget by extracting settings, popups, and status surfaces
    without changing the visible behavior those flows already provide. This
    is once again a mechanical movement of existing functions. No functional
    changes.
    
    ## What Changed
    
    - Added focused modules for runtime settings/model coordination,
    model/reasoning/collaboration popups,
    settings/personality/theme/audio/experimental popups, permission
    prompts, status setup/output controls, and Windows sandbox prompt flows.
    - Moved the remaining rate-limit nudge/status helpers and connectors
    popup/loading/update helpers into their existing focused modules.
    - Preserved the existing picker flows, approval behavior, status/title
    setup previews, rate-limit notices, and connectors/app list behavior
    while shrinking `chatwidget.rs` back toward orchestration.
    - Left `codex-rs/tui/src/chatwidget.rs` as the registration and
    composition surface for these extracted behaviors.
    
    ## Cleanup Phases
    
    The five-phase cleanup plan from #22269 is:
    
    1. Phase 1: mechanical helper and state moves. Completed in #22269.
    2. Phase 2: extract input and submission flow, including queued user
    messages, shell prompt submission, pending steer restoration, and thread
    input snapshot/restore behavior. Completed in #22407.
    3. Phase 3: extract protocol, replay, streaming, and tool lifecycle
    handling, while preserving active-cell grouping, transcript
    invalidation, interrupt deferral, and final-message separator behavior.
    Completed in #22433.
    4. Phase 4: extract settings, popups, and status surfaces, including
    model/reasoning/collaboration/personality popups, permission prompts,
    rate-limit UI, and connectors helpers. This PR.
    5. Phase 5: clean up the remaining constructor and orchestration code
    once the larger behavior domains have moved out, leaving `chatwidget.rs`
    as the composition layer.
    
    ## Verification
    
    - `cargo check -p codex-tui`
    - `cargo test -p codex-tui chatwidget::tests::permissions`
    - `cargo test -p codex-tui chatwidget::tests::status_surface_previews`
    - `cargo test -p codex-tui chatwidget::tests::popups_and_settings`
    - `cargo test -p codex-tui chatwidget::tests::status_and_layout`
    
    `cargo test -p codex-tui` also compiles and begins running, but aborts
    in the unchanged app-side test
    `app::tests::discard_side_thread_keeps_local_state_when_server_close_fails`
    with a reproducible stack overflow.
  • config: add strict config parsing (#20559)
    ## Why
    
    Codex intentionally ignores unknown `config.toml` fields by default so
    older and newer config files keep working across versions. That leniency
    also makes typo detection hard because misspelled or misplaced keys
    disappear silently.
    
    This change adds an opt-in strict config mode so users and tooling can
    fail fast on unrecognized config fields without changing the default
    permissive behavior.
    
    This feature is possible because `serde_ignored` exposes the exact
    signal Codex needs: it lets Codex run ordinary Serde deserialization
    while recording fields Serde would otherwise ignore. That avoids
    requiring `#[serde(deny_unknown_fields)]` across every config type and
    keeps strict validation opt-in around the existing config model.
    
    ## What Changed
    
    ### Added strict config validation
    
    - Added `serde_ignored`-based validation for `ConfigToml` in
    `codex-rs/config/src/strict_config.rs`.
    - Combined `serde_ignored` with `serde_path_to_error` so strict mode
    preserves typed config error paths while also collecting fields Serde
    would otherwise ignore.
    - Added strict-mode validation for unknown `[features]` keys, including
    keys that would otherwise be accepted by `FeaturesToml`'s flattened
    boolean map.
    - Kept typed config errors ahead of ignored-field reporting, so
    malformed known fields are reported before unknown-field diagnostics.
    - Added source-range diagnostics for top-level and nested unknown config
    fields, including non-file managed preference source names.
    
    ### Kept parsing single-pass per source
    
    - Reworked file and managed-config loading so strict validation reuses
    the already parsed `TomlValue` for that source.
    - For actual config files and managed config strings, the loader now
    reads once, parses once, and validates that same parsed value instead of
    deserializing multiple times.
    - Validated `-c` / `--config` override layers with the same
    base-directory context used for normal relative-path resolution, so
    unknown override keys are still reported when another override contains
    a relative path.
    
    ### Scoped `--strict-config` to config-heavy entry points
    
    - Added support for `--strict-config` on the main config-loading entry
    points where it is most useful:
      - `codex`
      - `codex resume`
      - `codex fork`
      - `codex exec`
      - `codex review`
      - `codex mcp-server`
      - `codex app-server` when running the server itself
      - the standalone `codex-app-server` binary
      - the standalone `codex-exec` binary
    - Commands outside that set now reject `--strict-config` early with
    targeted errors instead of accepting it everywhere through shared CLI
    plumbing.
    - `codex app-server` subcommands such as `proxy`, `daemon`, and
    `generate-*` are intentionally excluded from the first rollout.
    - When app-server strict mode sees invalid config, app-server exits with
    the config error instead of logging a warning and continuing with
    defaults.
    - Introduced a dedicated `ReviewCommand` wrapper in `codex-rs/cli`
    instead of extending shared `ReviewArgs`, so `--strict-config` stays on
    the outer config-loading command surface and does not become part of the
    reusable review payload used by `codex exec review`.
    
    ### Coverage
    
    - Added tests for top-level and nested unknown config fields, unknown
    `[features]` keys, typed-error precedence, source-location reporting,
    and non-file managed preference source names.
    - Added CLI coverage showing invalid `--enable`, invalid `--disable`,
    and unknown `-c` overrides still error when `--strict-config` is
    present, including compound-looking feature names such as
    `multi_agent_v2.subagent_usage_hint_text`.
    - Added integration coverage showing both `codex app-server
    --strict-config` and standalone `codex-app-server --strict-config` exit
    with an error for unknown config fields instead of starting with
    fallback defaults.
    - Added coverage showing unsupported command surfaces reject
    `--strict-config` with explicit errors.
    
    ## Example Usage
    
    Run Codex with strict config validation enabled:
    
    ```shell
    codex --strict-config
    ```
    
    Strict config mode is also available on the supported config-heavy
    subcommands:
    
    ```shell
    codex --strict-config exec "explain this repository"
    codex review --strict-config --uncommitted
    codex mcp-server --strict-config
    codex app-server --strict-config --listen off
    codex-app-server --strict-config --listen off
    ```
    
    For example, if `~/.codex/config.toml` contains a typo in a key name:
    
    ```toml
    model = "gpt-5"
    approval_polic = "on-request"
    ```
    
    then `codex --strict-config` reports the misspelled key instead of
    silently ignoring it. The path is shortened to `~` here for readability:
    
    ```text
    $ codex --strict-config
    Error loading config.toml:
    ~/.codex/config.toml:2:1: unknown configuration field `approval_polic`
      |
    2 | approval_polic = "on-request"
      | ^^^^^^^^^^^^^^
    ```
    
    Without `--strict-config`, Codex keeps the existing permissive behavior
    and ignores the unknown key.
    
    Strict config mode also validates ad-hoc `-c` / `--config` overrides:
    
    ```text
    $ codex --strict-config -c foo=bar
    Error: unknown configuration field `foo` in -c/--config override
    
    $ codex --strict-config -c features.foo=true
    Error: unknown configuration field `features.foo` in -c/--config override
    ```
    
    Invalid feature toggles are rejected too, including values that look
    like nested config paths:
    
    ```text
    $ codex --strict-config --enable does_not_exist
    Error: Unknown feature flag: does_not_exist
    
    $ codex --strict-config --disable does_not_exist
    Error: Unknown feature flag: does_not_exist
    
    $ codex --strict-config --enable multi_agent_v2.subagent_usage_hint_text
    Error: Unknown feature flag: multi_agent_v2.subagent_usage_hint_text
    ```
    
    Unsupported commands reject the flag explicitly:
    
    ```text
    $ codex --strict-config cloud list
    Error: `--strict-config` is not supported for `codex cloud`
    ```
    
    ## Verification
    
    The `codex-cli` `strict_config` tests cover invalid `--enable`, invalid
    `--disable`, the compound `multi_agent_v2.subagent_usage_hint_text`
    case, unknown `-c` overrides, app-server strict startup failure through
    `codex app-server`, and rejection for unsupported commands such as
    `codex cloud`, `codex mcp`, `codex remote-control`, and `codex
    app-server proxy`.
    
    The config and config-loader tests cover unknown top-level fields,
    unknown nested fields, unknown `[features]` keys, source-location
    reporting, non-file managed config sources, and `-c` validation for keys
    such as `features.foo`.
    
    The app-server test suite covers standalone `codex-app-server
    --strict-config` startup failure for an unknown config field.
    
    ## Documentation
    
    The Codex CLI docs on developers.openai.com/codex should mention
    `--strict-config` as an opt-in validation mode for supported
    config-heavy entry points once this ships.
  • Refactor chatwidget protocol flows into modules (phase 3) (#22433)
    ## Why
    
    `chatwidget.rs` is still carrying too many unrelated responsibilities in
    one file. #22269 started a five-phase cleanup to move coherent behavior
    domains into focused modules while keeping `chatwidget.rs` as the
    composition layer. #22407 completed phase 2 by extracting input and
    submission flow.
    
    This PR is phase 3. It keeps moving high-churn event handling out of the
    central widget by extracting protocol, replay, streaming, and tool
    lifecycle handling without changing the visible behavior those flows
    already provide. This is once again just a mechanical movement of
    existing functions. No functional changes.
    
    ## What Changed
    
    - Added focused modules for protocol request dispatch, replay rendering,
    assistant/plan/reasoning streaming, turn runtime bookkeeping, hook
    lifecycle handling, command lifecycle handling, tool lifecycle
    rendering, and interactive tool request prompts.
    - Kept active-cell grouping, transcript invalidation, interrupt
    deferral, and final-message separator behavior in the same flows, just
    moved into smaller files.
    - Added module header comments to the new files so the ownership
    boundaries are explicit.
    - Left `codex-rs/tui/src/chatwidget.rs` as the registration and
    orchestration surface for these extracted behaviors.
    
    ## Cleanup Phases
    
    The five-phase cleanup plan from #22269 is:
    
    1. Phase 1: mechanical helper and state moves. Completed in #22269.
    2. Phase 2: extract input and submission flow, including queued user
    messages, shell prompt submission, pending steer restoration, and thread
    input snapshot/restore behavior. Completed in #22407.
    3. Phase 3: extract protocol, replay, streaming, and tool lifecycle
    handling, while preserving active-cell grouping, transcript
    invalidation, interrupt deferral, and final-message separator behavior.
    This PR.
    4. Phase 4: extract settings, popups, and status surfaces, including
    model/reasoning/collaboration/personality popups, permission prompts,
    rate-limit UI, and connectors helpers.
    5. Phase 5: clean up the remaining constructor and orchestration code
    once the larger behavior domains have moved out, leaving `chatwidget.rs`
    as the composition layer.
  • feat(tui): standardize picker navigation keys (#22347)
    ## Why
    
    Picker-style UI in the TUI has accumulated a mix of hardcoded navigation
    keys. Some lists supported page movement, some did not; some accepted
    Vim-like keys, while others only accepted arrows; and tabbed or
    horizontally adjustable pickers had no shared keymap action for
    left/right movement.
    
    This PR makes picker/list navigation consistent and configurable so
    users can rely on the same defaults across the TUI.
    
    ## What Changed
    
    - Adds shared list keymap actions for:
      - vertical movement: `move_up`, `move_down`
      - horizontal movement: `move_left`, `move_right`
      - paging and jumps: `page_up`, `page_down`, `jump_top`, `jump_bottom`
    - Adds defaults:
    - Up/down: arrows, `Ctrl+P/N`, `Ctrl+K/J`, and plain `k/j` where text
    input is not active
      - Page up/down: `PageUp/PageDown` and `Ctrl+B/F`
      - First/last: `Home/End`
      - Left/right: `Left/Right` and `Ctrl+H/L`
    - Wires the shared list keymap through picker and list surfaces
    including session resume, multi-select, tabbed selection lists,
    settings-style lists, app-link selection, MCP elicitation,
    request-user-input, and the OSS selection wizard.
    - Keeps search behavior intact by reserving printable characters for
    query text in searchable pickers.
    - Updates keymap setup actions, config schema, snapshots, and focused
    coverage for the new list actions.
    
    ## How to Test
    
    1. Start Codex from this branch and open the session picker, for example
    with an existing session history.
    2. In the session list, verify that `Ctrl+J/K` moves the selection
    down/up.
    3. Verify that `Ctrl+F/B` pages down/up and `Home/End` jumps to the
    first/last visible session.
    4. Type printable search text such as `j` or `k` and confirm it updates
    the query instead of navigating.
    5. Focus a picker control that changes values horizontally, such as a
    session picker toolbar control, and verify `Ctrl+H/L` changes the
    focused value like left/right arrows.
    
    Targeted tests run:
    
    - `cargo test -p codex-tui keymap::tests::`
    - `cargo test -p codex-tui keymap_setup::tests::`
    - `cargo test -p codex-tui horizontal_list_keys`
    - `cargo test -p codex-tui page_and_jump_navigation_use_list_keymap`
    - `cargo test -p codex-tui ctrl_h_l_move_provider_selection`
    - `cargo test -p codex-tui scroll_state::tests`
    - `cargo test -p codex-tui
    switching_tabs_changes_visible_items_and_clears_search`
    - `cargo test -p codex-tui toggle_sort_key_reloads_with_new_sort`
    
    Also ran `just write-config-schema`, `just fmt`, `just fix -p
    codex-tui`, `just argument-comment-lint`, and `git diff --check`.
    
    Note: `cargo test -p codex-tui` was attempted and still aborts in the
    pre-existing
    `tests::fork_last_filters_latest_session_by_cwd_unless_show_all` stack
    overflow, which is unrelated to this branch.
  • feat(tui): remove Zellij TUI workarounds (#22214)
    ## Why
    
    We added Zellij-specific TUI workarounds because older Zellij behavior
    did not work with Codex's normal terminal model:
    
    - #8555 made `tui.alternate_screen = "auto"` disable alternate screen in
    Zellij so transcript history stayed available.
    - #16578 avoided scroll-region operations in Zellij by emitting raw
    newlines and using a separate composer styling path.
    
    This PR removes both workarounds because the latest Zellij release
    tested locally (`zellij 0.44.1`) works correctly with Codex's standard
    TUI behavior: normal alternate-screen handling, redraw, and history
    insertion.
    
    ## What Changed
    
    - Removed the `InsertHistoryMode::Zellij` path and the Zellij-only
    newline scrollback insertion behavior.
    - Removed cached `is_zellij` state from the TUI and composer.
    - Removed Zellij-specific composer styling, the helper snapshot, and the
    `TerminalInfo::is_zellij()` convenience method that only served this
    workaround.
    - Changed `tui.alternate_screen = "auto"` to use alternate screen for
    Zellij too; `--no-alt-screen` and `tui.alternate_screen = "never"` still
    preserve the inline mode escape hatch.
    - Updated the generated config schema description for
    `tui.alternate_screen`.
    
    ## How to Test
    
    Manual smoke path used with `zellij 0.44.1`:
    
    1. Build and run this branch inside a Zellij `0.44.1` session with
    default config.
    2. Start Codex normally and produce enough assistant/tool output to
    create scrollback.
    3. Confirm the transcript remains readable, the composer renders
    normally, and scrolling through terminal history works.
    4. Resize the Zellij pane while output exists and confirm the TUI
    redraws without duplicated, missing, or stale rows.
    5. Compare with `--no-alt-screen` or `-c tui.alternate_screen=never` if
    you want to verify the inline fallback still works.
    
    Targeted tests:
    - `just write-config-schema`
    - `just fmt`
    - `just fix -p codex-tui`
    - `cargo test -p codex-terminal-detection`
    - `cargo test -p codex-tui alternate_screen_auto_uses_alt_screen`
    
    Attempted but did not complete locally:
    - `cargo test -p codex-tui` built and ran the new test successfully,
    then failed later on unrelated local failures in
    `status_permissions_full_disk_managed_*` and a stack overflow in
    `tests::fork_last_filters_latest_session_by_cwd_unless_show_all`.
    
    ## Documentation
    
    No developers.openai.com Codex documentation update is needed for this
    revert.
  • add --dangerously-bypass-hook-trust CLI flag (#21768)
    # Why
    
    Hook trust happens through the TUI in `/hooks` so it can block
    non-interactive use cases. This flag will allow users that are using
    codex headlessly to bypass hooks when they want to.
    
    # What
    
    This adds one invocation-scoped escape hatch.
    
    - the CLI flag sets a runtime-only `bypass_hook_trust` override; there
    is no durable `config.toml` setting
    - hook discovery still respects normal enablement, so explicitly
    disabled hooks remain disabled
    - we show a `--dangerously-bypass-hook-trust is enabled. Enabled hooks
    may run without review for this invocation.` message on startup so
    accidental use is visible in both interactive and exec flows
    
    This keeps “enabled” and “trusted” as separate concepts in the normal
    path, while giving CI/E2E callers a stable way to opt into the
    exceptional path when they already control the hook set.
  • Refactor chatwidget input flow into modules (#22407)
    ## Why
    
    `chatwidget.rs` is still carrying too many unrelated responsibilities in
    one file. #22269 started a five-phase effort to move coherent behavior
    domains into focused modules while keeping `chatwidget.rs` as the
    composition layer.
    
    This PR is phase 2 of that plan. It extracts the input and submission
    flow as a mechanical move before the later protocol, popup/status, and
    constructor/orchestration phases.
    
    ## What Changed
    
    - Added `codex-rs/tui/src/chatwidget/input_flow.rs` for composer input
    results, queued user-message draining, pending-input previews, and
    mode-specific submission entry points.
    - Added `codex-rs/tui/src/chatwidget/input_submission.rs` for
    user-message construction/submission, shell prompt submission,
    structured mention resolution, and blocked image draft restoration.
    - Added `codex-rs/tui/src/chatwidget/input_restore.rs` for
    initial-message submission, pending steer restoration after interrupts,
    and thread input snapshot/restore behavior.
    - Registered the new modules and removed the moved `ChatWidget` impl
    methods from `codex-rs/tui/src/chatwidget.rs`.
    
    ## Follow-On Refactor Phases
    
    The five-phase plan from #22269 is:
    
    - Phase 1: mechanical helper and state moves. Completed in #22269.
    - Phase 2: extract input and submission flow, including queued user
    messages, shell prompt submission, pending steer restoration, and thread
    input snapshot/restore behavior. This PR.
    - Phase 3: extract protocol, replay, streaming, and tool lifecycle
    handling, while preserving active-cell grouping, transcript
    invalidation, interrupt deferral, and final-message separator behavior.
    - Phase 4: extract settings, popups, and status surfaces, including
    model/reasoning/collaboration/personality popups, permission prompts,
    rate-limit UI, and connectors helpers.
    - Phase 5: clean up the remaining constructor and orchestration code
    once the larger behavior domains have moved out, leaving `chatwidget.rs`
    as the composition layer.
  • Add support for UDS in codex --remote (#22414)
    ## Why
    
    Added support for UDS connections in `codex --remote`.
    
    TUI also now connects to local app-server using UDS by default if it is
    running and set to listen to UDS connection.
    
    ## What Changed
    
    - Introduced `RemoteAppServerEndpoint` with `WebSocket` and `UnixSocket`
    variants.
    - Reused the existing JSON-RPC-over-WebSocket protocol over either a TCP
    WebSocket stream or a UDS stream.
    - Updated `codex --remote` to accept `ws://host:port`,
    `wss://host:port`, `unix://`, and `unix://PATH`.
    - Kept `--remote-auth-token-env` restricted to `wss://` and loopback
    `ws://` remotes.
    - Added a fast TUI startup probe for the default daemon socket, falling
    back to the embedded app server when the daemon is absent or
    unresponsive.
    
    ## Verification
    
    - Manually verified that the updated remote flow works.
    - Added coverage for UDS remote round trips, WebSocket auth headers,
    auth-token transport policy, remote address parsing, and missing-daemon
    fallback.
    - Ran focused remote test coverage locally.
  • Remove CODEX_RS_SSE_FIXTURE test hook (#22413)
    ## Why
    
    `CODEX_RS_SSE_FIXTURE` let integration-style CLI, exec, and TUI tests
    bypass the normal Responses transport by reading SSE from local files.
    That kept test-only behavior wired through production client code. The
    affected tests can stay hermetic by using the existing
    `core_test_support::responses` mock server and passing `openai_base_url`
    instead.
    
    ## What Changed
    
    - Removed the `CODEX_RS_SSE_FIXTURE` flag,
    `codex_api::stream_from_fixture`, the `env-flags` dependency, and the
    checked-in SSE fixture files.
    - Repointed the affected core, exec, and TUI tests at `MockServer` with
    the existing SSE event constructors.
    - Removed the Bazel test data plumbing for the deleted fixtures and
    refreshed cargo/Bazel lock state.
    
    ## Verification
    
    - `cargo build -p codex-cli`
    - `cargo test -p codex-api`
    - `cargo test -p codex-core --test all responses_api_stream_cli`
    - `cargo test -p codex-core --test all
    integration_creates_and_checks_session_file`
    - `cargo test -p codex-exec --test all ephemeral`
    - `cargo test -p codex-exec --test all resume`
    - `cargo test -p codex-tui --test all
    resume_startup_does_not_consume_model_availability_nux_count`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    - `just fix -p codex-api -p codex-core -p codex-exec -p codex-tui`
    - `git diff --check`
  • Add allow_managed_hooks_only hook requirement (#20319)
    ## Why
    
    Enterprise-managed hook policy needs a narrow way to require Codex to
    ignore user-controlled lifecycle hooks without adopting the broader
    trust-precedence model from earlier hook work. This keeps the policy
    anchored in `requirements.toml`, so admins can opt into managed hooks
    only while normal `config.toml` files cannot enable the restriction
    themselves.
    
    ## What changed
    
    - Added `allow_managed_hooks_only` to the requirements data flow and
    preserved explicit `false` values.
    - Also adds it to /debug-config
    - Marked MDM, system, and legacy managed config layers as managed for
    hook discovery.
    - Updated hook discovery so `allow_managed_hooks_only = true`:
      - keeps managed requirements hooks and managed config-layer hooks,
    - skips user/project/session `hooks.json` and `[hooks]` entries with
    concise startup warnings,
      - skips current unmanaged plugin hooks,
    - ignores any `allow_managed_hooks_only` key placed in ordinary
    `config.toml` layers.
  • feat: Expose plugin versions and gate plugin sharing (#22397)
    - Adds localVersion to plugin summaries and remoteVersion to share
    context, including generated API schemas.
    - Hydrates local and remote plugin versions from manifests and remote
    release metadata.
    - Adds default-on plugin_sharing gate for shared-with-me listing and
    plugin/share/save, with disabled-path errors
        and focused coverage.
  • Refactor chatwidget state into modules (#22269)
    ## Why
    
    `chatwidget.rs` is still carrying too many unrelated responsibilities in
    one file. After #21866 consolidated some of the state it tracks, this
    starts the next phase by moving coherent state/helper clusters out of
    the main module without changing behavior.
    
    This PR is intentionally mechanical: it only moves existing functions,
    structs, and helpers into focused modules so the boundaries are easier
    to review before the less mechanical refactors that should follow.
    
    ## What Changed
    
    - Moved user-message, composer, queue, pending steer, and merge/remap
    helpers into `codex-rs/tui/src/chatwidget/user_messages.rs`.
    - Added `codex-rs/tui/src/chatwidget/exec_state.rs` for unified exec
    bookkeeping helpers.
    - Added `codex-rs/tui/src/chatwidget/rate_limits.rs` for rate-limit
    warning, prompt, and error classification state.
    - Moved plugin list fetch and install auth-flow state into
    `codex-rs/tui/src/chatwidget/plugins.rs`.
    - Made a couple of test-only `VecDeque` imports explicit now that those
    tests no longer inherit the parent module import.
    
    ## Verification
    
    - `cargo test -p codex-tui` was run
    
    ## Follow-On Refactor Phases
    
    This PR is phase 1: mechanical helper and state moves. Planned follow-up
    PRs:
    
    - Phase 2: extract input and submission flow, including queued user
    messages, shell prompt submission, pending steer restoration, and thread
    input snapshot/restore behavior.
    - Phase 3: extract protocol, replay, streaming, and tool lifecycle
    handling, while preserving active-cell grouping, transcript
    invalidation, interrupt deferral, and final-message separator behavior.
    - Phase 4: extract settings, popups, and status surfaces, including
    model/reasoning/collaboration/personality popups, permission prompts,
    rate-limit UI, and connectors helpers.
    - Phase 5: clean up the remaining constructor and orchestration code
    once the larger behavior domains have moved out, leaving `chatwidget.rs`
    as the composition layer.
  • [codex] Remove workspace owner usage nudge gate (#20509)
    ## Summary
    - make workspace owner nudge handling unconditional in the TUI now that
    it is fully rolled out
    - keep `workspace_owner_usage_nudge` as a removed no-op compatibility
    flag so old configs/app overrides remain accepted during rollout
    - remove flag-disabled test setup
    
    ## Companion PR
    - https://github.com/openai/openai/pull/876351 removes the Codex Apps
    Statsig rollout gate override after this change is available to the
    app/runtime path
    
    ## Validation
    - `just write-config-schema`
    - `just fmt`
    - `cargo test -p codex-features`
    - `cargo test -p codex-tui status_and_layout`
  • test(tui): relax configured pet load timeout (#22392)
    ## Why
    
    Windows CI has been timing out in
    `configured_pet_load_is_deferred_until_after_construction` while waiting
    for the deferred configured-pet load event.
    
    The test still needs to prove construction returns before the pet image
    is available, but the background load slices the built-in pet
    spritesheet into frame cache files. That work can exceed the old 2
    second deadline on slower or more contended CI machines.
    
    ## What Changed
    
    - Increased the test wait for `ConfiguredPetLoaded` from 2 seconds to 30
    seconds.
    - Kept the post-construction assertion intact so the test still verifies
    that the pet is not loaded synchronously during `ChatWidget`
    construction.
    
    ## How to Test
    
    Targeted tests:
    - `cargo test -p codex-tui
    configured_pet_load_is_deferred_until_after_construction`
    - `just argument-comment-lint`
    
    Additional check:
    - `cargo test -p codex-tui` was run, but the broader crate suite did not
    complete successfully due to unrelated existing failures:
    -
    `status::tests::status_permissions_full_disk_managed_without_network_is_external_sandbox`
    -
    `status::tests::status_permissions_full_disk_managed_with_network_is_danger_full_access`
    - later abort in
    `tests::fork_last_filters_latest_session_by_cwd_unless_show_all` from
    stack overflow
  • code-mode: carry nested tool kind through runtime (#22377)
    ## Why
    
    Code mode only used nested spec lookup at execution time to rediscover
    whether a nested tool should be invoked as a function tool or a freeform
    tool.
    
    That information is already present in the enabled tool metadata that
    code mode builds to expose `tools.*` and `ALL_TOOLS`, so re-looking it
    up from the router was redundant and kept execution coupled to a
    separate spec lookup path.
    
    ## What Changed
    
    - thread `CodeModeToolKind` through the code-mode runtime `ToolCall`
    event and `CodeModeNestedToolCall`
    - emit the nested tool kind directly from the V8 callback using the
    already-enabled tool metadata
    - build nested tool payloads from the propagated kind instead of calling
    `find_spec`
    - remove the now-unused `find_spec` plumbing from the router and
    parallel runtime helpers
    - add unit coverage for function vs freeform payload shaping and update
    affected router tests
    
    ## Testing
    
    - `cargo test -p codex-code-mode`
    - `cargo test -p codex-core code_mode::tests`
    - `cargo test -p codex-core
    extension_tool_bundles_are_model_visible_and_dispatchable`
    - `cargo test -p codex-core
    model_visible_specs_filter_deferred_dynamic_tools`
  • feat(tui): add ambient terminal pets (#21206)
    ## Why
    
    The Codex App has animated pets, but the TUI had no equivalent ambient
    companion surface. This brings that experience into terminal Codex while
    keeping the main chat flow usable: the pet should feel present, but it
    cannot cover transcript text, composer input, approvals, or picker
    content.
    
    The feature also needs to be terminal-aware. Different terminals support
    different image protocols, tmux can interfere with image rendering, and
    some users will want pets disabled entirely or anchored differently
    depending on their layout.
    
    <table>
    <tr><td>
    <img width="4110" height="2584" alt="CleanShot 2026-05-05 at 12 41
    45@2x"
    src="https://github.com/user-attachments/assets/68a1fcbc-2104-48d6-b834-69c6aaa95cdf"
    />
    <p align="center">macOS - Ghostty, iTerm2 and WezTerm with Custom
    Pet</p>
    </td></tr>
    <tr><td>
    ![Uploading CleanShot 2026-05-10 at 20.28.30.png…]()
    <p align="center">Windows Terminal</p>
    </td></tr>
    <tr><td>
    <img width="3902" height="2752" alt="CleanShot 2026-05-05 at 12 39
    02@2x"
    src="https://github.com/user-attachments/assets/300e2931-6b00-467e-91cb-ab8e28470500"
    />
    <p align="center">Linux - WezTerm and Ghostty</p>
    </td></tr>
    </table>
    
    ## What Changed
    
    - Add a TUI ambient pet renderer in `codex-rs/tui/src/pets/`.
    - Port the app-style pet animation states so the sprite changes with
    task status, waiting-for-input states, review/ready states, and
    failures.
    - Add `/pets` selection UI with a preview pane, loading state, built-in
    pet choices, and a first-row `Disable terminal pets` option.
    - Download built-in pet spritesheets on demand from the same public CDN
    path already used by Android, under
    `https://persistent.oaistatic.com/codex/pets/v1/...`, and cache them
    locally under `~/.codex/cache/tui-pets/`.
    - Keep custom pets local.
    - Add config support for pet selection, disabling pets, and choosing
    whether the pet follows the composer bottom or anchors to the terminal
    bottom.
    - Reserve layout space around the pet so transcript wrapping, live
    responses, and composer input do not render underneath the sprite.
    - Gate image rendering by terminal capability, disable image pets under
    tmux, and support both Kitty Graphics and SIXEL terminals.
    - Add redraw cleanup for terminal image artifacts, including sixel cell
    clearing.
    
    ## Current Scope
    
    - This is an initial TUI version of ambient pets, not full App parity.
    - It focuses on ambient sprite rendering, `/pets` selection, custom
    pets, terminal capability gating, and on-demand CDN-backed built-in
    assets.
    - The ambient text overlay is currently disabled, so the TUI renders the
    pet sprite without extra status text beside it.
    
    ## How to Test
    
    1. Start Codex TUI in a terminal with image support.
    2. Run `/pets`.
    3. Confirm the picker shows built-in pets plus custom pets, and the
    first item is `Disable terminal pets`.
    4. On a fresh `~/.codex/cache/tui-pets/`, move onto a built-in pet and
    confirm the first preview downloads the spritesheet from the shared
    Codex pets CDN and renders successfully.
    5. Move through the pet list and confirm subsequent built-in previews
    use the local cache.
    6. Select a pet, then send and receive messages. Confirm transcript and
    composer text wrap before the pet instead of rendering underneath the
    sprite.
    7. Change the pet anchor setting and confirm the pet can either follow
    the composer bottom or sit at the terminal bottom.
    8. Return to `/pets`, choose `Disable terminal pets`, and confirm the
    sprite disappears cleanly.
    
    Targeted tests:
    - `cargo test -p codex-tui ambient_pet_`
    - `cargo test -p codex-tui
    resize_reflow_wraps_transcript_early_when_pet_is_enabled`
    - `cargo insta pending-snapshots`
  • feat: Normalize remote plugin summary identities. (#22265)
    Makes plugin summaries use config-style plugin@marketplace IDs while
    exposing backend remote IDs separately as remotePluginId.
    
    Also fix the consistency issue of REMOTE_SHARED_WITH_ME_MARKETPLACE_NAME
  • Add Windows hook command overrides (#22159)
    # Why
    
    Managed hook configs need a shared cross-platform shape without making
    the existing `command` field polymorphic. The common case is still one
    command string, with Windows needing a different entrypoint only when
    the runtime is actually Windows.
    
    Keeping `command` as the portable/default path and adding an optional
    Windows override keeps the config easier to read, preserves the existing
    scalar shape for non-Windows users, and avoids forcing every caller into
    a `{ unix, windows }` object when only one platform needs special
    handling.
    
    # What
    
    - Add optional `command_windows` / `commandWindows` alongside the
    existing hook `command` field.
    - Resolve `command_windows` only on Windows during hook discovery; other
    platforms continue to use `command` unchanged.
    - Keep trust hashing aligned to the effective command selected for the
    current runtime.
    
    # Docs
    
    The Codex hooks/config reference should document `command_windows` as
    the Windows-only override for command hooks.
  • feat(connectors): support managed app tool approval requirements (#21061)
    ## Why
    
    Managed requirements can already centrally disable apps, but they could
    not express the per-tool app approval rules that normal config already
    supports. That left admins without a way to enforce connector tool
    approvals through `/etc/codex/requirements.toml` or cloud requirements.
    
    ## What changed
    
    - Extend app requirements with per-tool `approval_mode` entries.
    - Merge managed app tool requirements across managed sources while
    preserving higher-precedence exact tool settings.
    - Apply managed tool approvals separately from user app config so
    managed policy is matched only on raw MCP `tool.name`, while user config
    keeps the existing raw-name-then-title convenience fallback.
    - Add coverage for local requirements, cloud requirements parsing,
    managed-over-user precedence, and a title-collision case that must not
    widen managed auto-approval.
    
    ## Configuration shape
    
    Local `/etc/codex/requirements.toml` and cloud requirements use the same
    TOML shape:
    
    ```toml
    [apps.connector_123123.tools."calendar/list_events"]
    approval_mode = "approve"
    ```
    
    This is a per-tool approval rule keyed by app ID and raw MCP tool name,
    not an app-level boolean such as `apps.connector_123123.approve = true`.
  • Unified mentions in TUI (#19068)
    This PR replaces the TUI’s file-only `@mention` popup with a unified
    mentions experience. Typing `@...` now searches across filesystem
    matches, installed plugins, and skills in one popup, with result types
    clearly labeled and selectable from the same flow.
    
    - Adds a unified `@mentions` popup that returns:
      - plugins
      - skills
      - files
      - directories
    
    - Adds search modes so users can narrow the popup without changing their
    query:
      - All Results _(default/same as Codex App)_
      - Filesystem Only
      - Plugins _(...and skills)_
    
    - Preserves existing insertion behavior:
      - selected file paths are inserted into the prompt
      - paths with spaces are quoted
      - image file selections still attach as images when possible
      - selecting a plugin or skill inserts the corresponding `$name`
    - the composer records the canonical mention binding, such as
    `plugin://...` or the skill path
    
    - Expanded `@mentions` rendering:
      - type tags for Plugin, Skill, File, and Dir
      - distinct plugin/filesystem colors
      - stable fixed-height layout (8 rows)
      - truncation behavior for narrow terminals
    
    Note:
    - The unified mentions popup does not display app connectors under
    `@mention` results for Codex App parity. Connector mentions remain
    available through the existing `$mention` path.
    
    
    https://github.com/user-attachments/assets/f93781ed-57d3-4cb5-9972-675bc5f3ef3f