feat: Use remote installed plugin cache for skills and MCP (#20096)

- Fetches and caches remote /installed plugin state
- Lets skills/list load skills from remote-installed cached plugins
without requiring a local marketplace entry
- Routes plugin list/startup/install/uninstall changes through async
plugin cache invalidation and MCP refresh
This commit is contained in:
xl-openai
2026-04-29 12:09:49 -07:00
committed by GitHub
Unverified
parent 5cf0adba93
commit 73cd831952
9 changed files with 751 additions and 54 deletions
+57
View File
@@ -39,6 +39,14 @@ pub struct RemoteMarketplace {
pub plugins: Vec<RemotePluginSummary>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RemoteInstalledPlugin {
pub marketplace_name: String,
pub id: String,
pub name: String,
pub enabled: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RemotePluginSummary {
pub id: String,
@@ -369,6 +377,39 @@ pub async fn fetch_remote_marketplaces(
Ok(marketplaces)
}
pub async fn fetch_remote_installed_plugins(
config: &RemotePluginServiceConfig,
auth: Option<&CodexAuth>,
) -> Result<Vec<RemoteInstalledPlugin>, RemotePluginCatalogError> {
let auth = ensure_chatgpt_auth(auth)?;
let global = async {
let scope = RemotePluginScope::Global;
let installed_plugins = fetch_installed_plugins_for_scope(config, auth, scope).await?;
Ok::<_, RemotePluginCatalogError>((scope, installed_plugins))
};
let workspace = async {
let scope = RemotePluginScope::Workspace;
let installed_plugins = fetch_installed_plugins_for_scope(config, auth, scope).await?;
Ok::<_, RemotePluginCatalogError>((scope, installed_plugins))
};
let (global, workspace) = tokio::try_join!(global, workspace)?;
let mut installed_plugins = [global, workspace]
.into_iter()
.flat_map(|(scope, plugins)| {
plugins
.into_iter()
.map(move |plugin| remote_installed_plugin_to_info(scope, &plugin))
})
.collect::<Vec<_>>();
installed_plugins.sort_by(|left, right| {
left.marketplace_name
.cmp(&right.marketplace_name)
.then_with(|| left.id.cmp(&right.id))
});
Ok(installed_plugins)
}
pub async fn fetch_remote_plugin_detail(
config: &RemotePluginServiceConfig,
auth: Option<&CodexAuth>,
@@ -671,6 +712,22 @@ fn build_remote_plugin_summary(
}
}
fn remote_installed_plugin_to_info(
scope: RemotePluginScope,
installed_plugin: &RemotePluginInstalledItem,
) -> RemoteInstalledPlugin {
let plugin = &installed_plugin.plugin;
// Remote per-skill disabled state (`disabled_skill_names`) is intentionally
// not projected into skills/list yet; local skills.config remains the
// supported source for skill enablement.
RemoteInstalledPlugin {
marketplace_name: scope.marketplace_name().to_string(),
id: plugin.id.clone(),
name: plugin.name.clone(),
enabled: installed_plugin.enabled,
}
}
fn remote_plugin_interface_to_info(plugin: &RemotePluginDirectoryItem) -> Option<PluginInterface> {
let interface = &plugin.release.interface;
let display_name = non_empty_string(Some(&plugin.release.display_name));