[codex] Return workspace directory installed plugins (#27098)

## Summary

- return installed `workspace-directory` remote plugins by default in
`plugin/installed`
- keep shared-with-me installed plugins gated behind `plugin_sharing`
- filter remote installed plugin marketplaces by canonical marketplace
name instead of coarse workspace scope

## Validation

- `just fmt`
- `just test -p codex-core-plugins`
- `just test -p codex-app-server`
- `just fix -p codex-core-plugins`
- `just fix -p codex-app-server`
- `$xin-build` targeted verification:
- `just test -p codex-core-plugins
build_remote_installed_plugin_marketplaces_from_cache_filters_by_marketplace_name`
- `just test -p codex-app-server
plugin_installed_includes_workspace_directory_without_plugin_sharing`
- `just test -p codex-app-server
plugin_installed_includes_remote_shared_with_me_plugins`
- `just test -p codex-app-server
plugin_list_omits_shared_with_me_kind_when_plugin_sharing_disabled`
This commit is contained in:
xl-openai
2026-06-09 01:23:16 -07:00
committed by GitHub
Unverified
parent 14660c22d1
commit a304569c79
7 changed files with 170 additions and 31 deletions
+3 -2
View File
@@ -14,7 +14,6 @@ use crate::PluginsConfigInput;
use crate::PluginsManager;
use crate::marketplace::MarketplacePluginInstallPolicy;
use crate::remote::REMOTE_GLOBAL_MARKETPLACE_NAME;
use crate::remote::RemotePluginScope;
const TOOL_SUGGEST_DISCOVERABLE_PLUGIN_ALLOWLIST: &[&str] = &[
"github@openai-curated",
@@ -100,7 +99,9 @@ impl PluginsManager {
.collect::<HashSet<_>>();
installed_app_connector_ids.extend(input.loaded_plugin_app_connector_ids.iter().cloned());
let remote_installed_marketplaces = if input.plugins.remote_plugin_enabled {
self.build_remote_installed_plugin_marketplaces_from_cache(&[RemotePluginScope::Global])
self.build_remote_installed_plugin_marketplaces_from_cache(&[
REMOTE_GLOBAL_MARKETPLACE_NAME,
])
} else {
None
};
+12 -6
View File
@@ -38,7 +38,6 @@ use crate::marketplace_upgrade::configured_git_marketplace_names;
use crate::marketplace_upgrade::upgrade_configured_git_marketplaces;
use crate::remote::RemoteInstalledPlugin;
use crate::remote::RemotePluginCatalogError;
use crate::remote::RemotePluginScope;
use crate::remote::RemotePluginServiceConfig;
use crate::remote_legacy::RemotePluginFetchError;
use crate::remote_legacy::RemotePluginMutationError;
@@ -560,14 +559,19 @@ impl PluginsManager {
pub fn build_remote_installed_plugin_marketplaces_from_cache(
&self,
visible_scopes: &[RemotePluginScope],
visible_marketplaces: &[&str],
) -> Option<Vec<crate::remote::RemoteMarketplace>> {
let cache = match self.remote_installed_plugins_cache.read() {
Ok(cache) => cache,
Err(err) => err.into_inner(),
};
let plugins = cache.as_ref()?;
Some(crate::remote::group_remote_installed_plugins_by_marketplaces(plugins, visible_scopes))
Some(
crate::remote::group_remote_installed_plugins_by_marketplaces(
plugins,
visible_marketplaces,
),
)
}
pub fn cached_global_remote_discoverable_plugins_for_config(
@@ -599,7 +603,7 @@ impl PluginsManager {
&self,
config: &PluginsConfigInput,
auth: Option<&CodexAuth>,
visible_scopes: &[RemotePluginScope],
visible_marketplaces: &[&str],
on_effective_plugins_changed: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
) -> Result<Vec<crate::remote::RemoteMarketplace>, RemotePluginCatalogError> {
let plugins = crate::remote::fetch_remote_installed_plugins(
@@ -607,8 +611,10 @@ impl PluginsManager {
auth,
)
.await?;
let marketplaces =
crate::remote::group_remote_installed_plugins_by_marketplaces(&plugins, visible_scopes);
let marketplaces = crate::remote::group_remote_installed_plugins_by_marketplaces(
&plugins,
visible_marketplaces,
);
let changed = self.write_remote_installed_plugins_cache(plugins);
if changed && let Some(on_effective_plugins_changed) = on_effective_plugins_changed {
on_effective_plugins_changed();
+46 -4
View File
@@ -6,8 +6,10 @@ use crate::loader::load_plugins_from_layer_stack;
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::REMOTE_GLOBAL_MARKETPLACE_NAME;
use crate::remote::REMOTE_WORKSPACE_MARKETPLACE_NAME;
use crate::remote::REMOTE_WORKSPACE_SHARED_WITH_ME_MARKETPLACE_NAME;
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;
@@ -137,8 +139,15 @@ fn remote_installed_linear_plugin() -> RemoteInstalledPlugin {
}
fn remote_installed_plugin(name: &str) -> RemoteInstalledPlugin {
remote_installed_plugin_in_marketplace(name, REMOTE_GLOBAL_MARKETPLACE_NAME)
}
fn remote_installed_plugin_in_marketplace(
name: &str,
marketplace_name: &str,
) -> RemoteInstalledPlugin {
RemoteInstalledPlugin {
marketplace_name: "openai-curated-remote".to_string(),
marketplace_name: marketplace_name.to_string(),
id: format!("plugins~Plugin_{name}"),
name: name.to_string(),
enabled: true,
@@ -484,7 +493,7 @@ async fn build_remote_installed_plugin_marketplaces_from_cache_uses_remote_metad
manager.write_remote_installed_plugins_cache(vec![plugin]);
let marketplaces = manager
.build_remote_installed_plugin_marketplaces_from_cache(&[RemotePluginScope::Global])
.build_remote_installed_plugin_marketplaces_from_cache(&[REMOTE_GLOBAL_MARKETPLACE_NAME])
.expect("remote installed cache should be present");
assert_eq!(marketplaces.len(), 1);
assert_eq!(marketplaces[0].name, "openai-curated-remote");
@@ -521,12 +530,45 @@ async fn build_remote_installed_plugin_marketplaces_from_cache_uses_remote_metad
);
assert_eq!(
manager
.build_remote_installed_plugin_marketplaces_from_cache(&[RemotePluginScope::Workspace])
.build_remote_installed_plugin_marketplaces_from_cache(&[
REMOTE_WORKSPACE_MARKETPLACE_NAME
])
.expect("remote installed cache should be present"),
Vec::new()
);
}
#[tokio::test]
async fn build_remote_installed_plugin_marketplaces_from_cache_filters_by_marketplace_name() {
let codex_home = TempDir::new().unwrap();
let manager = PluginsManager::new(codex_home.path().to_path_buf());
manager.write_remote_installed_plugins_cache(vec![
remote_installed_plugin_in_marketplace(
"workspace-linear",
REMOTE_WORKSPACE_MARKETPLACE_NAME,
),
remote_installed_plugin_in_marketplace(
"shared-linear",
REMOTE_WORKSPACE_SHARED_WITH_ME_MARKETPLACE_NAME,
),
]);
let marketplaces = manager
.build_remote_installed_plugin_marketplaces_from_cache(&[REMOTE_WORKSPACE_MARKETPLACE_NAME])
.expect("remote installed cache should be present");
assert_eq!(marketplaces.len(), 1);
assert_eq!(marketplaces[0].name, REMOTE_WORKSPACE_MARKETPLACE_NAME);
assert_eq!(
marketplaces[0]
.plugins
.iter()
.map(|plugin| plugin.id.as_str())
.collect::<Vec<_>>(),
vec!["workspace-linear@workspace-directory"]
);
}
#[tokio::test]
async fn load_plugins_resolves_disabled_skill_names_against_loaded_plugin_skills() {
let codex_home = TempDir::new().unwrap();
+2 -4
View File
@@ -851,14 +851,12 @@ pub(crate) async fn fetch_remote_installed_plugins(
pub fn group_remote_installed_plugins_by_marketplaces(
plugins: &[RemoteInstalledPlugin],
visible_scopes: &[RemotePluginScope],
visible_marketplaces: &[&str],
) -> Vec<RemoteMarketplace> {
let mut plugins_by_marketplace = BTreeMap::<String, Vec<RemotePluginSummary>>::new();
for plugin in plugins {
if !RemotePluginScope::from_marketplace_name(&plugin.marketplace_name)
.is_some_and(|scope| visible_scopes.contains(&scope))
{
if !visible_marketplaces.contains(&plugin.marketplace_name.as_str()) {
continue;
}
let Ok(plugin_id) = PluginId::new(plugin.name.clone(), plugin.marketplace_name.clone())