From 1ff7845d67078e38bea9a09656e4fc49c41ca8dd Mon Sep 17 00:00:00 2001 From: xli-oai Date: Thu, 14 May 2026 23:25:22 -0700 Subject: [PATCH] Restore remote installed plugin fallback --- .../src/request_processors/plugins.rs | 38 ++++++++++++++++--- .../app-server/tests/suite/v2/plugin_list.rs | 36 ++++++++++++++++-- codex-rs/core-plugins/src/manager.rs | 12 ++++++ 3 files changed, 77 insertions(+), 9 deletions(-) diff --git a/codex-rs/app-server/src/request_processors/plugins.rs b/codex-rs/app-server/src/request_processors/plugins.rs index bd42d717ff..c10da1c955 100644 --- a/codex-rs/app-server/src/request_processors/plugins.rs +++ b/codex-rs/app-server/src/request_processors/plugins.rs @@ -864,14 +864,40 @@ impl PluginRequestProcessor { } if config.features.enabled(Feature::RemotePlugin) { - if let Some(remote_marketplaces) = + let remote_marketplaces = if let Some(remote_marketplaces) = plugins_manager.remote_installed_marketplaces_from_cache() { - for remote_marketplace in remote_marketplaces - .into_iter() - .map(remote_marketplace_to_info) - { - merge_plugin_marketplace_entry(&mut data, remote_marketplace); + Ok(remote_marketplaces) + } else { + let remote_plugin_service_config = RemotePluginServiceConfig { + chatgpt_base_url: config.chatgpt_base_url.clone(), + }; + plugins_manager + .fetch_and_cache_remote_installed_marketplaces( + &remote_plugin_service_config, + auth.as_ref(), + ) + .await + }; + + match remote_marketplaces { + Ok(remote_marketplaces) => { + for remote_marketplace in remote_marketplaces + .into_iter() + .map(remote_marketplace_to_info) + { + merge_plugin_marketplace_entry(&mut data, remote_marketplace); + } + } + Err( + RemotePluginCatalogError::AuthRequired + | RemotePluginCatalogError::UnsupportedAuthMode, + ) => {} + Err(err) => { + warn!( + error = %err, + "plugin/installed remote installed plugin fetch failed; returning local marketplaces only" + ); } } } diff --git a/codex-rs/app-server/tests/suite/v2/plugin_list.rs b/codex-rs/app-server/tests/suite/v2/plugin_list.rs index 428e7c9a75..74268d12dd 100644 --- a/codex-rs/app-server/tests/suite/v2/plugin_list.rs +++ b/codex-rs/app-server/tests/suite/v2/plugin_list.rs @@ -1802,7 +1802,7 @@ async fn plugin_list_does_not_append_global_remote_when_marketplace_kinds_are_ex } #[tokio::test] -async fn plugin_installed_does_not_fetch_remote_installed_when_cache_is_empty() -> Result<()> { +async fn plugin_installed_falls_back_to_remote_installed_and_caches_response() -> Result<()> { let codex_home = TempDir::new()?; let server = MockServer::start().await; write_remote_plugin_catalog_config( @@ -1827,6 +1827,7 @@ async fn plugin_installed_does_not_fetch_remote_installed_when_cache_is_empty() mount_remote_installed_plugins(&server, "GLOBAL", &global_installed_body).await; mount_remote_installed_plugins(&server, "WORKSPACE", empty_remote_installed_plugins_body()) .await; + write_installed_plugin_with_version(&codex_home, "chatgpt-global", "linear", "1.2.3")?; let mut mcp = McpProcess::new(codex_home.path()).await?; timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??; @@ -1845,14 +1846,43 @@ async fn plugin_installed_does_not_fetch_remote_installed_when_cache_is_empty() .await??; let response: PluginInstalledResponse = to_response(response)?; - assert_eq!(response.marketplaces, Vec::new()); + assert_eq!(response.marketplaces.len(), 1); + assert_eq!(response.marketplaces[0].name, "chatgpt-global"); + assert_eq!( + response.marketplaces[0] + .plugins + .iter() + .map(|plugin| (plugin.id.clone(), plugin.installed, plugin.enabled)) + .collect::>(), + vec![("linear@chatgpt-global".to_string(), true, true)] + ); wait_for_remote_plugin_request_count( &server, "/ps/plugins/installed", - /*expected_count*/ 0, + /*expected_count*/ 2, ) .await?; wait_for_remote_plugin_request_count(&server, "/ps/plugins/list", /*expected_count*/ 0).await?; + + let second_request_id = mcp + .send_plugin_installed_request(PluginInstalledParams { + cwds: None, + install_suggestion_plugin_names: None, + }) + .await?; + let _: PluginInstalledResponse = to_response( + timeout( + DEFAULT_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(second_request_id)), + ) + .await??, + )?; + wait_for_remote_plugin_request_count( + &server, + "/ps/plugins/installed", + /*expected_count*/ 2, + ) + .await?; Ok(()) } diff --git a/codex-rs/core-plugins/src/manager.rs b/codex-rs/core-plugins/src/manager.rs index 6c9045b470..771512e240 100644 --- a/codex-rs/core-plugins/src/manager.rs +++ b/codex-rs/core-plugins/src/manager.rs @@ -618,6 +618,18 @@ impl PluginsManager { )) } + pub async fn fetch_and_cache_remote_installed_marketplaces( + &self, + config: &RemotePluginServiceConfig, + auth: Option<&CodexAuth>, + ) -> Result, RemotePluginCatalogError> { + let plugins = crate::remote::fetch_remote_installed_plugins(config, auth).await?; + let marketplaces = + crate::remote::remote_installed_marketplaces_from_cache(&plugins, &self.store); + self.write_remote_installed_plugins_cache(plugins); + Ok(marketplaces) + } + fn write_remote_installed_plugins_cache(&self, plugins: Vec) -> bool { let mut cache = match self.remote_installed_plugins_cache.write() { Ok(cache) => cache,