Commit Graph

5907 Commits

  • 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.
  • feat: house-keeping memories 2 (#20000)
    Just move metrics in a dedicated file
  • feat: house-keeping memories 1 (#19998)
    Just move metrics in a dedicated file
  • feat: skip memory startup when Codex rate limits are low (#19990)
    ## Why
    
    Memory startup runs in the background after an eligible turn, but it can
    consume Codex backend quota at exactly the wrong time: when the user is
    already near a rate-limit boundary. This PR adds a guard so the memory
    pipeline backs off when the Codex rate-limit snapshot says the remaining
    budget is too low.
    
    ## What Changed
    
    - Added `memories.min_rate_limit_remaining_percent` with a default of
    `25`, clamped to `0..=100`, and regenerated `core/config.schema.json`.
    - Added `codex-rs/memories/write/src/guard.rs`, which fetches Codex
    backend rate limits before memory startup and skips phase 1 / phase 2
    when the Codex limit is reached or either tracked window is above the
    configured usage ceiling.
    - Keeps startup best-effort: non-Codex auth or rate-limit fetch/client
    failures preserve the existing memory startup behavior.
    - Records a `codex.memory.startup` counter with
    `status=skipped_rate_limit` when startup is skipped.
    - Added config parsing/clamping coverage and guard unit tests.
    
    ## Verification
    
    - Added `codex-rs/memories/write/src/guard_tests.rs` for threshold,
    primary/secondary window, and reached-limit behavior.
    - Added config tests for TOML parsing and clamping.
  • fix: configure AgentIdentity AuthAPI base URL (#19904)
    ## Summary
    
    AgentIdentity runtime loading currently registers tasks against a single
    hardcoded AuthAPI base URL. That works for production, but local and
    staging validation may need registration to target a different
    authapi-login-provider without baking internal staging service URLs into
    the OSS binary.
    
    This PR adds a small config surface for
    `agent_identity_authapi_base_url` and threads it through the existing
    auth-loading path as a direct argument. Explicit config wins. Without
    config, task registration keeps using the production AuthAPI URL,
    matching the current default behavior.
    
    ## Stack
    
    1. openai/codex#19762 - `refactor: make auth loading async` (merged)
    2. openai/codex#19763 - `refactor: load agent identity runtime eagerly`
    3. This PR - `fix: configure AgentIdentity AuthAPI base URL`
    4. openai/codex#19764 - `feat: verify agent identity JWTs with JWKS`
    
    ## Design decisions
    
    - Keep the existing auth-loading shape and pass the new value as an
    argument. This avoids another wrapper loader and keeps the call path
    readable.
    - Add config instead of embedding internal staging URLs. Environments
    that need a non-production AuthAPI can configure it explicitly.
    - Keep the default AuthAPI registration URL as production.
    `chatgpt_base_url` remains separate and is used by the follow-up JWKS
    verification PR for fetching public keys from the ChatGPT backend route.
    - Resolve the AuthAPI base URL inside AgentIdentity loading, because
    task registration is the only consumer of this value.
    
    ## Testing
    
    Tests: targeted Rust checks, AgentIdentity auth tests, config schema
    regeneration, formatter/fix pass, and whitespace diff check.
  • feat: trigger memories from user turns with cooldown (#19970)
    ## Why
    
    Memory startup was tied to thread lifecycle events such as create, load,
    and fork. That can run memory work before a thread receives real user
    input, and it makes startup cost scale with thread management instead of
    actual turns. Moving the trigger to `thread/sendInput` keeps memory
    startup aligned with the first real user turn and lets it use the
    current thread config at turn time.
    
    The idea is to prevent ghost cost due to pre-warm triggered by the app
    
    Turn-based startup can also make global phase-2 consolidation easier to
    request repeatedly, so this adds a success cooldown and tightens the
    default startup scan window.
    
    ## What Changed
    
    - Start `codex_memories_write::start_memories_startup_task` after a
    non-empty `thread/sendInput` turn is submitted, instead of from thread
    create/load/fork paths:
    https://github.com/openai/codex/blob/d4a6885b7829e2fd2ec7a09355e4f75ebe1d1fe3/codex-rs/app-server/src/codex_message_processor.rs#L6477-L6487
    - Expose `CodexThread::config()` so app-server can pass the live config
    into memory startup at turn time.
    - Add a six-hour successful-run cooldown for global phase-2
    consolidation via `SkippedCooldown`:
    https://github.com/openai/codex/blob/d4a6885b7829e2fd2ec7a09355e4f75ebe1d1fe3/codex-rs/state/src/runtime/memories.rs#L963-L966
    - Reduce memory startup defaults to at most 2 rollouts over 10 days:
    https://github.com/openai/codex/blob/d4a6885b7829e2fd2ec7a09355e4f75ebe1d1fe3/codex-rs/config/src/types.rs#L31-L34
    
    ## Verification
    
    Updated the memory runtime coverage around phase-2 reclaim behavior,
    including `phase2_global_lock_respects_success_cooldown`.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Stabilize memory Phase 2 input ordering (#19967)
    ## Why
    
    Phase 2 still needs to choose the most relevant stage-1 memory outputs
    by usage and recency, but exposing that ranking as the rendered
    `raw_memories.md` order creates unnecessary large diff. Usage-count or
    timestamp changes can reshuffle otherwise unchanged memories, making the
    workspace diff noisy and giving the consolidation prompt a misleading
    recency signal from file position.
    This fix will reduce token consumption
    
    ## What Changed
    
    - Keep the existing top-N Phase 2 selection ranking by `usage_count`,
    `last_usage`, `source_updated_at`, and `thread_id`.
    - Return the selected rows in stable ascending `thread_id` order before
    syncing Phase 2 filesystem inputs.
    - Update the memory README, raw memories header, and consolidation
    prompt so they describe the stable order and tell the prompt to use
    metadata and workspace diffs instead of file order as the recency
    signal.
    - Adjust the memory runtime tests to use deterministic thread IDs and
    assert the stable return order separately from the ranked selection
    semantics.
    
    ## Test Coverage
    
    - Existing memory runtime tests in
    `codex-rs/state/src/runtime/memories.rs` now cover the stable returned
    ordering for Phase 2 inputs.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: split memories part 2 (#19860)
    Keep extracting memories out of core and moving the write trigger in the
    app-server
    This is temporary and it should move at the client level as a follow-up
    This makes core fully independant from `codex-memories-write`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Add MultiAgentV2 root and subagent context hints (#19805)
    ## Why
    
    MultiAgentV2 sessions need startup guidance that matches the role of the
    thread that is actually being created. Root agents and subagents have
    different responsibilities, and forked subagents can inherit parent
    rollout history. If the parent hint is carried into the child context,
    the child can see stale or conflicting developer guidance before its own
    session-specific context is added.
    
    ## What changed
    
    - Added `features.multi_agent_v2.root_agent_usage_hint_text` and
    `features.multi_agent_v2.subagent_usage_hint_text` config fields,
    including schema/config parsing support.
    - Injected the matching root or subagent hint into the initial context
    as its own developer message when `multi_agent_v2` is enabled.
    - Filtered configured MultiAgentV2 usage-hint developer messages out of
    forked parent history so a child thread receives fresh guidance for its
    own session source/config.
    - Added targeted coverage for config parsing, initial-context rendering,
    feature-config deserialization, and forked-history filtering.
    
    ## Context examples
    
    With this config:
    
    ```toml
    [features.multi_agent_v2]
    enabled = true
    root_agent_usage_hint_text = "Root guidance."
    subagent_usage_hint_text = "Subagent guidance."
    ```
    
    A root thread initial context renders the root hint as a standalone
    developer message:
    
    ```text
    [developer]
    <existing developer context, when present>
    
    [developer]
    Root guidance.
    ```
    
    A subagent thread initial context renders the subagent hint instead:
    
    ```text
    [developer]
    <existing developer context, when present>
    
    [developer]
    Subagent guidance.
    ```
    
    When a subagent forks parent history, any parent developer message whose
    text exactly matches the configured MultiAgentV2 root or subagent hint
    is omitted from the forked history before the child receives its fresh
    subagent hint.
  • Add remote plugin uninstall API (#19456)
    ## Summary
    - Adds the remote `plugin/uninstall` request form using required
    `pluginId` plus optional `remoteMarketplaceName`, while preserving local
    `pluginId` uninstall.
    - Adds `codex_core_plugins::remote::uninstall_remote_plugin` for the
    deployed ChatGPT plugin backend uninstall path and validates the backend
    returns the same id with `enabled: false`.
    - Routes app-server remote uninstall through feature checks, remote
    plugin id validation, backend mutation, local downloaded cache deletion,
    cache clearing, docs, and regenerated protocol schemas.
    
    ## Tests
    - `just write-app-server-schema`
    - `just fmt`
    - `cargo test -p codex-app-server-protocol
    plugin_uninstall_params_serialization_omits_force_remote_sync`
    - `cargo test -p codex-app-server plugin_uninstall --test all`
    - `cargo test -p codex-app-server plugin_uninstall`
    - `cargo build -p codex-cli`
    - `CODEX_BIN=/Users/xli/code/codex/codex-rs/target/debug/codex python3
    /Users/xli/.codex/skills/xli-test-marketplace-api/scripts/run_marketplace_api_matrix.py`
    (44 pass / 0 fail)
    - `just fix -p codex-app-server-protocol -p codex-app-server -p
    codex-tui`
    - `just fix -p codex-app-server`
  • feat: Cache remote plugin bundles on install (#19914)
    Remote installs now fetch, validate, download, and cache the plugin
    bundle locally
  • 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`
  • app-server-protocol: mark permission profiles experimental (#19899)
    ## Why
    
    `PermissionProfile` is now the canonical internal permissions
    representation, but the app-server wire shape is still intentionally
    unstable while the migration continues. Stable app-server clients should
    not see or generate code for these fields until the wire format settles.
    
    ## What changed
    
    - Marks every app-server v2 field that sends `PermissionProfile` as
    experimental, including `command/exec`, `thread/start`, `thread/resume`,
    `thread/fork`, and `turn/start` request/response payloads.
    - Enables per-field experimental inspection for `command/exec`, so
    `permissionProfile` is gated without making the entire method
    experimental.
    - Fixes the generated TypeScript schema filter to be comment-aware. The
    previous scanner treated apostrophes inside doc comments as string
    delimiters, so some experimental fields leaked into stable TypeScript
    even though stable JSON was filtered correctly.
    
    ## Verification
    
    - `cargo test -p codex-app-server-protocol`
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19899).
    * #19900
    * __->__ #19899
  • 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
  • Allow large remote app-server resume responses (#19920)
    ## Why
    
    Remote TUI resume uses the app-server websocket client. That client
    inherited tungstenite's default `16 MiB` frame limit, so a large saved
    session could make `thread/resume` return a single JSON-RPC response
    frame that the client rejected before the TUI could deserialize or
    render it.
    
    Fixes #19837
    
    ## What Changed
    
    - Configure the remote app-server websocket client with a bounded `128
    MiB` max frame/message size.
    - Preserve the concrete remote worker exit reason when completing
    pending requests after a transport/read failure instead of replacing it
    with a generic channel-closed error.
    - Add a regression test that sends a single `>16 MiB` JSON-RPC response
    frame and verifies the typed request succeeds.
    
    Note: This isn't a perfect fix. It really just moves the limit to a much
    larger value. I looked at a bunch of other potential fixes (both
    server-side and client-side), and they all involved significant
    complexity, had backward-compatibility impact, or impacted performance
    of common use cases. This simple fix should address the vast majority of
    remote use cases.
    
    ## Verification
    
    I reproed the problem locally using a long rollout. Verified that fix
    addresses connection drop.
  • permissions: derive snapshot sandbox projections (#19775)
    ## Why
    
    `ThreadConfigSnapshot` is used by app-server and thread metadata code as
    a stable view of active runtime settings. Keeping both `sandbox_policy`
    and `permission_profile` in the snapshot duplicates permission state and
    makes it possible for the legacy projection to drift from the canonical
    profile.
    
    The legacy `sandbox` value is still needed at app-server compatibility
    boundaries, so this PR derives it on demand from the snapshot profile
    and cwd instead of storing it.
    
    ## What Changed
    
    - Removes `ThreadConfigSnapshot.sandbox_policy`.
    - Adds `ThreadConfigSnapshot::sandbox_policy()` as a compatibility
    projection from `permission_profile` plus `cwd`.
    - Updates app-server response/metadata code and tests to call the
    projection only where legacy fields still exist.
    - Keeps snapshot construction profile-only so split filesystem rules,
    disabled enforcement, and external enforcement remain represented by the
    canonical profile.
    
    ## Verification
    
    - `cargo test -p codex-app-server
    thread_response_permission_profile_preserves_enforcement --lib`
    - `cargo test -p codex-core
    dispatch_reclaims_stale_global_lock_and_starts_consolidation --lib`
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19775).
    * #19900
    * #19899
    * #19776
    * __->__ #19775
  • 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
  • Avoid persisting ShutdownComplete after thread shutdown (#19630)
    ## Why
    
    Fixes #19475.
    
    `codex exec` can finish successfully and then emit an `ERROR` on stderr:
    
    ```text
    failed to record rollout items: thread <id> not found
    ```
    
    That happens because shutdown closes the live thread writer before
    emitting `ShutdownComplete`. The terminal event was still using the
    normal `send_event_raw` path, so it tried to append rollout items
    through a recorder that had already been removed. The answer is correct,
    but wrappers that treat stderr as failure can retry completed exec runs.
    
    This looks like a likely recent regression from
    [#18882](https://github.com/openai/codex/pull/18882), which routed live
    thread writes through `ThreadStore` and added the shutdown-time live
    writer close. I have not bisected this, so the PR treats #18882 as the
    likely source based on the affected shutdown code path rather than a
    proven first-bad commit.
    
    ## What Changed
    
    `ShutdownComplete` now bypasses rollout persistence after thread
    shutdown and is delivered directly to clients. The shutdown path still
    records the protocol event in the rollout trace before delivery,
    preserving trace visibility without attempting a post-shutdown
    thread-store append.
    
    The change also adds a regression test with the in-memory thread store
    to assert that shutdown creates and shuts down the live thread without
    appending another item after shutdown.
  • Clarify PR template invitation requirement (#19912)
    Addresses #19856
    
    ## Summary
    - Clarifies that external code contributions are invitation only.
    - Points contributors to `docs/contributing.md` for the full policy
    instead of using the previous warning phrasing.
  • [codex-analytics] include user agent in default headers (#17689)
    ## Summary
    Adds the standard Codex `User-Agent` to shared default headers so the
    responses-api WS handshake carries the same client OS and version
    context as HTTP requests.
    
    ## Testing
    - `cargo test -p codex-core
    build_ws_client_metadata_includes_window_lineage_and_turn_metadata`
    - `cargo test -p codex-core --test all responses_websocket`
  • refactor: load agent identity runtime eagerly (#19763)
    ## Summary
    
    AgentIdentity auth previously registered the process task lazily behind
    a `OnceCell`. That meant the auth object could be constructed before its
    runtime task binding was known.
    
    This PR makes AgentIdentity auth load the runtime task at auth load time
    and stores the resulting process task id directly on the auth object.
    The model-provider call path can then read a concrete task id instead of
    handling a missing lazy value.
    
    ## Stack
    
    1. [refactor: make auth loading
    async](https://github.com/openai/codex/pull/19762) (merged)
    2. **This PR:** [refactor: load AgentIdentity runtime
    eagerly](https://github.com/openai/codex/pull/19763)
    3. [fix: configure AgentIdentity AuthAPI base
    URL](https://github.com/openai/codex/pull/19904)
    4. [feat: verify AgentIdentity JWTs with
    JWKS](https://github.com/openai/codex/pull/19764)
    
    ## Important call sites
    
    | Area | Change |
    | --- | --- |
    | `AgentIdentityAuth::load` | Registers the process task during auth
    loading and stores `process_task_id`. |
    | `CodexAuth::from_agent_identity_jwt` | Awaits AgentIdentity auth
    loading. |
    | model-provider auth | Reads a concrete `process_task_id` instead of an
    optional lazy value. |
    | AgentIdentity auth tests | Mock task registration now covers eager
    runtime allocation. |
    
    ## Design decisions
    
    AgentIdentity auth now treats task registration as part of constructing
    a usable auth object. That matches how callers use the value: once auth
    is present, the model-provider path expects the task-scoped assertion
    data to be ready.
    
    ## Testing
    
    Tests: targeted Rust auth test compilation, formatter, scoped Clippy
    fix, and Bazel lock check.
  • 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.
  • Stabilize plugin MCP fixture tests (#19452)
    ## Why
    
    Recent `main` CI had repeated flakes in the plugin fixture tests:
    
    - `codex-core::all
    suite::plugins::explicit_plugin_mentions_inject_plugin_guidance` failed
    in runs
    [24909500958](https://github.com/openai/codex/actions/runs/24909500958),
    [24908076251](https://github.com/openai/codex/actions/runs/24908076251),
    [24906197645](https://github.com/openai/codex/actions/runs/24906197645),
    and
    [24898949647](https://github.com/openai/codex/actions/runs/24898949647).
    - `codex-core::all suite::plugins::plugin_mcp_tools_are_listed` failed
    in runs
    [24909500958](https://github.com/openai/codex/actions/runs/24909500958),
    [24908076251](https://github.com/openai/codex/actions/runs/24908076251),
    and
    [24898949647](https://github.com/openai/codex/actions/runs/24898949647).
    
    The failures were in the same plugin/MCP fixture family: assertions
    expected sample plugin guidance or tool inventory, but the test could
    observe the session before the sample MCP server had finished startup.
    
    ## Root Cause
    
    `explicit_plugin_mentions_inject_plugin_guidance` submitted the user
    turn immediately after constructing the session. MCP startup is
    asynchronous, so on a slower or busier CI runner the prompt could be
    built before the sample plugin MCP server had reported its tools. That
    made the test depend on scheduler timing rather than the fixture being
    ready.
    
    `plugin_mcp_tools_are_listed` already needed the same readiness
    condition, but its wait logic was local to that test.
    
    ## What Changed
    
    - Added a shared `wait_for_sample_mcp_ready` helper for the plugin
    fixture tests.
    - Wait for `McpStartupComplete` before submitting the explicit plugin
    mention turn.
    - Reuse the same readiness helper in the MCP tool-listing test.
    
    ## Why This Should Be Reliable
    
    The tests now wait for the explicit readiness signal from the sample MCP
    server before asserting guidance or tools derived from that server. This
    removes the startup race while still exercising the real fixture path,
    so the assertions should only run after the plugin inventory is
    deterministic.
    
    ## Verification
    
    - `cargo test -p codex-core --test all plugins::`
    - GitHub CI for this PR is passing.
  • Refactor exec-server filesystem API into codex-file-system (#19892)
    ## Summary
    - Extracted the shared filesystem types and `ExecutorFileSystem` trait
    into a new `codex-file-system` crate
    - Switched `codex-config` and `codex-git-utils` to depend on that crate
    instead of `codex-exec-server`
    - Kept `codex-exec-server` re-exporting the same API for existing
    callers
    
    ## Testing
    - Ran `cargo test -p codex-file-system`
    - Ran `cargo test -p codex-git-utils`
    - Ran `cargo test -p codex-config`
    - Ran `cargo test -p codex-exec-server`
    - Ran `just fix -p codex-file-system`, `just fix -p codex-git-utils`,
    `just fix -p codex-config`, `just fix -p codex-exec-server`
    - Ran `just fmt`
    - Updated and verified the Bazel module lockfile
  • disallow fileparams metadata for custom mcps (#19836)
    ## Summary
    Disallow fileParams metadata for custom MCPs 
    
    Restricts Codex openai/fileParams handling to the first-party codex_apps
    MCP server. Custom MCP servers may still advertise the metadata, but
    Codex now ignores it for upload rewriting, preventing non-Apps tools
    from receiving signed OpenAI file refs for local paths. Added a
    regression test for the allowed and denied cases.
  • permissions: derive config defaults as profiles (#19772)
    ## Why
    
    This continues the permissions migration by making legacy config default
    resolution produce the canonical `PermissionProfile` first. The legacy
    `SandboxPolicy` projection should stay available at compatibility
    boundaries, but config loading should not create a legacy policy just to
    immediately convert it back into a profile.
    
    Specifically, when `default_permissions` is not specified in
    `config.toml`, instead of creating a `SandboxPolicy` in
    `codex-rs/core/src/config/mod.rs` and then trying to derive a
    `PermissionProfile` from it, we use `derive_permission_profile()` to
    create a more faithful `PermissionProfile` using the values of
    `ConfigToml` directly.
    
    This also keeps the existing behavior of `sandbox_workspace_write` and
    extra writable roots after #19841 replaced `:cwd` with `:project_roots`.
    Legacy workspace-write defaults are represented as symbolic
    `:project_roots` write access plus symbolic project-root metadata
    carveouts. Extra absolute writable roots are still added directly and
    continue to get concrete metadata protections for paths that exist under
    those roots.
    
    The platform sandboxes differ when a symbolic project-root subpath does
    not exist yet.
    
    * **Seatbelt** can encode literal/subpath exclusions directly, so macOS
    emits project-root metadata subpath policies even if `.git`, `.agents`,
    or `.codex` do not exist.
    * **bwrap** has to materialize bind-mount targets. Binding `/dev/null`
    to a missing `.git` can create a host-visible placeholder that changes
    Git repo discovery. Binding missing `.agents` would not affect Git
    discovery, but it would still create a host-visible project metadata
    placeholder from an automatic compatibility carveout. Linux therefore
    skips only missing automatic `.git` and `.agents` read-only metadata
    masks; missing `.codex` remains protected so first-time project config
    creation goes through the protected-path approval flow. User-authored
    `read` and `none` subpath rules keep normal bwrap behavior, and `none`
    can still mask the first missing component to prevent creation under
    writable roots.
    
    ## What Changed
    
    - Adds profile-native helpers for legacy workspace-write semantics,
    including `PermissionProfile::workspace_write_with()`,
    `FileSystemSandboxPolicy::workspace_write()`, and
    `FileSystemSandboxPolicy::with_additional_legacy_workspace_writable_roots()`.
    - Makes `FileSystemSandboxPolicy::workspace_write()` the single legacy
    workspace-write constructor so both `from_legacy_sandbox_policy()` and
    `From<&SandboxPolicy>` include the project-root metadata carveouts.
    - Removes the no-carveout `legacy_workspace_write_base_policy()` path
    and the `prune_read_entries_under_writable_roots()` cleanup that was
    only needed by that split construction.
    - Adds `ConfigToml::derive_permission_profile()` for legacy sandbox-mode
    fallback resolution; named `default_permissions` profiles continue
    through the permissions profile pipeline instead of being reconstructed
    from `sandbox_mode`.
    - Updates `Config::load()` to start from the derived profile, validate
    that it still has a legacy compatibility projection, and apply
    additional writable roots directly to managed workspace-write filesystem
    policies.
    - Updates Linux bwrap argument construction so missing automatic
    `.git`/`.agents` symbolic project-root read-only carveouts are skipped
    before emitting bind args; missing `.codex`, user-authored `read`/`none`
    subpath rules, and existing missing writable-root behavior are
    preserved.
    - Adds coverage that legacy workspace-write config produces symbolic
    project-root metadata carveouts, extra legacy workspace writable roots
    still protect existing metadata paths such as `.git`, and bwrap skips
    missing `.git`/`.agents` project-root carveouts while preserving missing
    `.codex` and user-authored missing subpath rules.
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19772).
    * #19776
    * #19775
    * #19774
    * #19773
    * __->__ #19772
  • Streamline review and feedback handlers (#19498)
    ## Why
    
    The remaining review, interrupt, fuzzy search, feedback, and git-diff
    handlers still had local send-error branches that obscured otherwise
    simple request handling. This final slice flattens those handlers
    without changing the public protocol behavior.
    
    ## What Changed
    
    - Streamlined review start, turn interrupt, fuzzy search session,
    feedback upload, and git diff handlers in
    `codex-rs/app-server/src/codex_message_processor.rs`.
    - Converted validation and upload failures into returned JSON-RPC errors
    where that avoids nested `send_error`/`return` blocks.
    - Left unrelated sandbox setup and notification code untouched.
    
    ## Verification
    
    - `cargo check -p codex-app-server`
    - `cargo test -p codex-app-server --test all v2::review --
    --test-threads=1`
  • Add MCP app feature flag (#19884)
    ## Summary
    - Add the `enable_mcp_apps` feature flag to the `codex-features`
    registry
    - Keep it under development and disabled by default
    
    ## Testing
    - Unit tests for `codex-features` passed
    - Formatting passed
  • 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
  • Streamline turn and realtime handlers (#19497)
    ## Why
    
    Turn and realtime handlers had nested validation and send-error branches
    that made the request path longer than the behavior warranted. This
    slice keeps the same request semantics while letting the handlers return
    errors from the failing step.
    
    ## What Changed
    
    - Streamlined turn start, injected item, and turn steer request handling
    in `codex-rs/app-server/src/codex_message_processor.rs`.
    - Applied the same result-returning shape to realtime session response
    handlers.
    - Preserved existing request validation and thread-manager interactions.
    
    ## Verification
    
    - `cargo check -p codex-app-server`
    - `cargo test -p codex-app-server --test all v2::turn_start --
    --test-threads=1`
    - `cargo test -p codex-app-server --test all v2::turn_steer --
    --test-threads=1`
    - `cargo test -p codex-app-server --test all v2::thread_inject_items --
    --test-threads=1`
  • Streamline thread resume and fork handlers (#19495)
    ## Why
    
    Thread resume and fork had some of the deepest error-handling
    indentation in this area because helpers emitted request errors
    directly. Returning those failures gives the handlers a single request
    boundary while preserving the async pending-resume behavior.
    
    ## What Changed
    
    - Converted thread resume helpers in
    `codex-rs/app-server/src/codex_message_processor.rs` to return `Result`
    values for validation and view loading failures.
    - Applied the same pattern to thread fork request handling.
    - Simplified pending resume error construction by using the shared
    JSON-RPC error helpers.
    
    ## Verification
    
    - `cargo check -p codex-app-server`
    - `cargo test -p codex-app-server --test all v2::thread_resume --
    --test-threads=1`
    - `cargo test -p codex-app-server --test all v2::thread_fork --
    --test-threads=1`
  • [codex] Trace cancelled inference streams (#19839)
    Records cancelled inference streams when Codex stops consuming a
    provider response before `response.completed`, preserving complete
    output items observed before cancellation.
    
    Also closes still-running inference calls when the owning turn ends, so
    reduced rollout traces do not leave stale `Running` inference nodes.
    
    Covered by focused reducer coverage and a core stream-drop test for
    partial output preservation.
  • Streamline thread read handlers (#19494)
    ## Why
    
    The thread read/list handlers mostly assemble views, but their error
    handling was interleaved with response emission. Returning view-building
    errors from the helper path keeps those handlers focused on data
    assembly.
    
    ## What Changed
    
    - Added a small mapper for `ThreadReadViewError` to JSON-RPC errors in
    `codex-rs/app-server/src/codex_message_processor.rs`.
    - Streamlined thread list, loaded-thread, read, turn-list, and summary
    handlers to produce result values for the request boundary.
    - Kept the existing invalid-request vs internal-error distinctions for
    missing or unreadable thread data.
    
    ## Verification
    
    - `cargo check -p codex-app-server`
    - `cargo test -p codex-app-server --test all conversation_summary --
    --test-threads=1`
  • Publish Python SDK with Codex-pinned versioning (#18996)
    **note**: a large chunk of this diff comes from regenerating Python
    types after app-server schema changes on `main`.
    
    This is PR 3 of 3 for the Python SDK PyPI publishing split. PR #18862
    refreshed the generated SDK surface, and PR #18865 made the runtime
    package publishable as `openai-codex-cli-bin`; this final PR makes the
    SDK package publishable as `openai-codex-app-server-sdk` and pins both
    packages to the same Codex runtime version.
    
    The key idea is that the published SDK version is the Codex runtime
    version. That one version now drives the SDK package version, the exact
    runtime dependency, the client version reported by the SDK, and the
    bootstrap runtime pin. This keeps release-time versioning in one lane
    instead of scattering checked-in literals through the package.
    
    ## What changed
    
    - Rename the SDK distribution from `codex-app-server-sdk` to
    `openai-codex-app-server-sdk` for conflict-free PyPI publishing.
    - Use `stage-sdk --codex-version ...` with one Codex version for both
    the SDK package version and exact `openai-codex-cli-bin` dependency.
    - Preserve hidden legacy `--runtime-version` / `--sdk-version` args only
    to reject mismatched versions during staging.
    - Map PEP 440 package versions back to Codex release tags for runtime
    setup downloads, e.g. `0.116.0a1` -> `rust-v0.116.0-alpha.1`.
    - Derive `codex_app_server.__version__`, the default
    `AppServerConfig.client_version`, and
    `_runtime_setup.pinned_runtime_version()` from the SDK package/project
    version instead of hardcoding duplicate version strings.
    - Carry the current generated SDK refresh from `main` so
    `generate-types` stays clean after recent app-server schema changes.
    - Update `sdk/python/uv.lock` for the renamed editable package.
    
    ## Validation
    
    - `uv run --extra dev pytest` in `sdk/python` -> 59 passed, 37 skipped.
    - Targeted `uv run ruff check` for the touched SDK files.
    - `git diff --check`.
    - Staged runtime with `--codex-version rust-v0.116.0-alpha.1
    --platform-tag macosx_11_0_arm64`.
    - Staged SDK with `--codex-version rust-v0.116.0-alpha.1`.
    - Built runtime wheel, SDK wheel, and SDK sdist.
    - `twine check /tmp/codex-python-pr3-build/dist/*` -> passed.
    - Clean venv smoke installed `openai-codex-app-server-sdk==0.116.0a1`
    from local dist and pulled `openai-codex-cli-bin==0.116.0a1`.
    - Smoke imports passed for `Codex` and `bundled_codex_path()`.
  • [codex] Shard exec Bazel integration test (#19862)
    ## Summary
    
    - shard `//codex-rs/exec:exec-all-test` into 8 Bazel shards
    - keep the existing `no-sandbox` test tag unchanged
    
    ## Why
    
    The Windows Bazel lane has been timing out this aggregated integration
    test target at the default 300s test timeout. The target runs the
    combined `codex-rs/exec/tests/all.rs` integration binary; sharding lets
    Bazel split the Rust test cases across parallel test actions instead of
    running the whole integration suite as one long action.
    
    ## Validation
    
    Not run locally, per the Codex repo workflow for development-phase
    changes.
    
    Co-authored-by: Codex <noreply@openai.com>
  • Streamline thread mutation handlers (#19493)
    ## Why
    
    Thread mutation handlers had many short error branches whose only job
    was to emit a JSON-RPC error and stop. This slice keeps those errors
    visible, but lets each handler build a result and return early from
    validation helpers instead of nesting the main path.
    
    ## What Changed
    
    - Streamlined thread archive/unarchive, rename, memory, metadata,
    rollback, compact, background terminal, shell, and guardian handlers in
    `codex-rs/app-server/src/codex_message_processor.rs`.
    - Reused shared JSON-RPC error constructors in
    `codex-rs/app-server/src/bespoke_event_handling.rs` for rollback-related
    request failures.
    - Preserved direct `send_error` calls where they remain the simplest
    boundary for pending async event responses.
    
    ## Verification
    
    - `cargo check -p codex-app-server`
    - `cargo test -p codex-app-server --test all v2::thread_rollback --
    --test-threads=1`
  • [codex-backend] Prefer state git metadata in filtered thread lists (#19874)
    ### Summary
    
    - `thread/list` filtered filesystem results already overlay state DB
    metadata, but the existing merge only filled missing git fields.
    - Prefer non-null SQLite git metadata over stale non-null rollout values
    so persisted branch/SHA/origin updates are reflected in filtered thread
    lists.
    - Update the focused merge test to cover stale filesystem git metadata
    being replaced by state-backed values.
    
    ### Testing
    
    now getting expected icons
    <img width="426" height="913" alt="Screenshot 2026-04-27 at 1 45 45 PM"
    src="https://github.com/user-attachments/assets/027fb7e7-f54d-4353-8423-cb76f3c8f5ac"
    />
  • Streamline thread start handler (#19492)
    ## Why
    
    The thread start handler mixed request validation, thread construction,
    dynamic-tool validation, and JSON-RPC error emission in one nested flow.
    Returning request errors from the helper path makes the successful setup
    path easier to follow.
    
    ## What Changed
    
    - Reworked `thread/start` handling in
    `codex-rs/app-server/src/codex_message_processor.rs` so helper methods
    return `Result` and the handler emits one result.
    - Moved dynamic-tool validation failures into returned JSON-RPC errors
    instead of local `send_error` branches.
    - Preserved the existing thread creation and task-spawning behavior.
    
    ## Verification
    
    - `cargo check -p codex-app-server`
    - `cargo test -p codex-app-server --test all v2::dynamic_tools --
    --test-threads=1`
    - `cargo test -p codex-app-server --test all v2::turn_start --
    --test-threads=1`
  • permissions: remove cwd special path (#19841)
    ## Why
    
    The experimental `PermissionProfile` API had both `:cwd` and
    `:project_roots` special filesystem paths, which made the permission
    root ambiguous. This PR removes the unstable `current_working_directory`
    special path before the permissions API is stabilized, so callers use
    `:project_roots` for symbolic project-root access.
    
    ## What changed
    
    - Removes `FileSystemSpecialPath::CurrentWorkingDirectory` from protocol
    and app-server protocol models, plus regenerated app-server
    JSON/TypeScript schemas.
    - Replaces internal `:cwd` permission entries with `:project_roots`
    entries.
    - Keeps the existing cwd-update behavior for legacy-shaped
    workspace-write profiles, while removing the deleted
    `CurrentWorkingDirectory` case from that compatibility path.
    - Keeps `PermissionProfile::workspace_write()` as the reusable symbolic
    workspace-write helper, with docs noting that `:project_roots` entries
    resolve at enforcement time.
    - Updates app-server docs/examples and approval UI labeling to stop
    advertising `:cwd` as a permission token.
    
    ## Compatibility
    
    Persisted rollout items may contain the old
    `{"kind":"current_working_directory"}` tag from earlier experimental
    `permissionProfile` snapshots. This PR keeps that tag as a
    deserialize-only alias for `ProjectRoots { subpath: None }`, while
    continuing to serialize only the new `project_roots` tag.
    
    ## Follow-up
    
    This PR intentionally does not introduce an explicit project-root set on
    `SessionConfiguration` or runtime sandbox resolution. Today, the
    resolver still uses the active cwd as the single implicit project root.
    A follow-up should model project roots separately from tool cwd so
    `:project_roots` entries can resolve against the configured project
    roots, and resolve to no entries when there are no project roots.
    
    ## Verification
    
    - `cargo test -p codex-protocol permissions:: --lib`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-sandboxing -p codex-exec-server --lib`
    - `cargo test -p codex-core session_configuration_apply_ --lib`
    - `cargo test -p codex-app-server
    command_exec_permission_profile_project_roots_use_command_cwd --test
    all`
    - `cargo test -p codex-tui
    thread_read_session_state_does_not_reuse_primary_permission_profile
    --lib`
    - `cargo test -p codex-tui
    preset_matching_accepts_workspace_write_with_extra_roots --lib`
    - `cargo test -p codex-config --lib`
  • Preserve TUI markdown list spacing after code blocks (#19706)
    ## Why
    
    Fixes #19702.
    
    The TUI markdown renderer could visually attach the next list marker to
    a fenced code block inside the previous list item, even when the source
    markdown included a blank line before the next item. That made
    block-heavy loose lists harder to read, while the desired behavior is
    still to keep simple lists compact.
    
    ## What changed
    
    - Track whether the current rendered list item contains a code block.
    - Preserve one blank separator before the following list marker only
    when the previous item contained a code block.
    - Add regression coverage for both paths: code-block list items keep the
    separator, and simple loose list items stay compact.
    
    ## Verification
    
    - `cargo test -p codex-tui markdown_render`
    
    I also manually verified that the bug exists before and is fixed after.
    
    ## Before
    <img width="437" height="240" alt="Screenshot 2026-04-26 at 1 19 01 PM"
    src="https://github.com/user-attachments/assets/3bc9d64d-2dba-40d9-9d6b-a1d0b3c0f728"
    />
    
    ## After
    <img width="410" height="269" alt="Screenshot 2026-04-26 at 1 18 54 PM"
    src="https://github.com/user-attachments/assets/19c15bee-da32-455e-a7cb-e05eb85f4ea0"
    />
  • Delay approval prompts while typing (#19513)
    ## Why
    
    Fixes #7744. Approval modals can currently appear while the user is
    typing ahead in the TUI composer, which lets plain letters like `y` or
    `a` get consumed as approval shortcuts instead of staying in the draft
    input.
    
    ## What changed
    
    - Track recent composer typing activity in `bottom_pane/mod.rs`.
    - Delay new approval overlays for 1 second while the composer is active,
    keeping delayed requests queued until the user is idle.
    - Preserve the existing active-overlay behavior so approvals that arrive
    while an approval modal is already open are still queued into that
    overlay.
    - Prune delayed approvals when app-server resolution says the request
    has already been handled.
    
    ## Verification
    
    Added unit coverage for immediate approvals, delayed approvals, idle
    deadline reset, typed shortcut letters staying in the composer, shortcut
    handling after the delay, and resolved delayed-request pruning.
    
    Focused `codex-tui` test groups pass locally. The full `cargo test -p
    codex-tui` run currently aborts in
    `app::tests::attach_live_thread_for_selection_rejects_unmaterialized_fallback_threads`;
    that same test also fails when run alone with the same stack overflow.
    
    Manual reviewer check:
    
    1. Start the TUI from the repo root:
    
       ```bash
       RUST_LOG=trace just codex \
         -c log_dir=<temp-log-dir> \
         --ask-for-approval untrusted \
         --sandbox workspace-write
       ```
    
    2. Submit this prompt:
    
       ```text
       create a file text.txt on my desktop
       ```
    
    3. While the agent is preparing the approval request, immediately type
    text such as `ya this should stay in the composer`.
    4. Confirm the typed-ahead `y`/`a` remains in the composer instead of
    approving the request.
    5. Stop typing for about 1 second; the approval modal should then
    appear.
    6. Once the modal is visible, press `y` and confirm the approval
    shortcut works normally.
  • Fix filtered thread-list resume regression in TUI (#19591)
    ## Why
    
    `codex resume` regressed after
    [#18502](https://github.com/openai/codex/pull/18502) changed the default
    `thread/list` scan-and-repair path for metadata-filtered listings. The
    TUI resume picker uses `thread/list` with source/provider/cwd filters
    and `useStateDbOnly: false`, which is the intended
    correctness-preserving mode: it should still consult the filesystem so
    healthy, missing, or stale SQLite state can be repaired.
    
    The regression was that #18502 made that filtered, filesystem-backed
    path call `reconcile_rollout` for every filesystem hit, and then call it
    again for each SQLite hit. When `reconcile_rollout` does not already
    have extracted rollout items, it falls back to loading the full JSONL
    rollout. That changed the resume picker’s first page from a cheap
    rollout-head scan plus SQLite read-repair into full-file reads for large
    sessions, so a few long threads could dominate TUI startup/resume
    latency.
    
    This change addresses the regression by keeping `useStateDbOnly: false`
    on the correctness-preserving path while avoiding unnecessary full JSONL
    reads for rows the filesystem scan has already validated.
    Source/provider/cwd filters can be decided from rollout-head metadata,
    so non-search resume listings only need the lightweight read-repair path
    for filesystem hits. Full reconciliation is still used for DB-only
    filtered rows because those can be stale false positives, and for search
    listings because search can depend on title metadata that may require
    scanning the full rollout.
    
    This fixes #19483.
    
    ## What changed
    
    - For non-search filtered listings, repair filesystem hits with the
    lightweight `read_repair_rollout_path` path instead of full
    `reconcile_rollout`.
    - Track thread IDs proven by the filesystem scan and only fully
    reconcile SQLite-filtered hits that the filesystem scan did not return,
    preserving stale-DB false-positive cleanup without full-reading every
    healthy rollout.
    - Leave search listings on full reconciliation, since search depends on
    full title metadata rather than only source/provider/cwd metadata from
    the rollout head.
    
    ## Verification
    
    - `cargo test -p codex-rollout list_threads`
    - `cargo test -p codex-app-server thread_list`
  • Cap original-detail image token estimates (#19865)
    Clamp original-detail image patch estimates to the current 10k patch
    budget so large images cannot inflate local context accounting without
    bound. Add regression coverage for an over-budget image.
    
    Fixes openai/codex#19806.