fix: Deduplicate installed local and remote curated plugins (#25681)

## Summary
- Deduplicate installed `openai-curated` and `openai-curated-remote`
plugin conflicts by feature flag.
- Prefer remote when remote plugins are enabled; otherwise prefer local,
while preserving one-sided installs.

## Testing
- `just fmt`
- `git diff --check`
- Targeted `just test` was blocked locally because `cargo-nextest` is
not installed.
This commit is contained in:
xl-openai
2026-06-01 14:27:18 -07:00
committed by GitHub
Unverified
parent 433ac84102
commit 6ae99fd35f
6 changed files with 323 additions and 5 deletions
@@ -302,7 +302,6 @@ use codex_core::windows_sandbox::WindowsSandboxLevelExt;
use codex_core::windows_sandbox::WindowsSandboxSetupMode as CoreWindowsSandboxSetupMode;
use codex_core::windows_sandbox::WindowsSandboxSetupRequest;
use codex_core::windows_sandbox::sandbox_setup_is_complete;
use codex_core_plugins::OPENAI_CURATED_MARKETPLACE_NAME;
use codex_core_plugins::PluginInstallError as CorePluginInstallError;
use codex_core_plugins::PluginInstallRequest;
use codex_core_plugins::PluginLoadOutcome;
@@ -6,6 +6,8 @@ use codex_app_server_protocol::PluginInstallPolicy;
use codex_app_server_protocol::PluginSharePrincipalRole;
use codex_app_server_protocol::PluginShareTargetRole;
use codex_config::types::McpServerConfig;
use codex_core_plugins::OPENAI_CURATED_MARKETPLACE_NAME;
use codex_core_plugins::remote::REMOTE_GLOBAL_MARKETPLACE_NAME;
use codex_core_plugins::remote::RemotePluginScope;
use codex_core_plugins::remote::is_valid_remote_plugin_id;
use codex_core_plugins::remote::validate_remote_plugin_id;
@@ -156,6 +158,52 @@ fn remote_installed_plugin_visible_scopes(config: &Config) -> Vec<RemotePluginSc
scopes
}
fn filter_openai_curated_installed_conflicts(
marketplaces: &mut Vec<PluginMarketplaceEntry>,
prefer_remote_curated_conflicts: bool,
) {
let local_installed_plugin_names = marketplaces
.iter()
.find(|marketplace| marketplace.name == OPENAI_CURATED_MARKETPLACE_NAME)
.map(|marketplace| installed_plugin_names(&marketplace.plugins))
.unwrap_or_default();
let remote_installed_plugin_names = marketplaces
.iter()
.find(|marketplace| marketplace.name == REMOTE_GLOBAL_MARKETPLACE_NAME)
.map(|marketplace| installed_plugin_names(&marketplace.plugins))
.unwrap_or_default();
let conflicting_plugin_names = local_installed_plugin_names
.intersection(&remote_installed_plugin_names)
.cloned()
.collect::<HashSet<_>>();
if conflicting_plugin_names.is_empty() {
return;
}
let marketplace_to_filter = if prefer_remote_curated_conflicts {
OPENAI_CURATED_MARKETPLACE_NAME
} else {
REMOTE_GLOBAL_MARKETPLACE_NAME
};
for marketplace in marketplaces.iter_mut() {
if marketplace.name != marketplace_to_filter {
continue;
}
marketplace
.plugins
.retain(|plugin| !plugin.installed || !conflicting_plugin_names.contains(&plugin.name));
}
marketplaces.retain(|marketplace| !marketplace.plugins.is_empty());
}
fn installed_plugin_names(plugins: &[PluginSummary]) -> HashSet<String> {
plugins
.iter()
.filter(|plugin| plugin.installed)
.map(|plugin| plugin.name.clone())
.collect()
}
fn remote_plugin_share_discoverability(
discoverability: PluginShareDiscoverability,
) -> codex_core_plugins::remote::RemotePluginShareDiscoverability {
@@ -740,6 +788,10 @@ impl PluginRequestProcessor {
)
.await,
);
filter_openai_curated_installed_conflicts(
&mut data,
config.features.enabled(Feature::RemotePlugin),
);
Ok(PluginInstalledResponse {
marketplaces: data,