Commit Graph

8 Commits

  • [codex-core-plugins] Remote Plugin ID Persisted to File (#27669)
    ## This PR
    
    Remote plugin analytics cannot rely only on the in-memory
    installed-plugin snapshot because that snapshot is refreshed
    asynchronously after startup. This PR persists the authoritative backend
    identity alongside each cached remote plugin bundle so later consumers
    can resolve it without a network request.
    
    ### Behavior
    
    - Store Codex-owned remote installation metadata in an atomic
    `.codex-remote-plugin-install.json` sidecar under the plugin cache root.
    - Use a versioned, snake_case schema:
    
      ```json
      {
        "schema_version": 1,
        "remote_plugin_id": "plugins~Plugin_..."
      }
      ```
    
    - Write the metadata during remote bundle installation.
    - Backfill it when bundle sync finds an already-current cached bundle.
    - Clear it when a generic/local install replaces the cache.
    - Let existing uninstall and stale-cache removal delete it with the
    plugin cache root.
    - Reject unsupported schema versions rather than silently misreading
    future formats.
    
    This PR does not change analytics serialization or event behavior.
    
    ### Review surface
    
    The implementation is limited to four `codex-core-plugins` files:
    
    - `store.rs`: owns the versioned sidecar read/write/remove lifecycle.
    - `remote_bundle.rs`: persists the backend ID after a remote bundle
    install.
    - `remote/remote_installed_plugin_sync.rs`: backfills metadata for an
    already-current cached bundle.
    - Tests cover the storage lifecycle and both remote write paths.
    
    ## Testing / Validation
    
    ### Automated
    
    - `just test -p codex-core-plugins` (268 tests passed)
    - `just fix -p codex-core-plugins` passes with one pre-existing
    `large_enum_variant` warning in `manifest.rs`.
    - Coverage verifies the exact filename and JSON schema, identity
    replacement, local reinstall clearing, uninstall cleanup, remote bundle
    installation, unsupported schema rejection, and installed-plugin sync
    backfill.
    
    ### Live manual validation
    
    Validated the production app-server RPC path with an isolated temporary
    `CODEX_HOME` and the PR-built Codex binary. The app-server communicated
    over stdio and did not bind a port.
    
    Test plugin: `plugins~Plugin_b80dd84519148191a409cde181c9b3d6`
    (`build-macos-apps@openai-curated-remote`).
    
    1. Confirmed `plugin/read` initially reported the plugin uninstalled.
    2. Installed it through `plugin/install` and confirmed version `0.1.4`
    was cached.
    3. Verified
    `$CODEX_HOME/plugins/cache/openai-curated-remote/build-macos-apps/.codex-remote-plugin-install.json`
    was created beside the `0.1.4/` bundle directory with mode `0600` and
    the expected contents:
    
       ```json
       {
         "schema_version": 1,
    "remote_plugin_id": "plugins~Plugin_b80dd84519148191a409cde181c9b3d6"
       }
       ```
    
    4. Deleted only the sidecar, restarted the app-server, and confirmed
    installed-plugin startup sync recreated it with the same contents.
    5. Uninstalled through `plugin/uninstall`, confirmed `plugin/read`
    returned `installed: false`, and verified the local plugin cache root
    was removed.
    6. Restored the account's original uninstalled state and removed the
    isolated home and copied credentials.
    
    ## Split Overview
    
    ```text
    main
    ├── #27093  Debug analytics capture                     merged
    │   └── #27099  Non-mutating plugin smoke               merged
    │       └── #27100  Remote install/uninstall smoke      merged
    └── #27102  Plugin telemetry metadata refactor          merged
        └── #27669  Persist remote plugin identity           ← this PR
    
    Next:
    └── Final PR: add explicit local and remote IDs to plugin analytics
    ```
    
    This PR is based directly on `main`; prerequisite
    [#27102](https://github.com/openai/codex/pull/27102) has merged. The
    original combined [#26281](https://github.com/openai/codex/pull/26281)
    remains the aggregate reference until the final replacement PR is
    published.
  • [codex] Support marketplace plugin manifest fallback (#28789)
    ## Summary
    
    Support marketplace plugins whose source directory does not include a
    discoverable plugin manifest. Metadata-rich `marketplace.json` entries
    now act as fallback plugin manifests for listing, local detail reads,
    install, and non-curated cache refresh.
    
    The fallback preserves marketplace-entry plugin fields wholesale, then
    adds the small Codex-facing compatibility bridge for presentation
    metadata. A real source `plugin.json` always wins when present.
    
    ## Details
    
    - Capture flattened marketplace-entry fields into
    `MarketplacePluginManifestFallback`, preserving fields such as
    `version`, `description`, `skills`, `mcpServers`, `apps`, `hooks`,
    `agents`, `commands`, `strict`, `author`, and future manifest fields
    without a per-field translation list.
    - Bridge Claude-style top-level `displayName`, `author.name`,
    `homepage`, and marketplace `category` into Codex's nested `interface`
    fields only when the nested values are absent.
    - Treat fallback metadata as installable only when the marketplace entry
    contributes metadata beyond bare `name` and `source`; existing
    missing-manifest behavior remains for metadata-free entries.
    - Read local plugin details from the already parsed fallback manifest,
    including fallback-declared app and MCP paths, instead of rereading only
    an on-disk manifest.
    - Pass fallback contents into `PluginStore`, which validates them and
    injects `.codex-plugin/plugin.json` into Store's existing atomic copy.
    Local marketplace source directories are never mutated, and the fallback
    path no longer needs an additional staging directory.
    - Keep Git source materialization unchanged; Git clones still use the
    existing marketplace source staging area before Store installation.
  • [codex] Support object-valued plugin MCP manifests (#28580)
    ## Summary
    This fixes plugin manifest parsing for MCP servers declared as an object
    directly in `plugin.json`.
    
    Before this change, Codex modeled `mcpServers` as only a string path,
    for example:
    
    ```json
    {
      "name": "counter-sample",
      "version": "1.1.1",
      "mcpServers": "./.mcp.json"
    }
    ```
    
    Some migrated plugins instead provide the server map directly in the
    manifest:
    
    ```json
    {
      "name": "counter-sample",
      "version": "1.1.1",
      "description": "Plugin that declares MCP servers in the manifest",
      "mcpServers": {
        "counter": {
          "type": "http",
          "url": "https://sample.example/counter/mcp"
        }
      }
    }
    ```
    
    That object form previously failed during install/load with an error
    like:
    
    ```text
    failed to parse plugin manifest: invalid type: map, expected a string
    ```
    
    ## What changed
    - Add a manifest representation for `mcpServers` as either
    `Path(Resource)` or `Object(map)`.
    - Parse `plugin.json` `mcpServers` as either a string path or an object.
    - Route object-valued MCP server maps through the existing plugin MCP
    config parser instead of adding a second parser.
    - Apply existing per-plugin MCP server policy to object-valued MCP
    servers the same way as file-backed MCP servers.
    - Include object-valued MCP server names in plugin telemetry/capability
    metadata.
    - Support object-valued MCP config for executor plugins without
    requiring a `.mcp.json` filesystem read.
    - Update the bundled plugin-creator validator and `plugin-json-spec.md`
    so generated-plugin validation accepts the same object-valued shape.
    
    ## Compatibility
    Existing plugin manifests that use `"mcpServers": "./.mcp.json"`
    continue to work. Plugins can now also use the object shape shown above.
    
    ## Tests
    Added coverage for the new manifest attribute shape at the install,
    normal load, telemetry, and executor-provider layers:
    
    - `install_accepts_manifest_mcp_server_objects`
    - `load_plugins_loads_manifest_mcp_server_objects`
    - `plugin_telemetry_metadata_uses_manifest_mcp_server_objects`
    - `reads_manifest_object_config_without_executor_file_system_access`
    
    Also smoke-tested the plugin-creator validator against both supported
    forms:
    
    - `mcpServers` as a direct object in `plugin.json`
    - `mcpServers` as `"./.mcp.json"` with a companion `.mcp.json`
    
    ## Validation
    - `just test -p codex-plugin`
    - `just test -p codex-core-plugins`
    - `just test -p codex-mcp-extension`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    - `just fmt`
    - `git diff --check`
    - Focused rename/object-form rerun: `just test -p codex-core-plugins
    manager::tests::load_plugins_loads_manifest_mcp_server_objects
    manager::tests::plugin_telemetry_metadata_uses_manifest_mcp_server_objects
    store::tests::install_accepts_manifest_mcp_server_objects`
    - Focused executor rerun: `just test -p codex-mcp-extension
    executor_plugin::provider::tests::reads_manifest_object_config_without_executor_file_system_access`
    - `python3
    codex-rs/skills/src/assets/samples/plugin-creator/scripts/validate_plugin.py
    /private/tmp/codex-validator-object`
    - `python3
    codex-rs/skills/src/assets/samples/plugin-creator/scripts/validate_plugin.py
    /private/tmp/codex-validator-path`
  • 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`
  • 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>
  • 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.
  • 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.
  • Extract plugin loading and marketplace logic into codex-core-plugins (#18070)
    Split plugin loading, marketplace, and related infrastructure out of
    core into codex-core-plugins, while keeping the core-facing
    configuration and orchestration flow in codex-core.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>