43 Commits

  • [codex] Enable remote plugins by default (#30297)
    ## Summary
    
    - enable the remote plugin feature by default
    - promote the remote plugin feature from under development to stable
    - preserve the existing `features.remote_plugin` override for explicitly
    disabling it
    - keep legacy disabled-path coverage explicit in TUI and app-server
    tests
    
    ## Impact
    
    Remote plugin functionality is enabled by default for configurations
    that do not set the feature flag. The existing Codex backend
    authentication gate still applies.
    
    ## Validation
    
    - `just fmt`
    - `just test -p codex-features`
    - `just test -p codex-tui
    plugins_popup_remote_section_fallback_states_snapshot`
    - targeted `codex-app-server` plugin-list and skills-list tests
    - `git diff --check`
    
    The full TUI and app-server suites were also exercised locally. All
    remote-plugin-related coverage passed; unrelated local
    sandbox/test-binary failures remain outside this change.
  • [apps] Thread structured icon assets through app list (#29889)
    ## Summary
    
    - Add `iconAssets` and `iconDarkAssets` to the app-list protocol.
    - Preserve structured icons through directory merging and the connector,
    app-
      server, and TUI boundaries.
    - Keep legacy logo URLs unchanged as compatibility fallbacks.
    - Update generated protocol schemas and TypeScript types.
  • Separate local and remote plugin analytics IDs (#29495)
    ## Why
    
    Plugin analytics overloaded `plugin_id`: most events used the Codex
    `<plugin>@<marketplace>` identity, while remote install events used the
    backend plugin ID. That makes the same field change meaning across event
    types and complicates downstream identity resolution.
    
    This change makes the contract unambiguous:
    
    - `plugin_id`: the local Codex `<plugin>@<marketplace>` identity, when
    resolved
    - `remote_plugin_id`: the backend plugin identity, when available
    
    For a remote install failure that happens before plugin details resolve,
    `plugin_id` is `null` and `remote_plugin_id` remains populated.
    
    ## What changed
    
    All six plugin analytics events use the same identity contract:
    
    - `codex_plugin_installed`
    - `codex_plugin_install_failed`
    - `codex_plugin_uninstalled`
    - `codex_plugin_enabled`
    - `codex_plugin_disabled`
    - `codex_plugin_used`
    
    Remote identity is resolved from the current installed-plugin snapshot
    first, with persisted install metadata as fallback. The telemetry
    metadata type keeps local identity optional for failures that occur
    before remote details are available.
    
    The app-server test client's manual analytics smokes now find remote
    mutation events through `remote_plugin_id` and validate that `plugin_id`
    remains local.
    
    ## Remote uninstall
    
    Resolve and capture telemetry metadata before removing the local plugin
    cache, then emit `codex_plugin_uninstalled` after the backend confirms
    success. The event is also emitted when backend uninstall succeeds but
    local cache cleanup reports `CacheRemove`.
    
    If a concurrent remote-cache refresh removes the local bundle before
    telemetry capture, the already-fetched remote plugin detail supplies
    fallback capability metadata.
    
    ## Validation
    
    - `just test -p codex-analytics` — 82 passed
    - `just test -p codex-core-plugins` — 271 passed
    - `just test -p codex-app-server-test-client` — 5 passed
    - `just test -p codex-plugin` — 3 passed
    - `just test -p codex-app-server plugin_install` — 37 passed
    - `just test -p codex-app-server plugin_uninstall` — 10 passed
    
    The production app-server install/uninstall flow was also exercised
    against `plugins~Plugin_f1b845ac33888191ac156169c58733c2`
    (`build-ios-apps@openai-curated-remote`), and the plugin's original
    uninstalled state was restored.
  • [codex] Support protected resource OAuth discovery (#29022)
    ## Why
    
    Plugin-install preflight and the actual OAuth login flow used different
    discovery implementations. Preflight had a Codex-specific implementation
    that only queried authorization-server metadata on the MCP host, while
    login already used the upstream `rmcp` Rust MCP SDK. As a result,
    servers that advertise a separate authorization server through RFC 9728
    Protected Resource Metadata were classified as OAuth-unsupported during
    plugin installation, so login was skipped.
    
    ## What changed
    
    - delegate plugin-install OAuth discovery to
    `rmcp::transport::AuthorizationManager`, the same implementation used by
    the login flow
    - let `rmcp` follow Protected Resource Metadata first and perform direct
    RFC 8414 authorization-server discovery when protected-resource
    discovery does not yield usable metadata
    - retain Codex's existing HTTP headers, timeout, `no_proxy` behavior,
    and scope normalization around that discovery
    - add unit coverage and a pure-MCP plugin-install integration test that
    proves the protected-resource path reaches OAuth client registration
    
    This only changes shared MCP OAuth discovery. App declarations and
    `appsNeedingAuth` behavior are unchanged.
    
    ## Verification
    
    - `just test -p codex-rmcp-client auth_status`
    - `just test -p codex-app-server plugin_install_starts_mcp_oauth`
    - real plugin-install smoke test with an isolated `CODEX_HOME`: both
    DigitalOcean MCP servers started OAuth callback listeners, while Linear
    continued to start its existing direct-discovery OAuth flow
  • [codex] Preserve remote plugin download status errors (#28863)
    ## Summary
    
    - preserve the original HTTP status when a remote plugin bundle download
    returns a non-success response
    - retain at most 8 KiB of the error response body and annotate
    truncation or body-read failures
    - add regression coverage for an oversized error response
    
    ## Root cause
    
    The non-success response path reused the normal size-limited body
    reader. When an error response exceeded 8 KiB, that reader returned
    `DownloadTooLarge` before the code constructed `DownloadStatus`, masking
    the upstream HTTP status and response context.
    
    ## Impact
    
    Remote plugin installation failures now retain the actionable upstream
    HTTP status without allowing unbounded error bodies into logs.
    
    ## Validation
    
    - `just test -p codex-app-server
    plugin_install_preserves_status_when_remote_bundle_error_body_is_too_large`
    - `just fmt`
    - `git diff --check`
  • [codex] Remove hardcoded app ID filters (#28947)
    ## Summary
    
    - remove the duplicated originator-specific connector ID denylists
    - stop filtering connector directory/accessibility results and
    live/cached Codex Apps MCP tools by hardcoded connector ID
    - remove the now-unused `codex-login` dependency from
    `codex-utils-plugins`
    - update regression coverage so formerly blocked connector IDs are
    preserved
    
    ## Why
    
    The client-side policy was duplicated across crates, used opaque IDs
    without ownership or expiry information, and could drift between app
    listing and MCP tool behavior. Server-provided visibility,
    authorization, plugin discoverability, accessibility, enabled-state
    handling, and consequential-tool approval templates remain unchanged.
    
    ## Validation
    
    - `just fmt`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    - `git diff --check`
    - confirmed the final diff contains no hardcoded denylist symbols
    
    A targeted `codex-mcp` test build spent an unusually long time in local
    compilation/linking. Its first attempt exposed a test-only `PartialEq`
    assertion issue, which was corrected. A follow-up non-linking `cargo
    check -p codex-mcp --tests` was still running when this draft was
    opened; CI should provide the complete Rust validation.
  • [codex] Track plugin install and import telemetry failures (#28731)
    ## Summary
    - Track plugin install failures through the unified
    `codex_plugin_install_failed` event for local installs, remote install
    preflight failures, bundle failures, and remote catalog/backend
    failures.
    - Send classified `error_type` values in plugin install failure
    analytics instead of raw error strings.
    - Stop sending raw external-agent import errors in analytics while
    preserving raw failure details in app-facing import
    notifications/history.
    - Keep raw plugin/migration diagnostics in `tracing::warn!` logs.
    - Keep remote failure plugin names as the existing local placeholder
    (`unknown`) and remove the extra telemetry plugin-name override.
    - Change `ExternalAgentConfigImportParams.source` from a generated enum
    to `string | null`, with legacy `claudeCode` / `claudeCowork` inputs
    normalized to existing analytics values.
    
    ## Testing
  • [codex] Skip plugin MCP OAuth for matching app routes (#27461)
    ## Context
    
    This is PR5 in the plugin auth-routing stack. Earlier PRs make plugin
    surface projection auth-aware, narrow App/MCP conflicts by App
    declaration name, and keep connector listings auth-aware. This PR
    applies the same name-based App/MCP conflict rule into plugin MCP
    loading, so install-time MCP OAuth and plugin detail metadata both
    reflect the MCPs available for the current auth route.
    
    ## Stack
    
    - PR1: #27652 seed plugin manager auth at construction.
    - PR2: #27459 route plugin surfaces by auth mode.
    - PR3: #27607 dedupe plugin MCP servers by App declaration name.
    - PR4: #27602 preserve plugin Apps in connector listings.
    - PR5: #27461 skip install-time plugin MCP OAuth for matching App
    routes.
    
    ## Summary
    
    - Make `load_plugin_mcp_servers` auth-aware and let it load App
    declarations before filtering same-name MCP servers for Codex-backend
    auth.
    - Use that filtered MCP list for both install-time MCP OAuth and
    marketplace plugin detail metadata.
    - Preserve API-key/direct auth behavior so plugin MCP servers remain
    visible and can still start OAuth.
    
    ## Validation
    
    ```bash
    cargo fmt --all
    cargo test -p codex-core-plugins read_plugin_for_config_filters_mcp_servers_for_codex_backend_auth
    cargo check -p codex-core-plugins -p codex-app-server
    git diff --check
    git diff --cached --check
    ```
  • [codex] Propagate plugin app categories (#27420)
    ## What
    - Parse optional `.app.json` `category` overrides for plugin apps.
    - Add nullable `category` to `AppSummary` and `AppTemplateSummary` in
    the app-server protocol.
    - Fall back from `branding.category` to the first non-empty
    `app_metadata.categories` value when building app/template summaries.
    - Regenerate schema/type fixtures and update plugin read/install tests.
    
    ## Why
    The plugin details UI needs a normalized per-app category. Some apps
    only provide their default category in metadata, while others need a
    local `.app.json` override.
  • [codex] Remove redundant plugin app auth state (#27465)
    ## Summary
    
    - remove the redundant `needsAuth` field from `AppSummary` and generated
    app-server schemas
    - stop `plugin/read` from querying Apps MCP solely to hydrate unused
    connector auth state
    - preserve `plugin/install.appsNeedingAuth` membership and
    `app/list.isAccessible` as the authentication signals
    
    ## Why
    
    Codex App and TUI do not consume `plugin/read.plugin.apps[].needsAuth`.
    Hydrating it could establish an Apps MCP connection and discover tools
    on a cold `plugin/read` request, adding avoidable latency. The plugin
    APIs are still marked under development, so removing this wire field is
    preferable to retaining a misleading default.
    
    ## Verification
    
    - `just write-app-server-schema`
    - `just fmt`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server
    plugin_install_uses_remote_apps_needing_auth_response`
    - `just test -p codex-app-server
    plugin_install_returns_apps_needing_auth`
    - `just test -p codex-app-server
    plugin_read_returns_plugin_details_with_bundle_contents`
    - `just test -p codex-tui
    plugin_detail_popup_snapshot_shows_install_actions_and_capability_summaries`
    - `$xin-build` simplify and debug reviews
  • Use plugin-service MCP as the hosted plugin runtime (#27198)
    ## Stack
    
    - Base: #27191
    - This PR is the third vertical and should be reviewed against
    `jif/external-plugins-2`, not `main`.
    
    ## Why
    
    #27191 moves the host-owned Apps MCP registration behind an extension
    contributor, but deliberately preserves the existing endpoint-selection
    feature while that contribution contract lands. App-server can therefore
    resolve the server through extensions, yet the hosted plugin endpoint is
    still selected through temporary `apps_mcp_path_override` plumbing.
    
    That is not the long-term plugin model. A plugin can bundle skills,
    connectors, MCP servers, and hooks, and those components do not all need
    the same source or execution environment. In particular, an
    authenticated HTTP MCP server can expose plugin capabilities directly
    from a backend without an executor or an orchestrator filesystem.
    
    This PR completes that hosted vertical. App-server's MCP extension now
    owns the aggregate hosted plugin runtime at `/ps/mcp`. Connector actions
    continue to arrive as MCP tools, while backend-provided skills arrive as
    MCP resources and use Codex's existing resource list/read paths. No
    second backend client, skill filesystem, or generic plugin activation
    framework is introduced.
    
    The backend route remains the hosted implementation. This change
    replaces Codex's temporary endpoint-selection mechanism, not the service
    behind the endpoint.
    
    ## What changed
    
    ### Hosted plugin runtime
    
    The MCP extension now contributes `codex_apps` as the hosted plugin
    runtime rather than as a configurable Apps endpoint:
    
    - `https://chatgpt.com` resolves to
    `https://chatgpt.com/backend-api/ps/mcp`;
    - a bare custom ChatGPT base resolves to `/api/codex/ps/mcp`;
    - the existing product-SKU header and ChatGPT authentication behavior
    are preserved;
    - executor availability is never consulted for this streamable HTTP
    transport.
    
    The same MCP connection carries both component shapes supported by the
    hosted endpoint:
    
    - connector actions are discovered and invoked as MCP tools;
    - hosted skills are enumerated and read as MCP resources through the
    existing `list_mcp_resources` and `read_mcp_resource` paths.
    
    This keeps component access in the subsystem that already owns the
    protocol instead of downloading backend skills into an orchestrator
    filesystem or inventing a parallel hosted-skill client.
    
    ### Explicit runtime ordering
    
    `McpManager` now resolves the reserved `codex_apps` entry in three
    ordered phases:
    
    1. install the legacy Apps fallback for compatibility;
    2. apply ordered extension `Set` or `Remove` overlays;
    3. apply the final ChatGPT-auth gate without synthesizing the server
    again.
    
    This ordering is important:
    
    - an ordinary configured or plugin MCP server cannot claim the
    auth-bearing `codex_apps` name;
    - an extension-contributed hosted runtime wins over the fallback;
    - an extension `Remove` remains authoritative;
    - a host without the MCP extension retains the legacy Apps endpoint and
    current local-only behavior.
    
    The temporary `legacy_apps_mcp_loader_enabled` coordination flag is no
    longer needed.
    
    ### Remove the path override
    
    The `apps_mcp_path_override` feature and its runtime plumbing are
    removed, including:
    
    - the feature registry entry and structured feature config;
    - `Config` and `McpConfig` fields;
    - config schema output;
    - config-lock materialization;
    - URL override handling in `codex-mcp`.
    
    Existing boolean and structured forms still deserialize as ignored
    compatibility input. They are omitted from new serialized config, and
    config-lock comparison normalizes the removed input so older locks
    remain replayable.
    
    ### App-server coverage
    
    App-server MCP fixtures now serve the hosted route at
    `/api/codex/ps/mcp`. Existing resource-read and tool/elicitation flows
    therefore exercise the extension-owned endpoint rather than succeeding
    through the legacy fallback.
    
    The stack also adds the missing `codex_chatgpt::connectors` re-export
    for the manager-backed connector helper introduced in #27191.
    
    ## Compatibility
    
    - App-server installs the extension and uses `/ps/mcp` for the hosted
    runtime.
    - CLI and other hosts that do not install the extension retain the
    legacy Apps endpoint.
    - Apps disabled or non-ChatGPT authentication removes `codex_apps` from
    the effective runtime view.
    - Existing local plugins, local skills, executor-selected skills,
    configured MCP servers, and MCP OAuth behavior are otherwise unchanged.
    - Backend plugin enablement remains account/workspace state owned by the
    hosted endpoint; this PR does not add thread-local backend plugin
    selection.
    
    ## Architectural fit
    
    The stack now proves two independent runtime shapes:
    
    1. #27184 resolves filesystem-backed skills through the executor that
    owns a selected root.
    2. #27191 and this PR resolve a backend-hosted HTTP MCP through an
    extension with no executor.
    
    Together they preserve the intended separation:
    
    - selection identifies a plugin/root when explicit selection is needed;
    - each component's owning extension resolves its concrete access
    mechanism;
    - execution stays with the runtime required by that component;
    - existing skills, MCP, connector, and hook subsystems remain the
    downstream consumers.
    
    ## Planned follow-ups
    
    1. **Executor stdio MCP:** selecting an executor plugin registers a
    manifest-declared stdio MCP server and executes it in the environment
    that owns the plugin.
    2. **Optional backend selection:** only if CCA needs thread-local
    selection distinct from backend account/workspace enablement, add a
    concrete backend-owned capability location and surface those selected
    skills through the skills catalog.
    3. **Connector metadata and hooks:** activate those plugin components
    through their existing owning subsystems, with executor hooks remaining
    environment-bound.
    4. **Propagation and persistence:** define explicit resume, fork,
    subagent, refresh, and environment-removal semantics once selected roots
    have multiple real consumers.
    5. **Local convergence:** migrate legacy local skill, MCP, connector,
    and hook paths behind their owning extensions one vertical at a time,
    then remove duplicate core managers and compatibility plumbing after
    parity.
    
    ## Verification
    
    Coverage in this change exercises:
    
    - extension-owned `/backend-api/ps/mcp` registration without an
    executor;
    - preservation of the legacy endpoint in hosts without the extension;
    - extension `Set` and `Remove` precedence over the legacy fallback;
    - ChatGPT-auth gating for the reserved server;
    - hosted MCP resource reads with and without an active thread;
    - connector tool invocation and MCP elicitation through the hosted
    route;
    - ignored boolean and structured forms of the removed path override;
    - config-lock replay compatibility for the removed feature.
    
    `cargo check -p codex-features -p codex-mcp-extension -p
    codex-app-server` passes. Tests and Clippy were not run locally under
    the current development instruction; CI provides the full validation
    pass.
  • 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
  • fix: rename McpServer to TestAppServer (#25701)
    This PR brought to you via VS Code rather than Codex...
    
    - opened `codex-rs/app-server/tests/common/mcp_process.rs`
    - put the cursor on `McpServer`
    - hit `F2` and renamed the symbol to `TestAppServer`
    - went to the file tree
    - hit enter and renamed `mcp_process.rs` to `test_app_server.rs`
    - ran **Save All Files** from the Command Palette
    - ran `just fmt`
    
    The End
    
    (Admittedly, most of the local variables for `TestAppServer` are still
    named `mcp`, though.)
  • [codex] Avoid forced directory refresh during plugin install auth checks (#25381)
    ## Summary
    - Use normal directory loading for plugin install app metadata so
    install avoids forced directory refresh while still loading metadata on
    cold cache.
    - Continue force-refreshing codex_apps tools for auth state.
    - Add regression coverage that pre-warms the directory cache and asserts
    install returns cached app metadata without extra directory requests.
    
    ## Validation
    - just fmt
    - git diff --check
    - just test -p codex-app-server plugin_install_returns_apps_needing_auth
    plugin_install_filters_disallowed_apps_needing_auth (blocked locally:
    cargo-nextest is not installed)
  • Update rmcp to 1.7.0 (#24763)
    WIll make it easier to uprev when the new draft spec is supported.
    
    Also updates reqwest where needed for compatibility but doesn't update
    it everywhere since this is already a large diff.
    
    The new version of rmcp handles certain kinds of authentication failures
    differently, this patch includes support for identifying the failing scope
    in a WWW-Authenticate header.
  • 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.
  • 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.
  • 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`
  • 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.
  • feat: Cache remote plugin bundles on install (#19914)
    Remote installs now fetch, validate, download, and cache the plugin
    bundle locally
  • respect workspace option for disabling plugins (#18907)
    Respects the workspace setting for plugins in Codex
    
    Plugins menu disappears
    Plugins do not load
    Plugins do not load in composer
    
    no plugins loaded
    <img width="809" height="226" alt="Screenshot 2026-04-23 at 3 20 45 PM"
    src="https://github.com/user-attachments/assets/3a4dba8e-69c3-4046-a77e-f13ab77f84b4"
    />
    
    
    no plugins in menu
    <img width="293" height="204" alt="Screenshot 2026-04-23 at 3 20 35 PM"
    src="https://github.com/user-attachments/assets/5cb9bf52-ad72-488f-b90c-5eb457da09a3"
    />
  • [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: Add remote plugin fields to plugin API (#17277)
    ## Summary
    Update the plugin API for the new remote plugin model.
    
    The mental model is no longer “keep local plugin state in sync with
    remote.” Instead, local and remote plugins are becoming separate
    sources. Remote catalog entries can be shown directly from the remote
    API before installation; after installation they are still downloaded
    into the local cache for execution, but remote installed state will come
    from the API and be held in memory rather than being read from config.
    
    • ## API changes
    - Remove `forceRemoteSync` from `plugin/list`, `plugin/install`, and
    `plugin/uninstall`.
      - Remove `remoteSyncError` from `plugin/list`.
      - Add remote-capable metadata to `plugin/list` / `plugin/read`:
        - nullable `marketplaces[].path`
        - `source: { type: "remote", downloadUrl }`
        - URL asset fields alongside local path fields:
      `composerIconUrl`, `logoUrl`, `screenshotUrls`
      - Make `plugin/read` and `plugin/install` source-compatible:
        - `marketplacePath?: AbsolutePathBuf | null`
        - `remoteMarketplaceName?: string | null`
        - exactly one source is required at runtime
  • Refactor config types into a separate crate (#16962)
    Move config types into a separate crate because their macros expand into
    a lot of new code.
  • core: remove cross-crate re-exports from lib.rs (#16512)
    ## Why
    
    `codex-core` was re-exporting APIs owned by sibling `codex-*` crates,
    which made downstream crates depend on `codex-core` as a proxy module
    instead of the actual owner crate.
    
    Removing those forwards makes crate boundaries explicit and lets leaf
    crates drop unnecessary `codex-core` dependencies. In this PR, this
    reduces the dependency on `codex-core` to `codex-login` in the following
    files:
    
    ```
    codex-rs/backend-client/Cargo.toml
    codex-rs/mcp-server/tests/common/Cargo.toml
    ```
    
    ## What
    
    - Remove `codex-rs/core/src/lib.rs` re-exports for symbols owned by
    `codex-login`, `codex-mcp`, `codex-rollout`, `codex-analytics`,
    `codex-protocol`, `codex-shell-command`, `codex-sandboxing`,
    `codex-tools`, and `codex-utils-path`.
    - Delete the `default_client` forwarding shim in `codex-rs/core`.
    - Update in-crate and downstream callsites to import directly from the
    owning `codex-*` crate.
    - Add direct Cargo dependencies where callsites now target the owner
    crate, and remove `codex-core` from `codex-rs/backend-client`.
  • chore: clean up argument-comment lint and roll out all-target CI on macOS (#16054)
    ## Why
    
    `argument-comment-lint` was green in CI even though the repo still had
    many uncommented literal arguments. The main gap was target coverage:
    the repo wrapper did not force Cargo to inspect test-only call sites, so
    examples like the `latest_session_lookup_params(true, ...)` tests in
    `codex-rs/tui_app_server/src/lib.rs` never entered the blocking CI path.
    
    This change cleans up the existing backlog, makes the default repo lint
    path cover all Cargo targets, and starts rolling that stricter CI
    enforcement out on the platform where it is currently validated.
    
    ## What changed
    
    - mechanically fixed existing `argument-comment-lint` violations across
    the `codex-rs` workspace, including tests, examples, and benches
    - updated `tools/argument-comment-lint/run-prebuilt-linter.sh` and
    `tools/argument-comment-lint/run.sh` so non-`--fix` runs default to
    `--all-targets` unless the caller explicitly narrows the target set
    - fixed both wrappers so forwarded cargo arguments after `--` are
    preserved with a single separator
    - documented the new default behavior in
    `tools/argument-comment-lint/README.md`
    - updated `rust-ci` so the macOS lint lane keeps the plain wrapper
    invocation and therefore enforces `--all-targets`, while Linux and
    Windows temporarily pass `-- --lib --bins`
    
    That temporary CI split keeps the stricter all-targets check where it is
    already cleaned up, while leaving room to finish the remaining Linux-
    and Windows-specific target-gated cleanup before enabling
    `--all-targets` on those runners. The Linux and Windows failures on the
    intermediate revision were caused by the wrapper forwarding bug, not by
    additional lint findings in those lanes.
    
    ## Validation
    
    - `bash -n tools/argument-comment-lint/run.sh`
    - `bash -n tools/argument-comment-lint/run-prebuilt-linter.sh`
    - shell-level wrapper forwarding check for `-- --lib --bins`
    - shell-level wrapper forwarding check for `-- --tests`
    - `just argument-comment-lint`
    - `cargo test` in `tools/argument-comment-lint`
    - `cargo test -p codex-terminal-detection`
    
    ## Follow-up
    
    - Clean up remaining Linux-only target-gated callsites, then switch the
    Linux lint lane back to the plain wrapper invocation.
    - Clean up remaining Windows-only target-gated callsites, then switch
    the Windows lint lane back to the plain wrapper invocation.
  • [plugins] Install MCPs when calling plugin/install (#15195)
    - [x] Auth MCPs when installing plugins.
  • feat: support product-scoped plugins. (#15041)
    1. Added SessionSource::Custom(String) and --session-source.
      2. Enforced plugin and skill products by session_source.
      3. Applied the same filtering to curated background refresh.
  • Revert "fix: harden plugin feature gating" (#15102)
    Reverts openai/codex#15020
    
    I messed up the commit in my PR and accidentally merged changes that
    were still under review.
  • fix: harden plugin feature gating (#15020)
    1. Use requirement-resolved config.features as the plugin gate.
    2. Guard plugin/list, plugin/read, and related flows behind that gate.
    3. Skip bad marketplace.json files instead of failing the whole list.
    4. Simplify plugin state and caching.
  • feat: Add product-aware plugin policies and clean up manifest naming (#14993)
    - Add shared Product support to marketplace plugin policy and skill
    policy (no enforced yet).
    - Move marketplace installation/authentication under policy and model it
    as MarketplacePluginPolicy.
    - Rename plugin/marketplace local manifest types to separate raw serde
    shapes from resolved in-memory models.
  • feat: support remote_sync for plugin install/uninstall. (#14878)
    - Added forceRemoteSync to plugin/install and plugin/uninstall.
    - With forceRemoteSync=true, we update the remote plugin status first,
    then apply the local change only if the backend call succeeds.
    - Kept plugin/list(forceRemoteSync=true) as the main recon path, and for
    now it treats remote enabled=false as uninstall. We
    will eventually migrate to plugin/installed for more precise state
    handling.
  • Add plugin usage telemetry (#14531)
    adding metrics including: 
    * plugin used
    * plugin installed/uninstalled
    * plugin enabled/disabled
  • chore: use AVAILABLE and ON_INSTALL as default plugin install and auth policies (#14407)
    make `AVAILABLE` the default plugin installPolicy when unset in
    `marketplace.json`. similarly, make `ON_INSTALL` the default authPolicy.
    
    this means, when unset, plugins are available to be installed (but not
    auto-installed), and the contained connectors will be authed at
    install-time.
    
    updated tests.
  • chore: wire through plugin policies + category from marketplace.json (#14305)
    wire plugin marketplace metadata through app-server endpoints:
    - `plugin/list` has `installPolicy` and `authPolicy`
    - `plugin/install` has plugin-level `authPolicy`
    
    `plugin/install` also now enforces `NOT_AVAILABLE` `installPolicy` when
    installing.
    
    
    added tests.
  • feat: Add curated plugin marketplace + Metadata Cleanup. (#13712)
    1. Add a synced curated plugin marketplace and include it in marketplace
    discovery.
    2. Expose optional plugin.json interface metadata in plugin/list
    3. Tighten plugin and marketplace path handling using validated absolute
    paths.
    4. Let manifests override skill, MCP, and app config paths.
    5. Restrict plugin enablement/config loading to the user config layer so
    plugin enablement is at global level
  • check app auth in plugin/install (#13685)
    #### What
    on `plugin/install`, check if installed apps are already authed on
    chatgpt, and return list of all apps that are not. clients can use this
    list to trigger auth workflows as needed.
    
    checks are best effort based on `codex_apps` loading, much like
    `app/list`.
    
    #### Tests
    Added integration tests, tested locally.
  • support plugin/list. (#13540)
    Introduce a plugin/list which reads from local marketplace.json.
    Also update the signature for plugin/install.