From 6db937275fd610f3964b935b46144c74a0e0b55c Mon Sep 17 00:00:00 2001 From: Abhinav Date: Wed, 24 Jun 2026 20:13:03 -0700 Subject: [PATCH] [codex] Populate remote plugin local versions (#29956) # What - Carry installed remote release versions through remote plugin summaries as `localVersion`. - Keep the app-server mapping a pure adapter by populating that value in the remote catalog layer. # Why Remote plugin summaries always returned `localVersion: null` even after their versioned bundles had been installed locally. Consumers such as scheduled-task template discovery use `localVersion` to resolve a plugin's materialized root, so templates from remote curated plugins were silently skipped. --- codex-rs/app-server/src/request_processors/plugins.rs | 2 +- codex-rs/app-server/tests/suite/v2/plugin_list.rs | 6 ++++++ codex-rs/app-server/tests/suite/v2/plugin_share.rs | 6 +++--- codex-rs/core-plugins/src/remote.rs | 4 ++++ codex-rs/core-plugins/src/remote/share/tests.rs | 2 ++ 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/codex-rs/app-server/src/request_processors/plugins.rs b/codex-rs/app-server/src/request_processors/plugins.rs index a98fddb5b..4aa8352d1 100644 --- a/codex-rs/app-server/src/request_processors/plugins.rs +++ b/codex-rs/app-server/src/request_processors/plugins.rs @@ -2158,7 +2158,7 @@ fn remote_plugin_summary_to_info(summary: RemoteCatalogPluginSummary) -> PluginS PluginSummary { id: summary.id, remote_plugin_id: Some(summary.remote_plugin_id), - local_version: None, + local_version: summary.local_version, name: summary.name, share_context: summary .share_context 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 4d143109f..a301714cf 100644 --- a/codex-rs/app-server/tests/suite/v2/plugin_list.rs +++ b/codex-rs/app-server/tests/suite/v2/plugin_list.rs @@ -1655,6 +1655,7 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled() - .chatgpt_account_id("account-123"), AuthCredentialsStoreMode::File, )?; + write_installed_plugin_with_version(&codex_home, "openai-curated-remote", "linear", "1.2.3")?; let global_directory_body = r#"{ "plugins": [ @@ -1704,6 +1705,7 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled() - "authentication_policy": "ON_USE", "status": "ENABLED", "release": { + "version": "1.2.3", "display_name": "Linear", "description": "Track work in Linear", "app_ids": [], @@ -1813,6 +1815,10 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled() - ); assert_eq!(remote_marketplace.plugins[0].name, "linear"); assert_eq!(remote_marketplace.plugins[0].source, PluginSource::Remote); + assert_eq!( + remote_marketplace.plugins[0].local_version.as_deref(), + Some("1.2.3") + ); assert_eq!(remote_marketplace.plugins[0].installed, true); assert_eq!(remote_marketplace.plugins[0].enabled, true); assert_eq!( diff --git a/codex-rs/app-server/tests/suite/v2/plugin_share.rs b/codex-rs/app-server/tests/suite/v2/plugin_share.rs index ba142feb9..5257ea03b 100644 --- a/codex-rs/app-server/tests/suite/v2/plugin_share.rs +++ b/codex-rs/app-server/tests/suite/v2/plugin_share.rs @@ -171,7 +171,7 @@ async fn plugin_share_save_uploads_local_plugin() -> Result<()> { plugin: PluginSummary { id: "demo-plugin@workspace-shared-with-me".to_string(), remote_plugin_id: Some("plugins_123".to_string()), - local_version: None, + local_version: Some("0.1.0".to_string()), name: "demo-plugin".to_string(), share_context: Some(expected_share_context("plugins_123")), source: PluginSource::Remote, @@ -575,7 +575,7 @@ async fn plugin_share_list_returns_created_workspace_plugins() -> Result<()> { plugin: PluginSummary { id: "demo-plugin@workspace-shared-with-me".to_string(), remote_plugin_id: Some("plugins_123".to_string()), - local_version: None, + local_version: Some("0.1.0".to_string()), name: "demo-plugin".to_string(), share_context: Some(expected_share_context("plugins_123")), source: PluginSource::Remote, @@ -1176,7 +1176,7 @@ async fn plugin_share_delete_removes_created_workspace_plugin() -> Result<()> { plugin: PluginSummary { id: "demo-plugin@workspace-shared-with-me".to_string(), remote_plugin_id: Some("plugins_123".to_string()), - local_version: None, + local_version: Some("0.1.0".to_string()), name: "demo-plugin".to_string(), share_context: Some(expected_share_context("plugins_123")), source: PluginSource::Remote, diff --git a/codex-rs/core-plugins/src/remote.rs b/codex-rs/core-plugins/src/remote.rs index 506b943b1..515bbb71c 100644 --- a/codex-rs/core-plugins/src/remote.rs +++ b/codex-rs/core-plugins/src/remote.rs @@ -161,6 +161,7 @@ pub struct RemoteInstalledPlugin { pub struct RemotePluginSummary { pub id: String, pub remote_plugin_id: String, + pub local_version: Option, pub name: String, pub share_context: Option, pub installed: bool, @@ -1040,6 +1041,7 @@ pub fn group_remote_installed_plugins_by_marketplaces( let plugin_summary = RemotePluginSummary { id: plugin_id.as_key(), remote_plugin_id: plugin.id.clone(), + local_version: None, name: plugin.name.clone(), share_context: None, installed: true, @@ -1470,6 +1472,8 @@ fn build_remote_plugin_summary( Ok(RemotePluginSummary { id: plugin_id.as_key(), remote_plugin_id: plugin.id.clone(), + local_version: installed_plugin + .and_then(|installed| installed.plugin.release.version.clone()), name: plugin.name.clone(), share_context: remote_plugin_share_context(plugin)?, installed: installed_plugin.is_some(), diff --git a/codex-rs/core-plugins/src/remote/share/tests.rs b/codex-rs/core-plugins/src/remote/share/tests.rs index ed4834123..c8d06b234 100644 --- a/codex-rs/core-plugins/src/remote/share/tests.rs +++ b/codex-rs/core-plugins/src/remote/share/tests.rs @@ -618,6 +618,7 @@ async fn list_remote_plugin_shares_fetches_created_workspace_plugins() { summary: RemotePluginSummary { id: "demo-plugin@workspace-shared-with-me".to_string(), remote_plugin_id: "plugins_123".to_string(), + local_version: None, name: "demo-plugin".to_string(), share_context: Some(RemotePluginShareContext { remote_plugin_id: "plugins_123".to_string(), @@ -657,6 +658,7 @@ async fn list_remote_plugin_shares_fetches_created_workspace_plugins() { summary: RemotePluginSummary { id: "demo-plugin@workspace-shared-with-me".to_string(), remote_plugin_id: "plugins_456".to_string(), + local_version: Some("0.1.0".to_string()), name: "demo-plugin".to_string(), share_context: Some(RemotePluginShareContext { remote_plugin_id: "plugins_456".to_string(),