Commit Graph

1728 Commits

  • Enforce animations = false for screen readers (#20564)
    ## Why
    
    Issue #20489 calls out that animated TUI affordances can be noisy for
    screen-reader users. Codex already has `tui.animations = false` as a
    reduced-motion setting, but some live activity rows render spinner-style
    prefixes in that mode. These were relatively recent regressions.
    
    We have also regressed this pattern more than once by adding new
    spinner/shimmer callsites that do not think through the reduced-motion
    path, so this PR adds a small guardrail while fixing the current
    surfaces.
    
    ## What changed
    
    - Omit the live status-row spinner when animations are disabled, so the
    row starts with stable text like `Working (...)`.
    - Render running hook headers without the spinner prefix when animations
    are disabled, while preserving shimmer/spinner behavior when animations
    are enabled.
    - Centralize TUI activity indicators in `tui/src/motion.rs`, with
    explicit reduced-motion choices for hidden prefixes, static bullets, and
    plain shimmer-text fallbacks.
    - Route existing spinner/shimmer callsites through the central motion
    helper, including exec rows, MCP/web-search/loading rows, hook rows,
    plugin loading, and onboarding loading text.
    - Add a source-scan regression test that rejects direct `spinner(...)`
    or `shimmer_spans(...)` usage outside the central module and primitive
    definition.
    - Add focused coverage that reduced-motion active exec rows are stable,
    status rows start without a spinner, running hooks omit the spinner, and
    MCP inventory loading stays stable.
    - Update the one affected status-indicator snapshot; the existing detail
    tree prefix remains unchanged.
    
    ## Verification
    
    - `cargo test -p codex-tui`
  • feat: export and replay effective config locks (#20405)
    ## Why
    
    For reproducibility. A hand-written `config.toml` is not enough to
    recreate what a Codex session actually ran with because layered config,
    CLI overrides, defaults, feature aliases, resolved feature config,
    prompt setup, and model-catalog/session values can all affect the final
    runtime behavior.
    
    This PR adds an effective config lockfile path: one run can export the
    resolved session config, and a later run can replay that lockfile and
    fail early if the regenerated effective config drifts.
    
    ## What Changed
    
    - Add a dedicated `ConfigLockfileToml` wrapper with top-level lockfile
    metadata plus the replayable config:
    
      ```toml
      version = 1
      codex_version = "..."
    
      [config]
      # effective ConfigToml fields
      ```
    
    - Keep lockfile metadata out of regular `ConfigToml`; replay loads
    `ConfigLockfileToml` and then uses its nested `config` as the
    authoritative config layer.
    - Add `debug.config_lockfile.export_dir` to write
    `<thread_id>.config.lock.toml` when a root session starts.
    - Add `debug.config_lockfile.load_path` to replay a saved lockfile and
    validate the regenerated session lockfile against it.
    - Add `debug.config_lockfile.allow_codex_version_mismatch` to optionally
    tolerate Codex binary version drift while still comparing the rest of
    the lockfile.
    - Add `debug.config_lockfile.save_fields_resolved_from_model_catalog` so
    lock creation can either save model-catalog/session-resolved fields or
    intentionally leave those fields dynamic.
    - Build lockfiles from the effective config plus resolved runtime values
    such as model selection, reasoning settings, prompts, service tier, web
    search mode, feature states/config, memories config, skill instructions,
    and agent limits.
    - Materialize feature aliases and custom feature config into the
    lockfile so replay compares canonical resolved behavior instead of
    user-authored alias shape.
    - Strip profile/debug/file-include/environment-specific inputs from
    generated lockfiles so they contain replayable values rather than the
    inputs that produced those values.
    - Surface JSON-RPC server error code/data in app-server client and TUI
    bootstrap errors so config-lock replay failures include the actual TOML
    diff.
    - Regenerate the config schema for the new debug config keys.
    
    ## Review Notes
    
    The main flow is split across these files:
    
    - `config/src/config_toml.rs`: lockfile/debug TOML shapes.
    - `core/src/config/mod.rs`: loading `debug.config_lockfile.*`, replaying
    a lockfile as a config layer, and preserving the expected lockfile for
    validation.
    - `core/src/session/config_lock.rs`: exporting the current session
    lockfile and materializing resolved session/config values.
    - `core/src/config_lock.rs`: lockfile parsing, metadata/version checks,
    replay comparison, and diff formatting.
    
    ## Usage
    
    Export a lockfile from a normal session:
    
    ```sh
    codex -c 'debug.config_lockfile.export_dir="/tmp/codex-locks"'
    ```
    
    Export a lockfile without saving model-catalog/session-resolved fields:
    
    ```sh
    codex -c 'debug.config_lockfile.export_dir="/tmp/codex-locks"' \
      -c 'debug.config_lockfile.save_fields_resolved_from_model_catalog=false'
    ```
    
    Replay a saved lockfile in a later session:
    
    ```sh
    codex -c 'debug.config_lockfile.load_path="/tmp/codex-locks/<thread_id>.config.lock.toml"'
    ```
    
    If replay resolves to a different effective config, startup fails with a
    TOML diff.
    
    To tolerate Codex binary version drift during replay:
    
    ```sh
    codex -c 'debug.config_lockfile.load_path="/tmp/codex-locks/<thread_id>.config.lock.toml"' \
      -c 'debug.config_lockfile.allow_codex_version_mismatch=true'
    ```
    
    ## Limitations
    
    This does not support custom rules/network policies.
    
    ## Verification
    
    - `cargo test -p codex-core config_lock`
    - `cargo test -p codex-config`
    - `cargo test -p codex-thread-manager-sample`
  • Color TUI statusline from active theme (#19631)
    ## Why
    
    Users have shared that the TUI can feel too visually flat because themes
    mostly show up in code syntax highlighting. The configurable statusline
    is a natural place to make the active theme more visible, while still
    letting users keep the existing monotone statusline if they prefer it.
    
    ## What Changed
    
    - Added a statusline styling helper that builds the rendered statusline
    from `(StatusLineItem, text)` segments, preserving item identity while
    keeping the plain text output unchanged.
    - Derived foreground accent colors from the active syntax theme by
    looking up TextMate scopes through the existing syntax highlighter, with
    conservative ANSI fallbacks when a scope does not provide a foreground.
    - Tuned theme-derived colors to keep the accents visible without making
    the statusline feel overly bright.
    - Added `[tui].status_line_use_colors`, defaulting to `true`, plus a
    separated `/statusline` toggle so users can enable or disable
    theme-derived statusline colors from the setup UI.
    - Updated the live statusline and `/statusline` preview to use the same
    styled builder, while keeping terminal-title preview text plain.
    - Kept statusline separators and active-agent add-ons subdued while
    removing blanket dimming from the whole passive statusline.
    
    ## Verification
    
    - `cargo test -p codex-tui status_line`
    - `cargo test -p codex-tui theme_picker`
    - `cargo test -p codex-tui foreground_style_for_scopes`
    - `cargo test -p codex-tui`
    - `cargo test -p codex-config`
    - `cargo test -p codex-core status_line_use_colors`
    - `cargo insta pending-snapshots --manifest-path tui/Cargo.toml`
    
    ## Visual
    
    <img width="369" height="23" alt="Screenshot 2026-04-30 at 6 16 08 PM"
    src="https://github.com/user-attachments/assets/11d03efb-8e4f-4450-8f4d-00a9659ef4cd"
    />
    
    <img width="385" height="23" alt="Screenshot 2026-04-30 at 6 16 02 PM"
    src="https://github.com/user-attachments/assets/a3d89f36-bdc1-42e8-8e84-61350e3999e2"
    />
  • Format multi-day goal durations in the TUI (#20558)
    ## Why
    
    Goal mode shows elapsed time in compact hour/minute form. That is easy
    to scan for shorter runs, but once a goal runs past 24 hours, large hour
    counts become harder to read at a glance.
    
    ## What changed
    
    Updated `codex-rs/tui/src/goal_display.rs` so unbudgeted goal elapsed
    time keeps the existing compact format below one day, then switches to a
    day-aware format once the elapsed time reaches 24 hours:
    
    - `23h 59m`
    - `1d 0h 0m`
    - `2d 23h 42m`
    
    The formatter now covers the 24-hour boundary in unit tests, and the TUI
    status-line snapshot for a completed elapsed goal now exercises the
    multi-day display.
    
    ## Verification
    
    - `cargo test -p codex-tui`
    
    Here's my longest-running test task:
    
    <img width="186" height="23" alt="image"
    src="https://github.com/user-attachments/assets/cedfcdab-7f6e-44e6-8495-8a39f63973fb"
    />
  • Make thread store process-scoped (#19474)
    - Build one app-server process ThreadStore from startup config and share
    it with ThreadManager and CodexMessageProcessor.
    - Remove per-thread/fork store reconstruction so effective thread config
    cannot switch the persistence backend.
    - Add params to ThreadStore create/resume for specifying thread
    metadata, since otherwise the metadata from store creation would be used
    (incorrectly).
  • [codex] Remove unused event messages (#20511)
    ## Why
    
    Several legacy `EventMsg` variants were still emitted or mapped even
    though clients either ignored them or had moved to item/lifecycle
    events. `Op::Undo` had also degraded to an unavailable shim, so this
    removes that dead task path instead of preserving a command that cannot
    do useful work.
    
    `McpStartupComplete`, `WebSearchBegin`, and `ImageGenerationBegin` are
    intentionally kept because useful consumers still depend on them: MCP
    startup completion drives readiness behavior, and the begin events let
    app-server/core consumers surface in-progress web-search and
    image-generation items before the final payload arrives.
    
    ## What Changed
    
    - Removed weak legacy event variants and payloads from `codex-protocol`,
    including legacy agent deltas, background events, and undo lifecycle
    events.
    - Kept/restored `EventMsg::McpStartupComplete`,
    `EventMsg::WebSearchBegin`, and `EventMsg::ImageGenerationBegin` with
    serializer and emission coverage.
    - Updated core, rollout, MCP server, app-server thread history,
    review/delegate filtering, and tests to rely on the useful replacement
    events that remain.
    - Removed `Op::Undo`, `UndoTask`, the undo test module, and stale TUI
    slash-command comments.
    - Stopped agent job/background progress and compaction retry notices
    from emitting `BackgroundEvent` payloads.
    
    ## Verification
    
    - `cargo check -p codex-protocol -p codex-app-server-protocol -p
    codex-core -p codex-rollout -p codex-rollout-trace -p codex-mcp-server`
    - `cargo test -p codex-protocol -p codex-app-server-protocol -p
    codex-rollout -p codex-rollout-trace -p codex-mcp-server`
    - `cargo test -p codex-core --test all suite::items`
    - `just fix -p codex-protocol -p codex-app-server-protocol -p codex-core
    -p codex-rollout -p codex-rollout-trace -p codex-mcp-server`
    - Earlier coverage on this PR also included `codex-mcp`, `codex-tui`,
    core library tests, MCP/plugin/delegate/review/agent job tests, and MCP
    startup TUI tests.
  • Surface admin-disabled remote plugin status (#20298)
    ## Summary
    
    Remote plugin-service returns plugin availability separately from a
    user's installed/enabled state. This adds `PluginAvailabilityStatus` to
    the app-server protocol, propagates remote catalog `status` into
    `PluginSummary`, and rejects install attempts for remote plugins marked
    `DISABLED_BY_ADMIN` before downloading or caching the bundle.
    
    This is the `openai/codex` half of the change. The companion
    `openai/openai` webview PR is
    https://github.com/openai/openai/pull/873269.
    
    ## Validation
    
    - `cargo run -p codex-app-server-protocol --bin write_schema_fixtures`
    - `cargo test -p codex-app-server --test all
    plugin_list_marks_remote_plugin_disabled_by_admin`
    - `cargo test -p codex-app-server --test all
    plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled`
    - `cargo test -p codex-app-server --test all
    plugin_install_rejects_remote_plugin_disabled_by_admin_before_download`
    - `cargo test -p codex-app-server-protocol schema_fixtures`
  • feat(tui): add vim composer mode (#18595)
    ## Why
    
    Codex now has configurable TUI keymaps, but the composer still behaves
    like a plain text field. Users who prefer modal editing need a way to
    keep Vim muscle memory while drafting prompts, and the keymap picker
    needs to expose Vim-specific actions if those bindings are configurable
    instead of hardcoded.
    
    ## What Changed
    
    - Adds composer Vim mode with insert/normal state, common normal-mode
    movement and editing commands, `d`/`y` operator-pending flows, and
    mode-aware footer and cursor indicators.
    - Adds `/vim`, an optional global `toggle_vim_mode` binding, and
    `tui.vim_mode_default` so Vim mode can be toggled per session or enabled
    as the default composer state.
    - Extends runtime and config keymaps with `vim_normal` and
    `vim_operator` contexts, exposes those contexts in `/keymap`, refreshes
    the config schema, and validates Vim bindings separately.
    - Integrates Vim normal mode with existing composer behavior: `/` opens
    slash command entry, `!` enters shell mode, `j`/`k` navigate history at
    history boundaries, successful submissions reset back to normal mode,
    and paste burst handling remains insert-mode only.
    - Teaches the TUI render path to apply and restore cursor style so Vim
    insert mode can use a bar cursor without leaving the terminal in that
    state after exit.
    
    ## Validation
    
    - `cargo test -p codex-tui keymap -- --nocapture` on the keymap/Vim
    coverage
    - `cargo insta pending-snapshots`
    
    ## Docs
    
    This introduces user-facing `/vim`, `tui.vim_mode_default`, and Vim
    keymap contexts under `tui.keymap`, so the public CLI configuration and
    slash-command docs should be updated before the feature ships.
  • fix(tui): set persist_extended_history: false (#20502)
    Large rollouts are no good. This updates the TUI to behave the same as
    the Codex App, which is also turning it off.
  • fix: show correct Bedrock runtime endpoint in /status (#20275)
    ## Why
    
    `/status` was showing the configured `ModelProviderInfo.base_url` for
    Amazon Bedrock, which can be stale or misleading because the actual
    Bedrock Mantle endpoint is derived at runtime from the resolved AWS
    region. This made sessions report the wrong provider endpoint even
    though requests used the correct runtime URL.
    
    ## What changed
    
    - Added `ModelProvider::runtime_base_url()` so provider implementations
    can expose the request-time base URL through the shared runtime provider
    abstraction.
    - Moved Bedrock region-to-Mantle URL resolution into
    `amazon_bedrock::mantle::runtime_base_url()`, keeping region resolution
    private to the Mantle module.
    - Overrode `runtime_base_url()` for Amazon Bedrock so it returns the
    resolved Mantle endpoint instead of the configured default.
    - Resolved and cached the runtime provider base URL during TUI startup,
    then used that cached value when rendering `/status`.
    - Added status coverage that verifies Bedrock displays the runtime URL
    and ignores the configured Bedrock `base_url` when they differ.
    
    ## Verification
    model provider is resolved correctly in local build:
    <img width="696" height="245" alt="Screenshot 2026-04-29 at 5 01 36 PM"
    src="https://github.com/user-attachments/assets/a13c10a5-3720-41ab-8ace-3c4bc573f971"
    />
  • Add /hooks browser for lifecycle hooks (#19882)
    ## Why
    
    `hooks/list` and `hooks/config/write` give us read/write access to hooks
    and their state. This hooks up the TUI as a client so users can inspect
    and manage that state directly.
    
    ## What
    
    - add a two-page `/hooks` browser in the TUI: an event overview with
    installed/active counts, followed by a per-event handler page with
    toggle controls and detail rendering
    - thread managed-state metadata through hook discovery and `hooks/list`
    so the UI can label admin-managed hooks and suppress toggles for them
    - persist hook toggles through the existing config-write path and add
    snapshot coverage for the event list, handler list, managed-hook, and
    empty states
    
    ## Stack
    
    1. openai/codex#19705
    2. openai/codex#19778
    3. openai/codex#19840
    4. This PR - openai/codex#19882
    
    ## Reviewer Notes
    
    - Main UI logic is in
    `codex-rs/tui/src/bottom_pane/hooks_browser_view.rs`; most of the diff
    is the new view plus its snapshot coverage
    - Request / write plumbing for opening the browser and persisting
    toggles is in `codex-rs/tui/src/app/background_requests.rs` and
    `codex-rs/tui/src/chatwidget/hooks.rs`
    - Outside the TUI, the only behavioral change in this PR is threading
    `is_managed` through hook discovery and `hooks/list` so managed hooks
    render as non-toggleable
    - The `codex-rs/tui/src/status/snapshots/` churn is unrelated merge
    fallout from the stacked base branch's newer permission-label rendering
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Remove core protocol dependency [2/2] (#20325)
    ## Why
    
    With the local model layer and app-server routing in place from PR1,
    this PR moves the active TUI runtime onto app-server notifications. The
    affected pieces share the same event flow, so the command surface,
    session state, bottom-pane prompts, chat rendering, history/status
    views, and tests move together to keep the stacked branch buildable.
    
    This PR also removes the obsolete compatibility surface that is no
    longer used after the migration. The proposed protocol-boundary verifier
    layer was dropped from the stack; enforcing that final boundary will be
    simpler once `codex-tui` no longer needs any `codex_protocol`
    references.
    
    This PR is part 2 of a 2-PR stack:
    
    1. Add TUI-owned replacement models and extract app-server event
    routing.
    2. Move the active TUI flow to app-server notifications and delete
    obsolete adapter code.
    
    ## What changed
    
    - Rewired app command and session handling to use app-server request and
    notification shapes.
    - Moved approval overlays, request-user-input flows, MCP elicitation,
    realtime events, and review commands onto the app-server-facing model
    surface.
    - Updated chat rendering, history cells, status views, multi-agent UI,
    replay state, and TUI tests to use app-server notifications plus the
    local models introduced in PR1.
    - Deleted `codex-rs/tui/src/app/app_server_adapter.rs` and the
    superseded `chatwidget/tests/background_events.rs` fixture path.
    
    ## Verification
    
    - `cargo check -p codex-tui --tests`
    - Top of stack: `cargo test -p codex-tui`
  • Remove core protocol dependency [1/2] (#20324)
    ## Why
    
    This stack moves `codex-tui` away from the core protocol event surface
    and toward app-server API shapes plus TUI-owned local models. This first
    PR sets up the lower-risk foundation: it introduces the local model
    surface and extracts app-server event routing into focused TUI modules
    while preserving the existing behavior for the larger migration in PR2.
    
    This PR is part 1 of a 2-PR stack:
    
    1. Add TUI-owned replacement models and extract app-server event
    routing.
    2. Move the active TUI flow to app-server notifications and delete
    obsolete adapter code.
    
    ## What changed
    
    - Added TUI-owned approval, diff, session state, session resume, token
    usage, and user-message models.
    - Added `app/app_server_event_targets.rs` and `app/app_server_events.rs`
    to hold app-server event targeting and dispatch logic outside `app.rs`.
    - Updated app/status tests to use the local model layer and added
    focused routing coverage.
    - Boxed a few large async TUI test futures so this base layer remains
    checkable without overflowing the default test stack.
    
    ## Verification
    
    - `cargo check -p codex-tui --tests`
  • /plugins: remove marketplace (#19843)
    This PR adds marketplace removal to the /plugins menu, giving users a
    way to remove user-configured plugin marketplaces. It adds a `Ctrl+R`
    shortcut to remove selected marketplace tabs, a confirmation prompt,
    loading and error states, and the app-server request flow needed to
    perform marketplace/remove. After a successful removal, the TUI
    refreshes config, plugin mentions, user config, and plugin data so the
    removed marketplace disappears from the menu and other surfaces in the
    TUI.
    
    - Add `Ctrl+R` removal option for user-configured marketplace tabs
    - Show marketplace removal confirmation, loading, and error states
    - Route `marketplace/remove` through the TUI background request flow
    - Refresh config, plugin mentions, and plugin data after successful
    removal
    - Adds reusable per-tab footer hints so removal guidance only appears on
    applicable tabs
    - Add test coverage for `Ctrl+R` behavior while plugin search is active
    
    Steps to test:
    - Add a marketplace using the TUI /plugins menu
    - Use Ctrl+R to remove the marketplace
    - Accept the confirmation prompt
    - Confirm the marketplace is removed when the process completes.
  • realtime: rename provider session ids (#20361)
    ## Summary
    
    Codex is repurposing `session` to mean a thread group, so the realtime
    provider session id should no longer use `session_id` / `sessionId` in
    Codex-facing protocol payloads. This PR renames that provider-specific
    field to `realtime_session_id` / `realtimeSessionId` and intentionally
    breaks clients that still send the old field names.
    
    ## What Changed
    
    - Renamed realtime provider session fields in `ConversationStartParams`,
    `RealtimeConversationStartedEvent`, and `RealtimeEvent::SessionUpdated`.
    - Renamed app-server v2 realtime request and notification fields to
    `realtimeSessionId`.
    - Removed legacy serde aliases for `session_id` / `sessionId`; clients
    must send the new names.
    - Propagated the rename through core realtime startup, app-server
    adapters, codex-api websocket handling, and TUI realtime state.
    - Regenerated app-server protocol schema/TypeScript outputs and updated
    app-server README examples.
    - Kept upstream Realtime API concepts unchanged: provider `session.id`
    parsing and `x-session-id` headers still use the upstream wire names.
    
    ## Testing
    
    - CI is running on the latest pushed commit.
    - Earlier local verification on this PR:
      - `cargo test -p codex-protocol`
    - `CODEX_SKIP_VENDORED_BWRAP=1 cargo test -p codex-core
    realtime_conversation`
      - `cargo test -p codex-app-server-protocol`
    - `CODEX_SKIP_VENDORED_BWRAP=1 cargo test -p codex-app-server
    realtime_conversation`
    - attempted `CODEX_SKIP_VENDORED_BWRAP=1 cargo test -p codex-tui` (local
    linker bus error while linking the test binary)
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • tui: return from side chat on Ctrl-D (#20282)
    ## Why
    
    Fixes #20264.
    
    Side conversations are an ephemeral layer on top of the main chat.
    Pressing `Ctrl+D` from an empty side-chat composer should unwind back to
    the parent thread, matching the existing side-return behavior, instead
    of falling through to the global quit shortcut and exiting Codex.
    
    ## What changed
    
    The side-return shortcut matcher now treats `Ctrl+D` the same way it
    already treats `Esc` and `Ctrl+C`. Because app-level side-return
    handling runs before the chat widget's global quit handling, this
    returns from `/side` while preserving normal `Ctrl+D` quit behavior
    outside side conversations.
    
    The existing shortcut coverage was updated to include lowercase and
    uppercase `Ctrl+D` key events.
    
    ## Verification
    
    - `cargo test -p codex-tui
    side_return_shortcuts_match_esc_ctrl_c_and_ctrl_d`
    - `cargo test -p codex-tui` starts successfully and the new shortcut
    test passes, but the broader suite later aborts in the unrelated
    existing test
    `app::tests::attach_live_thread_for_selection_rejects_unmaterialized_fallback_threads`
    with a stack overflow.
  • Reduce the surface of collaboration modes (#20149)
    Collaboration modes were slightly invasive both into ThreadManager
    construction and ModelProvider
  • Update Codex login success page UX (#20136)
    ## Summary
    
    update the local login success page to match the Codex desktop auth UX
    use theme-aware colors and an inline 20px Codex mark
    keep the actual localhost success page aligned with the browser auth UX
    PR
    
    ## Tests
    
    <img width="1728" height="1117" alt="Screenshot 2026-04-29 at 12 00
    34 PM"
    src="https://github.com/user-attachments/assets/76a40c3f-07c3-452c-97da-e7c43717cd2c"
    />
  • [mcp] Fix plugin MCP approval policy. (#19537)
    Plugin MCP servers are loaded from plugin manifests rather than
    top-level `[mcp_servers]`, so their tool approval preferences need to be
    stored and applied through the owning plugin config. Without this,
    choosing "Always allow" for a plugin MCP tool could write a preference
    that was not reliably used on later tool calls.
    
    ## Summary
    - Add plugin-scoped MCP policy config under
    `plugins.<plugin>.mcp_servers`, including server enablement, tool
    allow/deny lists, server defaults, and per-tool approval modes.
    - Overlay plugin MCP policy onto manifest-provided server configs when
    plugins are loaded.
    - Route persistent "Always allow" writes for plugin MCP tools back to
    the owning `plugins.<plugin>.mcp_servers.<server>.tools.<tool>` config
    entry.
    - Reload user config after persisting an approval and make the plugin
    load cache config-aware so stale plugin MCP policy is not reused after
    `config.toml` changes.
    - Regenerate the config schema and add coverage for plugin MCP policy
    loading, approval lookup, persistence, and stale-cache prevention.
    
    ## Testing
    - `cargo test -p codex-config`
    - `cargo test -p codex-core-plugins`
    - `cargo test -p codex-core --lib plugin_mcp`
  • Include auto-review rollout in feedback uploads (#20064)
    ## Summary
    
    - include the live auto-review trunk rollout when `/feedback` uploads
    logs
    - upload that attachment as
    `auto-review-rollout-<parent-thread-id>.jsonl` so it is distinguishable
    from the parent rollout
    - show the same auto-review attachment name in the TUI consent popup
    
    ## Scope
    
    - this only covers the live cached auto-review trunk for the current
    parent thread
    - it does not add durable historical parent->auto-review lookup
    - it does not add persisted rollout support for ephemeral parallel
    review forks
    
    ## UI 
    
    <img width="599" height="185" alt="Screenshot 2026-04-28 at 1 17 18 PM"
    src="https://github.com/user-attachments/assets/6a0e79c2-5d21-4702-8a89-f765778bc9e9"
    />
    
    ## Validation
    
    - `cargo test -p codex-core
    cached_guardian_subagent_exposes_its_rollout_path`
    - `cargo test -p codex-feedback`
    - `cargo test -p codex-app-server`
    - `cargo test -p codex-tui feedback_upload_consent_popup_snapshot`
    - `cargo test -p codex-tui
    feedback_good_result_consent_popup_includes_connectivity_diagnostics_filename`
    
    ## Known unrelated local failures
    
    - `cargo test -p codex-core` currently fails in the pre-existing proxy
    env snapshot test
    `tools::runtimes::tests::maybe_wrap_shell_lc_with_snapshot_keeps_user_proxy_env_when_proxy_inactive`
    - `cargo test -p codex-tui` currently hits pre-existing `status::*`
    snapshot drift unrelated to this change
    
    ## Follow-Up 
    - persist parallel auto-review fork sessions so /feedback can include
    their rollout history too
    - attach each persisted fork as its own clearly named file, for example
    auto-review-rollout-<parent-thread-id>-fork <n>.jsonl, instead of
    merging multiple Guardian sessions into one attachment
    - keep the same live-session-only scope initially; durable historical
    parent -> auto-review lookup can remain a separate decision if we later
    need feedback from resumed sessions
  • TUI: Remove core protocol dependency [3/7] (#20174)
    ## Why
    
    This is part 3 of a 7-PR stack to remove direct
    `codex_protocol::protocol` usage from `codex-tui` while keeping each
    layer reviewable and shippable.
    
    With `AppCommand` now explicit, the internal app event bus can carry TUI
    commands directly instead of bouncing through core `Op` values.
    
    ## What changed
    
    - Changed `AppEvent::CodexOp` and `AppEvent::SubmitThreadOp` to carry
    `AppCommand`.
    - Updated app-event senders and direct emitters to submit `AppCommand`
    values.
    - Adjusted tests to match `AppCommand` or convert back through
    `into_core()` where they intentionally assert legacy payload equality.
    
    ## Verification
    
    - `cargo test -p codex-tui --no-run`
  • TUI: Remove core protocol dependency [2/7] (#20173)
    ## Why
    
    This is part 2 of a 7-PR stack to remove direct
    `codex_protocol::protocol` usage from `codex-tui` while keeping each
    layer reviewable and shippable.
    
    Before the TUI event bus can stop carrying core `Op` values,
    `AppCommand` needs to be an owned TUI command shape rather than a thin
    wrapper around `Op`.
    
    ## What changed
    
    - Replaced the opaque `AppCommand(Op)` wrapper with explicit owned
    variants for the commands the TUI submits.
    - Preserved `into_core()` so this layer does not yet change the
    app/thread submission boundary.
    - Kept existing core leaf types for now so this remains a mechanical
    command-shape refactor.
    
    ## Verification
    
    - `cargo check -p codex-tui`
  • TUI: Remove core protocol dependency [1/7] (#20172)
    ## Why
    
    This is part 1 of a 7-PR stack to remove direct
    `codex_protocol::protocol` usage from `codex-tui` while keeping each
    layer reviewable and shippable.
    
    This first layer reduces the size of the later `chatwidget` diff by
    mechanically moving MCP startup bookkeeping out of the central widget
    file without changing the event shapes or behavior.
    
    ## What changed
    
    - Extracted MCP startup status handling into
    `tui/src/chatwidget/mcp_startup.rs`.
    - Kept the existing core event types in place for this purely mechanical
    move.
    - Updated the MCP startup tests to import the moved test-only event
    types directly.
    
    ## Verification
    
    - `cargo test -p codex-tui chatwidget::tests::mcp_startup`
  • Use /goal resume for paused goals (#20082)
    ## Why
    
    The paused goal statusline currently points users at `/goal` to unpause
    a goal, but bare `/goal` is the summary command and does not change the
    goal state. Instead of making `/goal` mutate state only when a goal is
    paused, this gives the action an explicit command that reads naturally
    in the UI.
    
    ## What Changed
    
    - Replace `/goal unpause` with `/goal resume` for reactivating a paused
    goal.
    - Update the paused goal statusline and `/goal` summary copy to point at
    `/goal resume`.
  • chore(cli) deprecate --full-auto (#20133)
    ## Summary
    Starts the process of getting rid of `--full-auto`, with some
    concessions:
    1. Fully removes the command from the tui, since it just resolves to the
    default permissions there, and encourages users to use the one-time
    trust flow if they're not in a trusted repo.
    2. Marks the command as deprecated in `codex exec`, in case users are
    actively relying on this. We'll remove in an upcoming n+X release.
    3. Cleans up some of the `codex sandbox` cli logic, to keep supporting
    legacy sandbox policies for now.
    
    This isn't the cleanest setup, but I think it is worthwhile to warn
    users for one release before hard-removing it.
    
    ## Testing 
    - [x] Updated unit tests
  • Add environment provider snapshot (#20058)
    ## Summary
    - Change `EnvironmentProvider` to return concrete `Environment`
    instances instead of `EnvironmentConfigurations`.
    - Make `DefaultEnvironmentProvider` provide the provider-visible `local`
    environment plus optional `remote` environment from
    `CODEX_EXEC_SERVER_URL`.
    - Keep `EnvironmentManager` as the concrete cache while exposing its own
    explicit local environment for `local_environment()` fallback paths.
    
    ## Validation
    - `just fmt`
    - `git diff --check`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Soften skill description budget warnings (#20112)
    Updates skill description budget messaging to be less alarming
  • Restore TUI working status after steer message is set (#19939)
    Fix for #19925
    
    Restore the `Working` indicator after a streamed final answer finishes
    when a user steer message is sent.
    Add regression coverage for long output plus a mid-stream steer:
    `cargo test -p codex-tui
    final_answer_completion_restores_status_indicator_for_pending_steer`
    
    Duplication/testing steps:
    1. Start a new thread and ask for a long response.
    2. While the response is streaming, submit a steer message.
    3. When the first response finishes, observe whether `Working...` is
    shown while waiting for the steer message response.
  • Support detect and import MCP, Subagents, hooks, commands from external (#19949)
    ## Why
    This PR expands the migration path so Codex can detect and import MCP
    server config, hooks, commands, and subagents configs in a Codex-native
    shape.
    
    ## What changed
    
    - Added a `codex-external-agent-migration` crate that owns conversion
    logic for external-agent MCP servers, hooks, commands, and subagents.
    - Extended the app-server external-agent config detection/import API
    with migration item types for MCP server config, hooks, commands, and
    subagents.
    
    ## Migration strategy
    
    The migration is intentionally conservative: Codex only imports
    external-agent config that can be represented safely in Codex today.
    Unsupported or ambiguous config is skipped instead of being partially
    translated into behavior that may not match the source system.
    
    - **MCP servers**: import supported stdio and HTTP MCP server
    definitions into `mcp_servers`. Disabled servers and servers filtered
    out by source `enabledMcpjsonServers` / `disabledMcpjsonServers` are
    skipped. Project-scoped MCP entries from `.claude.json` are included
    when they match the repo path.
    - **Hooks**: import only supported command hooks into
    `.codex/hooks.json`. Unsupported hook features such as conditional
    groups, async handlers, prompt/http hooks, or unknown fields are
    skipped. Referenced hook scripts are copied into `.codex/hooks/`,
    preserving any existing target scripts.
    - **Commands**: import supported external commands as Codex skills under
    `.agents/skills/source-command-*`. Commands that rely on source runtime
    expansion such as `$ARGUMENTS`, `$1`, `@file` references, shell
    interpolation, or colliding generated names are skipped.
    - **Subagents**: import valid subagent Markdown files into
    `.codex/agents/*.toml` when they have the minimum Codex agent fields.
    Source model names are not migrated, so imported agents keep the user’s
    Codex default model; compatible reasoning effort and sandbox mode are
    migrated when present.
    - **Skills and project guidance**: copy missing skill directories into
    `.agents/skills` and migrate `CLAUDE.md` guidance into `AGENTS.md`,
    rewriting source-agent terminology to Codex terminology where
    appropriate.
    - **Detection details**: detected migration items include lightweight
    details for UI preview, such as MCP server names, hook event names,
    generated command skill names, and subagent names. Import still
    recomputes from disk instead of trusting details as the source of truth.
    
    - Adds focused coverage for the new migration behavior and app-server
    import flow.
    
    ## Verification
    
    - `cargo test -p codex-external-agent-migration`
    - `cargo test -p codex-hooks`
    - `cargo test -p codex-app-server external_agent_config`
    - `just bazel-lock-check`
  • app-server: notify clients of remote-control status changes (#19919)
    ## Why
    
    Remote-control app-server enrollments have both an internal server id
    and the environment id exposed to remote-control clients. App-server
    clients need one current status snapshot that says whether remote
    control is usable and which environment id, if any, is exposed.
    
    A temporary websocket disconnect is not itself an identity change.
    Account changes, stale enrollment invalidation, successful
    re-enrollment, and missing ChatGPT auth are meaningful status changes.
    Disabled remote control remains `disabled` regardless of auth or SQLite
    state. SQLite startup failure disablement and enrollment persistence
    failures are handled in #20068; this PR reports the resulting effective
    status to clients.
    
    ## What changed
    
    - Adds v2 `remoteControl/status/changed` carrying `state` and
    `environmentId`.
    - Adds `RemoteControlConnectionState` values: `disabled`, `connecting`,
    `connected`, and `errored`.
    - Exposes remote-control status updates through `RemoteControlHandle`
    using a Tokio watch channel.
    - Always sends the current remote-control status snapshot to newly
    initialized app-server clients.
    - Broadcasts status changes to initialized app-server clients when state
    or environment id changes.
    - Treats missing ChatGPT auth as an `errored` status while leaving it
    retryable because auth can change at runtime.
    - Clears `environmentId` when enrollment is cleared for account changes,
    auth loss, stale backend invalidation, or disabled remote control.
    - Updates app-server protocol schema fixtures, generated TypeScript,
    app-server README, remote-control tests, and TUI exhaustive notification
    matches.
    
    ## Stack
    
    - Builds on #20068.
    
    ## Verification
    
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server transport::remote_control --lib`
    - `cargo check -p codex-tui`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-app-server`
    - `just fix -p codex-tui`
  • core tests: submit turns with permission profiles (#20010)
    ## Summary
    
    - Add `PermissionProfile`-based turn submission helpers to
    `core_test_support`, while keeping the legacy `SandboxPolicy` helper for
    tests that intentionally exercise legacy fallback behavior.
    - Switch the default `TestCodex::submit_turn()` path to send a real
    `PermissionProfile` plus the required legacy compatibility projection in
    `Op::UserTurn`.
    - Migrate straightforward app/search/shell/truncation tests from
    `SandboxPolicy::{DangerFullAccess, ReadOnly}` to
    `PermissionProfile::{Disabled, read_only}`.
    - Add a TUI compatibility projection helper for legacy app-server fields
    so non-legacy writable roots are preserved instead of being downgraded
    to read-only.
    - Fix remote start/resume/fork sandbox-mode projection to classify any
    managed profile with writable roots as workspace-write, not only
    profiles that can write `cwd`.
    - Reduce `SandboxPolicy` references in `codex-rs/core/tests` from 47
    files to 41 files without changing production behavior.
    
    ## Testing
    
    - `cargo check -p codex-core --tests`
    - `cargo test -p codex-tui
    compatibility_profile_preserves_unbridgeable_write_roots`
    - `cargo test -p codex-tui
    sandbox_mode_preserves_non_cwd_write_roots_for_remote_sessions`
    - `just fmt`
    - `just fix -p core_test_support`
    - `just fix -p codex-core`
  • /plugins: add marketplace install flow (#18704)
    This PR adds a new feature to the `/plugins` menu that gives users the
    ability to add new plugin marketplaces. It introduces an Add Marketplace
    tab to the right of installed marketplaces, a source prompt, loading and
    error states, and the app-server request flow needed to perform the
    install. After a successful `marketplace/add`, the popup refreshes back
    into the newly added marketplace tab so the new plugins are immediately
    visible.
    
    - Add an Add Marketplace tab to the `/plugins` menu
    - Prompt for marketplace source input from git repo, URL, or local path
    - Show loading and error states during `marketplace/add`
    - Refresh plugin data after success and switch into the newly added
    marketplace tab
    - Add tests and snapshot updates
  • tui: use permission profiles for sandbox state (#20008)
    ## Summary
    - Move TUI permission state from legacy `SandboxPolicy` values to
    canonical `PermissionProfile` values across presets, app events, chat
    widget state, app commands, thread routing, and cached thread session
    state.
    - Keep app-server compatibility boundaries explicit: embedded sessions
    send `permissionProfile`, while remote sessions send only a legacy
    `sandbox` projection and fall back to read-only when a custom profile
    cannot be projected.
    - Update status/add-dir UI summaries and snapshots to render the active
    permission profile, including workspace profiles selected by the new
    built-in defaults.
    
    ## Verification
    - `rg '\bSandboxPolicy\b' codex-rs/tui -n` returns no matches.
    - `cargo test -p codex-tui`
    - `cargo check -p codex-tui --tests`
    - `cargo test -p codex-tui additional_dirs`
    - `just fmt`
    - `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/20008).
    * #20041
    * #20040
    * #20037
    * #20035
    * #20034
    * #20033
    * #20032
    * #20030
    * #20028
    * #20027
    * #20026
    * #20024
    * #20021
    * #20018
    * #20016
    * #20015
    * #20013
    * #20011
    * #20010
    * __->__ #20008
  • Fix plan mode nudge test after task completion signature change (#20045)
    Updates the plan mode nudge test to pass the new `duration_ms` argument
    to task completion.
    
    Co-authored-by: Codex <noreply@openai.com>
  • External agent session support (#19895)
    ## Summary
    
    This extends external agent detection/import beyond config artifacts so
    Codex can detect recent sessions files from the external agent home and
    import them into Codex rollout history.
    
    ## What changed
    
    - Added a focused `external_agent_sessions` module for:
      - session discovery
      - source-record parsing
      - rollout construction
      - import ledger tracking
    - Wired session detection/import into the app-server external agent
    config API.
    - Added compaction handling so large imported sessions can be resumed
    safely before the first follow-up turn.
    
    ## Testing
    
    Added coverage for:
    - recent-session detection
    - custom-title handling
    - recency filtering
    - dedupe and re-detect-after-source-change behavior
    - visible imported turn construction
    - backward-compatible import payload deserialization
    - end-to-end RPC import flow
    - rejection of undetected session paths
    - repeat-import behavior
    - large-session compaction before first follow-up
    
    Ran:
    - `cargo test -p codex-app-server external_agent_config_import_ --test
    all`
  • fix(tui): let esc exit empty shell mode (#19986)
    ## Summary
    
    - exit shell mode when `Esc` is pressed while the absorbed `!` is the
    only input
    - add direct regression coverage plus a composer snapshot for the
    restored normal prompt state
    
    ## Root cause
    
    Shell mode stores the leading `!` outside the editable textarea. After
    typing only `!`, the textarea is empty but the composer is still in bash
    mode, so the existing empty-composer `Esc` handling never runs.
    
    ## Validation
    
    - `just fmt`
    - `cargo test -p codex-tui
    bottom_pane::chat_composer::tests::esc_exits_empty_shell_mode`
    - `cargo test -p codex-tui
    bottom_pane::chat_composer::tests::footer_mode_snapshots`
    - `cargo insta pending-snapshots`
    
    `cargo test -p codex-tui` still reports unrelated existing `/status`
    snapshot drift in this local environment because the rendered
    permissions text is `workspace-write with network access` instead of the
    older `read-only` fixture text.
  • Move local /resume cwd filtering into thread/list (#19931)
    Move local resume and fork cwd filtering to `thread/list` instead of
    filtering in the TUI. This makes the `/resume` menu feel slightly faster
    to load when working in repos with many historical threads, and
    centralizes the cwd filtering in app-server.
    
    **Affected:**
    - /resume from inside the TUI.
    - codex resume with no session ID and without --last
    - codex resume --all
    - codex fork with no session ID and without --last
    - codex fork --all
    
    **Not affected:**
    - codex resume <id>
    - codex fork <id>
    - codex resume --last
    - codex fork --last
    
    Steps to test performance improvement in a real Codex environment:
    - Launch `codex resume` using compiled binary in a directory that has
    seen many threads.
    - Launch `codex resume` using release binary in same directory.
    - Observe difference in time-to-full-page as threads load.
  • feat(tui): suggest plan mode from composer drafts (#19901)
    ## Summary
    
    - suggest Plan mode when the current composer draft contains the
    standalone word `plan`
    - shares the Codex App heuristics for detection
    - excludes things line `/plan` and the word plan in shell mode
    - reuse the existing `Shift+Tab` mode cycle and add thread-scoped
    dismissal with `Esc`
    - replace the normal footer hint while the reminder is visible so the
    statusline stays anchored
    
    
    https://github.com/user-attachments/assets/01123ae8-cee6-4e95-b563-44655c071cde
    
    ## Why
    
    The desktop app already nudges users toward Plan mode when their draft
    clearly signals planning intent. The TUI had the underlying `/plan` and
    `Shift+Tab` flows, but no equivalent reminder at the moment the user was
    most likely to benefit from them.
    
    ## Details
    
    The reminder is shown only when Plan mode is available, the draft
    contains standalone `plan`, the user is not already in Plan mode, the
    composer is actionable, and the current thread has not dismissed the
    reminder. Slash-command and shell-command drafts are excluded.
    
    The first implementation used an extra composer row, but that moved the
    statusline whenever the heuristic fired. This version keeps the layout
    stable by rendering the reminder in the existing footer row instead.
    
    ## Validation
    
    - `INSTA_UPDATE=always cargo test -p codex-tui
    chatwidget::tests::plan_mode::plan_mode_nudge -- --nocapture`
    - `just fmt`
    - `just fix -p codex-tui`
    - `./tools/argument-comment-lint/run.py -p codex-tui`
    - `cargo insta pending-snapshots`
    - `git diff --check`
  • TUI: use cumulative turn duration for worked-for separator (#19929)
    ## Why
    
    Fixes #19814.
    
    The TUI's current `Worked for ...` timing behavior is a leftover from
    #9599. At that point, models could emit multiple assistant messages in
    one turn for preambles/commentary, but the TUI did not yet have a
    reliable signal that an assistant message was the final answer when it
    started streaming. To avoid showing an ever-growing elapsed time on each
    preamble separator, #9599 made the separator timer incremental by
    tracking elapsed time since the previous separator.
    
    That workaround is no longer the right model for the final
    completed-turn display. Since then, #16638 added protocol-native turn
    timing, including `duration_ms` on turn completion. With that cumulative
    duration available at the point where the TUI renders the completed-turn
    separator, the UI can show the actual turn duration directly instead of
    carrying per-separator timing state.
    
    ## What Changed
    
    - Thread `duration_ms` into `ChatWidget::on_task_complete` from both
    legacy `TurnCompleteEvent` handling and app-server `TurnCompleted`
    notifications.
    - Use `duration_ms` for the final `Worked for ...` separator, falling
    back to the status indicator timer only when the protocol duration is
    unavailable.
    - Keep mid-turn separators before later assistant text as plain visual
    dividers instead of clocked `Worked for ...` separators.
    - Remove the old incremental separator timer state and helper
    (`last_separator_elapsed_secs` / `worked_elapsed_from`).
    - Add a snapshot regression test for a turn that runs a command and then
    completes with a final answer, verifying the final separator uses the
    cumulative turn duration.
    
    ## Verification
    
    - `cargo test -p codex-tui
    final_worked_for_uses_cumulative_turn_duration_snapshot`
    - `just fix -p codex-tui`
    
    Manual repro prompt:
    
    ```text
    Manual timing repro. First send a short preamble/commentary sentence before using tools. Then run exactly this shell command: sleep 75; echo MANUAL_TIMING_DONE. After the command finishes, give a final answer that says "done". Do not skip the preamble.
    ```
    
    After this change, the mid-turn break before the final answer should be
    a plain divider, and the final completed-turn separator should show
    `Worked for ...` using the cumulative turn duration.
    
    Before:
    <img width="414" height="102" alt="Screenshot 2026-04-27 at 10 09 01 PM"
    src="https://github.com/user-attachments/assets/b9e2ce01-2460-40e4-a5c4-c9ba8add2557"
    />
    
    
    After:
    <img width="485" height="149" alt="Screenshot 2026-04-27 at 10 09 07 PM"
    src="https://github.com/user-attachments/assets/d24089ae-d4e2-41b6-b966-07c98706ead4"
    />
  • feat(tui): add configurable keymap support (#18593)
    ## Why
    
    The TUI currently handles keyboard shortcuts as hard-coded event matches
    spread across app, composer, pager, list, approval, and navigation code.
    That makes shortcuts hard to customize, makes displayed hints easy to
    drift from actual behavior, and makes future keymap work riskier because
    there is no central action inventory.
    
    This PR adds the foundation for configurable, action-based keymaps
    without adding the interactive remapping UI yet. Onboarding
    intentionally stays on fixed startup shortcuts because users cannot
    reasonably configure keymaps before completing onboarding.
    
    This is PR1 in the keymap stack:
    
    - PR1: #18593: configurable keymap foundation
    - PR2: #18594: `/keymap` picker and guided remapping UI
    - PR3: #18595: Vim composer mode and the remap option
    
    ## Design Notes
    
    The new model resolves named actions into concrete runtime bindings once
    from config, then passes those bindings to the UI surfaces that handle
    input or render shortcut hints.
    
    The main concepts are:
    
    - **Context**: a scope where an action is active, such as `global`,
    `chat`, `composer`, `editor`, `pager`, `list`, or `approval`.
    - **Action**: a named operation inside a context, such as
    `global.open_transcript`, `composer.submit`, or `pager.close`.
    - **Binding**: one or more single-key shortcuts assigned to an action,
    written as config strings such as `ctrl-t`, `alt-backspace`, or
    `page-down`. Multi-step sequences such as `ctrl-x ctrl-s`, `g g`, or
    leader-key flows are not part of this PR.
    - **Resolution order**: context-specific config wins first, supported
    global fallbacks come next, and built-in defaults fill in anything
    unset.
    - **Explicit unbinding**: an empty array removes an action binding in
    that scope and does not fall through to a fallback binding.
    - **Conflict validation**: a resolved keymap rejects duplicate active
    bindings inside the same scope so one keypress cannot dispatch two
    actions.
    
    ## What Changed
    
    - Added `TuiKeymap` config support under `[tui.keymap]`, including typed
    contexts/actions, key alias normalization, generated schema coverage,
    and user-facing config errors.
    - Added `RuntimeKeymap` resolution in `codex-rs/tui/src/keymap.rs`,
    including fallback precedence, built-in defaults, explicit unbinding,
    and per-context conflict validation.
    - Rewired existing TUI handlers to consume resolved keymap actions
    instead of directly matching hard-coded keys in each component.
    - Updated key hint rendering and footer/pager/list surfaces so displayed
    shortcuts follow the resolved keymap.
    - Kept onboarding shortcuts fixed in
    `codex-rs/tui/src/onboarding/keys.rs` instead of exposing them through
    `[tui.keymap]`.
    
    ## Validation
    
    The branch includes focused coverage for config parsing, key
    normalization, runtime fallback resolution, explicit unbinding,
    duplicate-key conflict validation, default keymap consistency,
    onboarding startup key behavior, and UI hint snapshots affected by
    resolved key bindings.
  • Reset TUI keyboard reporting on exit (#19625)
    ## Why
    
    Codex enables enhanced keyboard reporting while the TUI owns the
    terminal. In iTerm2, exiting the TUI with Ctrl+C can intermittently
    leave the parent shell receiving raw CSI-u / `modifyOtherKeys` fragments
    instead of normal key input.
    
    Final terminal cleanup should put the parent shell back into normal
    keyboard reporting even if the terminal misses the usual stack pop.
    
    Fixes #19553.
    
    ## What Changed
    
    - Move TUI keyboard enhancement setup and detection into
    `tui/src/tui/keyboard_modes.rs`.
    - Add an exit-only `restore_after_exit()` path that performs the normal
    keyboard enhancement pop plus unconditional keyboard enhancement and
    `modifyOtherKeys` resets.
    - Keep temporary restore paths, such as external-editor handoff, using
    the balanced stack pop behavior.
    
    ## Confidence
    
    Medium. This is a speculative fix: I was not able to reproduce the
    reported iTerm2 behavior manually, but the symptoms line up with
    terminal keyboard reporting state surviving Codex exit. The added reset
    sequences are scoped to final TUI shutdown and should be harmless when
    the terminal is already clean.
  • Add codex update command (#19933)
    ## Why
    
    Addresses #9274
    
    Running `codex update` currently starts an interactive Codex session
    with `update` as the prompt. That is a rough edge for users who expect a
    direct self-update command after seeing the existing update notice, and
    it forces them to copy the suggested package-manager command manually.
    
    ## What changed
    
    - Added a top-level `codex update` subcommand.
    - Reused the existing install-channel detection and update command
    runner that the TUI already uses for update prompts.
    - Exposed the update-action lookup from `codex-tui` so the CLI can
    invoke the same behavior.
    - Added CLI coverage to ensure `codex update` is parsed as a subcommand
    instead of becoming an interactive prompt.
    
    ## Verification
    
    - `cargo test -p codex-cli`
    - `cargo test -p codex-tui update_action::tests`
  • permissions: store thread sessions as profiles (#19776)
    ## Why
    
    After thread sessions have a required `PermissionProfile`, the TUI no
    longer needs to cache a separate legacy `SandboxPolicy` in
    `ThreadSessionState`. Keeping the legacy field would reintroduce two
    permission authorities in the session cache and make later
    replay/switching logic easier to get wrong.
    
    This PR keeps legacy app-server compatibility at the ingestion boundary:
    old `sandbox` response values are still accepted, but they are
    immediately converted to a cwd-anchored profile.
    
    ## What Changed
    
    - Removes `ThreadSessionState.sandbox_policy`.
    - Updates active-session permission syncing to write only the current
    `PermissionProfile`.
    - Updates thread-read/replay/test fixtures to use profiles as the cached
    session permission source.
    - Leaves legacy `sandbox` fields in app-server request/response protocol
    paths unchanged; those are compatibility boundaries and are converted
    before entering cached TUI state.
    
    ## Verification
    
    - `cargo test -p codex-tui thread_session_state::tests --lib`
    - `cargo test -p codex-tui
    inactive_thread_started_notification_initializes_replay_session --lib`
    - `cargo test -p codex-tui thread_events --lib`
    - `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/19776).
    * #19900
    * #19899
    * __->__ #19776
  • permissions: make SessionConfigured profile-only (#19774)
    ## Why
    
    `SessionConfiguredEvent` is the internal event that tells clients what
    permissions are active for a session. Emitting both `sandbox_policy` and
    `permission_profile` leaves two possible authorities and forces every
    consumer to decide which one to honor. At this point in the migration,
    the profile is expressive enough to represent managed, disabled, and
    external sandbox enforcement, so the internal event can be profile-only.
    
    The wire compatibility concern is older serialized events or rollout
    data that only contain `sandbox_policy`; those still need to
    deserialize.
    
    ## What Changed
    
    - Removes `sandbox_policy` from `SessionConfiguredEvent` and makes
    `permission_profile` required.
    - Adds custom deserialization so old payloads with only `sandbox_policy`
    are upgraded to a cwd-anchored `PermissionProfile`.
    - Updates core event emission and TUI session handling to sync
    permissions from the profile directly.
    - Updates app-server response construction to derive the legacy
    `sandbox` response field from the active thread snapshot instead of from
    `SessionConfiguredEvent`.
    - Updates yolo-mode display logic to treat both
    `PermissionProfile::Disabled` and managed unrestricted filesystem plus
    enabled network as full-access, while still preserving the distinction
    between no sandbox and external sandboxing.
    
    ## Verification
    
    - `cargo test -p codex-protocol session_configured_event --lib`
    - `cargo test -p codex-protocol serialize_event --lib`
    - `cargo test -p codex-exec session_configured --lib`
    - `cargo test -p codex-app-server
    thread_response_permission_profile_preserves_enforcement --lib`
    - `cargo test -p codex-core
    session_configured_reports_permission_profile_for_external_sandbox
    --lib`
    - `cargo test -p codex-tui session_configured --lib`
    - `cargo test -p codex-tui
    yolo_mode_includes_managed_full_access_profiles --lib`
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19774).
    * #19900
    * #19899
    * #19776
    * #19775
    * __->__ #19774
  • Allow /statusline and /title slash commands during active turns (#19917)
    - Marks `/title` and `/statusline` as available during active tasks.
    - Extends the existing slash-command availability test coverage to
    include these commands alongside `/goal`.
  • permissions: require profiles in TUI thread state (#19773)
    ## Why
    
    `ThreadSessionState` is the TUI's cached view of an app-server session.
    To make `PermissionProfile` the canonical runtime permissions model,
    cached thread sessions need to always have a profile instead of treating
    the profile as an optional supplement to a legacy `sandbox` response
    field.
    
    The main compatibility concern is older app-server v2 lifecycle
    responses that only include `sandbox` and omit `permissionProfile`:
    
    - `thread/start` -> `ThreadStartResponse.sandbox`
    - `thread/resume` -> `ThreadResumeResponse.sandbox`
    - `thread/fork` -> `ThreadForkResponse.sandbox`
    
    Those responses must still hydrate correctly when the TUI is pointed at
    an older app-server. This PR converts the legacy `sandbox` value into a
    `PermissionProfile` immediately at response ingestion time, using the
    response `cwd`, so cached sessions do not carry an optional profile that
    can later reinterpret cwd-bound grants against a different thread cwd.
    
    This fallback is intentionally boundary compatibility. The follow-up PRs
    in this stack continue the cleanup by making `SessionConfiguredEvent`
    profile-only, deriving sandbox projections from snapshots only when an
    API still needs them, and then removing `sandbox_policy` from
    `ThreadSessionState`.
    
    ## What Changed
    
    - Makes `ThreadSessionState.permission_profile` required.
    - Converts legacy app-server response `sandbox` values into a
    `PermissionProfile` at ingestion time using the response cwd.
    - Ensures `thread/read` hydration does not reuse a primary session
    profile that may be anchored to a different cwd; it uses the active
    widget permission settings for the read thread fallback instead of
    reusing cached primary-session permissions.
    - Keeps the app-server request path unchanged: embedded sessions send
    profiles, while remote sessions continue using legacy sandbox overrides
    for compatibility.
    
    ## Verification
    
    - `cargo test -p codex-tui thread_read --lib`
    - `cargo test -p codex-tui
    permission_settings_sync_preserves_active_profile_only_rules --lib`
    - `cargo test -p codex-tui
    resume_response_restores_turns_from_thread_items --lib`
    - `cargo test -p codex-tui thread_session_state::tests --lib`
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19773).
    * #19900
    * #19899
    * #19776
    * #19775
    * #19774
    * __->__ #19773
  • Remove ghost snapshots (#19481)
    ## Summary
    - Remove `ghost_snapshot` / `GhostCommit` from the Responses API surface
    and generated SDK/schema artifacts.
    - Keep legacy config loading compatible, but make undo a no-op that
    reports the feature is unavailable.
    - Clean up core history, compaction, telemetry, rollout, and tests to
    stop carrying ghost snapshot items.
    
    ## Testing
    - Unit tests passed for `codex-protocol`, `codex-core` targeted undo and
    compaction flows, `codex-rollout`, and `codex-app-server-protocol`.
    - Regenerated config and app-server schemas plus Python SDK artifacts
    and verified they match the checked-in outputs.