Commit Graph

5896 Commits

  • 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.
  • fix: filter dynamic deferred tools from model_visible_specs (#19771)
    fixes #19486
    
    ### Problem
    Right now dynamic deferred tools are filtered at normal-turn prompt
    building time, rather than upstream while building the `ToolRouter`
    itself. This causes issues because dynamic deferred tools are then
    wrongly included in the router's `model_visible_specs`, which is what
    the compaction request-building flow relies on.
    
    ### Fix
    Move the dynamic deferred tool filtering to `ToolRouter` creation time
    to solve this problem for every request that relies on `ToolRouter` for
    `model_visible_specs`, which solves the issue generically.
    
    ### Tests
    Added unit + integration tests to ensure dynamic deferred tools are
    omitted from `model_visible_specs` and compaction request respectively.
    
    Tested against live `/compact` endpoint; raw deferred dynamic tools
    without `tool_search` returned `400` (current bug), while the filtered
    payload (this fix) returns `200`.
  • Streamline account and command handlers (#19491)
    ## Why
    
    Account login/logout and command exec handlers were doing local error
    sends in the middle of each handler. That made these request flows
    branch heavily even though most of the logic is validate, perform the
    operation, and return the response.
    
    ## What Changed
    
    - Converted ChatGPT/API-key login, login cancel, logout, rate-limit, and
    add-credit handlers in
    `codex-rs/app-server/src/codex_message_processor.rs` to compute `Result`
    values and send them once at the request boundary.
    - Applied the same shape to command exec start/write/resize/terminate
    handlers.
    - Kept side-effect notifications in the same places after successful
    request handling.
    
    ## Verification
    
    - `cargo check -p codex-app-server`
    - `cargo test -p codex-app-server --test all v2::account --
    --test-threads=1`
    - `cargo test -p codex-app-server --test all v2::command_exec --
    --test-threads=1`
  • ci: migrate Bazel setup away from archived setup-bazelisk (#19851)
    ## Why
    
    All Bazel CI jobs are currently blocked in the `setup-bazelisk` step
    while trying to download Bazelisk.
    [`bazelbuild/setup-bazelisk`](https://github.com/bazelbuild/setup-bazelisk)
    is archived, and its README now recommends migrating to
    [`bazel-contrib/setup-bazel`](https://github.com/bazel-contrib/setup-bazel),
    so leaving our workflows on the archived action leaves CI exposed to
    exactly this sort of outage.
    
    Because `v8-canary` now consumes the shared local `setup-bazel-ci`
    action, that workflow also needs to trigger when the action changes.
    Without that follow-up, Bazel bootstrap regressions specific to the V8
    canary path could be skipped by the workflow path filters.
    
    ## What Changed
    
    - Switched `.github/actions/setup-bazel-ci/action.yml` from
    `bazelbuild/setup-bazelisk` to `bazel-contrib/setup-bazel`, pinned to
    `0.19.0`.
    - Left `bazelisk-version` unset so GitHub-hosted runners can use their
    preinstalled Bazelisk instead of downloading `1.x` at job start.
    - Updated `.github/workflows/rusty-v8-release.yml` and
    `.github/workflows/v8-canary.yml` to use the shared `setup-bazel-ci`
    action instead of referencing `setup-bazelisk` directly.
    - Added `.github/actions/setup-bazel-ci/**` to the `pull_request` and
    `push` path filters in `.github/workflows/v8-canary.yml` so changes to
    the shared Bazel setup action still run the canary workflow.
    - Kept the existing repository-cache and Windows-specific Bazel setup
    logic intact.
    
    This keeps Bazel version selection anchored by `.bazelversion` while
    removing the failing dependency on the archived setup action.
    
    ## Verification
    
    - Searched `.github/` to confirm there are no remaining `setup-bazelisk`
    references.
    - Parsed the updated workflow and action YAML locally with Ruby's
    `YAML.load_file`.
  • ci: pin npm staging smoke test to a recent rust-release run (#19854)
    ## Why
    
    The `build-test` workflow stages a representative `codex` npm tarball by
    asking `scripts/stage_npm_packages.py` to look up a past `rust-release`
    run for a hardcoded release version. That started failing in CI because
    the representative version in `.github/workflows/ci.yml` was stale:
    
    - the workflow was still using `0.115.0`
    - `stage_npm_packages.py` resolves native artifacts by looking for a
    `rust-release` run on the `rust-v<version>` branch
    - that lookup no longer found a matching run for `rust-v0.115.0`, so the
    smoke test failed before it could stage the package
    
    This PR makes that smoke test depend on a known-good recent release run
    instead of an older branch lookup that is no longer reliable.
    
    ## What Changed
    
    - Updated the representative release version in
    `.github/workflows/ci.yml` from `0.115.0` to `0.125.0`.
    - Added an explicit `WORKFLOW_URL` pointing at a recent successful
    `rust-release` run:
    `https://github.com/openai/codex/actions/runs/24901475298`.
    - Passed that URL to `scripts/stage_npm_packages.py` via
    `--workflow-url` so the job can reuse the expected native artifacts
    directly instead of relying on `gh run list --branch rust-v<version>` to
    discover them.
    
    That keeps the npm staging smoke test representative while making it
    less sensitive to older release branch history disappearing from the
    GitHub Actions lookup path.
    
    ## Verification
    
    - Inspected the failing CI log from `build-test` and confirmed the
    failure came from `scripts/stage_npm_packages.py` being unable to
    resolve `rust-v0.115.0`.
    - Confirmed that
    `https://github.com/openai/codex/actions/runs/24901475298` is a
    successful `rust-release` run for `rust-v0.125.0`.
  • refactor: make auth loading async (#19762)
    ## Summary
    
    Auth loading used to expose synchronous construction helpers in several
    places even though some auth sources now need async work. This PR makes
    the auth-loading surface async and updates the callers to await it.
    
    This is intentionally only plumbing. It does not change how
    AgentIdentity tokens are decoded, how task runtime ids are allocated, or
    how JWT signatures are verified.
    
    ## Stack
    
    1. **This PR:** [refactor: make auth loading
    async](https://github.com/openai/codex/pull/19762)
    2. [refactor: load AgentIdentity runtime
    eagerly](https://github.com/openai/codex/pull/19763)
    3. [feat: verify AgentIdentity JWTs with
    JWKS](https://github.com/openai/codex/pull/19764)
    
    ## Important call sites
    
    | Area | Change |
    | --- | --- |
    | `codex-login` auth loading | `CodexAuth` and `AuthManager`
    construction paths now await auth loading. |
    | app-server startup | Auth manager construction is awaited during
    initialization. |
    | CLI/TUI/exec/MCP/chatgpt callers | Existing auth-loading calls now
    await the same behavior. |
    | cloud requirements storage loader | The loader becomes async so it can
    share the same auth construction path. |
    | auth tests | Tests that load auth now run in async contexts. |
    
    ## Testing
    
    Tests: targeted Rust auth test compilation, formatter, scoped Clippy
    fix, and Bazel lock check.
  • Streamline plugin, apps, and skills handlers (#19490)
    ## Why
    
    The plugin, app, and skills handlers had a lot of repeated
    `send_error`/`return` branches that made the success path hard to scan.
    This slice keeps behavior the same while moving fallible steps into
    local response-producing helpers, so the request boundary can send one
    result.
    
    ## What Changed
    
    - Converted plugin list/install/uninstall handlers in
    `codex-rs/app-server/src/codex_message_processor/plugins.rs` to return
    `Result<*Response, JSONRPCErrorError>` from helper methods and call
    `send_result` once.
    - Added local error-mapping helpers for plugin install/uninstall and
    marketplace failures.
    - Applied the same mechanical shape to app list, skills list/config, and
    marketplace add/remove/upgrade handlers in
    `codex-rs/app-server/src/codex_message_processor.rs`.
    
    ## Verification
    
    - `cargo check -p codex-app-server`
    - `cargo test -p codex-app-server --test all v2::app_list --
    --test-threads=1`
    - `cargo test -p codex-app-server --test all v2::plugin_ --
    --test-threads=1`
    - `cargo test -p codex-app-server --test all v2::skills_list --
    --test-threads=1`
  • Render delegated patch approval details (#19709)
    ## Why
    
    Fixes #19632.
    
    When a delegated agent requests approval for an in-progress file change,
    the parent TUI handles that request from an inactive thread. The app
    server already sent the `FileChange` item with the proposed diff, but
    the inactive-thread approval path was not recovering and rendering it
    the same way as the active-thread path.
    
    The result was an inconsistent approval prompt: main-thread edits show a
    normal patch preview history item before the approval modal, while
    delegated edits did not show that preview in the transcript flow.
    
    ## What Changed
    
    - Recover buffered or historical `FileChange` item changes when building
    inactive-thread file-change approval requests.
    - Reuse the app-server file-change conversion helper for both live
    transcript rendering and inactive-thread approvals.
    - Render recovered delegated patches as a normal patch preview history
    cell before the approval modal.
    - Keep apply-patch approval modals focused on the decision prompt and
    optional metadata; they do not render a synthetic command line or embed
    the diff body.
    
    ## Manual Repro And Verification
    
    I manually reproduced the issue using a file under `~/Desktop` so the
    write would require approval.
    
    Before the fix:
    
    1. Ask the main thread: `Use apply_patch, not shell redirection or
    Python, to create ~/Desktop/bug1.txt with three short lines.`
    2. Observe the expected TUI shape: the transcript shows a normal patch
    preview such as `• Added ~/Desktop/bug1.txt (+N -0)` above the approval
    modal, and the modal contains only the approval prompt/options without a
    synthetic command line.
    3. Ask for the delegated path: `Spawn a worker. Have it use apply_patch,
    not shell redirection or Python, to create ~/Desktop/bug1.txt with four
    short lines.`
    4. Observe the delegated approval is inconsistent: the parent view does
    not render the proposed patch as the normal transcript preview before
    the modal, so the diff context is missing from the stream or appears
    inside the modal instead of in the history flow.
    
    After the fix:
    
    1. Repeat the delegated worker prompt with `apply_patch`.
    2. Confirm the parent view renders the same normal patch preview history
    cell (`• Added ~/Desktop/bug1.txt (+N -0)` plus the diff) immediately
    before the approval modal.
    3. Confirm the approval modal remains focused on the decision prompt.
    For delegated approvals it may show the worker thread label, but it
    should not show a `$ apply_patch` command line or embed the diff body in
    the modal.
  • Persist shell mode commands in prompt history (#19618)
    ## Why
    
    `!` shell commands are currently surfaced as "Bash mode", which is
    misleading for users running shells such as PowerShell or zsh. Those
    commands also bypass the persistent prompt history path, so they cannot
    be recalled after starting a new session.
    
    Fixes #19613.
    
    ## What changed
    
    - Rename the TUI footer label and related test wording from "Bash mode"
    to "Shell mode".
    - Persist accepted `!` shell commands to prompt history with the leading
    `!`, so recall restores the composer into shell mode across sessions.
    - Add coverage for immediate and queued shell-command submissions
    emitting the prompt-history update.
    
    ## Verification
    
    - `cargo test -p codex-tui bang_shell`
    - `cargo test -p codex-tui shell_command_uses_shell_accent_style`
    - `cargo test -p codex-tui footer_mode_snapshots`
    - `cargo insta pending-snapshots --manifest-path tui/Cargo.toml`
    
    Manually verified fix after confirming presence of bug prior to fix.
  • Hide rewind preview when no user message exists (#19510)
    ## Why
    
    Fixes #19508.
    
    In a fresh TUI session, pressing `Esc` twice entered the rewind
    transcript overlay even though there was no user message to rewind to.
    That produced an empty header-only transcript view and exposed a rewind
    flow that could not select a valid target.
    
    ## What changed
    
    The backtrack flow now checks whether a user-message rewind target
    exists before opening the transcript preview. If no target exists, Codex
    stays in the main TUI and shows `No previous message to edit.` instead
    of opening an empty overlay.
    
    The same guard applies when starting rewind preview from the transcript
    overlay, and the first `Esc` no longer advertises the “edit previous
    message” hint when there is no previous message available.
    
    Snapshot coverage was added for the unavailable rewind info message,
    along with a small target-detection test.
  • chore: split memories part 1 (#19818)
    Extract memories into 2 different crates