mirror of
https://github.com/openai/codex.git
synced 2026-05-16 09:12:54 +00:00
Simplify remote installed scope gating
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user