[codex] Bound WSL local curated discovery (#26669)

## Context
The installed-app suggestion expansion added in #24996 reads plugin
details for trusted file-backed marketplace candidates because the list
response does not include app ids. On Windows-backed WSL mounts, the
local `openai-curated` checkout lives under `$CODEX_HOME/.tmp/plugins`,
and those per-plugin detail reads can be very slow.

Remote curated already has cached app ids, so it does not need the same
local filesystem traversal.

## Summary
- Keep only the WSL Windows-backed local `openai-curated` checkout on
the legacy fallback/configured discovery path.
- Preserve installed-app expansion for non-WSL file-backed marketplaces
and remote curated.
- Add focused tests for the WSL local curated path predicate.

## Test
- `just test -p codex-core-plugins discoverable`
- `just test -p codex-core plugins::discoverable::tests`
This commit is contained in:
xl-openai
2026-06-05 14:09:40 -07:00
committed by GitHub
Unverified
parent bb7d19bc24
commit 679cc08445
2 changed files with 104 additions and 0 deletions
+43
View File
@@ -4,6 +4,8 @@ use codex_app_server_protocol::PluginInstallPolicy;
use codex_login::CodexAuth;
use codex_plugin::PluginCapabilitySummary;
use std::collections::HashSet;
use std::path::Component;
use std::path::Path;
use tracing::warn;
use crate::OPENAI_BUNDLED_MARKETPLACE_NAME;
@@ -53,6 +55,9 @@ const TOOL_SUGGEST_DISCOVERABLE_MARKETPLACE_ALLOWLIST: &[&str] = &[
REMOTE_GLOBAL_MARKETPLACE_NAME,
];
const OPENAI_CURATED_MARKETPLACE_PATH_SUFFIX: &str =
".tmp/plugins/.agents/plugins/marketplace.json";
#[derive(Debug, Clone)]
pub struct ToolSuggestPluginDiscoveryInput {
pub plugins: PluginsConfigInput,
@@ -108,6 +113,10 @@ impl PluginsManager {
{
continue;
}
let use_legacy_local_curated_filter = should_use_legacy_local_curated_discovery_filter(
&marketplace_name,
marketplace.path.as_path(),
);
let is_allowlisted_marketplace = TOOL_SUGGEST_DISCOVERABLE_MARKETPLACE_ALLOWLIST
.contains(&marketplace_name.as_str());
@@ -123,6 +132,13 @@ impl PluginsManager {
continue;
}
// On Windows-backed WSL mounts, keep local curated discovery bounded to the
// legacy fallback/configured set instead of reading every plugin detail for app
// ids. Remote curated has cached app ids and still expands by installed apps.
if use_legacy_local_curated_filter && !is_configured_plugin && !is_fallback_plugin {
continue;
}
let plugin_id = plugin.id.clone();
match self
@@ -216,3 +232,30 @@ impl PluginsManager {
Ok(discoverable_plugins)
}
}
fn should_use_legacy_local_curated_discovery_filter(
marketplace_name: &str,
marketplace_path: &Path,
) -> bool {
marketplace_name == OPENAI_CURATED_MARKETPLACE_NAME
&& is_wsl_windows_drive_path(marketplace_path)
&& marketplace_path.ends_with(Path::new(OPENAI_CURATED_MARKETPLACE_PATH_SUFFIX))
}
fn is_wsl_windows_drive_path(path: &Path) -> bool {
let mut components = path.components();
if components.next() != Some(Component::RootDir) {
return false;
}
if components.next().and_then(|part| part.as_os_str().to_str()) != Some("mnt") {
return false;
}
let Some(drive) = components.next().and_then(|part| part.as_os_str().to_str()) else {
return false;
};
drive.len() == 1 && drive.as_bytes()[0].is_ascii_alphabetic()
}
#[cfg(test)]
#[path = "discoverable_tests.rs"]
mod tests;
@@ -0,0 +1,61 @@
use std::path::Path;
use super::is_wsl_windows_drive_path;
use super::should_use_legacy_local_curated_discovery_filter;
use crate::OPENAI_BUNDLED_MARKETPLACE_NAME;
use crate::OPENAI_CURATED_MARKETPLACE_NAME;
#[test]
fn legacy_local_curated_filter_matches_wsl_windows_backed_curated_checkout() {
let marketplace_path =
Path::new("/mnt/c/Users/user/.codex/.tmp/plugins/.agents/plugins/marketplace.json");
assert!(should_use_legacy_local_curated_discovery_filter(
OPENAI_CURATED_MARKETPLACE_NAME,
marketplace_path,
));
}
#[test]
fn legacy_local_curated_filter_does_not_match_native_wsl_curated_checkout() {
let marketplace_path =
Path::new("/home/user/.codex/.tmp/plugins/.agents/plugins/marketplace.json");
assert!(!should_use_legacy_local_curated_discovery_filter(
OPENAI_CURATED_MARKETPLACE_NAME,
marketplace_path,
));
}
#[test]
fn legacy_local_curated_filter_does_not_match_other_wsl_marketplaces() {
let other_marketplace_path = Path::new(
"/mnt/c/Users/user/.codex/.tmp/marketplaces/other/.agents/plugins/marketplace.json",
);
let local_curated_marketplace_path =
Path::new("/mnt/c/Users/user/.codex/.tmp/plugins/.agents/plugins/marketplace.json");
assert!(!should_use_legacy_local_curated_discovery_filter(
OPENAI_CURATED_MARKETPLACE_NAME,
other_marketplace_path,
));
assert!(!should_use_legacy_local_curated_discovery_filter(
OPENAI_BUNDLED_MARKETPLACE_NAME,
local_curated_marketplace_path,
));
}
#[test]
fn wsl_windows_drive_path_matches_only_mnt_drive_paths() {
assert!(is_wsl_windows_drive_path(Path::new(
"/mnt/c/Users/user/.codex/.tmp/plugins",
)));
assert!(is_wsl_windows_drive_path(Path::new("/mnt/Z/tmp")));
assert!(!is_wsl_windows_drive_path(Path::new("/home/user/.codex")));
assert!(!is_wsl_windows_drive_path(Path::new(
"/mnt/codex/Users/user/.codex",
)));
assert!(!is_wsl_windows_drive_path(Path::new(
"/media/c/Users/user/.codex",
)));
}