Commit Graph

586 Commits

  • Show action required in terminal title (#18372)
    Implements #18162
    
    This updates the TUI terminal title to show an explicit action-required
    state when Codex is blocked on user approval or input. The terminal
    title now uses the activity title item to cover both active work and
    blocked-on-user states, while still accepting the legacy spinner config
    value.
    
    Changes
    - Rename the terminal title item from `spinner` to `activity` while
    preserving legacy config compatibility
    - Show `[ ! ] Action Required `while approval or input overlays are
    active, with a blinking `[ . ]` alternate state
    - Suppress the normal working spinner while Codex is blocked on user
    action
    - Add targeted coverage for action-required title behavior and legacy
    title-item parsing
    
    Testing
    - Trigger an approval or input modal and confirm the tab title
    alternates between `[ ! ] Action Required` and `[ . ] Action Required`
    - Disable the activity title item and confirm the action-required title
    does not appear
    - Resolve the prompt and confirm the title returns to the normal
    spinning/idel state
    
    
    https://github.com/user-attachments/assets/e9ecc530-a6be-4fd7-b9a6-d550a790eb2c
  • Render delegated patch approval details (#19709)
    ## Why
    
    Fixes #19632.
    
    When a delegated agent requests approval for an in-progress file change,
    the parent TUI handles that request from an inactive thread. The app
    server already sent the `FileChange` item with the proposed diff, but
    the inactive-thread approval path was not recovering and rendering it
    the same way as the active-thread path.
    
    The result was an inconsistent approval prompt: main-thread edits show a
    normal patch preview history item before the approval modal, while
    delegated edits did not show that preview in the transcript flow.
    
    ## What Changed
    
    - Recover buffered or historical `FileChange` item changes when building
    inactive-thread file-change approval requests.
    - Reuse the app-server file-change conversion helper for both live
    transcript rendering and inactive-thread approvals.
    - Render recovered delegated patches as a normal patch preview history
    cell before the approval modal.
    - Keep apply-patch approval modals focused on the decision prompt and
    optional metadata; they do not render a synthetic command line or embed
    the diff body.
    
    ## Manual Repro And Verification
    
    I manually reproduced the issue using a file under `~/Desktop` so the
    write would require approval.
    
    Before the fix:
    
    1. Ask the main thread: `Use apply_patch, not shell redirection or
    Python, to create ~/Desktop/bug1.txt with three short lines.`
    2. Observe the expected TUI shape: the transcript shows a normal patch
    preview such as `• Added ~/Desktop/bug1.txt (+N -0)` above the approval
    modal, and the modal contains only the approval prompt/options without a
    synthetic command line.
    3. Ask for the delegated path: `Spawn a worker. Have it use apply_patch,
    not shell redirection or Python, to create ~/Desktop/bug1.txt with four
    short lines.`
    4. Observe the delegated approval is inconsistent: the parent view does
    not render the proposed patch as the normal transcript preview before
    the modal, so the diff context is missing from the stream or appears
    inside the modal instead of in the history flow.
    
    After the fix:
    
    1. Repeat the delegated worker prompt with `apply_patch`.
    2. Confirm the parent view renders the same normal patch preview history
    cell (`• Added ~/Desktop/bug1.txt (+N -0)` plus the diff) immediately
    before the approval modal.
    3. Confirm the approval modal remains focused on the decision prompt.
    For delegated approvals it may show the worker thread label, but it
    should not show a `$ apply_patch` command line or embed the diff body in
    the modal.
  • Persist shell mode commands in prompt history (#19618)
    ## Why
    
    `!` shell commands are currently surfaced as "Bash mode", which is
    misleading for users running shells such as PowerShell or zsh. Those
    commands also bypass the persistent prompt history path, so they cannot
    be recalled after starting a new session.
    
    Fixes #19613.
    
    ## What changed
    
    - Rename the TUI footer label and related test wording from "Bash mode"
    to "Shell mode".
    - Persist accepted `!` shell commands to prompt history with the leading
    `!`, so recall restores the composer into shell mode across sessions.
    - Add coverage for immediate and queued shell-command submissions
    emitting the prompt-history update.
    
    ## Verification
    
    - `cargo test -p codex-tui bang_shell`
    - `cargo test -p codex-tui shell_command_uses_shell_accent_style`
    - `cargo test -p codex-tui footer_mode_snapshots`
    - `cargo insta pending-snapshots --manifest-path tui/Cargo.toml`
    
    Manually verified fix after confirming presence of bug prior to fix.
  • Add /auto-review-denials retry approval flow (#19058)
    ## Why
    
    Auto-review can deny an action that the user later decides they want to
    retry. Today there is no TUI surface for selecting a recent denial and
    sending explicit approval context back into the session, so users have
    to restate intent manually and the retry can be reviewed without the
    original denied action context.
    
    This adds a narrow TUI-driven path for approving a recent denied action
    while still keeping the retry inside the normal auto-review flow.
    
    ## What Changed
    
    - Added `/auto-review-denials` to open a picker of recent denied
    auto-review actions.
    - Added a small in-memory TUI store for the 10 most recent denied
    auto-review events.
    - Selecting a denial sends the structured denied event back through the
    existing core/app-server op path.
    - Core now injects a developer message containing the approved action
    JSON rather than the full assessment event.
    - Auto-review transcript collection now preserves this specific approval
    developer message so follow-up review sessions can see the user approval
    context.
    - Added TUI snapshot/unit coverage for the picker and approval dispatch
    path.
    - Added core coverage for retaining the approval developer message in
    the auto-review transcript.
    
    ## Verification
    
    - `cargo test -p codex-core
    collect_guardian_transcript_entries_keeps_manual_approval_developer_message`
    - `cargo test -p codex-tui auto_review_denials`
    - `cargo test -p codex-tui
    approving_recent_denial_emits_structured_core_op_once`
    
    ## Notes
    
    This intentionally keeps retries going through auto-review. The approval
    signal is context for the exact previously denied action, not a blanket
    bypass for similar future actions.
  • permissions: centralize legacy sandbox projection (#19734)
    ## Why
    
    The remaining migration work still needs `SandboxPolicy` at a few
    compatibility boundaries, but those projections should come from one
    canonical path. Keeping ad hoc legacy projections scattered through
    app-server, CLI, and config code makes it easy for behavior to drift as
    `PermissionProfile` gains fidelity that the legacy enum cannot
    represent.
    
    ## What Changed
    
    - Adds `Permissions::legacy_sandbox_policy(cwd)` and
    `Config::legacy_sandbox_policy()` as the compatibility projection from
    the canonical `PermissionProfile`.
    - Adds `Permissions::can_set_legacy_sandbox_policy()` so legacy inputs
    are checked after they are converted into profile semantics.
    - Updates app-server command handling, Windows sandbox setup, session
    configuration, and sandbox summaries to use the centralized projection
    helper.
    - Leaves `SandboxPolicy` in place only for boundary inputs/outputs that
    still speak the legacy abstraction.
    
    ## Verification
    
    - `cargo check -p codex-config -p codex-core -p codex-sandboxing -p
    codex-app-server -p codex-cli -p codex-tui`
    - `cargo test -p codex-tui
    permissions_selection_history_snapshot_full_access_to_default --
    --nocapture`
    - `cargo test -p codex-tui
    permissions_selection_sends_approvals_reviewer_in_override_turn_context
    -- --nocapture`
    - `bazel test //codex-rs/tui:tui-unit-tests-bin
    --test_arg=permissions_selection_history_snapshot_full_access_to_default
    --test_output=errors`
    - `bazel test //codex-rs/tui:tui-unit-tests-bin
    --test_arg=permissions_selection_sends_approvals_reviewer_in_override_turn_context
    --test_output=errors`
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19734).
    * #19737
    * #19736
    * #19735
    * __->__ #19734
  • permissions: derive compatibility policies from profiles (#19392)
    ## Why
    
    After #19391, `PermissionProfile` and the split filesystem/network
    policies could still be stored in parallel. That creates drift risk: a
    profile can preserve deny globs, external enforcement, or split
    filesystem entries while a cached projection silently loses those
    details. This PR makes the profile the runtime source and derives
    compatibility views from it.
    
    ## What Changed
    
    - Removes stored filesystem/network sandbox projections from
    `Permissions` and `SessionConfiguration`; their accessors now derive
    from the canonical `PermissionProfile`.
    - Derives legacy `SandboxPolicy` snapshots from profiles only where an
    older API still needs that field.
    - Updates MCP connection and elicitation state to track
    `PermissionProfile` instead of `SandboxPolicy` for auto-approval
    decisions.
    - Adds semantic filesystem-policy comparison so cwd changes can preserve
    richer profiles while still recognizing equivalent legacy projections
    independent of entry ordering.
    - Updates config/session tests to assert profile-derived projections
    instead of parallel stored fields.
    
    ## Verification
    
    - `cargo test -p codex-core direct_write_roots`
    - `cargo test -p codex-core runtime_roots_to_legacy_projection`
    - `cargo test -p codex-app-server
    requested_permissions_trust_project_uses_permission_profile_intent`
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19392).
    * #19395
    * #19394
    * #19393
    * __->__ #19392
  • permissions: make runtime config profile-backed (#19606)
    ## Why
    
    This supersedes #19391. During stack repair, GitHub marked #19391 as
    merged into a temporary stack branch rather than into `main`, so the
    runtime-config change needed a fresh PR.
    
    `PermissionProfile` is now the canonical permissions shape after #19231
    because it can distinguish `Managed`, `Disabled`, and `External`
    enforcement while also carrying filesystem rules that legacy
    `SandboxPolicy` cannot represent cleanly. Core config and session state
    still needed to accept profile-backed permissions without forcing every
    profile through the strict legacy bridge, which rejected valid runtime
    profiles such as direct write roots.
    
    The unrelated CI/test hardening that previously rode along with this PR
    has been split into #19683 so this PR stays focused on the permissions
    model migration.
    
    ## What Changed
    
    - Adds `Permissions.permission_profile` and
    `SessionConfiguration.permission_profile` as constrained runtime state,
    while keeping `sandbox_policy` as a legacy compatibility projection.
    - Introduces profile setters that keep `PermissionProfile`, split
    filesystem/network policies, and legacy `SandboxPolicy` projections
    synchronized.
    - Uses a compatibility projection for requirement checks and legacy
    consumers instead of rejecting profiles that cannot round-trip through
    `SandboxPolicy` exactly.
    - Updates config loading, config overrides, session updates, turn
    context plumbing, prompt permission text, sandbox tags, and exec request
    construction to carry profile-backed runtime permissions.
    - Preserves configured deny-read entries and `glob_scan_max_depth` when
    command/session profiles are narrowed.
    - Adds `PermissionProfile::read_only()` and
    `PermissionProfile::workspace_write()` presets that match legacy
    defaults.
    
    ## Verification
    
    - `cargo test -p codex-core direct_write_roots`
    - `cargo test -p codex-core runtime_roots_to_legacy_projection`
    - `cargo test -p codex-app-server
    requested_permissions_trust_project_uses_permission_profile_intent`
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19606).
    * #19395
    * #19394
    * #19393
    * #19392
    * __->__ #19606
  • Add goal TUI UX (5 / 5) (#18077)
    Adds the TUI user experience for goals on top of the core runtime from
    PR 4.
    
    ## Why
    
    Users need a direct TUI control surface for long-running goals. The UI
    should make the current goal visible, support common goal actions
    without waiting for a model turn, and avoid confusing end-of-turn
    notifications while an active goal is immediately continuing.
    
    ## What changed
    
    - Added `/goal` summary rendering for the current goal, including
    active, paused, budget-limited, and complete states.
    - Added `/goal <objective>` creation/replacement through the app-server
    goal API rather than a model prompt.
    - Added `/goal clear`, `/goal pause`, and `/goal unpause` command
    variants.
    - Added a confirmation menu when the user enters a new goal while
    another goal already exists.
    - Updated `/goal` help and summary tip text so it reflects the supported
    command variants without advertising slash-command token budgets.
    - Added footer/statusline goal indicators, including elapsed time and
    token budget display when a budget exists from API/tool-created goals.
    - Consumes goal updated/cleared notifications so the TUI stays in sync
    with external app-server changes.
    - Suppresses end-of-turn desktop notifications only when a goal is still
    active and follow-up work is expected.
    - Preserves slash-command history behavior and avoids leaking queued
    `/goal` state into unrelated submissions.
    
    ## Verification
    
    - Added TUI unit and snapshot coverage for goal command availability,
    summary rendering, control commands, replacement menu behavior,
    status/footer display, notification handling, and command history.
  • permissions: remove legacy read-only access modes (#19449)
    ## Why
    
    `ReadOnlyAccess` was a transitional legacy shape on `SandboxPolicy`:
    `FullAccess` meant the historical read-only/workspace-write modes could
    read the full filesystem, while `Restricted` tried to carry partial
    readable roots. The partial-read model now belongs in
    `FileSystemSandboxPolicy` and `PermissionProfile`, so keeping it on
    `SandboxPolicy` makes every legacy projection reintroduce lossy
    read-root bookkeeping and creates unnecessary noise in the rest of the
    permissions migration.
    
    This PR makes the legacy policy model narrower and explicit:
    `SandboxPolicy::ReadOnly` and `SandboxPolicy::WorkspaceWrite` represent
    the old full-read sandbox modes only. Split readable roots, deny-read
    globs, and platform-default/minimal read behavior stay in the runtime
    permissions model.
    
    ## What changed
    
    - Removes `ReadOnlyAccess` from
    `codex_protocol::protocol::SandboxPolicy`, including the generated
    `access` and `readOnlyAccess` API fields.
    - Updates legacy policy/profile conversions so restricted filesystem
    reads are represented only by `FileSystemSandboxPolicy` /
    `PermissionProfile` entries.
    - Keeps app-server v2 compatible with legacy `fullAccess` read-access
    payloads by accepting and ignoring that no-op shape, while rejecting
    legacy `restricted` read-access payloads instead of silently widening
    them to full-read legacy policies.
    - Carries Windows sandbox platform-default read behavior with an
    explicit override flag instead of depending on
    `ReadOnlyAccess::Restricted`.
    - Refreshes generated app-server schema/types and updates tests/docs for
    the simplified legacy policy shape.
    
    ## Verification
    
    - `cargo check -p codex-app-server-protocol --tests`
    - `cargo check -p codex-windows-sandbox --tests`
    - `cargo test -p codex-app-server-protocol sandbox_policy_`
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19449).
    * #19395
    * #19394
    * #19393
    * #19392
    * #19391
    * __->__ #19449
  • permissions: make legacy profile conversion cwd-free (#19414)
    ## Why
    
    The profile conversion path still required a `cwd` even when it was only
    translating a legacy `SandboxPolicy` into a `PermissionProfile`. That
    made profile producers invent an ambient `cwd`, which is exactly the
    anchoring we are trying to remove from permission-profile data. A legacy
    workspace-write policy can be represented symbolically instead: `:cwd =
    write` plus read-only `:project_roots` metadata subpaths.
    
    This PR creates that cwd-free base so the rest of the stack can stop
    threading cwd through profile construction. Callers that actually need a
    concrete runtime filesystem policy for a specific cwd still have an
    explicitly named cwd-bound conversion.
    
    ## What Changed
    
    - `PermissionProfile::from_legacy_sandbox_policy` now takes only
    `&SandboxPolicy`.
    - `FileSystemSandboxPolicy::from_legacy_sandbox_policy` is now the
    symbolic, cwd-free projection for profiles.
    - The old concrete projection is retained as
    `FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd` for
    runtime/boundary code that must materialize legacy cwd behavior.
    - Workspace-write profiles preserve `CurrentWorkingDirectory` and
    `ProjectRoots` special entries instead of materializing cwd into
    absolute paths.
    
    ## Verification
    
    - `cargo check -p codex-protocol -p codex-core -p
    codex-app-server-protocol -p codex-app-server -p codex-exec -p
    codex-exec-server -p codex-tui -p codex-sandboxing -p
    codex-linux-sandbox -p codex-analytics --tests`
    - `just fix -p codex-protocol -p codex-core -p codex-app-server-protocol
    -p codex-app-server -p codex-exec -p codex-exec-server -p codex-tui -p
    codex-sandboxing -p codex-linux-sandbox -p codex-analytics`
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19414).
    * #19395
    * #19394
    * #19393
    * #19392
    * #19391
    * __->__ #19414
  • Skip disabled rows in selection menu numbering and default focus (#19170)
    Selection menus in the TUI currently let disabled rows interfere with
    numbering and default focus. This makes mixed menus harder to read and
    can land selection on rows that are not actionable. This change updates
    the shared selection-menu behavior in list_selection_view so disabled
    rows are not selected when these views open, and prevents them from
    being numbered like selectable rows.
    
    - Disabled rows no longer receive numeric labels
    - Digit shortcuts map to enabled rows only
    - Default selection moves to the first enabled row in mixed menus
    - Updated affected snapshot
    - Added snapshot coverage for a plugin detail error popup
    - Added a focused unit test for shared selection-view behavior
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Update models.json and related fixtures (#19323)
    Supersedes #18735.
    
    The scheduled rust-release-prepare workflow force-pushed
    `bot/update-models-json` back to the generated models.json-only diff,
    which dropped the test and snapshot updates needed for CI.
    
    This PR keeps the latest generated `models.json` from #18735 and adds
    the corresponding fixture updates:
    - preserve model availability NUX in the app-server model cache fixture
    - update core/TUI expectations for the new `gpt-5.4` `xhigh` default
    reasoning
    - refresh affected TUI chatwidget snapshots for the `gpt-5.5`
    default/model copy changes
    
    Validation run locally while preparing the fix:
    - `just fmt`
    - `cargo test -p codex-app-server model_list`
    - `cargo test -p codex-core includes_no_effort_in_request`
    - `cargo test -p codex-core
    includes_default_reasoning_effort_in_request_when_defined_by_model_info`
    - `cargo test -p codex-tui --lib chatwidget::tests`
    - `cargo insta pending-snapshots`
    
    ---------
    
    Co-authored-by: aibrahim-oai <219906144+aibrahim-oai@users.noreply.github.com>
  • permissions: make profiles represent enforcement (#19231)
    ## Why
    
    `PermissionProfile` is becoming the canonical permissions abstraction,
    but the old shape only carried optional filesystem and network fields.
    It could describe allowed access, but not who is responsible for
    enforcing it. That made `DangerFullAccess` and `ExternalSandbox` lossy
    when profiles were exported, cached, or round-tripped through app-server
    APIs.
    
    The important model change is that active permissions are now a disjoint
    union over the enforcement mode. Conceptually:
    
    ```rust
    pub enum PermissionProfile {
        Managed {
            file_system: FileSystemSandboxPolicy,
            network: NetworkSandboxPolicy,
        },
        Disabled,
        External {
            network: NetworkSandboxPolicy,
        },
    }
    ```
    
    This distinction matters because `Disabled` means Codex should apply no
    outer sandbox at all, while `External` means filesystem isolation is
    owned by an outside caller. Those are not equivalent to a broad managed
    sandbox. For example, macOS cannot nest Seatbelt inside Seatbelt, so an
    inner sandbox may require the outer Codex layer to use no sandbox rather
    than a permissive one.
    
    ## How Existing Modeling Maps
    
    Legacy `SandboxPolicy` remains a boundary projection, but it now maps
    into the higher-fidelity profile model:
    
    - `ReadOnly` and `WorkspaceWrite` map to `PermissionProfile::Managed`
    with restricted filesystem entries plus the corresponding network
    policy.
    - `DangerFullAccess` maps to `PermissionProfile::Disabled`, preserving
    the “no outer sandbox” intent instead of treating it as a lax managed
    sandbox.
    - `ExternalSandbox { network_access }` maps to
    `PermissionProfile::External { network }`, preserving external
    filesystem enforcement while still carrying the active network policy.
    - Split runtime policies that legacy `SandboxPolicy` cannot faithfully
    express, such as managed unrestricted filesystem plus restricted
    network, stay `Managed` instead of being collapsed into
    `ExternalSandbox`.
    - Per-command/session/turn grants remain partial overlays via
    `AdditionalPermissionProfile`; full `PermissionProfile` is reserved for
    complete active runtime permissions.
    
    ## What Changed
    
    - Change active `PermissionProfile` into a tagged union: `managed`,
    `disabled`, and `external`.
    - Keep partial permission grants separate with
    `AdditionalPermissionProfile` for command/session/turn overlays.
    - Represent managed filesystem permissions as either `restricted`
    entries or `unrestricted`; `glob_scan_max_depth` is non-zero when
    present.
    - Preserve old rollout compatibility by accepting the pre-tagged `{
    network, file_system }` profile shape during deserialization.
    - Preserve fidelity for important edge cases: `DangerFullAccess`
    round-trips as `disabled`, `ExternalSandbox` round-trips as `external`,
    and managed unrestricted filesystem + restricted network stays managed
    instead of being mistaken for external enforcement.
    - Preserve configured deny-read entries and bounded glob scan depth when
    full profiles are projected back into runtime policies, including
    unrestricted replacements that now become `:root = write` plus deny
    entries.
    - Regenerate the experimental app-server v2 JSON/TypeScript schema and
    update the `command/exec` README example for the tagged
    `permissionProfile` shape.
    
    ## Compatibility
    
    Legacy `SandboxPolicy` remains available at config/API boundaries as the
    compatibility projection. Existing rollout lines with the old
    `PermissionProfile` shape continue to load. The app-server
    `permissionProfile` field is experimental, so its v2 wire shape is
    intentionally updated to match the higher-fidelity model.
    
    ## Verification
    
    - `just write-app-server-schema`
    - `cargo check --tests`
    - `cargo test -p codex-protocol permission_profile`
    - `cargo test -p codex-protocol
    preserving_deny_entries_keeps_unrestricted_policy_enforceable`
    - `cargo test -p codex-app-server-protocol
    permission_profile_file_system_permissions`
    - `cargo test -p codex-app-server-protocol serialize_client_response`
    - `cargo test -p codex-core
    session_configured_reports_permission_profile_for_external_sandbox`
    - `just fix`
    - `just fix -p codex-protocol`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-core`
    - `just fix -p codex-app-server`
  • Stabilize approvals popup disabled-row test (#19178)
    ## Summary
    
    The Windows Bazel job has been failing in
    `chatwidget::tests::permissions::approvals_popup_navigation_skips_disabled`
    because the test assumed a fixed approvals popup row order and shortcut
    for the disabled permissions option. The approvals popup can include
    platform-specific rows, so those assumptions made the test brittle.
    
    This updates the test to derive the disabled row shortcut from the
    rendered popup and assert navigation continues to skip disabled rows
    before checking that disabled numeric shortcuts do not close or accept
    the popup.
  • tui: carry permission profiles on user turns (#18285)
    ## Why
    
    Per-turn permission overrides should use the same canonical profile
    abstraction as session configuration. That lets TUI submissions preserve
    exact configured permissions without round-tripping through legacy
    sandbox fields.
    
    ## What changed
    
    This adds `permission_profile` to user-turn operations, threads it
    through TUI/app-server submission paths, fills the new field in existing
    test fixtures, and adds coverage that composer submission includes the
    configured profile.
    
    ## Verification
    
    - `cargo test -p codex-tui permissions -- --nocapture`
    - `cargo test -p codex-core --test all permissions_messages --
    --nocapture`
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/18285).
    * #18288
    * #18287
    * #18286
    * __->__ #18285
  • Move marketplace add/remove and startup sync out of core. (#19099)
    Move more things to core-plugins.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • tui: sync session permission profiles (#18284)
    ## Why
    
    Once `SessionConfigured` carries the active `PermissionProfile`, the TUI
    must treat that as authoritative session state. Otherwise the widget can
    keep stale local permission details after a session is configured or
    resumed.
    
    The TUI also keeps a local `Config` copy used for later operations, so
    session-sourced profiles and subsequent local sandbox changes need to
    keep the derived split runtime permissions in sync. Because this PR may
    land before the follow-up user-turn profile plumbing, embedded
    app-server turns also need a standalone path for carrying local runtime
    sandbox overrides.
    
    ## What changed
    
    - Sync the chat widget runtime filesystem/network permissions from
    `SessionConfigured.permission_profile`, with the legacy `sandbox_policy`
    as the fallback.
    - Recompute split runtime permissions whenever the TUI applies or
    carries forward a local sandbox-policy override.
    - Mark feature-driven Auto-review sandbox changes as runtime sandbox
    overrides so the standalone embedded turn-start profile path is used
    even without the follow-up user-turn profile PR.
    - Send a turn-start `permissionProfile` for embedded,
    non-ExternalSandbox turns when the TUI has a runtime sandbox override;
    remote and ExternalSandbox turns keep using the legacy sandbox field.
    - Extend coverage for profile sync, local sandbox changes,
    ExternalSandbox fallback, feature-driven sandbox overrides, and
    turn-start permission override selection.
    
    ## Verification
    
    - `cargo test -p codex-tui
    update_feature_flags_enabling_guardian_selects_auto_review`
    - `cargo test -p codex-tui
    turn_start_permission_overrides_send_profiles_only_for_embedded_runtime_overrides`
    - `cargo test -p codex-tui permission_settings_sync`
    - `cargo test -p codex-tui
    session_configured_external_sandbox_keeps_external_runtime_policy`
    - `cargo test -p codex-tui
    session_configured_syncs_widget_config_permissions_and_cwd`
    - `just fix -p codex-tui`
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/18284).
    * #18288
    * #18287
    * #18286
    * #18285
    * __->__ #18284
  • Update safety check wording (#19149)
    Updates wording of cyber safety check.
  • Add safety check notification and error handling (#19055)
    Adds a new app-server notification that fires when a user account has
    been flagged for potential safety reasons.
  • Default Fast service tier for eligible ChatGPT plans (#19053)
    ## Why
    
    Enterprise and business-like ChatGPT plans should get Codex's Fast
    service tier by default when the user or caller has not made an explicit
    service-tier choice. At the same time, callers need a durable way to
    choose standard routing without adding a new persisted `standard`
    service tier value. This keeps existing config compatibility while
    letting core own the managed default policy.
    
    ## What changed
    
    - Resolve the effective service tier in core at session creation:
    explicit `fast` or `flex` wins, explicit null/clear or
    `[notice].fast_default_opt_out = true` resolves to standard routing, and
    otherwise eligible ChatGPT plans resolve to Fast when FastMode is
    enabled.
    - Add `[notice].fast_default_opt_out` as the persisted opt-out marker
    for managed Fast defaults.
    - Treat app-server/TUI `service_tier: null` as an explicit
    standard/clear choice by preserving that intent through config loading.
    - Update TUI rendering to use core's effective service tier for startup
    and status surfaces while still keeping `config.service_tier` as the
    explicit configured choice.
    - Update `/fast off` to clear `service_tier`, persist the opt-out
    marker, and send explicit standard for subsequent turns.
    
    ## Verification
    
    - Added unit coverage for config override/notice handling, service-tier
    resolution, runtime null clearing, and `/fast off` turn propagation.
    - `cargo build -p codex-cli`
    
    Full test suite was not run locally per author request.
  • protocol: report session permission profiles (#18282)
    ## Why
    
    Clients that observe `SessionConfigured` need the same canonical
    permission view that app-server thread responses provide. Reporting the
    profile in protocol events lets clients keep their local state
    synchronized without reinterpreting legacy sandbox fields.
    
    ## What changed
    
    This adds `permission_profile` to `SessionConfigured` and propagates it
    through core, exec JSON output, MCP server messages, and TUI
    history/widget handling.
    
    ## Verification
    
    - `cargo test -p codex-tui permissions -- --nocapture`
    - `cargo test -p codex-core --test all permissions_messages --
    --nocapture`
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/18282).
    * #18288
    * #18287
    * #18286
    * #18285
    * #18284
    * #18283
    * __->__ #18282
  • tui: fix approvals popup disabled shortcut test (#19072)
    ## Why
    
    This regressed in #19063, which made `GuardianApproval` stable and
    enabled by default. That adds an enabled `Auto-review` row to the
    permissions popup, but `approvals_popup_navigation_skips_disabled` still
    assumed the disabled `Full Access` row lived behind a hard-coded numeric
    shortcut, so the test started selecting a different row and closing the
    popup instead of verifying disabled-row behavior.
    
    ## What
    
    - disable `GuardianApproval` in
    `approvals_popup_navigation_skips_disabled` so the popup layout matches
    the scenario the test is exercising
    - choose the hidden numeric shortcut for the disabled `Full Access` row
    by platform (`2` on non-Windows, `3` on Windows where `Read Only` is
    shown) before asserting that selecting the disabled row leaves the popup
    open
    
    ## Testing
    
    - `cargo test -p codex-tui --lib
    chatwidget::tests::permissions::approvals_popup_navigation_skips_disabled
    -- --exact --nocapture`
    - `cargo test -p codex-tui --lib chatwidget::tests::permissions --
    --nocapture`
    - `cargo test -p codex-tui`
  • chore(auto-review) feature => stable (#19063)
    ## Summary
    Turn on Auto Review
    
    ## Testing
    - [x] Update unit tests
  • Rename approvals reviewer variant to auto-review (#19056)
    ## Why
    
    `approvals_reviewer` now uses `auto_review` as the canonical config/API
    value after #18504, but the Rust enum variant and nearby helper/test
    names still used `GuardianSubagent` / guardian approval wording. That
    made follow-up code and reviews confusing even though the external value
    had already moved to Auto-review.
    
    ## What changed
    
    - Renamed `ApprovalsReviewer::GuardianSubagent` to
    `ApprovalsReviewer::AutoReview`.
    - Updated protocol, app-server, config, core, TUI, exec, and analytics
    test callsites.
    - Renamed nearby helper/test names from guardian approval wording to
    Auto-review wording where they refer to the approvals reviewer mode.
    - Preserved wire compatibility:
      - `auto_review` remains the canonical serialized value.
      - `guardian_subagent` remains accepted as a legacy alias.
    
    This intentionally does not rename the `[features].guardian_approval`
    key, `Feature::GuardianApproval`, `core/src/guardian`, analytics event
    names, or app-server Guardian review event types.
    
    ## Verification
    
    - `cargo test -p codex-protocol
    approvals_reviewer_serializes_auto_review_and_accepts_legacy_guardian_subagent`
    - `cargo test -p codex-app-server-protocol
    approvals_reviewer_serializes_auto_review_and_accepts_legacy_guardian_subagent`
    - `cargo test -p codex-config approvals_reviewer`
    - `cargo test -p codex-tui update_feature_flags`
    - `cargo test -p codex-core permissions_instructions`
    - `cargo test -p codex-tui permissions_selection`
  • app-server: accept permission profile overrides (#18279)
    ## Why
    
    `PermissionProfile` is becoming the canonical permissions shape shared
    by core and app-server. After app-server responses expose the active
    profile, clients need to be able to send that same shape back when
    starting, resuming, forking, or overriding a turn instead of translating
    through the legacy `sandbox`/`sandboxPolicy` shorthands.
    
    This still needs to preserve the existing requirements/platform
    enforcement model. A profile-shaped request can be downgraded or
    rejected by constraints, but the server should keep the user's
    elevated-access intent for project trust decisions. Turn-level profile
    overrides also need to retain existing read protections, including
    deny-read entries and bounded glob-scan metadata, so a permission
    override cannot accidentally drop configured protections such as
    `**/*.env = deny`.
    
    ## What changed
    
    - Adds optional `permissionProfile` request fields to `thread/start`,
    `thread/resume`, `thread/fork`, and `turn/start`.
    - Rejects ambiguous requests that specify both `permissionProfile` and
    the legacy `sandbox`/`sandboxPolicy` fields, including running-thread
    resume requests.
    - Converts profile-shaped overrides into core runtime filesystem/network
    permissions while continuing to derive the constrained legacy sandbox
    projection used by existing execution paths.
    - Preserves project-trust intent for profile overrides that are
    equivalent to workspace-write or full-access sandbox requests.
    - Preserves existing deny-read entries and `globScanMaxDepth` when
    applying turn-level `permissionProfile` overrides.
    - Updates app-server docs plus generated JSON/TypeScript schema fixtures
    and regression coverage.
    
    ## Verification
    
    - `cargo test -p codex-app-server-protocol schema_fixtures`
    - `cargo test -p codex-core
    session_configuration_apply_permission_profile_preserves_existing_deny_read_entries`
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/18279).
    * #18288
    * #18287
    * #18286
    * #18285
    * #18284
    * #18283
    * #18282
    * #18281
    * #18280
    * __->__ #18279
  • feat(auto-review) short-circuit (#18890)
    ## Summary
    Short circuit the convo if auto-review hits too many denials
    
    ## Testing
    - [x] Added unit tests
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: Fairly trim skill descriptions within context budget (#18925)
    Preserve skill name/path entries whenever possible and trim descriptions
    first, using round-robin character allocation so short descriptions do
    not waste budget.
  • [codex-analytics] guardian review TTFT plumbing and emission (#17696)
    ## Why
    
    Guardian analytics includes time-to-first-token, but the Guardian
    reviewer runs as a normal Codex session and `TurnCompleteEvent` did not
    expose TTFT. The timing needs to flow through the standard
    turn-completion protocol so Guardian review analytics can consume the
    same value as the rest of the session machinery.
    
    ## What changed
    
    Adds optional `time_to_first_token_ms` to `TurnCompleteEvent` and
    populates it from `TurnTiming`. The value is carried through app-server
    thread history, rollout reconstruction, TUI/app-server adapters, and
    Guardian review session handling.
    
    Guardian review analytics now captures TTFT from the reviewer
    turn-complete event when available. Existing tests and fixtures are
    updated to set the new optional field to `None` where TTFT is not
    relevant.
    
    ## Verification
    
    - `cargo clippy -p codex-tui --tests -- -D warnings`
    - `cargo clippy -p codex-core --lib --tests -- -D warnings`
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/17696).
    * __->__ #17696
    * #17695
    * #17693
    * #18278
    * #18953
  • feat: Support remote plugin list/read. (#18452)
    Add a temporary internal remote_plugin feature flag that merges remote
    marketplaces into plugin/list and routes plugin/read through the remote
    APIs when needed, while keeping pure local marketplaces working as
    before.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Update /statusline and /title snapshots (#18909)
    Update `/statusline` and `/title` snapshots
  • Normalize /statusline & /title items (#18886)
    This change aligns the `/statusline` and `/title` UIs around the same
    normalized item model so both surfaces use consistent ids, labels, and
    preview semantics. It keeps the shared preview work from #18435 ,
    tightens the remaining mismatches by standardizing item naming, expands
    title/status item coverage where appropriate, and makes `/title` preview
    use the same title-specific formatting path as the real rendered
    terminal title.
    
    - Normalizes persisted item ids and keeps legacy aliases for
    compatibility
    - Aligns `status-line` and `terminal-title` items with the shared
    preview model
    - Routes `terminal-title` preview through title-specific formatting and
    truncation
    - Updates the affected status/title setup snapshots
    
    Added to `/statusline`:
    - status
    - task-progress
      
    Normalized in `/statusline`:
    - model-name -> model
    - project-root -> project-name
    
    Added to `/title`:
    - current-dir
    - context-remaining
    - context-used
    - five-hour-limit
    - weekly-limit
    - codex-version
    - used-tokens
    - total-input-tokens
    - total-output-tokens
    - session-id
    - fast-mode
    - model-with-reasoning
    
    Normalized in `/title`:
    - project -> project-name
    - thread -> thread-title
    - model-name -> model
  • feat(tui): shortcuts to change reasoning level temporarily (#18866)
    ## Summary
    
    Adds main-chat shortcuts for changing reasoning effort one step at a
    time:
    
    - `Alt+,` lowers reasoning (has the `<` arrow on the key)
    - `Alt+.` raises reasoning (similarly, has the `>` arrow)
    
    The shortcut updates the active session only. It does not persist the
    selected reasoning level as the default for future sessions. In Plan
    mode, it applies temporarily to Plan mode without opening the
    global-vs-Plan scope prompt.
    
    ## Details
    
    The shortcut uses the active model preset to decide which reasoning
    levels are valid. If the current session has no explicit reasoning
    effort, it starts from the model default. Each keypress moves to the
    next supported level in the requested direction.
    
    The shortcut only runs from the main chat surface. If a popup or modal
    is open, input remains owned by that UI.
    
    In Plan mode, the shortcut updates the in-memory Plan reasoning override
    directly. The model/reasoning picker still keeps the existing scope
    prompt for explicit picker changes.
    
    ## Notes
    
    Ctrl-plus and Ctrl-minus were considered, but terminals do not deliver
    those combinations consistently, so this PR uses Alt shortcuts instead.
    
    If the current effort is unsupported by the selected model, the shortcut
    skips to the nearest supported level in the requested direction. If
    there is no valid step, it shows the existing boundary message.
    
    ## Tests
    
    - `cargo test -p codex-tui reasoning_shortcuts`
    - `cargo test -p codex-tui reasoning_effort`
    - `cargo test -p codex-tui reasoning_shortcut`
    - `cargo test -p codex-tui footer_snapshots`
    - `cargo test -p codex-tui`
    - `just fix -p codex-tui`
    - `./tools/argument-comment-lint/run.py -p codex-tui -- --tests`
    
    ---------
    
    Co-authored-by: Eric Traut <etraut@openai.com>
  • sandboxing: intersect permission profiles semantically (#18275)
    ## Why
    
    Permission approval responses must not be able to grant more access than
    the tool requested. Moving this flow to `PermissionProfile` means the
    comparison must be profile-shaped instead of `SandboxPolicy`-shaped, and
    cwd-relative special paths such as `:cwd` and `:project_roots` must stay
    anchored to the turn that produced the request.
    
    ## What changed
    
    This implements semantic `PermissionProfile` intersection in
    `codex-sandboxing` for file-system and network permissions. The
    intersection accepts narrower path grants, rejects broader grants,
    preserves deny-read carve-outs and glob scan depth, and materializes
    cwd-dependent special-path grants to absolute paths before they can be
    recorded for reuse.
    
    The request-permissions response paths now use that intersection
    consistently. App-server captures the request turn cwd before waiting
    for the client response, includes that cwd in the v2 approval params,
    and core stores the requested profile plus cwd for direct TUI/client
    responses and Guardian decisions before recording turn- or
    session-scoped grants. The TUI app-server bridge now preserves the
    app-server request cwd when converting permission approval params into
    core events.
    
    ## Verification
    
    - `cargo test -p codex-sandboxing intersect_permission_profiles --
    --nocapture`
    - `cargo test -p codex-app-server request_permissions_response --
    --nocapture`
    - `cargo test -p codex-core
    request_permissions_response_materializes_session_cwd_grants_before_recording
    -- --nocapture`
    - `cargo check -p codex-tui --tests`
    - `cargo check --tests`
    - `cargo test -p codex-tui
    app_server_request_permissions_preserves_file_system_permissions`
  • Queue follow-up input during user shell commands (#18820)
    Fixes #17954.
    
    ## Why
    When a manual shell command like `!sleep 10` is running, submitting
    plain text such as `hi` currently sends that text as a steer for the
    active shell turn. User shell turns are not steerable like model turns,
    so the TUI can remain stuck in `Working` after the shell command
    finishes.
    
    ## What Changed
    - Detect when the only active work is one or more
    `ExecCommandSource::UserShell` commands.
    - Queue plain submitted input in that state so it drains after the shell
    command and shell turn complete.
    - Preserve `!cmd` submissions during running work so explicit shell
    commands keep their existing behavior.
    - Add regression coverage for the `!sleep 10` plus `hi` flow in
    `chatwidget::tests::exec_flow::user_message_during_user_shell_command_is_queued_not_steered`.
    
    ## Verification
    - Manually confirmed hang before the fix and no hang after the fix
  • feat(auto-review) Handle request_permissions calls (#18393)
    ## Summary
    When auto-review is enabled, it should handle request_permissions tool.
    We'll need to clean up the UX but I'm planning to do that in a separate
    pass
    
    ## Testing
    - [x] Ran locally
    <img width="893" height="396" alt="Screenshot 2026-04-17 at 1 16 13 PM"
    src="https://github.com/user-attachments/assets/4c045c5f-1138-4c6c-ac6e-2cb6be4514d8"
    />
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Fallback display names for TUI skill mentions (#18786)
    This updates TUI skill mentions to show a fallback label when a skill
    does not define a display name, so unnamed skills remain understandable
    in the picker without changing behavior for skills that already have
    one.
    
    <img width="1028" height="198" alt="Screenshot 2026-04-20 at 6 25 15 PM"
    src="https://github.com/user-attachments/assets/84077b85-99d0-4db9-b533-37e1887b4506"
    />
  • protocol: preserve glob scan depth in permission profiles (#18713)
    ## Why
    
    #18274 made `PermissionProfile` the canonical file-system permissions
    shape, but the round-trip from `FileSystemSandboxPolicy` to
    `PermissionProfile` still dropped one piece of policy metadata:
    `glob_scan_max_depth`.
    
    That field is security-relevant for deny-read globs such as `**/*.env`.
    On Linux, bubblewrap sandbox construction uses it to bound unreadable
    glob expansion. If a profile copied from active runtime permissions
    loses this value and is submitted back as an override, the resulting
    `FileSystemSandboxPolicy` can behave differently even though the visible
    permission entries look equivalent.
    
    ## What changed
    
    - Add `glob_scan_max_depth` to protocol `FileSystemPermissions` and
    preserve it when converting to/from `FileSystemSandboxPolicy`.
    - Keep legacy `read`/`write` JSON for simple path-only permissions, but
    force canonical JSON when glob scan depth is present so the metadata is
    not silently dropped.
    - Carry `globScanMaxDepth` through app-server
    `AdditionalFileSystemPermissions`, generated JSON/TypeScript schemas,
    and app-server/TUI conversion call sites.
    - Preserve the metadata through sandboxing permission normalization,
    merging, and intersection.
    - Carry the merged scan depth into the effective
    `FileSystemSandboxPolicy` used for command execution, so bounded
    deny-read globs reach Linux bubblewrap materialization.
    
    ## Verification
    
    - `cargo test -p codex-sandboxing glob_scan -- --nocapture`
    - `cargo test -p codex-sandboxing policy_transforms -- --nocapture`
    - `just fix -p codex-sandboxing`
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/18713).
    * #18288
    * #18287
    * #18286
    * #18285
    * #18284
    * #18283
    * #18282
    * #18281
    * #18280
    * #18279
    * #18278
    * #18277
    * #18276
    * #18275
    * __->__ #18713
  • /statusline & /title - Shared preview values (#18435)
    This PR makes the `/statusline` and `/title` setup UIs share one
    preview-value source instead of each surface using its own examples.
    Both pickers now render consistent live values when available, and
    stable placeholders when they are not. It also resolves live preview
    values at the shared preview-item layer, so `/title` preview can use
    real runtime values for title-specific cases like status text, task
    progress, and project-name fallback behavior.
    
    - Adds a shared preview data model for status surfaces
    - Maps status-line items and terminal-title items onto that shared
    preview list
    - Feeds both setup views from the same chatwidget-derived preview data,
    with terminal-title-specific formatting applied before `/title` preview
    renders
    - Keeps project-root preview aligned with status-line behavior while
    project in /title keeps its title fallback/truncation behavior
    - Adds snapshot coverage for live-only, hardcoded-only, and mixed cases
    
    Test Steps
    - Open Codex TUI and launch `/statusline`.
    - Toggle and reorder items, then verify the preview uses current session
    values when possible, and placeholder values for missing values (ex: no
    thread ID).
    - Open `/title` and verify it shows the same normalized values,
    including live status/task-progress values when available.
  • Add realtime silence tool (#18635)
    ## Summary
    
    Adds a second realtime v2 function tool, `remain_silent`, so the
    realtime model has an explicit non-speaking action when the
    collaboration mode or latest context says it should not answer aloud.
    This is stacked on #18597.
    
    ## Design
    
    - Advertise `remain_silent` alongside `background_agent` in realtime v2
    conversational sessions.
    - Parse `remain_silent` function calls into a typed
    `RealtimeEvent::NoopRequested` event.
    - Have core answer that function call with an empty
    `function_call_output` and deliberately avoid `response.create`, so no
    follow-up realtime response is requested.
    - Keep the event hidden from app-server/TUI surfaces; it is operational
    plumbing, not user-visible conversation content.
  • Use app server metadata for fork parent titles (#18632)
    ## Problem
    The TUI resolved fork parent titles from local CODEX_HOME metadata,
    which could show missing or stale titles when app-server metadata is
    authoritative.
    
    This is a lingering bug left over from the migration of the TUI to the
    app-server interface. I found it when I asked Codex to review all places
    where the TUI code was still directly accessing the local CODEX_HOME.
    
    ## Solution
    Route fork parent title metadata through the app-server session state
    and render only that supplied title, with focused snapshot coverage for
    stale local metadata.
    
    ## Testing
    I manually tested by renaming a thread then forking it and confirming
    that the "forked from" message indicated the parent thread's name.
  • fix(tui): keep /copy aligned with rollback (#18739)
    ## Why
    
    Fixes #18718.
    
    After rewinding a thread, `/copy` could still copy the latest assistant
    response from before the rewind. The transcript cells were rolled back,
    but the copy source was a single `last_agent_markdown` cache that was
    not synchronized with backtracking, so the visible conversation and
    copied content could diverge.
    
    ## What changed
    
    `ChatWidget` now keeps a bounded copy history for the most recent 32
    assistant responses, keyed by the visible user-turn count. When local
    rollback trims transcript cells, the copy cache is trimmed to the same
    surviving user-turn count so `/copy` uses the latest visible assistant
    response.
    
    If the user rewinds past the retained copy window, `/copy` now reports:
    
    ```text
    Cannot copy that response after rewinding. Only the most recent 32 responses are available to /copy.
    ```
    
    The change also adds coverage for copying the latest surviving response
    after rollback and for the over-limit rewind message.
    
    ## Verification
    
    - Manually resumed a synthetic 35-turn session, rewound within the
    retained window, and verified `/copy` copied the surviving response.
    - Manually rewound past the retained window and verified `/copy` showed
    the 32-response limit message.
    - `cargo test -p codex-tui slash_copy`
    - `just fix -p codex-tui`
    - `cargo insta pending-snapshots`
    
    Note: `cargo test -p codex-tui` currently fails on unrelated model
    catalog and snapshot drift around the default model changing to
    `gpt-5.4`; the focused `/copy` tests pass after fixing the new test
    setup.
  • Fix stale model test fixtures (#18719)
    Fixes stale test fixtures left after the active bundled model catalog
    updates in #18586 and #18388. Those changes made `gpt-5.4` the current
    default and removed several older hardcoded slugs, which left Windows
    Bazel shards failing TUI and config tests.
    
    What changed:
    - Refresh TUI model migration, availability NUX, plan-mode, status, and
    snapshot fixtures to use active bundled model slugs.
    - Update the config edit test expectation for the TOML-quoted
    `"gpt-5.2"` migration key.
    - Move the model catalog tests into
    `codex-rs/tui/src/app/tests/model_catalog.rs` so touching them does not
    trip the blob-size policy for `app.rs`.
    
    Verification:
    - CI Bazel/lint checks are expected to cover the affected test shards.
  • Remove simple TUI legacy_core reexports (#18631)
    ## Problem
    The TUI still imported path utilities and config-loader symbols through
    app-server-client's legacy_core facade even though those APIs already
    exist in utility/config crates. This is part of our ongoing effort to
    whittle away at these old dependencies.
    
    ## Solution
    Rewire imports to avoid the TUI directly importing from the core crate
    and instead import from common lower-level crates. This PR doesn't
    include any functional changes; it's just a simple rewiring.
  • protocol: canonicalize file system permissions (#18274)
    ## Why
    
    `PermissionProfile` needs stable, canonical file-system semantics before
    it can become the primary runtime permissions abstraction. Without a
    canonical form, callers have to keep re-deriving legacy sandbox maps and
    profile comparisons remain lossy or order-dependent.
    
    ## What changed
    
    This adds canonicalization helpers for `FileSystemPermissions` and
    `PermissionProfile`, expands special paths into explicit sandbox
    entries, and updates permission request/conversion paths to consume
    those canonical entries. It also tightens the legacy bridge so root-wide
    write profiles with narrower carveouts are not silently projected as
    full-disk legacy access.
    
    ## Verification
    
    - `cargo test -p codex-protocol
    root_write_with_read_only_child_is_not_full_disk_write -- --nocapture`
    - `cargo test -p codex-sandboxing permission -- --nocapture`
    - `cargo test -p codex-tui permissions -- --nocapture`
  • Surface parent thread status in side conversations (#18591)
    ## Summary
    
    Side conversations can hide important state changes from the parent
    conversation while the user is focused on the side thread. In
    particular, the parent may finish, fail, need user input, or require an
    approval while the side conversation remains visible. Users need a
    lightweight signal for those states, but parent approval overlays should
    not interrupt the side conversation itself.
    
    This change adds parent-conversation status to the side conversation
    context label and defers parent interactive overlays while side mode is
    active. When the user exits side mode, pending parent approvals and
    input requests are restored in the main thread. The pending approval
    footer avoids duplicating the same parent approval status, and replayed
    notice cells are filtered when restoring a pending interactive request
    so tips or warnings do not crowd out the approval prompt.
    
    The change is contained to the TUI side-conversation and thread replay
    paths.
    
    Example 1: Approval pending
    <img width="752" height="35" alt="Screenshot 2026-04-19 at 12 56 07 PM"
    src="https://github.com/user-attachments/assets/1cc0f1a3-9cab-4d60-aed2-96523ccafc20"
    />
    
    Example 2: Turn complete
    <img width="754" height="35" alt="Screenshot 2026-04-19 at 12 56 27 PM"
    src="https://github.com/user-attachments/assets/653521a5-e298-4366-ae1c-72b56eb88eeb"
    />
  • Use app server thread names in TUI picker (#18633)
    ## Problem
    
    The TUI resume/fork picker was backfilling thread names from local
    rollout indexes. This was left over from before the TUI was moved to the
    app server. It should be using app-server APIs because the TUI might be
    connected to a remote connection.
    
    This bug wasn't (yet) reported by a user. I found it by asking Codex to
    review places in the TUI code where it was still directly accessing the
    CODEX_HOME directory rather than going through app-server APIs.
    
    ## Solution
    
    The resume picker and session lookups should use app-server thread APIs
    only. Remove legacy rollout name/list backfills, and avoid local name
    reads in fork history.
    
    ## Testing
    
    I manually tested `codex resume` and `codex resume --all` to look for
    functional or performance regressions in the resume picker.
  • Add verbose diagnostics for /mcp (#18610)
    Fixes #18539.
    
    ## Summary
    The recent `/mcp` performance work kept the default command fast by
    avoiding resource and resource-template inventory probes, but it also
    removed useful diagnostics for users trying to confirm MCP server state.
    
    This keeps bare `/mcp` on the fast tools/auth path and adds `/mcp
    verbose` for the slower diagnostic view. Verbose mode requests full MCP
    server status from the app-server and restores status, resources, and
    resource templates in the TUI output.
    
    ## Testing
    In addition to running automation, I manually tested the feature to
    confirm that it works.