diff --git a/codex-rs/core-plugins/src/manager.rs b/codex-rs/core-plugins/src/manager.rs index 249fa3067e..66ef9947c7 100644 --- a/codex-rs/core-plugins/src/manager.rs +++ b/codex-rs/core-plugins/src/manager.rs @@ -36,9 +36,10 @@ use crate::marketplace_upgrade::ConfiguredMarketplaceUpgradeOutcome; use crate::marketplace_upgrade::configured_git_marketplace_names; use crate::marketplace_upgrade::upgrade_configured_git_marketplaces; use crate::remote::RemoteInstalledPlugin; -use crate::remote::RemoteInstalledPluginScopes; use crate::remote::RemotePluginCatalogError; +use crate::remote::RemotePluginScope; use crate::remote::RemotePluginServiceConfig; +use crate::remote::remote_installed_scope_allows_marketplace; use crate::remote_legacy::RemotePluginFetchError; use crate::remote_legacy::RemotePluginMutationError; use crate::startup_sync::curated_plugins_repo_path; @@ -113,11 +114,15 @@ impl PluginsConfigInput { } } - pub(crate) fn remote_installed_plugin_scopes(&self) -> RemoteInstalledPluginScopes { - RemoteInstalledPluginScopes::from_features( - self.remote_plugin_enabled, - self.plugin_sharing_enabled, - ) + pub(crate) fn remote_installed_plugin_scopes(&self) -> Vec { + let mut scopes = Vec::new(); + if self.remote_plugin_enabled { + scopes.push(RemotePluginScope::Global); + } + if self.plugin_sharing_enabled { + scopes.push(RemotePluginScope::Workspace); + } + scopes } } @@ -139,7 +144,7 @@ struct CachedFeaturedPluginIds { struct RemoteInstalledPluginsCacheRefreshRequest { service_config: RemotePluginServiceConfig, auth: Option, - scopes: RemoteInstalledPluginScopes, + scopes: Vec, notify: RemoteInstalledPluginsCacheRefreshNotify, // App-server attaches side effects such as skills metadata invalidation and MCP refreshes when // remote installed state changes. @@ -161,6 +166,11 @@ struct RemoteInstalledPluginsCacheRefreshState { in_flight: bool, } +struct CachedRemoteInstalledPlugins { + scopes: Vec, + plugins: Vec, +} + #[derive(Clone, PartialEq, Eq)] struct NonCuratedCacheRefreshRequest { roots: Vec, @@ -414,7 +424,7 @@ pub struct PluginsManager { configured_marketplace_upgrade_state: RwLock, non_curated_cache_refresh_state: RwLock, cached_enabled_outcome: RwLock>, - remote_installed_plugins_cache: RwLock>>, + remote_installed_plugins_cache: RwLock>, remote_installed_plugins_cache_refresh_state: RwLock, remote_sync_lock: Semaphore, restriction_product: Option, @@ -425,7 +435,7 @@ pub struct PluginsManager { struct CachedPluginLoadOutcome { config_version: String, plugin_hooks_enabled: bool, - remote_installed_plugin_scopes: RemoteInstalledPluginScopes, + remote_installed_plugin_scopes: Vec, outcome: PluginLoadOutcome, } @@ -503,7 +513,7 @@ impl PluginsManager { && let Some(outcome) = self.cached_enabled_outcome( &config_version, plugin_hooks_enabled, - remote_installed_plugin_scopes, + &remote_installed_plugin_scopes, ) { return outcome; @@ -511,7 +521,7 @@ impl PluginsManager { let outcome = load_plugins_from_layer_stack( &config.config_layer_stack, - self.remote_installed_plugin_configs(remote_installed_plugin_scopes), + self.remote_installed_plugin_configs(&remote_installed_plugin_scopes), &self.store, self.restriction_product, plugin_hooks_enabled, @@ -560,7 +570,7 @@ impl PluginsManager { } load_plugins_from_layer_stack( config_layer_stack, - self.remote_installed_plugin_configs(config.remote_installed_plugin_scopes()), + self.remote_installed_plugin_configs(&config.remote_installed_plugin_scopes()), &self.store, self.restriction_product, plugin_hooks_feature_enabled, @@ -583,7 +593,7 @@ impl PluginsManager { &self, config_version: &str, plugin_hooks_enabled: bool, - remote_installed_plugin_scopes: RemoteInstalledPluginScopes, + remote_installed_plugin_scopes: &[RemotePluginScope], ) -> Option { match self.cached_enabled_outcome.read() { Ok(cache) => cache @@ -608,19 +618,22 @@ impl PluginsManager { fn remote_installed_plugin_configs( &self, - scopes: RemoteInstalledPluginScopes, + scopes: &[RemotePluginScope], ) -> HashMap { let cache = match self.remote_installed_plugins_cache.read() { Ok(cache) => cache, Err(err) => err.into_inner(), }; - let Some(plugins) = cache.as_ref() else { + let Some(cache) = cache.as_ref() else { return HashMap::new(); }; - let plugins = plugins + let plugins = cache + .plugins .iter() - .filter(|plugin| scopes.allows_marketplace_name(&plugin.marketplace_name)) + .filter(|plugin| { + remote_installed_scope_allows_marketplace(scopes, &plugin.marketplace_name) + }) .cloned() .collect::>(); @@ -639,10 +652,16 @@ impl PluginsManager { Ok(cache) => cache, Err(err) => err.into_inner(), }; - let plugins = cache.as_ref()?; - let plugins = plugins + let cache = cache.as_ref()?; + if !scopes.iter().all(|scope| cache.scopes.contains(scope)) { + return None; + } + let plugins = cache + .plugins .iter() - .filter(|plugin| scopes.allows_marketplace_name(&plugin.marketplace_name)) + .filter(|plugin| { + remote_installed_scope_allows_marketplace(&scopes, &plugin.marketplace_name) + }) .cloned() .collect::>(); Some(crate::remote::group_remote_installed_plugins_by_marketplaces(&plugins)) @@ -661,26 +680,33 @@ impl PluginsManager { let plugins = crate::remote::fetch_remote_installed_plugins_for_scopes( &remote_plugin_service_config(config), auth, - scopes, + &scopes, ) .await?; let marketplaces = crate::remote::group_remote_installed_plugins_by_marketplaces(&plugins); - let changed = self.write_remote_installed_plugins_cache(plugins); + let changed = self.write_remote_installed_plugins_cache(plugins, scopes); if changed && let Some(on_effective_plugins_changed) = on_effective_plugins_changed { on_effective_plugins_changed(); } Ok(marketplaces) } - fn write_remote_installed_plugins_cache(&self, plugins: Vec) -> bool { + fn write_remote_installed_plugins_cache( + &self, + plugins: Vec, + scopes: Vec, + ) -> bool { let mut cache = match self.remote_installed_plugins_cache.write() { Ok(cache) => cache, Err(err) => err.into_inner(), }; - if cache.as_ref().is_some_and(|cache| cache.eq(&plugins)) { + if cache + .as_ref() + .is_some_and(|cache| cache.plugins == plugins && cache.scopes == scopes) + { return false; } - *cache = Some(plugins); + *cache = Some(CachedRemoteInstalledPlugins { scopes, plugins }); drop(cache); self.clear_enabled_outcome_cache(); true @@ -1683,7 +1709,9 @@ impl PluginsManager { Err(err) => err.into_inner(), }; if let Some(existing_request) = state.requested.as_ref() { - request.scopes = request.scopes.union(existing_request.scopes); + request.scopes.extend_from_slice(&existing_request.scopes); + request.scopes.sort_unstable(); + request.scopes.dedup(); if matches!( existing_request.notify, RemoteInstalledPluginsCacheRefreshNotify::AfterSuccessfulRefresh @@ -1841,14 +1869,17 @@ impl PluginsManager { let installed_plugins = crate::remote::fetch_remote_installed_plugins( &request.service_config, request.auth.as_ref(), - request.scopes, + &request.scopes, ) .await; match installed_plugins { Ok(installed_plugins) => { // TODO(remote plugins): reconcile missing or stale local bundles before // publishing remote installed state as effective local plugin config. - let changed = self.write_remote_installed_plugins_cache(installed_plugins); + let changed = self.write_remote_installed_plugins_cache( + installed_plugins, + request.scopes.clone(), + ); let should_notify = changed || matches!( request.notify, diff --git a/codex-rs/core-plugins/src/manager_tests.rs b/codex-rs/core-plugins/src/manager_tests.rs index a8658b0ba5..753135979e 100644 --- a/codex-rs/core-plugins/src/manager_tests.rs +++ b/codex-rs/core-plugins/src/manager_tests.rs @@ -7,6 +7,7 @@ use crate::loader::refresh_non_curated_plugin_cache; use crate::loader::refresh_non_curated_plugin_cache_force_reinstall; use crate::marketplace::MarketplacePluginInstallPolicy; use crate::remote::RemoteInstalledPlugin; +use crate::remote::RemotePluginScope; use crate::startup_sync::curated_plugins_repo_path; use crate::test_support::TEST_CURATED_PLUGIN_CACHE_VERSION; use crate::test_support::TEST_CURATED_PLUGIN_SHA; @@ -335,7 +336,7 @@ approval_mode = "approve" } #[tokio::test] -async fn remote_installed_cache_adds_plugin_skill_roots_without_remote_plugin_flag() { +async fn remote_installed_cache_ignores_global_plugins_without_remote_plugin_flag() { let codex_home = TempDir::new().unwrap(); let plugin_base = codex_home .path() @@ -350,25 +351,23 @@ plugins = true let config = load_config(codex_home.path(), codex_home.path()).await; let manager = PluginsManager::new(codex_home.path().to_path_buf()); - manager.write_remote_installed_plugins_cache(vec![RemoteInstalledPlugin { - marketplace_name: "chatgpt-global".to_string(), - id: "plugins~Plugin_linear".to_string(), - name: "linear".to_string(), - enabled: true, - install_policy: codex_app_server_protocol::PluginInstallPolicy::Available, - auth_policy: codex_app_server_protocol::PluginAuthPolicy::OnUse, - availability: codex_app_server_protocol::PluginAvailability::Available, - interface: None, - keywords: Vec::new(), - }]); + manager.write_remote_installed_plugins_cache( + vec![RemoteInstalledPlugin { + marketplace_name: "chatgpt-global".to_string(), + id: "plugins~Plugin_linear".to_string(), + name: "linear".to_string(), + enabled: true, + install_policy: codex_app_server_protocol::PluginInstallPolicy::Available, + auth_policy: codex_app_server_protocol::PluginAuthPolicy::OnUse, + availability: codex_app_server_protocol::PluginAvailability::Available, + interface: None, + keywords: Vec::new(), + }], + vec![RemotePluginScope::Global], + ); let outcome = manager.plugins_for_config(&config).await; - assert_eq!( - outcome.effective_skill_roots(), - vec![AbsolutePathBuf::try_from(plugin_base.join("local/skills")).unwrap()] - ); - assert_eq!(outcome.plugins().len(), 1); - assert_eq!(outcome.plugins()[0].config_name, "linear@chatgpt-global"); + assert_eq!(outcome, PluginLoadOutcome::default()); } #[tokio::test] @@ -384,17 +383,20 @@ remote_plugin = true let config = load_config(codex_home.path(), codex_home.path()).await; let manager = PluginsManager::new(codex_home.path().to_path_buf()); - manager.write_remote_installed_plugins_cache(vec![RemoteInstalledPlugin { - marketplace_name: "chatgpt-global".to_string(), - id: "plugins~Plugin_linear".to_string(), - name: "linear".to_string(), - enabled: true, - install_policy: codex_app_server_protocol::PluginInstallPolicy::Available, - auth_policy: codex_app_server_protocol::PluginAuthPolicy::OnUse, - availability: codex_app_server_protocol::PluginAvailability::Available, - interface: None, - keywords: Vec::new(), - }]); + manager.write_remote_installed_plugins_cache( + vec![RemoteInstalledPlugin { + marketplace_name: "chatgpt-global".to_string(), + id: "plugins~Plugin_linear".to_string(), + name: "linear".to_string(), + enabled: true, + install_policy: codex_app_server_protocol::PluginInstallPolicy::Available, + auth_policy: codex_app_server_protocol::PluginAuthPolicy::OnUse, + availability: codex_app_server_protocol::PluginAvailability::Available, + interface: None, + keywords: Vec::new(), + }], + config.remote_installed_plugin_scopes(), + ); let outcome = manager.plugins_for_config(&config).await; assert_eq!(outcome, PluginLoadOutcome::default()); @@ -413,35 +415,38 @@ remote_plugin = true let config = load_plugins_config_input(codex_home.path(), codex_home.path()).await; let manager = PluginsManager::new(codex_home.path().to_path_buf()); - manager.write_remote_installed_plugins_cache(vec![RemoteInstalledPlugin { - marketplace_name: "chatgpt-global".to_string(), - id: "plugins~Plugin_linear".to_string(), - name: "linear".to_string(), - enabled: true, - install_policy: codex_app_server_protocol::PluginInstallPolicy::InstalledByDefault, - auth_policy: codex_app_server_protocol::PluginAuthPolicy::OnInstall, - availability: codex_app_server_protocol::PluginAvailability::Available, - interface: Some(codex_app_server_protocol::PluginInterface { - display_name: Some("Linear".to_string()), - short_description: Some("Track remote work".to_string()), - long_description: None, - developer_name: None, - category: None, - capabilities: Vec::new(), - website_url: None, - privacy_policy_url: None, - terms_of_service_url: None, - default_prompt: None, - brand_color: Some("#111111".to_string()), - composer_icon: None, - composer_icon_url: None, - logo: None, - logo_url: None, - screenshots: Vec::new(), - screenshot_urls: Vec::new(), - }), - keywords: vec!["issues".to_string()], - }]); + manager.write_remote_installed_plugins_cache( + vec![RemoteInstalledPlugin { + marketplace_name: "chatgpt-global".to_string(), + id: "plugins~Plugin_linear".to_string(), + name: "linear".to_string(), + enabled: true, + install_policy: codex_app_server_protocol::PluginInstallPolicy::InstalledByDefault, + auth_policy: codex_app_server_protocol::PluginAuthPolicy::OnInstall, + availability: codex_app_server_protocol::PluginAvailability::Available, + interface: Some(codex_app_server_protocol::PluginInterface { + display_name: Some("Linear".to_string()), + short_description: Some("Track remote work".to_string()), + long_description: None, + developer_name: None, + category: None, + capabilities: Vec::new(), + website_url: None, + privacy_policy_url: None, + terms_of_service_url: None, + default_prompt: None, + brand_color: Some("#111111".to_string()), + composer_icon: None, + composer_icon_url: None, + logo: None, + logo_url: None, + screenshots: Vec::new(), + screenshot_urls: Vec::new(), + }), + keywords: vec!["issues".to_string()], + }], + config.remote_installed_plugin_scopes(), + ); let marketplaces = manager .build_remote_installed_plugin_marketplaces_from_cache(&config) diff --git a/codex-rs/core-plugins/src/remote.rs b/codex-rs/core-plugins/src/remote.rs index 24076f3742..ce1447f9f5 100644 --- a/codex-rs/core-plugins/src/remote.rs +++ b/codex-rs/core-plugins/src/remote.rs @@ -332,75 +332,12 @@ impl RemotePluginScope { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub(crate) struct RemoteInstalledPluginScopes { - global: bool, - workspace: bool, -} - -impl RemoteInstalledPluginScopes { - pub(crate) fn all() -> Self { - Self { - global: true, - workspace: true, - } - } - - pub(crate) fn from_features(remote_plugin_enabled: bool, plugin_sharing_enabled: bool) -> Self { - Self { - global: remote_plugin_enabled, - workspace: plugin_sharing_enabled, - } - } - - pub(crate) fn is_empty(self) -> bool { - !self.global && !self.workspace - } - - pub(crate) fn union(self, other: Self) -> Self { - Self { - global: self.global || other.global, - workspace: self.workspace || other.workspace, - } - } - - pub(crate) fn iter(self) -> impl Iterator { - [ - (self.global, RemotePluginScope::Global), - (self.workspace, RemotePluginScope::Workspace), - ] - .into_iter() - .filter_map(|(enabled, scope)| enabled.then_some(scope)) - } - - pub(crate) fn allows_marketplace_name(self, marketplace_name: &str) -> bool { - match RemotePluginScope::from_marketplace_name(marketplace_name) { - Some(RemotePluginScope::Global) => self.global, - Some(RemotePluginScope::Workspace) => self.workspace, - None => false, - } - } - - pub(crate) fn marketplace_names(self) -> impl Iterator { - [ - (self.global, REMOTE_GLOBAL_MARKETPLACE_NAME), - (self.workspace, REMOTE_WORKSPACE_MARKETPLACE_NAME), - ( - self.workspace, - REMOTE_WORKSPACE_SHARED_WITH_ME_MARKETPLACE_NAME, - ), - ( - self.workspace, - REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME, - ), - ( - self.workspace, - REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME, - ), - ] - .into_iter() - .filter_map(|(enabled, marketplace_name)| enabled.then_some(marketplace_name)) - } +pub(crate) fn remote_installed_scope_allows_marketplace( + scopes: &[RemotePluginScope], + marketplace_name: &str, +) -> bool { + RemotePluginScope::from_marketplace_name(marketplace_name) + .is_some_and(|scope| scopes.contains(&scope)) } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -717,7 +654,7 @@ fn build_remote_marketplace( pub(crate) async fn fetch_remote_installed_plugins( config: &RemotePluginServiceConfig, auth: Option<&CodexAuth>, - scopes: RemoteInstalledPluginScopes, + scopes: &[RemotePluginScope], ) -> Result, RemotePluginCatalogError> { fetch_remote_installed_plugins_for_scopes(config, auth, scopes).await } @@ -725,15 +662,15 @@ pub(crate) async fn fetch_remote_installed_plugins( pub(crate) async fn fetch_remote_installed_plugins_for_scopes( config: &RemotePluginServiceConfig, auth: Option<&CodexAuth>, - scopes: RemoteInstalledPluginScopes, + scopes: &[RemotePluginScope], ) -> Result, RemotePluginCatalogError> { if scopes.is_empty() { return Ok(Vec::new()); } let auth = ensure_chatgpt_auth(auth)?; let mut installed_plugins = Vec::new(); - for scope in scopes.iter() { - installed_plugins.extend(fetch_installed_plugins_for_scope(config, auth, scope).await?); + for scope in scopes { + installed_plugins.extend(fetch_installed_plugins_for_scope(config, auth, *scope).await?); } let mut installed_plugins = installed_plugins .into_iter() diff --git a/codex-rs/core-plugins/src/remote/remote_installed_plugin_sync.rs b/codex-rs/core-plugins/src/remote/remote_installed_plugin_sync.rs index c0874b7d09..5f6f45fa6d 100644 --- a/codex-rs/core-plugins/src/remote/remote_installed_plugin_sync.rs +++ b/codex-rs/core-plugins/src/remote/remote_installed_plugin_sync.rs @@ -1,5 +1,10 @@ -use super::RemoteInstalledPluginScopes; +use super::REMOTE_GLOBAL_MARKETPLACE_NAME; +use super::REMOTE_WORKSPACE_MARKETPLACE_NAME; +use super::REMOTE_WORKSPACE_SHARED_WITH_ME_MARKETPLACE_NAME; +use super::REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME; +use super::REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME; use super::RemotePluginCatalogError; +use super::RemotePluginScope; use super::RemotePluginServiceConfig; use super::ensure_chatgpt_auth; use super::fetch_installed_plugins_for_scope_with_download_url; @@ -22,17 +27,6 @@ use std::sync::OnceLock; use tracing::info; use tracing::warn; -#[cfg(test)] -use super::REMOTE_GLOBAL_MARKETPLACE_NAME; -#[cfg(test)] -use super::REMOTE_WORKSPACE_MARKETPLACE_NAME; -#[cfg(test)] -use super::REMOTE_WORKSPACE_SHARED_WITH_ME_MARKETPLACE_NAME; -#[cfg(test)] -use super::REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME; -#[cfg(test)] -use super::REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME; - static REMOTE_INSTALLED_PLUGIN_BUNDLE_SYNC_IN_FLIGHT: OnceLock< Mutex>, > = OnceLock::new(); @@ -71,7 +65,7 @@ pub enum RemoteInstalledPluginBundleSyncError { #[derive(Debug, Clone, PartialEq, Eq, Hash)] struct RemoteInstalledPluginBundleSyncKey { plugin_cache_root: PathBuf, - scopes: RemoteInstalledPluginScopes, + scopes: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -89,7 +83,7 @@ pub(crate) fn maybe_start_remote_installed_plugin_bundle_sync( codex_home: PathBuf, config: RemotePluginServiceConfig, auth: Option, - scopes: RemoteInstalledPluginScopes, + scopes: Vec, on_local_cache_changed: Option>, ) { if scopes.is_empty() { @@ -100,7 +94,7 @@ pub(crate) fn maybe_start_remote_installed_plugin_bundle_sync( }; let key = RemoteInstalledPluginBundleSyncKey { plugin_cache_root: remote_plugin_cache_root(&codex_home), - scopes, + scopes: scopes.clone(), }; if !mark_remote_installed_plugin_bundle_sync_in_flight(key.clone()) { return; @@ -111,7 +105,7 @@ pub(crate) fn maybe_start_remote_installed_plugin_bundle_sync( codex_home, &config, Some(&auth), - scopes, + &scopes, ) .await; match result { @@ -148,7 +142,7 @@ pub async fn sync_remote_installed_plugin_bundles_once( codex_home, config, auth, - RemoteInstalledPluginScopes::all(), + &[RemotePluginScope::Global, RemotePluginScope::Workspace], ) .await } @@ -157,27 +151,38 @@ async fn sync_remote_installed_plugin_bundles_once_for_scopes( codex_home: PathBuf, config: &RemotePluginServiceConfig, auth: Option<&CodexAuth>, - scopes: RemoteInstalledPluginScopes, + scopes: &[RemotePluginScope], ) -> Result { if scopes.is_empty() { return Ok(RemoteInstalledPluginBundleSyncOutcome::default()); } let auth = ensure_chatgpt_auth(auth)?; let mut installed_plugins_by_scope = Vec::new(); - for scope in scopes.iter() { + for scope in scopes { let installed_plugins = fetch_installed_plugins_for_scope_with_download_url( - config, auth, scope, /*include_download_urls*/ true, + config, auth, *scope, /*include_download_urls*/ true, ) .await?; - installed_plugins_by_scope.push((scope, installed_plugins)); + installed_plugins_by_scope.push((*scope, installed_plugins)); } let store = PluginStore::try_new(codex_home.clone())?; - let mut installed_plugin_names_by_marketplace = BTreeMap::>::from_iter( - scopes - .marketplace_names() - .map(|marketplace_name| (marketplace_name.to_string(), BTreeSet::new())), - ); + let mut installed_plugin_names_by_marketplace = BTreeMap::>::new(); + if scopes.contains(&RemotePluginScope::Global) { + installed_plugin_names_by_marketplace + .insert(REMOTE_GLOBAL_MARKETPLACE_NAME.to_string(), BTreeSet::new()); + } + if scopes.contains(&RemotePluginScope::Workspace) { + for marketplace_name in [ + REMOTE_WORKSPACE_MARKETPLACE_NAME, + REMOTE_WORKSPACE_SHARED_WITH_ME_MARKETPLACE_NAME, + REMOTE_WORKSPACE_SHARED_WITH_ME_PRIVATE_MARKETPLACE_NAME, + REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_NAME, + ] { + installed_plugin_names_by_marketplace + .insert(marketplace_name.to_string(), BTreeSet::new()); + } + } let mut installed_plugin_ids = BTreeSet::new(); let mut failed_remote_plugin_ids = BTreeSet::new(); @@ -431,7 +436,7 @@ mod tests { let codex_home = tempfile::tempdir().expect("create codex home"); let key = RemoteInstalledPluginBundleSyncKey { plugin_cache_root: remote_plugin_cache_root(codex_home.path()), - scopes: RemoteInstalledPluginScopes::all(), + scopes: vec![RemotePluginScope::Global, RemotePluginScope::Workspace], }; assert!(mark_remote_installed_plugin_bundle_sync_in_flight(