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
parent 433ac84102
commit 6ae99fd35f
6 changed files with 323 additions and 5 deletions
@@ -180,6 +180,107 @@ enabled = true
Ok(())
}
#[tokio::test]
async fn plugin_installed_prefers_remote_curated_conflicts_when_remote_plugin_enabled() -> Result<()>
{
let codex_home = TempDir::new()?;
let server = MockServer::start().await;
write_openai_curated_marketplace(codex_home.path(), &["linear", "calendar"])?;
write_installed_plugin(&codex_home, "openai-curated", "linear")?;
write_installed_plugin(&codex_home, "openai-curated", "calendar")?;
std::fs::write(
codex_home.path().join("config.toml"),
format!(
r#"chatgpt_base_url = "{}/backend-api/"
[features]
plugins = true
remote_plugin = true
plugin_sharing = false
[plugins."linear@openai-curated"]
enabled = true
[plugins."calendar@openai-curated"]
enabled = true
"#,
server.uri()
),
)?;
write_chatgpt_auth(
codex_home.path(),
ChatGptAuthFixture::new("chatgpt-token")
.account_id("account-123")
.chatgpt_user_id("user-123")
.chatgpt_account_id("account-123"),
AuthCredentialsStoreMode::File,
)?;
let mut global_installed_body: serde_json::Value = serde_json::from_str(
&remote_installed_plugin_body("", "1.2.3", /*enabled*/ true),
)?;
let mut remote_only = global_installed_body["plugins"][0].clone();
remote_only["id"] = serde_json::json!("plugins~Plugin_11111111111111111111111111111111");
remote_only["name"] = serde_json::json!("remote-only");
remote_only["release"]["display_name"] = serde_json::json!("Remote Only");
global_installed_body["plugins"]
.as_array_mut()
.expect("installed plugins should be an array")
.push(remote_only);
let global_installed_body = serde_json::to_string(&global_installed_body)?;
mount_remote_installed_plugins(&server, "GLOBAL", &global_installed_body).await;
mount_remote_installed_plugins(&server, "WORKSPACE", empty_remote_installed_plugins_body())
.await;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_plugin_installed_request(PluginInstalledParams {
cwds: None,
install_suggestion_plugin_names: None,
})
.await?;
let response: JSONRPCResponse = timeout(
DEFAULT_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await??;
let response: PluginInstalledResponse = to_response(response)?;
let local_marketplace = response
.marketplaces
.iter()
.find(|marketplace| marketplace.name == "openai-curated")
.expect("expected openai-curated marketplace entry");
assert_eq!(
local_marketplace
.plugins
.iter()
.map(|plugin| plugin.id.clone())
.collect::<Vec<_>>(),
vec!["calendar@openai-curated".to_string()]
);
let remote_marketplace = response
.marketplaces
.iter()
.find(|marketplace| marketplace.name == "openai-curated-remote")
.expect("expected openai-curated-remote marketplace entry");
assert_eq!(
remote_marketplace
.plugins
.iter()
.map(|plugin| plugin.id.clone())
.collect::<Vec<_>>(),
vec![
"linear@openai-curated-remote".to_string(),
"remote-only@openai-curated-remote".to_string(),
]
);
assert_eq!(response.marketplace_load_errors, Vec::new());
Ok(())
}
#[tokio::test]
async fn plugin_installed_ignores_local_cache_without_catalog() -> Result<()> {
let codex_home = TempDir::new()?;