-
Notifications
You must be signed in to change notification settings - Fork 15.4k
[codex] Support marketplace plugin manifest fallback #28789
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,10 +7,10 @@ use crate::loader::PluginHookLoadOutcome; | |
| use crate::loader::configured_curated_plugin_ids_from_codex_home; | ||
| use crate::loader::curated_plugin_cache_version; | ||
| use crate::loader::installed_plugin_telemetry_metadata; | ||
| use crate::loader::load_plugin_apps; | ||
| use crate::loader::load_plugin_apps_from_manifest; | ||
| use crate::loader::load_plugin_hooks; | ||
| use crate::loader::load_plugin_hooks_from_layer_stack; | ||
| use crate::loader::load_plugin_mcp_servers; | ||
| use crate::loader::load_plugin_mcp_servers_from_manifest; | ||
| use crate::loader::load_plugin_skills; | ||
| use crate::loader::load_plugins_from_layer_stack; | ||
| use crate::loader::log_plugin_load_errors; | ||
|
|
@@ -27,6 +27,7 @@ use crate::marketplace::MarketplaceInterface; | |
| use crate::marketplace::MarketplaceListError; | ||
| use crate::marketplace::MarketplaceListOutcome; | ||
| use crate::marketplace::MarketplacePluginAuthPolicy; | ||
| use crate::marketplace::MarketplacePluginManifestFallback; | ||
| use crate::marketplace::MarketplacePluginPolicy; | ||
| use crate::marketplace::MarketplacePluginSource; | ||
| use crate::marketplace::ResolvedMarketplacePlugin; | ||
|
|
@@ -315,6 +316,7 @@ pub struct ConfiguredMarketplacePlugin { | |
| pub policy: MarketplacePluginPolicy, | ||
| pub interface: Option<PluginManifestInterface>, | ||
| pub keywords: Vec<String>, | ||
| pub manifest_fallback: Option<MarketplacePluginManifestFallback>, | ||
| pub installed: bool, | ||
| pub enabled: bool, | ||
| } | ||
|
|
@@ -1256,15 +1258,32 @@ impl PluginsManager { | |
| }; | ||
| let store = self.store.clone(); | ||
| let codex_home = self.codex_home.clone(); | ||
| let manifest_fallback_contents = resolved | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Install passes fallback contents directly into PluginStore. There is no prepare-source copy here anymore; Store owns validation and injection inside its existing atomic cache copy, while the four match arms preserve explicit-version and inferred-version behavior. |
||
| .manifest_fallback | ||
| .contents_if_has_metadata() | ||
| .map(str::to_string); | ||
| let result: StorePluginInstallResult = tokio::task::spawn_blocking(move || { | ||
| let materialized = | ||
| materialize_marketplace_plugin_source(codex_home.as_path(), &resolved.source) | ||
| .map_err(PluginStoreError::Invalid)?; | ||
| let source_path = materialized.path; | ||
| if let Some(plugin_version) = plugin_version { | ||
| store.install_with_version(source_path, resolved.plugin_id, plugin_version) | ||
| } else { | ||
| store.install(source_path, resolved.plugin_id) | ||
| match (plugin_version, manifest_fallback_contents.as_deref()) { | ||
| (Some(plugin_version), Some(manifest_contents)) => store | ||
| .install_with_version_and_fallback_manifest( | ||
| source_path, | ||
| resolved.plugin_id, | ||
| plugin_version, | ||
| manifest_contents, | ||
| ), | ||
| (Some(plugin_version), None) => { | ||
| store.install_with_version(source_path, resolved.plugin_id, plugin_version) | ||
| } | ||
| (None, Some(manifest_contents)) => store.install_with_fallback_manifest( | ||
| source_path, | ||
| resolved.plugin_id, | ||
| manifest_contents, | ||
| ), | ||
| (None, None) => store.install(source_path, resolved.plugin_id), | ||
| } | ||
| }) | ||
| .await | ||
|
|
@@ -1394,6 +1413,7 @@ impl PluginsManager { | |
| let enabled = enabled_plugins.contains(&plugin_key); | ||
| let mut interface = plugin.interface; | ||
| let mut local_version = plugin.local_version; | ||
| let manifest_fallback = plugin.manifest_fallback.clone(); | ||
| if installed | ||
| && matches!(&plugin.source, MarketplacePluginSource::Git { .. }) | ||
| && let Some(plugin_id) = plugin_id.as_ref() | ||
|
|
@@ -1424,6 +1444,7 @@ impl PluginsManager { | |
| policy: plugin.policy, | ||
| keywords: plugin.keywords, | ||
| interface, | ||
| manifest_fallback, | ||
| }) | ||
| }) | ||
| .collect::<Vec<_>>(); | ||
|
|
@@ -1491,6 +1512,10 @@ impl PluginsManager { | |
|
|
||
| let marketplace_name = plugin.plugin_id.marketplace_name.clone(); | ||
| let plugin_key = plugin.plugin_id.as_key(); | ||
| let manifest_fallback = plugin | ||
| .manifest_fallback | ||
| .contents_if_has_metadata() | ||
| .map(|_| plugin.manifest_fallback.clone()); | ||
| let (installed_plugins, enabled_plugins) = self.configured_plugin_states(config); | ||
| let installed = installed_plugins.contains(&plugin_key); | ||
| let installed_version = if installed { | ||
|
|
@@ -1518,6 +1543,7 @@ impl PluginsManager { | |
| .as_ref() | ||
| .map(|manifest| manifest.keywords.clone()) | ||
| .unwrap_or_default(), | ||
| manifest_fallback, | ||
| installed, | ||
| enabled: enabled_plugins.contains(&plugin_key), | ||
| }, | ||
|
|
@@ -1604,9 +1630,18 @@ impl PluginsManager { | |
| "path does not exist or is not a directory".to_string(), | ||
| )); | ||
| } | ||
| let manifest = load_plugin_manifest(source_path.as_path()).ok_or_else(|| { | ||
| MarketplaceError::InvalidPlugin("missing or invalid plugin.json".to_string()) | ||
| })?; | ||
| let manifest = | ||
| if codex_utils_plugins::find_plugin_manifest_path(source_path.as_path()).is_some() { | ||
| load_plugin_manifest(source_path.as_path()) | ||
| } else { | ||
| plugin | ||
| .manifest_fallback | ||
| .as_ref() | ||
| .and_then(|fallback| fallback.parse_for_plugin_root(source_path.as_path())) | ||
| } | ||
| .ok_or_else(|| { | ||
| MarketplaceError::InvalidPlugin("missing or invalid plugin.json".to_string()) | ||
| })?; | ||
| let description = manifest.description.clone(); | ||
| let marketplace_category = plugin | ||
| .interface | ||
|
|
@@ -1637,8 +1672,14 @@ impl PluginsManager { | |
| }) | ||
| .collect(); | ||
| let auth_mode = self.auth_mode(); | ||
| let mut app_declarations = load_plugin_apps(source_path.as_path()).await; | ||
| let mut mcp_servers = load_plugin_mcp_servers(source_path.as_path(), auth_mode).await; | ||
| let mut app_declarations = | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These loaders now consume the paths from the manifest that was already selected above. This fixes the previous gap where detail reading accepted a fallback manifest, then app and MCP loading reread disk and dropped fallback-declared paths. |
||
| load_plugin_apps_from_manifest(source_path.as_path(), &manifest.paths).await; | ||
| let mut mcp_servers = load_plugin_mcp_servers_from_manifest( | ||
| source_path.as_path(), | ||
| &manifest.paths, | ||
| /*plugin_policy*/ None, | ||
| ) | ||
| .await; | ||
| if auth_mode.is_some() { | ||
| apply_app_mcp_routing_policy( | ||
| &mut app_declarations, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-curated cache refresh now retains fallback JSON with each source. That matters because refresh runs later than marketplace discovery and otherwise loses the metadata needed to compute the fallback version and reinstall a manifest-less plugin.