Commit Graph

81 Commits

  • [codex] Return workspace directory installed plugins (#27098)
    ## Summary
    
    - return installed `workspace-directory` remote plugins by default in
    `plugin/installed`
    - keep shared-with-me installed plugins gated behind `plugin_sharing`
    - filter remote installed plugin marketplaces by canonical marketplace
    name instead of coarse workspace scope
    
    ## Validation
    
    - `just fmt`
    - `just test -p codex-core-plugins`
    - `just test -p codex-app-server`
    - `just fix -p codex-core-plugins`
    - `just fix -p codex-app-server`
    - `$xin-build` targeted verification:
    - `just test -p codex-core-plugins
    build_remote_installed_plugin_marketplaces_from_cache_filters_by_marketplace_name`
    - `just test -p codex-app-server
    plugin_installed_includes_workspace_directory_without_plugin_sharing`
    - `just test -p codex-app-server
    plugin_installed_includes_remote_shared_with_me_plugins`
    - `just test -p codex-app-server
    plugin_list_omits_shared_with_me_kind_when_plugin_sharing_disabled`
  • Use server app auth requirements for remote plugin install (#27085)
    ## Summary
    - request `includeAppsNeedingAuth=true` when installing remote plugins
    - return backend-provided `app_ids_needing_auth` from the remote install
    client
    - use those app IDs to populate `appsNeedingAuth` without refetching
    accessible apps, with fallback for older responses
    
    ## Testing
    - `just fmt`
    - `just test -p codex-app-server`
    - `just test -p codex-core-plugins`
    - real app-server install/uninstall check with Notion remote plugin
    - subagent review found no blocking issues
  • Use cached remote plugin catalog for plugin list (#26932)
    ## Summary
    
    This changes the default remote plugin marketplace listing to use the
    cached global remote catalog when it is already present on disk. The
    foreground `plugin/list` response can then return from the local catalog
    cache instead of waiting on `/ps/plugins/list`.
    
    When a cached global catalog was present at the start of the request,
    `plugin/list` still schedules a background refresh through the existing
    plugin-list background task path so the disk cache is updated for future
    requests. Cache misses keep the existing synchronous remote fetch path
    and write the cache, and they do not schedule an extra duplicate
    background `/ps/plugins/list` refresh.
    
    Installed/enabled state continues to come from the existing remote
    installed overlay path. This change only affects the global remote
    catalog directory data used by `plugin/list`.
    
    ## Testing
    
    - `just fmt`
    - `just test -p codex-app-server
    plugin_list_uses_cached_global_remote_catalog_and_refreshes_it`
    - `just test -p codex-core-plugins`
    - `git diff --check`
  • [codex] Prune stale curated plugin caches (#26934)
    Curated plugin startup refresh now removes cached plugins whose names no
    longer appear in the raw openai-curated marketplace. This prevents users
    with the old standalone Google Sheets plugin selected locally from
    continuing to load its stale cache after the curated repo drops it.
    
    Existing config is left untouched, and plugins still present in the
    marketplace continue to refresh from local curated sources.
    
    Validation:
    - `just fmt`
    - `just test -p codex-core-plugins`
    - `git diff --check`
  • [codex] Remove legacy remote plugin startup sync (#25936)
    ## Summary
    
    - Remove the legacy startup remote plugin sync path that called
    `/plugins/list` and reconciled curated plugin cache/config.
    - Remove the `sync_plugins_from_remote` API, its result/error types,
    startup marker task, and tests that expected the legacy request.
    - Keep the current remote installed bundle sync and remote catalog flows
    (`/ps/plugins/installed` and `/ps/plugins/list`) intact.
    
    ## Validation
    
    - `just fmt`
    - `git diff --check`
    - `env HOME=/private/tmp/codex-xin-build-home
    USERPROFILE=/private/tmp/codex-xin-build-home just test -p
    codex-core-plugins`
    - Searched for legacy `/plugins/list` sync references; remaining matches
    are `/ps/plugins/list` catalog tests/code.
    
    ## Notes
    
    - `just test -p codex-app-server plugin_list` is currently blocked
    before running filtered tests by an unrelated compile error in
    `app-server/tests/suite/v2/image_generation.rs`:
    `app_test_support::McpProcess` is not exported.
  • [codex] Bound WSL local curated discovery (#26669)
    ## Context
    The installed-app suggestion expansion added in #24996 reads plugin
    details for trusted file-backed marketplace candidates because the list
    response does not include app ids. On Windows-backed WSL mounts, the
    local `openai-curated` checkout lives under `$CODEX_HOME/.tmp/plugins`,
    and those per-plugin detail reads can be very slow.
    
    Remote curated already has cached app ids, so it does not need the same
    local filesystem traversal.
    
    ## Summary
    - Keep only the WSL Windows-backed local `openai-curated` checkout on
    the legacy fallback/configured discovery path.
    - Preserve installed-app expansion for non-WSL file-backed marketplaces
    and remote curated.
    - Add focused tests for the WSL local curated path predicate.
    
    ## Test
    - `just test -p codex-core-plugins discoverable`
    - `just test -p codex-core plugins::discoverable::tests`
  • Speed up TUI startup by reusing plugin discovery (#26469)
    ## Summary
    
    TUI startup loads related plugin data from `hooks/list`, session MCP
    initialization, and plugin skill warmup. These paths repeated filesystem
    discovery and emitted the same plugin warnings, while `hooks/list` and
    account/model bootstrap ran serially.
    
    This change:
    
    - Reuses one immutable plugin load outcome across startup consumers.
    - Keys the cache only on plugin-relevant configuration.
    - Single-flights concurrent plugin loads and prevents invalidated loads
    from repopulating the cache.
    - Runs hook discovery and account/model bootstrap concurrently.
    - Preserves configuration-migration ordering, hook review behavior, and
    accurate startup telemetry.
    
    In 10 alternating release-build launches in the Ruff repository with the
    existing `~/.codex` configuration, median time to the first editable
    composer decreased from 833ms to 504ms. The branch was faster in 9 of 10
    pairs, with a paired median improvement of 312ms.
  • Pull plugin service less frequently (#26431)
    # Summary
    Reduce download traffic to `github.com/openai/plugins` while continuing
    to check for updates on every Codex startup.
    
    # Root cause
    The startup sync replaced the local repository with a fresh shallow
    clone whenever the remote revision changed. At Codex's global scale,
    repeatedly downloading the repository created excessive GitHub traffic.
    
    # Changes
    - Run `git ls-remote` on each startup to read the remote HEAD SHA.
    - Skip all repository downloads when the local and remote SHAs match.
    - Update existing checkouts with an exact-SHA shallow `git fetch`,
    followed by reset and clean.
    - Bootstrap new installations with `git init` plus the same shallow
    fetch, rather than cloning.
    - Keep the existing file lock so concurrent Codex processes serialize
    updates and do not duplicate fetches.
    - Preserve the existing GitHub HTTP and export archive fallback
    behavior.
    
    # Impact
    Each startup makes one lightweight remote HEAD check. Repository objects
    are downloaded only when the revision changes, and existing Git objects
    are reused during updates.
    
    # Validation
    - `just test -p codex-core-plugins startup_sync` (15 tests passed)
    - `just test -p codex-core-plugins` (201 tests passed)
    - `just clippy -p codex-core-plugins` (passes with one pre-existing
    `large_enum_variant` warning)
    - Production app-server smoke test against GitHub:
      - Fresh home: `ls-remote`, `git init`, one exact-SHA shallow fetch
    - Unchanged restart: `ls-remote` and local `rev-parse` only; no fetch or
    clone
    - Bench smoke passed
  • [codex] Expose unavailable app templates in plugin detail (#26317)
    ## Summary
    - Adds `unavailable_app_templates` to the app-server protocol and
    generated schemas/types.
    - Parses plugin-service `release.unavailable_app_templates` in the
    remote plugin client.
    - Maps remote unavailable templates into app-server `PluginDetail`.
    - Defaults local plugins to an empty unavailable app template list.
    
    ## Validation
    - `just write-app-server-schema`
    - `cargo +1.95.0 fmt --manifest-path codex-rs/Cargo.toml --all --check`
    - `cargo +1.95.0 test --manifest-path codex-rs/Cargo.toml -p
    codex-app-server-protocol schema_fixtures`
    - `cargo +1.95.0 check --manifest-path codex-rs/Cargo.toml -p
    codex-app-server-protocol -p codex-core-plugins -p codex-app-server`
    - `git diff --check`
    
    Note: default `cargo check` uses rustc 1.89 locally and failed because
    dependencies require newer Rust, so validation was rerun with installed
    Rust 1.95.
  • fix(app-server): expose remote MCP servers in plugin read (#26453)
    ## Why
    
    Remote plugin detail responses include MCP server metadata under
    `release.mcp_servers`, but Codex did not deserialize or propagate that
    field. As a result, `plugin/read` always returned an empty `mcpServers`
    list for remote plugins, so the plugin details pane omitted the MCP
    Servers section even when the remote plugin declares one.
    
    This affects uninstalled plugins as well: the remote detail API is the
    source of truth and returns MCP server keys without requiring a local
    plugin bundle.
    
    ## What changed
    
    - Deserialize MCP server entries from remote plugin detail responses.
    - Normalize their keys into a sorted, deduplicated list on
    `RemotePluginDetail`.
    - Return those keys from app-server `plugin/read` instead of hardcoding
    an empty list.
    - Add regression coverage proving an uninstalled remote plugin returns
    its MCP server names.
    
    ## Test plan
    
    - `just test -p codex-core-plugins`
    - `just test -p codex-app-server plugin_read`
  • Load plugin hooks without other plugin capabilities (#26272)
    ## Summary
    
    `hooks/list` only consumes plugin hook declarations, but previously
    loaded every enabled plugin's skills, MCP configuration, apps, and
    capability summary before discarding them.
    
    In a local benchmark, this reduced `hooks/list` latency by over 100ms
    (e.g., from 594 to 467ms on startup, and 168 to 16ms when making a
    `hooks/list` call later in the same TUI session). This is on the
    critical path to rendering the TUI, so every 10s of ms should be eyed
    skeptically (IMO).
    
    This change adds a hook-specific plugin loading path that preserves
    plugin enablement, remote/local conflict resolution, deterministic
    ordering, manifest resolution, and hook-loading warnings while skipping
    unrelated capabilities. (I think there's room for a more general design
    here that allows you to project the capabilities you need at load-time,
    but that seems unnecessary right now.)
  • Preserve remote plugin default prompts (#25887)
    ## Summary
    
    - Read `default_prompts` from remote plugin release metadata.
    - Prefer the plural prompt list over legacy `default_prompt`.
    - Fall back to `default_prompt` as a single-item list for backward
    compatibility.
    
    ## Testing
    
    - `just test -p codex-core-plugins`
    - `just test -p codex-app-server`
  • Switch runtime to cloud config bundle (#24622)
    ## Summary
    
    - Adapts the moved `codex-cloud-config` crate from the legacy cloud
    requirements endpoint to the new config bundle endpoint.
    - Switches runtime consumers from `CloudRequirementsLoader` to
    `CloudConfigBundleLoader` so one shared bundle supplies cloud-delivered
    config and requirements.
    - Removes the legacy cloud requirements domain loader path.
    
    ## Details
    
    This intentionally keeps `codex-cloud-config` monolithic for review
    lineage: the previous PR establishes the crate move, and this PR shows
    the behavior change against that moved implementation. A follow-up PR
    splits the module back into focused files.
    
    The new bundle path preserves the important cloud requirements loader
    semantics where intended: account-scoped signed cache, 30 minute TTL, 5
    minute refresh cadence, retry/backoff, auth recovery, and fail-closed
    startup loading. The cached payload changes from a single requirements
    TOML string to the backend-delivered bundle, and validation rejects
    malformed config or requirements fragments before cache write/use.
  • [codex] Move plugin discoverable logic into core-plugins (#25783)
    ## Summary
    - Move plugin discoverable recommendation filtering from `codex-core`
    into `codex-core-plugins` behind `ToolSuggestPluginDiscoveryInput`.
    - Keep `codex-core` as a thin adapter from `Config` to the core-plugins
    API and back to `DiscoverablePluginInfo`.
    - Keep the existing discoverable allowlist private to the core-plugins
    implementation.
    
    ## Validation
    - `just fmt`
    - `just test -p codex-core list_tool_suggest_discoverable_plugins`
    - `git diff --check`
    - Read-only subagent review: no findings
  • [codex] Cache remote plugin catalog for suggestions (#25457)
    ## Summary
    - cache the global remote plugin catalog when remote plugin listing runs
    and warm it during startup
    - use the cached remote catalog in plugin install recommendations with
    canonical `plugin@openai-curated-remote` ids
    - reuse the session `PluginsManager` for plugin recommendations so
    remote cache state is visible on the recommend path
    - skip core installed-state verification for remote plugin install
    suggestions while leaving local plugin and connector verification
    unchanged
    
    ## Testing
    - `just fmt`
    - `git diff --check`
    - `cargo test -p codex-core
    list_tool_suggest_discoverable_plugins_includes_cached_remote_global_plugins`
    - `cargo test -p codex-core
    remote_plugin_install_suggestions_skip_core_installed_verification`
    - `cargo test -p codex-app-server
    plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled`
    
    Earlier focused checks during the same branch: codex-tools TUI filter
    test, request_plugin_install tests, and codex-app-server build.
  • Handle invalid plugin skills manifest field (#25717)
    ## Summary
    - Treat invalid `plugin.json` `skills` shapes as a field-level warning
    instead of rejecting the whole manifest
    - Keep valid string path behavior unchanged and continue falling back to
    the default `skills/` root
    - Add regression coverage for array-shaped `skills`
    
    ## Tests
    - `just fmt`
    - `cargo test -p codex-core-plugins`
  • fix: Deduplicate installed local and remote curated plugins (#25681)
    ## Summary
    - Deduplicate installed `openai-curated` and `openai-curated-remote`
    plugin conflicts by feature flag.
    - Prefer remote when remote plugins are enabled; otherwise prefer local,
    while preserving one-sided installs.
    
    ## Testing
    - `just fmt`
    - `git diff --check`
    - Targeted `just test` was blocked locally because `cargo-nextest` is
    not installed.
  • Preserve plugin app manifest order (#25491)
    ## Summary
    - Preserve app declaration order when loading plugin .app.json files.
    - Keep plugin connector summaries in plugin app order after connector
    metadata is merged and filtered.
    - Add regression coverage for .app.json order and connector summary
    order.
    
    ## Validation
    - just fmt
    - just test -p codex-chatgpt
    connectors_for_plugin_apps_returns_only_requested_plugin_apps
    - just test -p codex-core-plugins
    effective_apps_preserves_app_config_order
    - just fix -p codex-core-plugins (passes with existing clippy
    large_enum_variant warning in core-plugins/src/manifest.rs)
    - just fix -p codex-chatgpt
    - just bazel-lock-update
    - just bazel-lock-check
  • Avoid repeated marketplace upgrades for alternate layouts (#24320)
    Fixes #24249.
    
    ## Why
    
    Codex already supports discovering marketplaces under both
    `.agents/plugins/marketplace.json` and
    `.claude-plugin/marketplace.json`. The Git marketplace auto-upgrade
    no-op check only looked for the `.agents` layout. That meant an
    installed `.claude-plugin` marketplace with matching revision metadata
    still looked absent, so plugin list/startup upgrade work could stage and
    re-activate the same marketplace again.
    
    That matches the failure shape in #24249: the report called out repeated
    marketplace sync/cache refresh logs and a large recently-touched
    `.tmp/marketplaces/.staging` directory. This change makes the
    auto-upgrade path recognize the installed `.claude-plugin` marketplace
    as already current, which should remove that staging/activation feedback
    loop.
    
    ## What changed
    
    `codex-rs/core-plugins/src/marketplace_upgrade.rs` now uses the existing
    supported marketplace manifest discovery helper when deciding whether an
    installed Git marketplace is already current. Existing local plugin
    source validation is unchanged; `source: "./"` still remains invalid.
    
    ## Confidence
    
    Confidence is high that this fixes the repeated marketplace upgrade
    path: the old hardcoded layout check was definitely wrong for installed
    `.claude-plugin` marketplaces, and the reported staging churn points
    directly at that path.
    
    Confidence is not 100% because we do not have a CPU profile or a fully
    re-run reporter repro. A malformed marketplace entry can still be logged
    as invalid if another caller repeatedly lists plugins; this PR fixes the
    staging/upgrade feedback loop that likely made the failure pathological,
    not every possible source of repeated marketplace resolution.
  • fix: plugin bundle archive handling for upload and install (#23983)
    Move plugin tar.gz packing and unpacking into a shared core-plugins
    archive helper so uploaded bundles are decoded through the same tar
    handling used for installs. This removes duplicate archive logic,
    supports GNU long-name entries on extraction, and keeps size, traversal,
    link, and entry-type checks in one place.
  • fix: Allow plugin skills to share plugin-level icon assets (#23776)
    Thread the plugin root through plugin skill loading so skill interface
    icons can reference shared plugin assets, such as ../../assets/logo.svg.
  • Remove plugin hooks feature flag (#22552)
    # Why
    
    This is a follow-up stacked on top of the `plugin_hooks` default-on
    change. Once we are comfortable making plugin hooks part of the normal
    plugin behavior, the separate feature flag stops buying us much and
    leaves extra branching/cache state behind.
    
    # What
    
    - remove the `PluginHooks` feature and generated config-schema entries
    - make plugin hook loading/listing follow plugin enablement directly
    - drop plugin-manager cache/state that only existed to distinguish
    hook-flag toggles
    - remove tests and fixtures that modeled `plugin_hooks = true/false`
  • Route MCP servers through explicit environments (#23583)
    ## Summary
    - route each configured MCP server through an explicit per-server
    `environment_id` instead of a manager-wide remote toggle
    - default omitted `environment_id` to `local`, resolve named ids through
    `EnvironmentManager`, and fail only the affected MCP server when an
    explicit id is unknown
    - keep local stdio on the existing local launcher path for now, while
    named-environment stdio uses the selected environment backend and
    requires an absolute `cwd`
    - allow local HTTP MCP servers to keep using the ambient HTTP client
    when no local `Environment` is configured; named-environment HTTP MCPs
    use that environment's HTTP client
    
    ## Validation
    - devbox Bazel build: `bazel build --bes_backend= --bes_results_url=
    //codex-rs/cli:codex //codex-rs/rmcp-client:test_stdio_server
    //codex-rs/rmcp-client:test_streamable_http_server`
    - devbox app-server config matrix with real `config.toml` /
    `environments.toml` files covering omitted local, explicit local,
    omitted local under remote default, explicit remote stdio, local HTTP
    without local env, explicit remote HTTP, local stdio without local env,
    unknown explicit env, and remote stdio without `cwd`
  • feat(plugins): tabulate plugin list output (#23727)
    ## Summary
    - render `codex plugin list` as one table per marketplace with the
    marketplace manifest path shown above each table
    - surface the installed plugin version in the CLI output by threading
    `installed_version` through marketplace listing state
    - narrow the system-root exemption so only known bundled/runtime
    marketplaces skip missing-manifest failures, and keep `VERSION` empty
    for cached-but-unconfigured plugins
    
    ## Rationale
    The plugin list UX was hard to scan as a flat list and did not show
    which installed version was active. This change makes the CLI output
    easier to read in the real multi-marketplace case, keeps the plugin path
    visible, fixes the Sapphire regression where bundled/runtime marketplace
    roots were blocking `plugin list`, and addresses the two review findings
    that came out of the follow-up deep review.
    
    ## Key Decisions
    - kept the CLI output grouped per marketplace instead of one global
    table so the marketplace path can live with the rows it owns
    - kept `VERSION` as the installed version, which means it is empty until
    a plugin is actually installed
    - handled the bundled/runtime regression in the CLI snapshot validation
    path rather than widening app-server protocol or changing marketplace
    loading behavior
    - narrowed the exemption to known system marketplace names plus expected
    system paths, so user-configured marketplaces under those directories
    still fail loudly
    - gated `installed_version` on actual installed state so `VERSION`
    cannot show stale cache state for `not installed` rows
    
    ## Validation
    - `just fmt`
    - Sapphire: `cargo test -p codex-cli --test plugin_cli` (`14 passed; 0
    failed`)
    - Sapphire smoke test: bundled/runtime roots still work
      - `cargo run -q -p codex-cli -- plugin add sample@debug`
      - `cargo run -q -p codex-cli -- plugin list`
    - verified the bundled/runtime-root scenario no longer errors and shows
    the expected marketplace table output
    - Sapphire smoke test: custom marketplace under bundled path still
    errors
    - verified `failed to load configured marketplace snapshot(s)` for
    `custom-marketplace`
    - Sapphire smoke test: cached-but-unconfigured plugin hides version
    - verified `sample@debug not installed` renders with an empty `VERSION`
    column
    
    ## Sample Output
    ```text
    /tmp/custom-marketplace/plugin.json
    NAME          VERSION  STATUS         DESCRIPTION
    sample@debug  1.0.0    enabled        Debug sample plugin
    other@local            not installed  Local development plugin
    ```
  • feat: Add vertical remote plugin collection support (#23584)
    - Adds an explicit vertical marketplace kind for plugin/list that
    fail-open fetches collection=vertical only when full remote plugins are
    disabled.
    
    - Renames the global remote marketplace/cache identity to
    openai-curated-remote and materializes remote installs with backend
    release versions and app manifests.
  • fix(plugins): keep version upgrades additive (#23356)
    ## Why
    
    Windows can reject plugin cache upgrades when a running MCP server still
    has its working directory inside the currently active plugin version.
    The existing cache refresh path replaces
    `plugins/cache/<marketplace>/<plugin>` as a whole, so a live handle
    under the old version can make an otherwise ordinary version bump fail.
    
    This PR keeps the existing plugin-selection model intact while making
    version bumps less disruptive.
    
    ## What changed
    
    - When installing a new version beside an existing plugin cache root,
    move only the staged version directory into place instead of replacing
    the whole plugin root.
    - Best-effort prune older sibling version directories after the new
    version is activated.
    - Preserve the existing whole-root replacement path for first installs
    and same-version refreshes.
    - Add regression coverage for upgrading from `1.0.0` to `2.0.0` without
    replacing the plugin root.
    
    ## Verification
    
    - `cargo test -p codex-core-plugins install_with_new_version`
    - `cargo fmt --package codex-core-plugins --check`
  • [codex] Add installed-plugin mention API (#22448)
    ## Summary
    - add app-server `plugin/installed` for mention-oriented plugin loading
    - return installed plugins plus explicitly requested install-suggestion
    rows
    - keep remote handling on installed-state data instead of the broad
    catalog listing path
    
    ## Why
    The `@` mention surface only needs plugins that are usable now, plus a
    small product-approved set of install suggestions. It does not need the
    full catalog-shaped `plugin/list` payload that the Plugins page uses.
    
    ## Validation
    - `just write-app-server-schema`
    - `just fmt`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-core-plugins`
    - `cargo test -p codex-app-server --test all plugin_installed_`
    
    ## Notes
    - The package-wide `cargo test -p codex-app-server` run still hits an
    existing unrelated stack overflow in
    `in_process::tests::in_process_start_clamps_zero_channel_capacity`.
    - Companion webview PR: https://github.com/openai/openai/pull/915672
  • 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`