## What changed
- Remove startup prefetching of remote catalogs, featured plugins, and recommendations while retaining installed-plugin refreshes and bundle downloads.
- Fetch featured plugin IDs on demand, concurrently with marketplace fetching when listing the remote global catalog, and reuse the cached results.
- Retire `plugin_discovery_prefetch` as an ignored compatibility flag and remove forced background catalog refreshes.
## Testing
Update app-server tests to verify that startup downloads installed bundles without discovery requests and that two `plugin/list` calls share one featured-plugin fetch.
GitOrigin-RevId: aa10474f3429102d624c83472e81ec85e3b470ae
## Why
Cache presence and freshness checks deserialize full plugin records even when callers only need to decide whether a cached catalog is available.
## What changed
- Probe cache availability without deserializing plugin records, while still requiring a valid catalog envelope and a `plugins` array.
- Let `PreferFreshCache` reject stale catalogs from a 256-byte prefix when the header provides enough information; fall back to a full read otherwise.
- Limit discovery cache probes to the user and workspace scopes that need them, skipping the global cache probe.
- Serialize cached plugin slices directly without cloning them.
## Testing
Add coverage for malformed cache headers and payloads, reordered fields, stale-prefix rejection, and deferring plugin-record validation until a full catalog load.
GitOrigin-RevId: 1e5ec95917f38c84bba30dbd3bb8c2f91cc7e3a8
## Why
Runtime metadata and tool suggestions do not require resolved image URLs or creator names. Callers that need display enrichment must still be able to distinguish a complete display snapshot from one fetched without it.
## What changed
- Add `InstalledPluginDisplayAssets` to select whether installed plugin requests include display enrichment, preserving `Include` as the default.
- Send `includeDisplayAssets=false` on every request page when `Skip` is selected.
- Track display enrichment in the installed plugin cache. Allow tool suggestions to use snapshots without enrichment, while treating those snapshots as cache misses for display callers.
- Avoid invalidating effective runtime metadata when only the cache's display enrichment state changes.
## Testing
Add coverage for paginated requests without display enrichment, cache eligibility and enrichment state transitions, and discovery using snapshots fetched with or without display assets.
GitOrigin-RevId: 1fdfd6443a300a03753416f2f6c633b52a97d0fb
## Why
Installed-plugin inventory fetches could keep following pagination cursors
without an overall bound, delaying release of the plugin mutation gate.
## What changed
- Limit each inventory fetch to 60 seconds and 100 pages, and reject repeated
pagination cursors.
- Request 200 items per page for scoped inventory fetches as well as all-scope
fetches.
- Report overall fetch timeouts as `remote_catalog_timeout`.
## Testing
Add coverage for cursor cycles, completion at the page limit, and overall
timeout cancellation after successful pages. Verify that failed pagination
preserves the cached inventory and plugin files and releases the mutation gate.
GitOrigin-RevId: e8429dd804581041d102defa9d19db02bcb1739b
## What changed
- Stop sending `includeExtensions=true` when fetching installed plugins and remove extension metadata from remote plugin models.
- Remove `PluginSummary.extensions` and the associated extension declaration types from the app-server protocol.
- Update generated JSON schemas, TypeScript and Python bindings, and precomputed protocol exports to match.
GitOrigin-RevId: 56509437b6bdf58d99badff68e86bcf7f09fe036
## What changed
- Request installed-plugin metadata with `includeExtensions=true` and carry optional `extensions` through the cache into `PluginSummary`.
- Add typed declarations for global, settings, thread, and file entrypoints, quick actions, settings tools, icons, and search mention providers. Update JSON schemas and TypeScript and Python bindings.
- Accept supported snake_case aliases and emit camelCase fields. Discard incompatible extension declarations without invalidating the installed-plugin inventory.
## Testing
Add an integration test covering extension metadata preservation, field normalization, and unknown entrypoint and quick-action types across live and cached `plugin/installed` reads. Update pagination assertions to require `includeExtensions=true`.
GitOrigin-RevId: b73b0d13c151a30dc1fb9cda70b5cd6d7e3e2441
## Why
Remote plugin requests always sent `OAI-Product-Sku: codex`, ignoring the existing `apps_mcp_product_sku` setting.
## What changed
Pass `apps_mcp_product_sku` through plugin configuration and use it for authenticated remote plugin requests, including discovery, search, installation, and sharing. Keep `codex` as the default and include the SKU when comparing remote service configurations.
## Testing
Extend app-server plugin listing and core agent-turn tests to check default and configured SKU headers. Update the CLI worktree test to wait for the user prompt's model request, skipping background title generation requests.
GitOrigin-RevId: 508c9ef9aa3e41b87c92383c3088eb1eb9227fd8
## What changed
Add an optional `onboardingSkill` summary to `plugin/read` responses and update the generated schemas, TypeScript types, and Python models.
- Resolve local declarations from `extensions["com.openai"].onboardingSkill`, accepting relative paths with or without `./`. Require the resolved path to stay within the plugin root and match a loaded skill.
- Match remote release metadata's `onboarding_skill_name` against the plugin's skills.
- Return the summary only when the plugin and matching visible skill are enabled; remote plugins must also be available. Otherwise, return `null`.
## Testing
Add parameterized local and remote `plugin/read` tests covering matching, missing, and unmatched declarations, disabled plugins and skills, and unavailable remote plugins.
GitOrigin-RevId: 88cabdfddbecba5efba756617da0cb1ff064bdf7
## Why
Disabling a connector's canonical plugin could leave its tools available when another enabled plugin contributed the same connector.
## What changed
Retain `canonical_app_id` in the remote installed-plugin cache and use ownership metadata for the current account when building connector snapshots. A disabled canonical owner now excludes its connector even when another plugin contributes it or the owner's bundle is absent from the host. Match owners by plugin name and marketplace, and ignore ownership metadata when the cache no longer matches the current authentication.
Build these snapshots through `PluginsManager` when the plugins feature is enabled; otherwise use an empty snapshot.
## Testing
Add manager coverage for combined local and canonical exclusions, marketplace matching, and authentication changes. Add an integration test showing that disabling a noncanonical contributor preserves shared calendar tools, disabling the canonical owner hides them, and clearing the exclusion restores them.
GitOrigin-RevId: ee2981d1277825fefb671dd7d1078cd7ea5c0ea5
## What changed
- Include remote catalog entries in `codex plugin list`, including their source,
version, install policy, and authentication policy in JSON output.
- Support adding and removing remote plugins through the existing plugin CLI.
- Cache remote catalogs by scope and collection. Prefer fresh cached results, and
refetch once when an add request misses a plugin in the cache.
- Preserve the local curated catalog when an unfiltered remote listing fails,
while surfacing errors for explicitly selected remote marketplaces.
## Testing
- Cover remote listing, installation, removal, catalog fallback, cache refresh,
collection isolation, and install failure behavior.
GitOrigin-RevId: 09796b2c393d102e00ba9289f784d78a2e166a54
## What changed
- Fetch recommendations from the Codex-specific `/ps/plugins/suggested/codex` endpoint and parse its compact response shape.
- Fetch the selected plugin's details before presenting an install request, using them to verify availability and populate connector metadata.
- Skip install elicitation when the selected recommendation is no longer available, and return a retryable response when its metadata cannot be verified.
## Testing
- Cover the new recommendation route and response shape.
- Cover metadata hydration for available plugins and rejection of unavailable plugins.
GitOrigin-RevId: 2b0e2d70572aae2b7cd8e458b42e9dd14be3dbaf
## What changed
- Fetch the paginated installed-plugin snapshot without a `scope` query instead of issuing separate requests for global, user, and workspace plugins.
- Use the combined snapshot for installed-plugin caching and bundle synchronization while continuing to reconcile each scope's marketplace independently.
## Testing
- Cover pagination across mixed scopes, request query parameters, marketplace reconciliation, stale cache cleanup, and discoverable-plugin filtering.
GitOrigin-RevId: 7428a9db81d7ec04e65cc4b236b652278b200956
## What changed
- Add nullable `installedAt` metadata to `PluginSummary` and its generated schemas, expressed as a Unix timestamp in seconds.
- Preserve the backend installation time for remote plugins across plugin list, installed, read, and share-list responses. Return `null` when the timestamp is unavailable, including for local or uninstalled plugins and older backend responses.
## Testing
- Cover timestamp conversion in the remote plugin list integration test and the field's serialization and backward-compatible default in protocol tests.
GitOrigin-RevId: 8a1640320adb298874e1074e9891d1017ad06937
## What changed
- Add nullable `disabledReason` and `eligiblePlanTypes` fields to v2
`PluginSummary` responses and generated schemas.
- Preserve the remote catalog values across discovered, installed, and cached
plugin summary paths while returning `null` for local plugins and older
remote responses.
- Treat unrecognized disabled reasons as `unknown` for forward compatibility.
## Testing
- Cover protocol round trips, remote summary propagation, unknown disabled
reasons, and app-server responses for admin-disabled and plan-ineligible
plugins.
GitOrigin-RevId: 657a7ea6f838a6ff8bd1769ebcca0f3432684437
## What changed
- Add nullable `canPublishToWorkspace` metadata to plugin share contexts and `plugin/share/save` responses.
- Preserve the remote capability through catalog parsing and app-server responses so clients can decide whether to offer workspace-directory publishing.
- Document that clients should fail closed when the capability is unavailable.
## Testing
- Cover remote catalog parsing, share-save response propagation, and protocol serialization.
GitOrigin-RevId: b0346b3b330169838f800dfdf21dfa5db1a47f4d
## What changed
- Cache global, user, and workspace remote plugin catalogs on disk with a three-hour TTL and scope- and account-specific keys.
- Serve cached catalogs from `plugin/list`, refresh stale entries in the background, and warm existing caches at app-server startup without blocking list requests.
- Make `forceRefetch: true` bypass cached catalog data and replace an entry only after a successful fetch.
- Invalidate user and workspace catalog caches after remote plugin sharing changes.
## Testing
- Cover cache freshness, scope isolation, legacy global cache compatibility, identity requirements for private catalogs, forced refetches, and non-blocking startup refreshes.
GitOrigin-RevId: 7844e9dfb72e43b26d749b4afd16fd9c1793caba
## Why
Remote plugin requests did not use Codex's effective outbound proxy policy, so
they could bypass configured system proxy and PAC routing.
## What changed
- Route remote plugin catalog, mutation, sharing, upload, and bundle download
requests through the configured route-aware HTTP client.
- Select routes using the complete request URL, including encoded query
parameters and backend-provided signed upload or download URLs.
- Preserve standard Codex headers and suppress diagnostics for URLs or headers
that may contain credentials.
## Testing
Add coverage for route selection of catalog queries, workspace plugin uploads,
and backend-provided bundle download URLs.
GitOrigin-RevId: 106936659e21decf145b6ab4d4be84992e386861
## What changed
- Add nullable `mustShowInstallationInterstitial` metadata to app-server
`PluginSummary` responses.
- Preserve the remote service value through catalog, installed-plugin cache,
list, read, and share-list paths.
- Return `null` for local plugins and remote responses that omit the field so
clients can fail closed when the policy is unavailable.
## Testing
- Cover `true`, `false`, and missing values across remote summary paths and
app-server plugin endpoints.
GitOrigin-RevId: db79702ee2bfa800a9910d64d933bcabecb3ce70
## What changed
- Add optional `scheduledTasks` metadata to `plugin/read` responses for remote
plugins, including hourly, daily, weekday, and weekly schedules.
- Preserve the distinction between unavailable metadata (`null`) and a catalog
response containing no scheduled tasks (`[]`). Local plugin details return
`null`.
- Export the corresponding Rust, JSON Schema, and TypeScript protocol types.
## Testing
- Cover scheduled-task deserialization and propagation through `plugin/read`.
- Verify missing and empty remote metadata remain distinct.
GitOrigin-RevId: 7af223ed0a1bf6d8f054293084a73f34c6d0332d
## Summary
- Add `PluginInstallPolicySource` to the app-server v2 `PluginSummary`
payload.
- Propagate remote `installation_policy_source` through plugin
summaries, details, installed-plugin caching, and app-server responses.
- Preserve the supported source values and map unrecognized backend
values to `null`.
- Return `null` for local plugins and expose the field in generated JSON
and TypeScript schemas.
- Update app-server API documentation and plugin list/read coverage.
## Testing
- `just test -p codex-app-server-protocol` (251 passed)
- Targeted `codex-app-server` plugin list, installed, and read tests (4
passed)
- Targeted `codex-core-plugins` known and unknown policy source tests (2
passed)
- `just fmt`
- `git diff --check`
Adds additive dark-mode plugin logo metadata across manifests, remote
catalogs, and the app-server protocol while keeping uninstalled Git
listings free of synthetic local paths.
Supersedes #28945. This replacement uses an upstream branch so trusted
CI can use the repository-provided remote Bazel configuration.
## Current state
Plugin interfaces expose only the default logo asset. Clients therefore
cannot select a dedicated dark-mode logo even when a plugin provides
one.
## What this PR changes
- Adds nullable `logoDark` and `logoUrlDark` fields to
`PluginInterface`.
- Resolves local `interface.logoDark` assets and maps remote
`logo_url_dark` values.
- Removes path-backed interface assets, including `logoDark`, from
uninstalled Git fallback listings until the plugin has a real local
root.
- Updates the bundled plugin validator and manifest reference.
- Regenerates the app-server JSON schemas and TypeScript types.
Local manifests expose `interface.logoDark` as a package-relative asset
path. Remote catalog responses expose `logo_url_dark`. These values map
into separate app-server fields so clients can preserve local-path and
remote-URL handling.
## Risk
The fields are additive and nullable, so existing clients retain their
current logo behavior. The main risks are an incomplete mapping path or
exposing a synthetic local path for an uninstalled Git plugin.
Local-manifest, remote-catalog, fallback-listing, protocol
serialization, and app-server integration tests cover those paths.
Spiciness: 2/5
## Testing
- `just write-app-server-schema`
- `just fmt`
- Regression test first failed with `logo_dark` resolved to
`/assets/logo-dark.png`, then passed after the fallback-listing fix.
- `just test -p codex-core-plugins` (267 tests passed)
- `just test -p codex-app-server 'suite::v2::plugin'` (114 tests passed)
- `just test -p codex-app-server-protocol -p codex-core-plugins -p
codex-plugin -p codex-skills` (517 tests passed before the follow-up)
- `just test -p codex-tui plugin` (47 tests passed)
- Validated a local plugin manifest containing `interface.logoDark` with
the bundled validator.
## Manual verification
Create a local plugin with both `interface.logo` and
`interface.logoDark`, then call `plugin/list` or `plugin/read`. Confirm
the response contains separate `logo` and `logoDark` paths. For a remote
catalog entry, confirm `logoUrlDark` is populated from `logo_url_dark`.
For an uninstalled Git marketplace entry, confirm path-backed interface
assets remain absent until installation.
Issue: N/A - coordinated maintainer change.
Summary
- Add authenticated parsing for `/ps/plugins/suggested?scope=GLOBAL`,
including remote plugin and connector app identities.
- Validate, deduplicate, sort, and cap endpoint candidates before
caching them by backend and account identity.
- Deduplicate concurrent cache misses and warm recommendations from the
existing remote-installed-plugin refresh path used at startup and after
account changes.
- Keep endpoint results model-invisible in this PR; failures and
responses without `enabled: true` resolve to legacy mode.
Stack
- 1/3. Follow-up: #28400 generalizes plugin suggestion presentation
without activating endpoint recommendations.
- Final activation: #27704.
Validation
- `just test -p codex-core-plugins recommended_plugins`
- `just fix -p codex-core-plugins`
- `just fmt`
- `git diff --check`
## Summary
- preserve the plugin directory endpoint's response order while merging
installed state
- append unmatched installed-only plugins afterward when requested
- add focused coverage for directory order and installed-only placement
## Why
The remote marketplace merge currently reconstructs plugins through
ordered maps and sets, then sorts the result alphabetically by display
name. That discards any ordering supplied by the plugin directory
endpoint before the list reaches Desktop.
## Implementation
Directory plugin IDs are unique, so the merge now iterates the directory
vector directly in response order. For each directory plugin, it removes
matching installed state from an ID-indexed map and builds the summary.
Any entries left in the installed map are installed-only plugins and are
appended when `include_installed_only` is enabled.
There is no separate rank field, rank map, or final sort. Desktop
therefore receives directory order—including any backend ranking—and can
preserve it within its existing stable UI state tiers.
## Testing
- `just test -p codex-core-plugins` (225 passed)