Simplify remote installed scope gating

This commit is contained in:
xli-oai
2026-05-15 22:14:46 -07:00
parent 09d90aae9e
commit 40fd064ce2
4 changed files with 164 additions and 186 deletions

View File

@@ -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<RemotePluginScope> {
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<CodexAuth>,
scopes: RemoteInstalledPluginScopes,
scopes: Vec<RemotePluginScope>,
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<RemotePluginScope>,
plugins: Vec<RemoteInstalledPlugin>,
}
#[derive(Clone, PartialEq, Eq)]
struct NonCuratedCacheRefreshRequest {
roots: Vec<AbsolutePathBuf>,
@@ -414,7 +424,7 @@ pub struct PluginsManager {
configured_marketplace_upgrade_state: RwLock<ConfiguredMarketplaceUpgradeState>,
non_curated_cache_refresh_state: RwLock<NonCuratedCacheRefreshState>,
cached_enabled_outcome: RwLock<Option<CachedPluginLoadOutcome>>,
remote_installed_plugins_cache: RwLock<Option<Vec<RemoteInstalledPlugin>>>,
remote_installed_plugins_cache: RwLock<Option<CachedRemoteInstalledPlugins>>,
remote_installed_plugins_cache_refresh_state: RwLock<RemoteInstalledPluginsCacheRefreshState>,
remote_sync_lock: Semaphore,
restriction_product: Option<Product>,
@@ -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<RemotePluginScope>,
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<PluginLoadOutcome> {
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<String, PluginConfig> {
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::<Vec<_>>();
@@ -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::<Vec<_>>();
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<RemoteInstalledPlugin>) -> bool {
fn write_remote_installed_plugins_cache(
&self,
plugins: Vec<RemoteInstalledPlugin>,
scopes: Vec<RemotePluginScope>,
) -> 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,

View File

@@ -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)

View File

@@ -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<Item = RemotePluginScope> {
[
(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<Item = &'static str> {
[
(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<Vec<RemoteInstalledPlugin>, 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<Vec<RemoteInstalledPlugin>, 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()

View File

@@ -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<HashSet<RemoteInstalledPluginBundleSyncKey>>,
> = 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<RemotePluginScope>,
}
#[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<CodexAuth>,
scopes: RemoteInstalledPluginScopes,
scopes: Vec<RemotePluginScope>,
on_local_cache_changed: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
) {
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<RemoteInstalledPluginBundleSyncOutcome, RemoteInstalledPluginBundleSyncError> {
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::<String, BTreeSet<String>>::from_iter(
scopes
.marketplace_names()
.map(|marketplace_name| (marketplace_name.to_string(), BTreeSet::new())),
);
let mut installed_plugin_names_by_marketplace = BTreeMap::<String, BTreeSet<String>>::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(