Commit Graph

982 Commits

  • Use selected turn environments for runtime context (#20281)
    ## Summary
    - make selected turn environments the source of truth for session
    runtime cwd and MCP runtime environment selection
    - keep local/no-selection fallback behavior intact
    - add coverage for duplicate selected environments, cwd resolution, and
    MCP runtime environment selection
    
    ## Validation
    - git diff --check
    - rustfmt was run on touched Rust files during the implementation
    workflow
    
    CI should provide the full Bazel/test signal.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [codex] Migrate loaded thread/read history to ThreadStore (#20486)
    ## Summary
    
    - Route loaded `thread/read` + `includeTurns` through
    `CodexThread::load_history` / ThreadStore history instead of direct
    rollout JSONL reads.
    - Add an in-memory ThreadStore regression test covering loaded
    `thread/read includeTurns` without a local rollout path.
  • app-server: move transport into dedicated crate (#20545)
    ## Why
    
    `codex-app-server` currently owns both request-processing code and
    transport implementation details. Splitting the transport layer into its
    own crate makes that boundary explicit, reduces the amount of
    transport-specific dependency surface carried by `codex-app-server`, and
    gives future transport work a narrower place to evolve.
    
    ## What changed
    
    - Added `codex-app-server-transport` and moved the existing transport
    tree into it, including stdio, unix socket, websocket, remote-control
    transport, and websocket auth.
    - Moved shared transport-facing message types into the new crate so both
    the transport implementation and `codex-app-server` use the same
    definitions.
    - Kept processor-facing connection state and outbound routing in
    `codex-app-server`, with the routing tests moved next to that local
    wrapper.
    - Updated workspace metadata, Bazel crate metadata, and
    `codex-app-server` dependencies for the new crate boundary.
    
    ## Validation
    
    - `cargo metadata --locked --no-deps`
    - `git diff --check`
    - Attempted `cargo test -p codex-app-server-transport`, `cargo test -p
    codex-app-server`, `just fix -p codex-app-server-transport`, and `just
    fix -p codex-app-server`; all were blocked before compilation by the
    existing `packageproxy` resolution failure for locked `rustls-webpki =
    0.103.13`.
    - Attempted Bazel build / lockfile validation; those were blocked by
    external fetch failures against BuildBuddy / GitHub while resolving
    `v8`.
  • Move apply-patch file changes into turn items (#20540)
    ## Why
    
    Apply-patch file changes are now part of the core turn item stream, so
    v2 clients can consume the same first-class item lifecycle path used by
    other turn items instead of relying on app-server-specific remapping
    from legacy patch events.
    
    ## What changed
    
    - Added a core `TurnItem::FileChange` carrying apply-patch changes and
    completion metadata.
    - Updated the apply-patch tool emitter to send `ItemStarted` /
    `ItemCompleted` with the new `FileChange` item while preserving legacy
    `PatchApplyBegin` / `PatchApplyEnd` fan-out.
    - Updated app-server v2 conversion to render the new core item directly
    and stopped `event_mapping` from remapping old patch begin/end events
    into item notifications.
    - Kept thread history reconstruction based on the existing old
    apply-patch events for rollout compatibility.
    
    ## Verification
    
    - `cargo test -p codex-protocol -p codex-app-server-protocol`
    - `cargo test -p codex-core --test all
    apply_patch_tool_executes_and_emits_patch_events`
    - `cargo test -p codex-app-server bespoke_event_handling`
  • feat: Track local paths for shared plugins (#20560)
    When a local plugin is shared, Codex now records the local plugin path
    by remote plugin id under CODEX_HOME/.tmp.
    
    plugin/share/list includes the remote share URL and the matching local
    plugin path when available, and plugin/share/delete
    clears the local mapping after deleting the remote share.
    
    Also add sharedURL to plugin/share/list.
  • Add remote plugin skill read API (#20150)
    ## Summary
    
    Adds an app-server `plugin/skill/read` method for remote plugin skill
    markdown. The new method calls the plugin-service skill detail endpoint
    and returns `skill_md_contents`, so clients can preview skills for
    remote plugins before the bundle is installed locally.
    
    ## Why
    
    Uninstalled remote plugin skills do not have local `SKILL.md` files.
    Without an on-demand remote read, the desktop plugin details UI cannot
    render the skill details modal for those skills.
    
    ## Validation
    
    - `just write-app-server-schema`
    - `just fmt`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server --test all --
    suite::v2::plugin_read::plugin_skill_read_reads_remote_skill_contents_when_remote_plugin_enabled
    --exact`
    - `just fix -p codex-app-server-protocol -p codex-core-plugins -p
    codex-app-server`
  • Refresh remote plugin cache on auth changes (#20265)
    ## Summary
    - Refresh the remote installed-plugin cache after login/logout instead
    of keying it by account or eagerly clearing it.
    - Reuse the existing single-flight remote installed refresh loop so
    newer queued auth refreshes replace older pending requests and the API
    result eventually overwrites or clears the cache.
    - Keep derived plugin/skills cache and MCP refresh side effects behind
    the existing effective-plugin-changed task when the refreshed installed
    state changes.
    - Leave `clear_plugin_related_caches` scoped to derived plugin/skills
    caches so share mutations do not drop remote installed plugins.
    
    ## Tests
    - `cargo fmt --all --manifest-path codex-rs/Cargo.toml` (passes; stable
    rustfmt warns that `imports_granularity = Item` is nightly-only)
    - `cargo test -p codex-core-plugins remote_installed_cache`
    - `cargo test -p codex-app-server
    skills_list_loads_remote_installed_plugin_skills_from_cache`
  • Make thread store process-scoped (#19474)
    - Build one app-server process ThreadStore from startup config and share
    it with ThreadManager and CodexMessageProcessor.
    - Remove per-thread/fork store reconstruction so effective thread config
    cannot switch the persistence backend.
    - Add params to ThreadStore create/resume for specifying thread
    metadata, since otherwise the metadata from store creation would be used
    (incorrectly).
  • Surface admin-disabled remote plugin status (#20298)
    ## Summary
    
    Remote plugin-service returns plugin availability separately from a
    user's installed/enabled state. This adds `PluginAvailabilityStatus` to
    the app-server protocol, propagates remote catalog `status` into
    `PluginSummary`, and rejects install attempts for remote plugins marked
    `DISABLED_BY_ADMIN` before downloading or caching the bundle.
    
    This is the `openai/codex` half of the change. The companion
    `openai/openai` webview PR is
    https://github.com/openai/openai/pull/873269.
    
    ## Validation
    
    - `cargo run -p codex-app-server-protocol --bin write_schema_fixtures`
    - `cargo test -p codex-app-server --test all
    plugin_list_marks_remote_plugin_disabled_by_admin`
    - `cargo test -p codex-app-server --test all
    plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled`
    - `cargo test -p codex-app-server --test all
    plugin_install_rejects_remote_plugin_disabled_by_admin_before_download`
    - `cargo test -p codex-app-server-protocol schema_fixtures`
  • app-server: switch remote control to protocol v3 segmentation (#20341)
    ## Why
    
    Remote-control protocol v3 makes segmentation an explicit wire-level
    feature. The app-server transport needs to support that protocol
    directly so large messages can be chunked, acknowledged, replayed, and
    reassembled consistently.
    
    ## What changed
    
    - Bump the remote-control websocket protocol version from `2` to `3`.
    - Add explicit client/server chunk envelope variants plus chunk-aware
    acknowledgements.
    - Split oversized outbound server messages into bounded transport
    chunks.
    - Reassemble ordered inbound client chunks with bounded memory usage and
    stream/client invalidation handling.
    - Track inbound chunk cursors and outbound ack cursors as `(seq_id,
    segment_id)` so duplicate chunks and partial replays behave correctly.
    - Add focused coverage for chunk splitting, reassembly, duplicate
    suppression, and stream replacement behavior.
    
    ## Validation
    
    - Added targeted unit coverage for segmented message handling in
    `remote_control`.
    - Local validation is currently blocked before compilation because
    `packageproxy` does not serve the locked `rustls-webpki 0.103.13`
    dependency required by the workspace.
  • Alias codex_hooks feature as hooks (#20522)
    # Why
    
    The hooks feature flag should use the concise canonical name `hooks`,
    while existing configs that still use `codex_hooks` continue to work
    during the rename.
    
    # What
    
    - change the canonical `Feature::CodexHooks` key from `codex_hooks` to
    `hooks`
    - register `codex_hooks` through the existing legacy-alias path
    - update the config schema and canonical config fixtures to prefer
    `hooks`
    - add regression coverage that both `hooks` and `codex_hooks` resolve to
    `Feature::CodexHooks`
    
    # Verification
    
    - `cargo test -p codex-features`
    - `cargo test -p codex-core config::schema_tests`
    - `cargo test -p codex-core
    pre_tool_use_blocks_shell_when_defined_in_config_toml`
    - `cargo test -p codex-app-server
    hooks_list_uses_each_cwds_effective_feature_enablement`
  • fix(app-server): mark thread/turns/list and exclude_turns as experime… (#20499)
    …ntal
    
    We have some bugs to work out and it is not quite ready to consume as a
    public API.
  • Emit analytics for remote plugin installs (#20267)
    ## Summary
    
    - emit `codex_plugin_installed` after a remote plugin install succeeds
    - keep local installs unchanged, but let remote installs override the
    analytics `plugin_id` with the backend remote plugin id
    (`plugins~Plugin_...`)
    - preserve the local/display identity in `plugin_name` and
    `marketplace_name`, plus capability metadata from the installed bundle
    - add regression coverage for local install analytics, remote install
    analytics, and analytics id override serialization
    
    ## Testing
    
    - `just fmt`
    - `cargo test -p codex-analytics`
    - `cargo test -p codex-app-server`
  • fix(tui): set persist_extended_history: false (#20502)
    Large rollouts are no good. This updates the TUI to behave the same as
    the Codex App, which is also turning it off.
  • Sync remote installed plugin bundles (#20268)
    ## Summary
    - Download missing remote installed plugin bundles during app-server
    startup and plugin/list refresh.
    - Upgrade cached remote installed bundles when the backend installed
    version changes.
    - Remove stale remote installed bundle caches without writing remote
    plugin state into config.toml.
    
    ## Review note
    This is a clean PR branch cut from the current diff on top of latest
    `origin/main`. The diff intentionally has no `codex-rs/core/**` files,
    so CODEOWNERS should not request the core-directory owner review from
    stale PR history.
    
    ## Validation
    Already run on the source branch before creating this clean PR:
    - `just fmt`
    - `cargo test -p codex-core-plugins`
    - `cargo test -p codex-app-server --test all
    app_server_startup_sync_downloads_remote_installed_plugin_bundles --
    --nocapture`
    - `cargo test -p codex-app-server --test all
    plugin_list_sync_upgrades_and_removes_remote_installed_plugin_bundles --
    --nocapture`
    - `cargo test -p codex-app-server --test all
    app_server_startup_remote_plugin_sync_runs_once -- --nocapture`
    - `just fix -p codex-core-plugins`
    - `just fix -p codex-app-server`
    - `git diff --check`
  • [codex] Migrate thread turns list to thread store (#19280)
    - migrate `thread/turns/list` to ThreadStore. Uses ThreadStore for most
    data now but merges in the in-memory state from thread manager
    - keep v2 `thread/list` pathless-store friendly by converting
    `StoredThread` directly to API `Thread`
    - add regression coverage for pathless store history/listing
  • Stop emitting item/fileChange/outputDelta output delta notifications (#20471)
    ## Why
    
    `item/fileChange/outputDelta` text output was only the tool's summary or
    error text and not used by client surfaces.
    
    We keep `item/fileChange/outputDelta` in the app-server protocol as a
    deprecated compatibility entry, but the server no longer emits it.
    
    ## What changed
    
    - stop the `apply_patch` runtime from emitting `ExecCommandOutputDelta`
    events
    - simplify `item_event_to_server_notification` so command output deltas
    always map to `item/commandExecution/outputDelta`
    - remove the app-server bookkeeping that tried to detect whether an
    output delta belonged to a file change
    - mark `item/fileChange/outputDelta` as a deprecated legacy protocol
    entry in the v2 types, schema, and README
    - simplify the file-change approval tests so they only wait for
    completion instead of expecting output-delta notifications
    
    ## Testing
    
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-thread-manager-sample`
    - `cargo test -p codex-app-server-protocol
    protocol::event_mapping::tests::exec_command_output_delta_maps_to_command_execution_output_delta
    -- --exact`
    - `cargo test -p codex-app-server
    turn_start_file_change_approval_accept_for_session_persists_v2 --
    --exact` *(failed before the test assertions because the wiremock
    `/responses` mock received 0 requests in setup)*
  • Move item event mapping into app-server-protocol (#20299)
    ## Why
    
    Follow-up to #20291.
    
    The v2 item-event-to-notification translation had been embedded in
    `app-server/src/bespoke_event_handling.rs`, which made it hard to reuse
    anywhere else. This PR moves that stateless mapping into shared protocol
    code so other entry points can produce the same `ServerNotification`
    payloads without copying app-server logic.
    
    That also lets `thread-manager-sample` demonstrate the same notification
    surface that the app server exposes, instead of only printing the final
    assistant message.
    
    ## What changed
    
    - move `item_event_to_server_notification` into
    `codex-app-server-protocol::protocol::event_mapping`
    - keep the mapper tests next to the shared implementation in
    `codex-app-server-protocol`
    - re-export the mapper from `codex-core-api` so lightweight consumers
    can use it without reaching into `app-server-protocol` directly
    - simplify `app-server/src/bespoke_event_handling.rs` so it delegates
    the stateless event-to-notification projection to the shared helper
    - update `thread-manager-sample` to:
      - print mapped notifications as newline-delimited JSON
      - use the shared mapper through `codex-core-api`
    - enable the default feature set so the sample exposes the normal tool
    surface
    - use a `read_only` permission profile so shell commands can run in the
    sample without widening permissions
    
    ## Testing
    
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-core-api`
    - `cargo test -p codex-app-server bespoke_event_handling::tests`
    - `cargo test -p codex-thread-manager-sample`
    - `cargo run -p codex-thread-manager-sample -- "briefly explore the repo
    with pwd and ls, then summarize it"`
  • fix(core): truncate large mcp tool outputs in rollouts (#20260)
    ## Why
    Large MCP tool call outputs can make rollout JSONL files enormous. In
    the session that motivated this change, the biggest JSONL records were:
    - `event_msg/mcp_tool_call_end`
    - `response_item/function_call_output`
    
    both containing the same unbounded MCP payloads - just 3 MCP tool calls
    that each were multi-hundred MBs 😱
    
    This PR truncates both of those JSONL records.
    
    ## How
    
    #### For `response_item/function_call_output`
    Unified exec already bounds tool output before it is injected into
    model-facing history, which also keeps the corresponding rollout
    `response_item/function_call_output` records small.
    
    MCP should follow the same pattern: truncate the model-facing tool
    output at the tool-output boundary, while leaving code-mode/raw hook
    consumers alone.
    
    #### For `event_msg/mcp_tool_call_end`
    `McpToolCallEnd` also needs its own bounded event copy because it is the
    app-server/replay/UI event shape that backs `ThreadItem::McpToolCall`.
    Unfortunately this is _not_ downstream of the `ToolOutput` trait.
    
    ## Model behavior 
    Model behavior is actually unchanged as a result of this PR. 
    
    Before this PR, MCP output was:
    1. Converted to `FunctionCallOutput`.
    2. Recorded into in-memory history.
    3. Truncated by `ContextManager::record_items()` before later model
    turns saw it.
    
    After this branch, MCP output is truncated earlier, in
    `McpToolOutput::response_payload()`, using the same helper. Then
    `ContextManager::record_items()` sees an already-truncated output and
    effectively has little/no additional work to do.
    
    So the model should still see the same kind of truncated function-call
    output. The practical difference is where truncation happens: earlier,
    before rollout persistence/app-server emission can see the giant
    payload.
    
    ## Verification
    
    - `cargo test -p codex-core mcp_tool_output`
    - `cargo test -p codex-core
    mcp_tool_call::tests::truncate_mcp_tool_result_for_event`
    - `cargo test -p codex-core
    mcp_post_tool_use_payload_uses_model_tool_name_args_and_result`
    - `just fmt`
    - `just fix -p codex-core`
    - `git diff --check`
  • realtime: rename provider session ids (#20361)
    ## Summary
    
    Codex is repurposing `session` to mean a thread group, so the realtime
    provider session id should no longer use `session_id` / `sessionId` in
    Codex-facing protocol payloads. This PR renames that provider-specific
    field to `realtime_session_id` / `realtimeSessionId` and intentionally
    breaks clients that still send the old field names.
    
    ## What Changed
    
    - Renamed realtime provider session fields in `ConversationStartParams`,
    `RealtimeConversationStartedEvent`, and `RealtimeEvent::SessionUpdated`.
    - Renamed app-server v2 realtime request and notification fields to
    `realtimeSessionId`.
    - Removed legacy serde aliases for `session_id` / `sessionId`; clients
    must send the new names.
    - Propagated the rename through core realtime startup, app-server
    adapters, codex-api websocket handling, and TUI realtime state.
    - Regenerated app-server protocol schema/TypeScript outputs and updated
    app-server README examples.
    - Kept upstream Realtime API concepts unchanged: provider `session.id`
    parsing and `x-session-id` headers still use the upstream wire names.
    
    ## Testing
    
    - CI is running on the latest pushed commit.
    - Earlier local verification on this PR:
      - `cargo test -p codex-protocol`
    - `CODEX_SKIP_VENDORED_BWRAP=1 cargo test -p codex-core
    realtime_conversation`
      - `cargo test -p codex-app-server-protocol`
    - `CODEX_SKIP_VENDORED_BWRAP=1 cargo test -p codex-app-server
    realtime_conversation`
    - attempted `CODEX_SKIP_VENDORED_BWRAP=1 cargo test -p codex-tui` (local
    linker bus error while linking the test binary)
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Make missing config clears no-ops (#20334)
    ## Why
    
    Fixes #20145.
    
    `config/value/write` treats a JSON `null` value as a request to clear
    the config key. Clearing a key that is already absent should be
    idempotent, but clearing a nested key such as `features.personality`
    from an empty `config.toml` returned `configPathNotFound` because
    `clear_path` treated the missing `features` parent table as an error.
    
    That makes app-server reset flows brittle because clients have to read
    first and avoid sending a clear request unless the parent path already
    exists.
    
    ## What Changed
    
    - Updated app-server config clearing so missing intermediate tables, or
    non-table parents, are treated as an unchanged no-op.
    - Removed the now-unreachable `MergeError::PathNotFound` path from
    config write merging.
    - Added a regression test covering `features.personality = null` against
    an empty user config.
    
    ## Verification
    
    - `cargo test -p codex-app-server clear_missing_nested_config_is_noop`
    - `cargo test -p codex-app-server` was run; the config manager unit
    suite passed, but one unrelated integration test failed because
    `turn_start_emits_thread_scoped_warning_notification_for_trimmed_skills`
    expected `7` trimmed skills and observed `8`.
    - `just fix -p codex-app-server`
  • feat: Add workspace plugin sharing APIs (#20278)
    1. Adds v2 plugin/share/save, plugin/share/list, and plugin/share/delete
    RPCs.
    2. Implements save by archiving a local plugin root, enforcing a size
    limit, uploading through the workspace upload flow, and supporting
    updates via remotePluginId.
    3. Lists created workspace plugins
    4. Deletes a previously uploaded/shared plugin.
  • Add persisted hook enablement state (#19840)
    ## Why
    
    After `hooks/list` exposes the hook inventory, clients need a way to
    persist user hook preferences, make those changes effective in
    already-open sessions, and distinguish user-controllable hooks from
    managed requirements without adding another bespoke app-server write
    API.
    
    ## What
    
    - Extends `hooks/list` entries with effective `enabled` state.
    - Persists user-level hook state under `hooks.state.<hook-id>` so the
    model can grow beyond a single boolean over time.
    - Uses the existing `config/batchWrite` path for hook state updates
    instead of introducing a dedicated hook write RPC.
    - Refreshes live session hook engines after config writes so
    already-open threads observe updated enablement without a restart.
    
    ## Stack
    
    1. openai/codex#19705
    2. openai/codex#19778
    3. This PR - openai/codex#19840
    4. openai/codex#19882
    
    ## Reviewer Notes
    
    The generated schema files account for much of the raw diff. The core
    behavior is in:
    
    - `hooks/src/config_rules.rs`, which resolves per-hook user state from
    the config layer stack.
    - `hooks/src/engine/discovery.rs`, which projects effective enablement
    into `hooks/list` from source-derived managedness.
    - `config/src/hook_config.rs`, which defines the new `hooks.state`
    representation.
    - `core/src/session/mod.rs`, which rebuilds live hook state after user
    config reloads.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Reduce the surface of collaboration modes (#20149)
    Collaboration modes were slightly invasive both into ThreadManager
    construction and ModelProvider
  • Import external agent sessions in background (#20284)
    Summary:
    - Return from external agent import before session history import
    finishes
    - Run session import work in the background and emit the existing
    completion notification when it is done
    - Serialize session imports so duplicate requests do not create
    duplicate imported threads
    
    Verification:
    - cargo test -p codex-app-server external_agent_config_
    - cargo test -p codex-external-agent-sessions
    - just fix -p codex-app-server
    - just fix -p codex-external-agent-sessions
    - git diff --check
  • Consume ai-title from external sessions and add end marker (#20261)
    ## Summary
    - Support Claude Code `ai-title` / `aiTitle` records when detecting and
    importing external agent sessions.
    - Preserve existing `custom-title` / `customTitle` precedence; only fall
    back to `aiTitle` when no custom title is present.
    - Add coverage for both detection and import title selection, including
    the custom-title-over-ai-title case.
    
    ## Testing
    - `cargo test -p codex-external-agent-sessions`
    - `just fix -p codex-external-agent-sessions`
  • Add hooks/list app-server RPC (#19778)
    ## Why
    
    We need a way to list the available hooks to expose via the TUI and App
    so users can view and manage their hooks
    
    ## What
    
    - Adds `hooks/list` for one or more `cwd` values that returns discovered
    hook metadata
    
    ## Stack
    
    1. openai/codex#19705
    2. This PR - openai/codex#19778
    3. openai/codex#19840
    4. openai/codex#19882
    
    ## Review Notes
    
    The generated schema files account for most of the raw diff, these files
    have the core change:
    
    - `hooks/src/engine/discovery.rs` builds the inventory entries during
    hook discovery while leaving runtime handlers focused on execution.
    - `app-server/src/codex_message_processor.rs` wires `hooks/list` into
    the app-server flow for each requested `cwd`.
    - `app-server-protocol/src/protocol/v2.rs` defines the new v2
    request/response payloads exposed on the wire.
    
    ### Core Changes
    
    `core/src/plugins/manager.rs` adds `plugins_for_layer_stack(...)` so
    `skills/list` and `hooks/list`can resolve plugin state for each
    requested `cwd`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Update Codex login success page UX (#20136)
    ## Summary
    
    update the local login success page to match the Codex desktop auth UX
    use theme-aware colors and an inline 20px Codex mark
    keep the actual localhost success page aligned with the browser auth UX
    PR
    
    ## Tests
    
    <img width="1728" height="1117" alt="Screenshot 2026-04-29 at 12 00
    34 PM"
    src="https://github.com/user-attachments/assets/76a40c3f-07c3-452c-97da-e7c43717cd2c"
    />
  • [mcp] Fix plugin MCP approval policy. (#19537)
    Plugin MCP servers are loaded from plugin manifests rather than
    top-level `[mcp_servers]`, so their tool approval preferences need to be
    stored and applied through the owning plugin config. Without this,
    choosing "Always allow" for a plugin MCP tool could write a preference
    that was not reliably used on later tool calls.
    
    ## Summary
    - Add plugin-scoped MCP policy config under
    `plugins.<plugin>.mcp_servers`, including server enablement, tool
    allow/deny lists, server defaults, and per-tool approval modes.
    - Overlay plugin MCP policy onto manifest-provided server configs when
    plugins are loaded.
    - Route persistent "Always allow" writes for plugin MCP tools back to
    the owning `plugins.<plugin>.mcp_servers.<server>.tools.<tool>` config
    entry.
    - Reload user config after persisting an approval and make the plugin
    load cache config-aware so stale plugin MCP policy is not reused after
    `config.toml` changes.
    - Regenerate the config schema and add coverage for plugin MCP policy
    loading, approval lookup, persistence, and stale-cache prevention.
    
    ## Testing
    - `cargo test -p codex-config`
    - `cargo test -p codex-core-plugins`
    - `cargo test -p codex-core --lib plugin_mcp`
  • [app-server] centralize client response analytics (#20059)
    ## Why
    
    The precursor PR keeps successful client responses typed until
    app-server's outgoing response seam. This follow-up uses that seam to
    move successful client-response analytics out of individual handlers and
    into the shared sender path, while keeping filtering decisions inside
    `codex-analytics`.
    
    ## What changed
    
    - Emit successful client-response analytics centrally from
    `OutgoingMessageSender::send_response`.
    - Remove duplicate handler-local response tracking for the current
    thread/turn lifecycle responses.
    - Keep analytics ingestion selective inside `AnalyticsEventsClient`, so
    unrelated client traffic is ignored before cloning or boxing.
    - Collapse client-response analytics facts onto one typed path and
    normalize payloads in the reducer.
    - Add direct client-filter coverage plus sender-level coverage for the
    centralized forwarding path.
    
    ## Verification
    
    - `cargo test -p codex-analytics`
    - `cargo test -p codex-app-server outgoing_message::tests --lib`
  • Require remote plugin detail before uninstall (#19966)
    ## Summary
    - Fetch remote plugin detail before sending the uninstall request.
    - Use the detail response to derive the marketplace namespace and plugin
    name for cache cleanup.
    - Stop the uninstall before the backend POST if detail lookup fails, so
    backend state and local cache state do not diverge.
    
    ## Testing
    - `just fmt`
    - `cargo test -p codex-app-server plugin_uninstall`
    - `cargo test -p codex-core-plugins`
    - `git diff --check`
  • [app-server] type client response payloads (#20050)
    ## Why
    
    `pr17088` adds typed server-originated request/response plumbing, but
    successful client responses are still erased into bare JSON-RPC `result`
    values before app-server can make any typed decision about them.
    
    This precursor PR keeps successful client responses typed until the
    outgoing response seam. It is intentionally limited to
    protocol/app-server plumbing so the analytics behavior change can review
    separately on top.
    
    ## What changed
    
    - Add `ClientResponsePayload` as the pre-serialization client response
    body type.
    - Route app-server successful response paths through the typed payload
    seam while preserving existing handler-local analytics behavior.
    - Keep `InterruptConversation` JSON-RPC-only because it has no
    `ClientResponse` variant.
    - Move the new payload conversion tests into a dedicated protocol test
    module.
    
    ## Verification
    
    - `cargo check -p codex-app-server`
    - `cargo test -p codex-app-server-protocol`
  • app-server: fix outgoing sender test setup (#20258)
    ## Why
    
    [#17088](https://github.com/openai/codex/pull/17088) changed
    `OutgoingMessageSender::new` to require an `AnalyticsEventsClient`, but
    one `command_exec` test added earlier on `main` still called the old
    one-argument constructor. That leaves current `main` failing to compile
    in Bazel and argument-comment-lint jobs.
    
    ## What changed
    
    - Pass `AnalyticsEventsClient::disabled()` to the missed
    `OutgoingMessageSender::new` test call site in `command_exec.rs`.
    
    ## Verification
    
    - `cargo test -p codex-app-server
    timeout_or_cancellation_reports_cancellation_without_timeout_exit_code`
  • [codex-analytics] ingest server requests and responses (#17088)
    ## Why
    
    Codex analytics needs a typed seam for app-server-originated
    request/response traffic so future tool-approval analytics can consume
    those facts without adding bespoke callsite tracking each time. Server
    responses arrive as JSON-RPC `id + result` payloads, so analytics has to
    reconstruct the matching typed response from the original typed request
    while that request context still exists in app-server.
    
    This also puts analytics on the app-server outbound path, which needs to
    avoid keeping the runtime alive during shutdown. The final ownership fix
    keeps the normal strong auth-manager retention in analytics and makes
    the external-auth refresh bridge hold a weak back-reference to
    `OutgoingMessageSender`, breaking the runtime cycle at the bridge
    boundary instead of exposing retention policy through the analytics
    client API.
    
    ## What changed
    
    - Adds typed `ServerRequest` and `ServerResponse` analytics facts, plus
    `AnalyticsEventsClient::track_server_request` and
    `track_server_response`.
    - Renames the existing client-side facts to `ClientRequest` and
    `ClientResponse` so reducers can distinguish client-to-server traffic
    from server-to-client traffic.
    - Adds `ServerRequest::response_from_result`, allowing a stored typed
    request to decode the matching typed server response from a raw JSON-RPC
    result payload.
    - Threads `AnalyticsEventsClient` through `OutgoingMessageSender` and
    records targeted server requests, replayed targeted requests, and
    matching targeted responses with the responding connection id needed for
    correlation.
    - Intentionally leaves broadcast server requests/responses out of
    analytics for now because the current model is per connection, while
    broadcasts fan one logical request out across multiple connections.
    - Breaks the app-server shutdown cycle by storing
    `Weak<OutgoingMessageSender>` in `ExternalAuthRefreshBridge` and
    upgrading it only when an external-auth refresh is actually requested.
    - Keeps reducer ingestion of the new server-side facts as no-ops for
    now; this PR is plumbing for later tool-approval analytics work.
    
    ## Verification
    
    - `cargo test -p codex-analytics`
    - `cargo test -p codex-app-server outgoing_message::tests::`
    - Covers typed-response reconstruction plus the targeted, replayed,
    broadcast-exclusion, and response-attribution analytics paths.
    
    ## Follow-up
    
    This PR intentionally stops at ingestion plumbing, so `ServerRequest`
    and `ServerResponse` facts are still reducer no-ops. Once a follow-up PR
    adds real downstream analytics output for those facts:
    
    - replace the temporary pre-reducer observation seam with reducer tests
    for the emitted event shape;
    - add end-to-end coverage in `app-server/tests/suite/v2/analytics.rs`
    for the real app-server workflow and captured analytics payload;
    - remove the temporary sender-level observer tests added here in favor
    of the real-output coverage above.
    
    ---
    
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/17088).
    * #18748
    * #18747
    * #17090
    * #17089
    * #20241
    * #20239
    * __->__ #17088
  • fix: handle deferred network proxy denials (#19184)
    ## Why
    
    This bug is exposed by Guardian/auto-review approvals. With the managed
    network proxy enabled, a blocked network request can be reported back
    through the network approval service as an approval denial after the
    command has already started. Before this change, the shell and unified
    exec runtimes registered those network approval calls, but did not have
    a way to observe an async proxy denial as a cancellation/failure signal
    for the running process.
    
    The result was confusing: Guardian/auto-review could correctly deny
    network access, but the command path could keep running or unregister
    the approval without surfacing the denial as the command failure.
    
    ## What Changed
    
    - `NetworkApprovalService` now attaches a cancellation token to active
    and deferred network approvals.
    - Proxy-denial outcomes are recorded only for active registrations,
    cancel the owning token, and are consumed when the approval is
    finalized.
    - The shell runtime combines the normal command timeout with the
    network-denial cancellation token.
    - Unified exec stores the deferred network approval object, terminates
    tracked processes when the proxy denial arrives, and returns the denial
    as a process failure while polling or completing the process.
    - Tool orchestration passes the active network approval cancellation
    token into the sandbox attempt and preserves deferred approval errors
    instead of silently unregistering them.
    - App-server `command/exec` now handles the combined
    timeout-or-cancellation expiration variant used by the runtime.
    
    ## Verification
    
    - `cargo test -p codex-core network_approval --lib`
    - `cargo clippy -p codex-app-server --all-targets -- -D warnings`
    - `cargo clippy -p codex-core --all-targets -- -D warnings`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: Use remote installed plugin cache for skills and MCP (#20096)
    - Fetches and caches remote /installed plugin state
    - Lets skills/list load skills from remote-installed cached plugins
    without requiring a local marketplace entry
    - Routes plugin list/startup/install/uninstall changes through async
    plugin cache invalidation and MCP refresh
  • Include auto-review rollout in feedback uploads (#20064)
    ## Summary
    
    - include the live auto-review trunk rollout when `/feedback` uploads
    logs
    - upload that attachment as
    `auto-review-rollout-<parent-thread-id>.jsonl` so it is distinguishable
    from the parent rollout
    - show the same auto-review attachment name in the TUI consent popup
    
    ## Scope
    
    - this only covers the live cached auto-review trunk for the current
    parent thread
    - it does not add durable historical parent->auto-review lookup
    - it does not add persisted rollout support for ephemeral parallel
    review forks
    
    ## UI 
    
    <img width="599" height="185" alt="Screenshot 2026-04-28 at 1 17 18 PM"
    src="https://github.com/user-attachments/assets/6a0e79c2-5d21-4702-8a89-f765778bc9e9"
    />
    
    ## Validation
    
    - `cargo test -p codex-core
    cached_guardian_subagent_exposes_its_rollout_path`
    - `cargo test -p codex-feedback`
    - `cargo test -p codex-app-server`
    - `cargo test -p codex-tui feedback_upload_consent_popup_snapshot`
    - `cargo test -p codex-tui
    feedback_good_result_consent_popup_includes_connectivity_diagnostics_filename`
    
    ## Known unrelated local failures
    
    - `cargo test -p codex-core` currently fails in the pre-existing proxy
    env snapshot test
    `tools::runtimes::tests::maybe_wrap_shell_lc_with_snapshot_keeps_user_proxy_env_when_proxy_inactive`
    - `cargo test -p codex-tui` currently hits pre-existing `status::*`
    snapshot drift unrelated to this change
    
    ## Follow-Up 
    - persist parallel auto-review fork sessions so /feedback can include
    their rollout history too
    - attach each persisted fork as its own clearly named file, for example
    auto-review-rollout-<parent-thread-id>-fork <n>.jsonl, instead of
    merging multiple Guardian sessions into one attachment
    - keep the same live-session-only scope initially; durable historical
    parent -> auto-review lookup can remain a separate decision if we later
    need feedback from resumed sessions
  • Add ThreadManager sample crate (#20141)
    Summary:
    - Add codex-thread-manager-sample, a one-shot binary that starts a
    ThreadManager thread, submits a prompt, and prints the final assistant
    output.
    - Pass ThreadStore into ThreadManager::new and expose
    thread_store_from_config for existing callsites.
    - Build the sample Config directly with only --model and prompt inputs.
    
    Verification:
    - just fmt
    - cargo check -p codex-thread-manager-sample -p codex-app-server -p
    codex-mcp-server
    - git diff --check
    
    Tests: Not run per request.
  • Fix migrated hook path rewriting (#20144)
    ## Summary
    - Rewrite migrated external-agent hook commands by replacing the full
    hook script path token instead of only the `.claude/hooks/` segment.
    - Preserve quoting around the full rewritten target path so script names
    with spaces, absolute paths, and shell operators/redirection continue to
    work.
    - Apply `.claude/settings.local.json` over `.claude/settings.json` for
    config, MCP, and plugin migration so local scope matches Claude settings
    precedence.
    - Skip legacy command markdown without `description` frontmatter,
    including README-style docs under `.claude/commands`.
    
    ## Root Cause
    The previous hook rewrite handled `.claude/hooks/` as a substring
    replacement. For absolute source commands, that left the original
    project-root prefix before the newly quoted `.codex/hooks` directory,
    producing invalid commands like
    `project/'project/.codex/hooks'/script.sh`.
    
    The migration also only used project `settings.json` for
    config/MCP/plugin decisions, so local settings such as
    `disabledMcpjsonServers` could be ignored even though Claude gives local
    settings higher precedence than project settings.
    
    ## Validation
    - `just fmt`
    - `cargo test -p codex-external-agent-migration`
    - `cargo test -p codex-app-server external_agent_config`
    - `just fix -p codex-external-agent-migration`
    - `just fix -p codex-app-server`
    - `git diff --check`
  • Add environment provider snapshot (#20058)
    ## Summary
    - Change `EnvironmentProvider` to return concrete `Environment`
    instances instead of `EnvironmentConfigurations`.
    - Make `DefaultEnvironmentProvider` provide the provider-visible `local`
    environment plus optional `remote` environment from
    `CODEX_EXEC_SERVER_URL`.
    - Keep `EnvironmentManager` as the concrete cache while exposing its own
    explicit local environment for `local_environment()` fallback paths.
    
    ## Validation
    - `just fmt`
    - `git diff --check`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Soften skill description budget warnings (#20112)
    Updates skill description budget messaging to be less alarming
  • feat: expose provider capability bounds to app server clients (#20049)
    follow up of #19442. The app server now exposes provider-derived bounds
    through a new v2 `modelProvider/read` method. The response reports the
    configured provider map key as `modelProvider` and returns the effective
    capability booleans so clients can align their UI with the same
    provider-owned limits used by core.
  • Fix plugin list workspace settings test isolation (#20086)
    Fixes test that often fails locally when running `cargo test`
    - Add an app-server test helper that combines managed-config isolation
    with custom env overrides.
    - Isolate `HOME` / `USERPROFILE` in plugin-list workspace settings tests
    so host home marketplaces do not affect results.
  • Support detect and import MCP, Subagents, hooks, commands from external (#19949)
    ## Why
    This PR expands the migration path so Codex can detect and import MCP
    server config, hooks, commands, and subagents configs in a Codex-native
    shape.
    
    ## What changed
    
    - Added a `codex-external-agent-migration` crate that owns conversion
    logic for external-agent MCP servers, hooks, commands, and subagents.
    - Extended the app-server external-agent config detection/import API
    with migration item types for MCP server config, hooks, commands, and
    subagents.
    
    ## Migration strategy
    
    The migration is intentionally conservative: Codex only imports
    external-agent config that can be represented safely in Codex today.
    Unsupported or ambiguous config is skipped instead of being partially
    translated into behavior that may not match the source system.
    
    - **MCP servers**: import supported stdio and HTTP MCP server
    definitions into `mcp_servers`. Disabled servers and servers filtered
    out by source `enabledMcpjsonServers` / `disabledMcpjsonServers` are
    skipped. Project-scoped MCP entries from `.claude.json` are included
    when they match the repo path.
    - **Hooks**: import only supported command hooks into
    `.codex/hooks.json`. Unsupported hook features such as conditional
    groups, async handlers, prompt/http hooks, or unknown fields are
    skipped. Referenced hook scripts are copied into `.codex/hooks/`,
    preserving any existing target scripts.
    - **Commands**: import supported external commands as Codex skills under
    `.agents/skills/source-command-*`. Commands that rely on source runtime
    expansion such as `$ARGUMENTS`, `$1`, `@file` references, shell
    interpolation, or colliding generated names are skipped.
    - **Subagents**: import valid subagent Markdown files into
    `.codex/agents/*.toml` when they have the minimum Codex agent fields.
    Source model names are not migrated, so imported agents keep the user’s
    Codex default model; compatible reasoning effort and sandbox mode are
    migrated when present.
    - **Skills and project guidance**: copy missing skill directories into
    `.agents/skills` and migrate `CLAUDE.md` guidance into `AGENTS.md`,
    rewriting source-agent terminology to Codex terminology where
    appropriate.
    - **Detection details**: detected migration items include lightweight
    details for UI preview, such as MCP server names, hook event names,
    generated command skill names, and subagent names. Import still
    recomputes from disk instead of trusting details as the source of truth.
    
    - Adds focused coverage for the new migration behavior and app-server
    import flow.
    
    ## Verification
    
    - `cargo test -p codex-external-agent-migration`
    - `cargo test -p codex-hooks`
    - `cargo test -p codex-app-server external_agent_config`
    - `just bazel-lock-check`
  • app-server: notify clients of remote-control status changes (#19919)
    ## Why
    
    Remote-control app-server enrollments have both an internal server id
    and the environment id exposed to remote-control clients. App-server
    clients need one current status snapshot that says whether remote
    control is usable and which environment id, if any, is exposed.
    
    A temporary websocket disconnect is not itself an identity change.
    Account changes, stale enrollment invalidation, successful
    re-enrollment, and missing ChatGPT auth are meaningful status changes.
    Disabled remote control remains `disabled` regardless of auth or SQLite
    state. SQLite startup failure disablement and enrollment persistence
    failures are handled in #20068; this PR reports the resulting effective
    status to clients.
    
    ## What changed
    
    - Adds v2 `remoteControl/status/changed` carrying `state` and
    `environmentId`.
    - Adds `RemoteControlConnectionState` values: `disabled`, `connecting`,
    `connected`, and `errored`.
    - Exposes remote-control status updates through `RemoteControlHandle`
    using a Tokio watch channel.
    - Always sends the current remote-control status snapshot to newly
    initialized app-server clients.
    - Broadcasts status changes to initialized app-server clients when state
    or environment id changes.
    - Treats missing ChatGPT auth as an `errored` status while leaving it
    retryable because auth can change at runtime.
    - Clears `environmentId` when enrollment is cleared for account changes,
    auth loss, stale backend invalidation, or disabled remote control.
    - Updates app-server protocol schema fixtures, generated TypeScript,
    app-server README, remote-control tests, and TUI exhaustive notification
    matches.
    
    ## Stack
    
    - Builds on #20068.
    
    ## Verification
    
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server transport::remote_control --lib`
    - `cargo check -p codex-tui`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-app-server`
    - `just fix -p codex-tui`