Commit Graph

51 Commits

  • Support explicit MCP OAuth client IDs (#22575)
    ## Why
    Some MCP OAuth providers require a pre-registered public client ID and
    cannot rely on dynamic client registration. Codex already supports MCP
    OAuth, but it had no way to supply that client ID from config into the
    PKCE flow.
    
    ## What changed
    - add `oauth.client_id` under `[mcp_servers.<server>]` config, including
    config editing and schema generation
    - thread the configured client ID through CLI, app-server, plugin login,
    and MCP skill dependency OAuth entrypoints
    - configure RMCP authorization with the explicit client when present,
    while preserving the existing dynamic-registration path when it is
    absent
    - add focused coverage for config parsing/serialization and OAuth URL
    generation
    
    ## Verification
    - `cargo test -p codex-config -p codex-rmcp-client -p codex-mcp -p
    codex-core-plugins`
    - `cargo test -p codex-core blocking_replace_mcp_servers_round_trips
    --lib`
    - `cargo test -p codex-core
    replace_mcp_servers_streamable_http_serializes_oauth_resource --lib`
    - `cargo test -p codex-core config_schema_matches_fixture --lib`
    
    ## Notes
    Broader local package runs still hit unrelated pre-existing stack
    overflows in:
    - `codex-app-server::in_process_start_clamps_zero_channel_capacity`
    -
    `codex-core::resume_agent_from_rollout_uses_edge_data_when_descendant_metadata_source_is_stale`
  • feat: add layered --profile-v2 config files (#17141)
    ## Why
    
    `--profile-v2 <name>` gives launchers and runtime entry points a named
    profile config without making each profile duplicate the base user
    config. The base `$CODEX_HOME/config.toml` still loads first, then
    `$CODEX_HOME/<name>.config.toml` layers above it and becomes the active
    writable user config for that session.
    
    That keeps shared defaults, plugin/MCP setup, and managed/user
    constraints in one place while letting a named profile override only the
    pieces that need to differ.
    
    ## What Changed
    
    - Added the shared `--profile-v2 <name>` runtime option with validated
    plain names, now represented by `ProfileV2Name`.
    - Extended config layer state so the base user config and selected
    profile config are both `User` layers; APIs expose the active user layer
    and merged effective user config.
    - Threaded profile selection through runtime entry points: `codex`,
    `codex exec`, `codex review`, `codex resume`, `codex fork`, and `codex
    debug prompt-input`.
    - Made user-facing config writes go to the selected profile file when
    active, including TUI/settings persistence, app-server config writes,
    and MCP/app tool approval persistence.
    - Made plugin, marketplace, MCP, hooks, and config reload paths read
    from the merged user config so base and profile layers both participate.
    - Updated app-server config layer schemas to mark profile-backed user
    layers.
    
    ## Limits
    
    `--profile-v2` is still rejected for config-management subcommands such
    as feature, MCP, and marketplace edits. Those paths remain tied to the
    base `config.toml` until they have explicit profile-selection semantics.
    
    Some adjacent background writes may still update base or global state
    rather than the selected profile:
    
    - marketplace auto-upgrade metadata
    - automatic MCP dependency installs from skills
    - remote plugin sync or uninstall config edits
    - personality migration marker/default writes
    
    ## Verification
    
    Added targeted coverage for profile name validation, layer
    ordering/merging, selected-profile writes, app-server config writes,
    session hot reload, plugin config merging, hooks/config fixture updates,
    and MCP/app approval persistence.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Relax remote plugin sync gate (#22594)
    ## Summary
    - Allow remote installed-plugin cache refresh to start whenever plugins
    are enabled.
    - Allow remote installed-plugin bundle sync to start whenever plugins
    are enabled.
    - Remove the extra local `remote_plugin_enabled` guard from those
    background sync paths.
    
    ## Context
    Server-side installed plugin state and optional bundle URL behavior are
    owned by plugin-service `/public/plugins/installed`, so these local sync
    paths only need the overall plugin enablement gate.
    
    ## Test plan
    - `just fmt`
    - `cargo test -p codex-core-plugins`
  • [codex] Canonicalize shared workspace plugin IDs (#22564)
    ## Summary
    - Canonicalize private and unlisted workspace shared plugin IDs to
    `workspace-shared-with-me`.
    - Keep `plugin/list` private/unlisted shared-with-me buckets as UI
    grouping only.
    - Update share read/list/checkout and cache cleanup coverage for the
    canonical namespace.
    
    ## Tests
    - `cargo test -p codex-app-server --test all
    plugin_list_fetches_shared_with_me_kind`
    - `cargo test -p codex-app-server --test all
    plugin_read_returns_share_context_for_shared_remote_plugin`
    - `cargo test -p codex-app-server --test all suite::v2::plugin_share`
    - `cargo test -p codex-core-plugins
    list_remote_plugin_shares_fetches_created_workspace_plugins`
    - `cargo test -p codex-core-plugins
    stale_remote_plugin_cleanup_removes_old_shared_with_me_cache_and_keeps_canonical_cache`
    - `git diff --check`
  • feat: Add plugin share checkout (#22435)
    Adds plugin/share/checkout to turn a shared remote plugin into a local
    working copy under ~/plugins/<name>.
    
    Registers the copy in the managed personal marketplace and records the
    remote-to-local mapping for later share/save flows.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: Split shared workspace plugins by discoverability (#22425)
    - Keep shared-with-me as the plugin/list request kind, but return
    private plugins under workspace-shared-with-me-private.
    - Add workspace-shared-with-me-unlisted for installed workspace plugins
    with UNLISTED discoverability,
  • feat: Expose plugin versions and gate plugin sharing (#22397)
    - Adds localVersion to plugin summaries and remoteVersion to share
    context, including generated API schemas.
    - Hydrates local and remote plugin versions from manifests and remote
    release metadata.
    - Adds default-on plugin_sharing gate for shared-with-me listing and
    plugin/share/save, with disabled-path errors
        and focused coverage.
  • feat: Normalize remote plugin summary identities. (#22265)
    Makes plugin summaries use config-style plugin@marketplace IDs while
    exposing backend remote IDs separately as remotePluginId.
    
    Also fix the consistency issue of REMOTE_SHARED_WITH_ME_MARKETPLACE_NAME
  • Read cached metadata for installed Git plugins (#20825)
    ## Summary
    - Populate `plugin/list` interface metadata for installed Git-sourced
    marketplace plugins from the active cached plugin bundle.
    - Preserve marketplace category precedence so list behavior matches
    `plugin/read`.
    - Keep existing fallback behavior when the cache or manifest is missing
    or invalid.
    
    ## Test Plan
    - `cd codex-rs && just fmt`
    - `cd codex-rs && cargo test -p codex-core-plugins
    list_marketplaces_installed_git_source_reads_metadata_from_cache_without_cloning`
    - `cd codex-rs && cargo test -p codex-app-server
    plugin_list_returns_installed_git_source_interface_from_cache`
    - `cd codex-rs && just fix -p codex-core-plugins`
    - `cd codex-rs && just fix -p codex-app-server`
    - `git diff --check`
    
    Server-truth check: OpenAI monorepo app-server generated types already
    expose `PluginSummary.interface`, and the webview consumes it for plugin
    cards. This PR keeps the protocol/schema unchanged and fills the
    existing field from the cached installed bundle for Git-backed
    cross-repo plugins.
  • feat: Add role-aware plugin share context APIs (#21867)
    Expose discoverability and full share principals in share context, carry
    roles through save/updateTargets, hydrate local shared plugin reads, and
    keep share URLs only under plugin.shareContext.
  • feat: Update plugin share settings with discoverability (#21637)
    Requires discoverability on plugin/share/updateTargets so the server can
    manage workspace link access consistently, including auto-adding the
    workspace principal for UNLISTED.
    
    Also rejects LISTED on share creation and blocks client-supplied
    workspace principals while preserving response parsing for LISTED.
  • feat: Expose plugin share metadata in shareContext (#21495)
    Extends PluginSummary.shareContext with shareUrl and reader shareTargets
  • Show plugin hooks in plugin details (#21447)
    Supersedes the abandoned #19859, rebuilt on latest `main`.
    
    # Why
    
    PR #19705 adds discovery for hooks bundled with plugins, but `/plugins`
    still only shows skills, apps, and MCP servers. This follow-up makes
    bundled hooks visible in the same plugin detail view so users can
    inspect the full plugin surface in one place.
    
    We also need `PluginHookSummary` to populate Plugin Hooks in the app;
    `hooks/list` is not enough there because plugin detail needs to show
    hooks for disabled plugins too.
    
    # What
    
    - extend `plugin/read` with `PluginHookSummary` entries for bundled
    hooks
    - summarize plugin hooks while loading plugin details
    - render a `Hooks` row in the `/plugins` detail popup
    
    <img width="3456" height="848" alt="CleanShot 2026-04-27 at 11 45 34@2x"
    src="https://github.com/user-attachments/assets/fe3a38d6-a260-4351-8513-fb04c93d725b"
    />
  • [codex] Add OpenAI Developers to tool suggest allowlist (#21423)
    ## Summary
    
    Add `openai-developers@openai-curated` to
    `TOOL_SUGGEST_DISCOVERABLE_PLUGIN_ALLOWLIST` so the OpenAI Developers
    plugin can be surfaced through tool suggestions once it is available in
    the Built by OpenAI marketplace.
    
    Update the discoverable plugin test fixture to assert the plugin is
    returned from the curated marketplace allowlist path.
    
    ## Validation
    
    - `cargo fmt --check` passed; rustfmt emitted the existing
    stable-channel warnings about `imports_granularity`.
    - `cargo test -p codex-core
    list_tool_suggest_discoverable_plugins_returns_uninstalled_curated_plugins`
    passed.
  • feat: Add marketplace source filtering and plugin share context (#21419)
    Adds marketplaceKinds to plugin/list for local, workspace-directory, and
    shared-with-me; omitted params keep default local plus gated global
    behavior, while explicit kinds are exact.
    
    Exposes shareContext on plugin summaries from local share mappings and
    remote workspace/shared responses, including remotePluginId and nullable
    creator metadata.
    
    Adds shared-with-me listing through /ps/plugins/workspace/shared,
    renames the workspace remote namespace to workspace-directory, and keeps
    direct remote read/share/install/update/delete paths gated by plugins
    rather than remote_plugin.
  • feat: Add plugin share access controls (#21124)
    Extends `plugin/share/save` to accept optional discoverability and
    shareTargets while uploading plugin contents, and adds
    `plugin/share/updateTargets` for share-only target updates without
    re-uploading.
  • Expose plugin manifest keywords in app server (#21271)
    ## Summary
    - Add plugin manifest keywords to core plugin marketplace/detail models
    - Expose keywords on app-server v2 PluginSummary and generated
    schema/types
    - Populate keywords in plugin/list and plugin/read responses for local
    plugins
    
    Depends on https://github.com/openai/openai/pull/891087
    
    ## Validation
    - just fmt
    - just write-app-server-schema
    - cargo test -p codex-app-server-protocol
    - cargo test -p codex-core-plugins
    - cargo test -p codex-app-server
    plugin_list_keeps_valid_marketplaces_when_another_marketplace_fails_to_load
    - cargo test -p codex-app-server
    plugin_read_returns_plugin_details_with_bundle_contents
  • Add plugin ID to skill analytics (#20923)
    ## Summary
    - thread plugin skill roots through the skills loader with their plugin
    ID
    - store plugin ID on loaded skill metadata for plugin-provided skills
    - include plugin ID on skill invocation analytics events
    
    ## Test plan
    - cargo check -p codex-core-skills
    - cargo check -p codex-core -p codex-core-plugins -p codex-analytics
    - cargo check -p codex-tui
    - cargo check -p codex-plugin -p codex-core -p codex-core-plugins -p
    codex-analytics
    - cargo check -p codex-app-server
    - cargo test -p codex-analytics
    - HOME=/private/tmp/codex-empty-home cargo test -p codex-core-skills
    - just fix -p codex-core-skills
    - just fix -p codex-analytics
    - just fix -p codex-core-plugins
    - just fix -p codex-core
    - just fmt
    - git diff --check
  • 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`
  • 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`
  • 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`
  • 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`
  • [plugin] Add Canva to suggesteable list. (#20474)
    - [x] Add Canva to suggesteable list.
  • [Extension] Allowlist Chrome Extension in the tool_suggest tool (#20458)
    ### Summary
    Allowlist chrome extension in tool_suggest tool
    
    ### Screenshot
    Allowlist chrome extension in tool_suggest tool
    <img width="808" height="309" alt="chrome_internal"
    src="https://github.com/user-attachments/assets/ed769d77-b635-4a40-a0c5-fbff05af3036"
    />
  • 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.
  • [plugins] Allow MSFT curated plugins in tool_suggest (#20304)
    ## Summary
    - [x] Move the allowlist out of core crate
    - [x] Add Teams, SharePoint, Outlook Email, and Outlook Calendar to the
    tool_suggest discoverable plugin allowlist
    - [x] Add focused coverage for Microsoft curated plugin discovery
    
    ## Testing
    - just fmt
    - cargo test -p codex-core-plugins
    - cargo test -p codex-core
    list_tool_suggest_discoverable_plugins_returns_
  • 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>
  • [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`
  • 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`
  • 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
  • /plugins: add marketplace install flow (#18704)
    This PR adds a new feature to the `/plugins` menu that gives users the
    ability to add new plugin marketplaces. It introduces an Add Marketplace
    tab to the right of installed marketplaces, a source prompt, loading and
    error states, and the app-server request flow needed to perform the
    install. After a successful `marketplace/add`, the popup refreshes back
    into the newly added marketplace tab so the new plugins are immediately
    visible.
    
    - Add an Add Marketplace tab to the `/plugins` menu
    - Prompt for marketplace source input from git repo, URL, or local path
    - Show loading and error states during `marketplace/add`
    - Refresh plugin data after success and switch into the newly added
    marketplace tab
    - Add tests and snapshot updates
  • Discover hooks bundled with plugins (#19705)
    ## Why
    
    Plugins can bundle lifecycle hooks, but Codex previously only discovered
    hooks from user, project, and managed config layers. This adds the
    plugin discovery and runtime plumbing needed for plugin-bundled hooks
    while keeping execution behind the `plugin_hooks` feature flag.
    
    ## What
    
    - Discovers plugin hook sources from each plugin's default
    `hooks/hooks.json`.
    - Supports `plugin.json` manifest `hooks` entries as either relative
    paths or inline hook objects.
    - Plumbs discovered plugin hook sources through plugin loading into the
    hook runtime when `plugin_hooks` is enabled.
    - Marks plugin-originated hook runs as `HookSource::Plugin`.
    - Injects `PLUGIN_ROOT` and `CLAUDE_PLUGIN_ROOT` into plugin hook
    command environments.
    - Updates generated schemas and hook source metadata for the plugin hook
    source.
    
    ## Stack
    
    1. This PR - openai/codex#19705
    2. openai/codex#19778
    3. openai/codex#19840
    4. openai/codex#19882
    
    ## Reviewer Notes
    
    - Core logic is in `codex-rs/core-plugins/src/loader.rs` and
    `codex-rs/hooks/src/engine/discovery.rs`
    - Moved existing / adding new tests to
    `codex-rs/core-plugins/src/loader_tests.rs` hence the large diff there
    - Otherwise mostly plumbing and minor schema updates
    
    ### Core Changes
    
    The `codex-rs/core` changes are limited to wiring plugin hook support
    into existing core flows:
    
    - `core/src/session/session.rs` conditionally pulls effective plugin
    hook sources and plugin hook load warnings from `PluginsManager` when
    `plugin_hooks` is enabled, then passes them into `HooksConfig`.
    - `core/src/hook_runtime.rs` adds the `plugin` metric tag for
    `HookSource::Plugin`.
    - `core/config.schema.json` picks up the new `plugin_hooks` feature
    flag, and `core/src/plugins/manager_tests.rs` updates fixtures for the
    added plugin hook fields.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • 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
  • test: stabilize app-server path assertions on Windows (#19604)
    ## Why
    
    Windows can represent the same canonical local path with either a normal
    drive path or a verbatim device path prefix. The failure pattern that
    motivated this PR was an assertion diff like `C:\...` versus
    `\\?\C:\...`: different spellings, same file.
    
    That became visible while validating the permissions stack above this
    PR. The stack increasingly routes paths through `AbsolutePathBuf`, which
    normalizes supported Windows device prefixes, while several existing
    tests still built expected values directly with
    `std::fs::canonicalize()` or compared `AbsolutePathBuf::as_path()` to a
    raw `PathBuf`. On Windows, that can make tests fail because the two
    sides choose different textual forms for an otherwise equivalent
    canonical path.
    
    This PR is intentionally split out as the bottom PR below #19606. The
    runtime permissions migration should not carry unrelated Windows test
    stabilization, and reviewers should be able to verify this as a
    test-only change before looking at the larger permissions changes.
    
    ## Failure Modes Covered
    
    - `conversation_summary` expected rollout paths were built from raw
    canonicalized `PathBuf`s, while app-server responses could carry
    `AbsolutePathBuf`-normalized paths.
    - `thread_resume` compared returned thread paths directly to previously
    stored or fixture paths, so a verbatim-prefix spelling could fail an
    otherwise correct resume.
    - `marketplace_add` compared plugin install roots through `as_path()`
    against raw canonicalized paths, reproducing the same `C:\...` versus
    `\\?\C:\...` mismatch in both app-server and core-plugin coverage.
    
    ## What Changed
    
    - In `app-server/tests/suite/conversation_summary.rs`, normalize both
    expected rollout paths and received `ConversationSummary.path` values
    through `AbsolutePathBuf` before comparing the full summary object.
    - In `app-server/tests/suite/v2/thread_resume.rs`, normalize both sides
    of thread path comparisons before asserting equality. This keeps the
    tests focused on whether resume returned the same existing path, not
    whether Windows used the same string spelling.
    - In `app-server/tests/suite/v2/marketplace_add.rs` and
    `core-plugins/src/marketplace_add.rs`, compare install roots as
    `AbsolutePathBuf` values instead of comparing an absolute-path wrapper
    to a raw canonicalized `PathBuf`.
    
    ## Behavior
    
    This PR does not change production app-server or marketplace behavior.
    It only changes tests to assert semantic path identity across Windows
    path spelling variants. It also leaves API response values untouched;
    the normalization happens inside assertions only.
    
    ## Verification
    
    Targeted local checks run while extracting this fix:
    
    - `cargo test -p codex-app-server
    get_conversation_summary_by_thread_id_reads_rollout`
    - `cargo test -p codex-app-server
    get_conversation_summary_by_relative_rollout_path_resolves_from_codex_home`
    - `cargo test -p codex-app-server
    thread_resume_prefers_path_over_thread_id`
    
    Windows-specific confidence comes from the Bazel Windows CI job for this
    PR, since the failure is platform-specific.
    
    ## Docs
    
    No docs update is needed because this is test-only infrastructure
    stabilization.
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19604).
    * #19395
    * #19394
    * #19393
    * #19392
    * #19606
    * __->__ #19604
  • [codex] Support remote plugin install writes (#18917)
    ## Summary
    - Add a remote plugin install write call that POSTs the selected remote
    plugin to the ChatGPT cloud plugin API.
    - Align remote install with the latest remote read contract:
    `pluginName` carries the backend remote plugin id directly, for example
    `plugins~Plugin_linear`, and install no longer synthesizes
    `<name>@<marketplace>` ids.
    - Validate remote install ids with the same character rules as remote
    read, return the same install response shape as local installs, and
    include mocked app-server coverage for the write path.
    
    ## Validation
    - `just fmt`
    - `cargo test -p codex-app-server --test all plugin_install`
    - `cargo test -p codex-core-plugins`
    - `just fix -p codex-app-server`
    - `just fix -p codex-core-plugins`
  • feat: Use short SHA versions for curated plugin cache entries (#19095)
    Curated plugin cache entries now use an 8-character SHA prefix, instead
    of the full SHA, as the cache folder version number.
  • refactor: route Codex auth through AuthProvider (#18811)
    ## Summary
    
    This PR moves Codex backend request authentication from direct
    bearer-token handling to `AuthProvider`.
    
    The new `codex-auth-provider` crate defines the shared request-auth
    trait. `CodexAuth::provider()` returns a provider that can apply all
    headers needed for the selected auth mode.
    
    This lets ChatGPT token auth and AgentIdentity auth share the same
    callsite path:
    - ChatGPT token auth applies bearer auth plus account/FedRAMP headers
    where needed.
    - AgentIdentity auth applies AgentAssertion plus account/FedRAMP headers
    where needed.
    
    Reference old stack: https://github.com/openai/codex/pull/17387/changes
    
    ## Callsite Migration
    
    | Area | Change |
    | --- | --- |
    | backend-client | accepts an `AuthProvider` instead of a raw
    token/header |
    | chatgpt client/connectors | applies auth through
    `CodexAuth::provider()` |
    | cloud tasks | keeps Codex-backend gating, applies auth through
    provider |
    | cloud requirements | uses Codex-backend auth checks and provider
    headers |
    | app-server remote control | applies provider headers for backend calls
    |
    | MCP Apps/connectors | gates on `uses_codex_backend()` and keys caches
    from generic account getters |
    | model refresh | treats AgentIdentity as Codex-backend auth |
    | OpenAI file upload path | rejects non-Codex-backend auth before
    applying headers |
    | core client setup | keeps model-provider auth flow and allows
    AgentIdentity through provider-backed OpenAI auth |
    
    ## Stack
    
    1. https://github.com/openai/codex/pull/18757: full revert
    2. https://github.com/openai/codex/pull/18871: isolated Agent Identity
    crate
    3. https://github.com/openai/codex/pull/18785: explicit AgentIdentity
    auth mode and startup task allocation
    4. This PR: migrate Codex backend auth callsites through AuthProvider
    5. https://github.com/openai/codex/pull/18904: accept AgentIdentity JWTs
    and load `CODEX_AGENT_IDENTITY`
    
    ## Testing
    
    Tests: targeted Rust checks, cargo-shear, Bazel lock check, and CI.
  • Add app-server marketplace upgrade RPC (#19074)
    ## Summary
    - add a v2 `marketplace/upgrade` app-server RPC that mirrors the
    existing configured Git marketplace upgrade path
    - expose typed request/response/error payloads and regenerate
    JSON/TypeScript schema fixtures
    - add app-server integration coverage for all, named, already
    up-to-date, and invalid marketplace upgrade requests
    
    ## Tests
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server marketplace_upgrade`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-app-server`
    - `just fmt`
  • Move marketplace add/remove and startup sync out of core. (#19099)
    Move more things to core-plugins.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Use remote plugin IDs for detail reads and enlarge list pages (#19079)
    1. For remote plugin use plugin id (plugin name) directly for read
    plugin details;
    2. Request up to 200 remote plugins per directory list page.
  • feat: Support remote plugin list/read. (#18452)
    Add a temporary internal remote_plugin feature flag that merges remote
    marketplaces into plugin/list and routes plugin/read through the remote
    APIs when needed, while keeping pure local marketplaces working as
    before.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: Support more plugin MCP file shapes. (#18780)
    Update core-plugins MCP loading to accept either an mcpServers object or
    a top-level server map in .mcp.json
  • Fix plugin cache panic when cwd is unavailable (#18499)
    ## Summary
    
    Fixes #16637. (I hit this bug after 11h of work on a long-running task.)
    
    Plugin cache initialization could panic when an already-absolute cache
    path was normalized through `AbsolutePathBuf::from_absolute_path`,
    because that path still consulted `current_dir()`.
    
    This changes absolute-path normalization so already-absolute paths do
    not depend on cwd, and makes plugin cache root construction available as
    a fallible path through `PluginStore::try_new()`. Plugin cache subpaths
    now use `AbsolutePathBuf::join()` instead of re-absolutizing derived
    absolute paths.
  • [codex] Add cross-repo plugin sources to marketplace manifests (#18017)
    ## Summary
    - add first-class marketplace support for git-backed plugin sources
    - keep the newer marketplace parsing behavior from `main`, including
    alternate manifest locations and string local sources
    - materialize remote plugin sources during install, detail reads, and
    non-curated cache refresh
    - expose git plugin source metadata through the app-server protocol
    
    ## Details
    This teaches the marketplace parser to accept all of the following:
    - local string sources such as `"source": "./plugins/foo"`
    - local object sources such as
    `{"source":"local","path":"./plugins/foo"}`
    - remote repo-root sources such as
    `{"source":"url","url":"https://github.com/org/repo.git"}`
    - remote subdir sources such as
    `{"source":"git-subdir","url":"owner/repo","path":"plugins/foo","ref":"main","sha":"..."}`
    
    It also preserves the newer tolerant behavior from `main`: invalid or
    unsupported plugin entries are skipped instead of breaking the whole
    marketplace.
    
    ## Validation
    - `cargo test -p codex-core plugins::marketplace::tests`
    - `just fix -p codex-core`
    - `just fmt`
    
    ## Notes
    - A full `cargo test -p codex-core` run still hit unrelated existing
    failures in agent and multi-agent tests during this session; the
    marketplace-focused suite passed after the rebase resolution.
  • feat: Handle alternate plugin manifest paths (#18182)
    Load plugin manifests through a shared discoverable-path helper so
    manifest reads, installs, and skill names all see the same alternate
    manifest location.
  • Auto-upgrade configured marketplaces (#17425)
    ## Summary
    - Add best-effort auto-upgrade for user-configured Git marketplaces
    recorded in `config.toml`.
    - Track the last activated Git revision with `last_revision` so
    unchanged marketplace sources skip clone work.
    - Trigger the upgrade from plugin startup and `plugin/list`, while
    preserving existing fail-open plugin behavior with warning logs rather
    than new user-visible errors.
    
    ## Details
    - Remote configured marketplaces use `git ls-remote` to compare the
    source/ref against the recorded revision.
    - Upgrades clone into a staging directory, validate that
    `.agents/plugins/marketplace.json` exists and that the manifest name
    matches the configured marketplace key, then atomically activate the new
    root.
    - Local `.agents/plugins/marketplace.json` marketplaces remain live
    filesystem state and are not auto-pulled.
    - Existing non-curated plugin cache refresh is kicked after successful
    marketplace root upgrades.
    
    ## Validation
    - `just write-config-schema`
    - `cargo test -p codex-core marketplace_upgrade`
    - `cargo check -p codex-cli -p codex-app-server`
    - `just fix -p codex-core`
    
    Did not run the complete `cargo test` suite because the repo
    instructions require asking before a full core workspace run.