mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
@@ -21,7 +21,7 @@ const TOOL_SUGGEST_DISCOVERABLE_PLUGIN_ALLOWLIST: &[&str] = &[
|
||||
"figma@openai-curated",
|
||||
];
|
||||
|
||||
pub(crate) fn list_tool_suggest_discoverable_plugins(
|
||||
pub(crate) async fn list_tool_suggest_discoverable_plugins(
|
||||
config: &Config,
|
||||
) -> anyhow::Result<Vec<DiscoverablePluginInfo>> {
|
||||
if !config.features.enabled(Feature::Plugins) {
|
||||
@@ -59,11 +59,10 @@ pub(crate) fn list_tool_suggest_discoverable_plugins(
|
||||
|
||||
let plugin_id = plugin.id.clone();
|
||||
|
||||
match plugins_manager.read_plugin_detail_for_marketplace_plugin(
|
||||
config,
|
||||
&curated_marketplace_name,
|
||||
plugin,
|
||||
) {
|
||||
match plugins_manager
|
||||
.read_plugin_detail_for_marketplace_plugin(config, &curated_marketplace_name, plugin)
|
||||
.await
|
||||
{
|
||||
Ok(plugin) => {
|
||||
let plugin: PluginCapabilitySummary = plugin.into();
|
||||
discoverable_plugins.push(DiscoverablePluginInfo {
|
||||
|
||||
@@ -21,7 +21,9 @@ async fn list_tool_suggest_discoverable_plugins_returns_uninstalled_curated_plug
|
||||
write_plugins_feature_config(codex_home.path());
|
||||
|
||||
let config = load_plugins_config(codex_home.path()).await;
|
||||
let discoverable_plugins = list_tool_suggest_discoverable_plugins(&config).unwrap();
|
||||
let discoverable_plugins = list_tool_suggest_discoverable_plugins(&config)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
discoverable_plugins,
|
||||
@@ -51,7 +53,9 @@ plugins = false
|
||||
);
|
||||
|
||||
let config = load_plugins_config(codex_home.path()).await;
|
||||
let discoverable_plugins = list_tool_suggest_discoverable_plugins(&config).unwrap();
|
||||
let discoverable_plugins = list_tool_suggest_discoverable_plugins(&config)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(discoverable_plugins, Vec::<DiscoverablePluginInfo>::new());
|
||||
}
|
||||
@@ -71,7 +75,9 @@ async fn list_tool_suggest_discoverable_plugins_normalizes_description() {
|
||||
);
|
||||
|
||||
let config = load_plugins_config(codex_home.path()).await;
|
||||
let discoverable_plugins = list_tool_suggest_discoverable_plugins(&config).unwrap();
|
||||
let discoverable_plugins = list_tool_suggest_discoverable_plugins(&config)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
discoverable_plugins,
|
||||
@@ -106,7 +112,9 @@ async fn list_tool_suggest_discoverable_plugins_omits_installed_curated_plugins(
|
||||
.expect("plugin should install");
|
||||
|
||||
let refreshed_config = load_plugins_config(codex_home.path()).await;
|
||||
let discoverable_plugins = list_tool_suggest_discoverable_plugins(&refreshed_config).unwrap();
|
||||
let discoverable_plugins = list_tool_suggest_discoverable_plugins(&refreshed_config)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(discoverable_plugins, Vec::<DiscoverablePluginInfo>::new());
|
||||
}
|
||||
@@ -127,7 +135,9 @@ discoverables = [{ type = "plugin", id = "sample@openai-curated" }]
|
||||
);
|
||||
|
||||
let config = load_plugins_config(codex_home.path()).await;
|
||||
let discoverable_plugins = list_tool_suggest_discoverable_plugins(&config).unwrap();
|
||||
let discoverable_plugins = list_tool_suggest_discoverable_plugins(&config)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
discoverable_plugins,
|
||||
@@ -182,7 +192,9 @@ async fn list_tool_suggest_discoverable_plugins_does_not_reload_marketplace_per_
|
||||
.finish();
|
||||
let _guard = tracing::subscriber::set_default(subscriber);
|
||||
|
||||
let discoverable_plugins = list_tool_suggest_discoverable_plugins(&config).unwrap();
|
||||
let discoverable_plugins = list_tool_suggest_discoverable_plugins(&config)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(discoverable_plugins.len(), 1);
|
||||
assert_eq!(discoverable_plugins[0].id, "slack@openai-curated");
|
||||
|
||||
@@ -374,11 +374,12 @@ impl PluginsManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugins_for_config(&self, config: &Config) -> PluginLoadOutcome {
|
||||
pub async fn plugins_for_config(&self, config: &Config) -> PluginLoadOutcome {
|
||||
self.plugins_for_config_with_force_reload(config, /*force_reload*/ false)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) fn plugins_for_config_with_force_reload(
|
||||
pub(crate) async fn plugins_for_config_with_force_reload(
|
||||
&self,
|
||||
config: &Config,
|
||||
force_reload: bool,
|
||||
@@ -395,7 +396,8 @@ impl PluginsManager {
|
||||
&config.config_layer_stack,
|
||||
&self.store,
|
||||
self.restriction_product,
|
||||
);
|
||||
)
|
||||
.await;
|
||||
log_plugin_load_errors(&outcome);
|
||||
let mut cache = match self.cached_enabled_outcome.write() {
|
||||
Ok(cache) => cache,
|
||||
@@ -419,7 +421,7 @@ impl PluginsManager {
|
||||
}
|
||||
|
||||
/// Resolve plugin skill roots for a config layer stack without touching the plugins cache.
|
||||
pub fn effective_skill_roots_for_layer_stack(
|
||||
pub async fn effective_skill_roots_for_layer_stack(
|
||||
&self,
|
||||
config_layer_stack: &ConfigLayerStack,
|
||||
plugins_feature_enabled: bool,
|
||||
@@ -428,6 +430,7 @@ impl PluginsManager {
|
||||
return Vec::new();
|
||||
}
|
||||
load_plugins_from_layer_stack(config_layer_stack, &self.store, self.restriction_product)
|
||||
.await
|
||||
.effective_skill_roots()
|
||||
}
|
||||
|
||||
@@ -585,10 +588,10 @@ impl PluginsManager {
|
||||
Err(err) => err.into_inner().clone(),
|
||||
};
|
||||
if let Some(analytics_events_client) = analytics_events_client {
|
||||
analytics_events_client.track_plugin_installed(plugin_telemetry_metadata_from_root(
|
||||
&result.plugin_id,
|
||||
&result.installed_path,
|
||||
));
|
||||
analytics_events_client.track_plugin_installed(
|
||||
plugin_telemetry_metadata_from_root(&result.plugin_id, &result.installed_path)
|
||||
.await,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(PluginInstallOutcome {
|
||||
@@ -622,10 +625,11 @@ impl PluginsManager {
|
||||
}
|
||||
|
||||
async fn uninstall_plugin_id(&self, plugin_id: PluginId) -> Result<(), PluginUninstallError> {
|
||||
let plugin_telemetry = self
|
||||
.store
|
||||
.active_plugin_root(&plugin_id)
|
||||
.map(|_| installed_plugin_telemetry_metadata(self.codex_home.as_path(), &plugin_id));
|
||||
let plugin_telemetry = if self.store.active_plugin_root(&plugin_id).is_some() {
|
||||
Some(installed_plugin_telemetry_metadata(self.codex_home.as_path(), &plugin_id).await)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let store = self.store.clone();
|
||||
let plugin_id_for_store = plugin_id.clone();
|
||||
tokio::task::spawn_blocking(move || store.uninstall(&plugin_id_for_store))
|
||||
@@ -931,7 +935,7 @@ impl PluginsManager {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_plugin_for_config(
|
||||
pub async fn read_plugin_for_config(
|
||||
&self,
|
||||
config: &Config,
|
||||
request: &PluginReadRequest,
|
||||
@@ -959,19 +963,21 @@ impl PluginsManager {
|
||||
)?;
|
||||
let plugin_key = plugin_id.as_key();
|
||||
let (installed_plugins, enabled_plugins) = self.configured_plugin_states(config);
|
||||
let plugin = self.read_plugin_detail_for_marketplace_plugin(
|
||||
config,
|
||||
&marketplace.name,
|
||||
ConfiguredMarketplacePlugin {
|
||||
id: plugin_key.clone(),
|
||||
name: plugin.name,
|
||||
source: plugin.source,
|
||||
policy: plugin.policy,
|
||||
interface: plugin.interface,
|
||||
installed: installed_plugins.contains(&plugin_key),
|
||||
enabled: enabled_plugins.contains(&plugin_key),
|
||||
},
|
||||
)?;
|
||||
let plugin = self
|
||||
.read_plugin_detail_for_marketplace_plugin(
|
||||
config,
|
||||
&marketplace.name,
|
||||
ConfiguredMarketplacePlugin {
|
||||
id: plugin_key.clone(),
|
||||
name: plugin.name,
|
||||
source: plugin.source,
|
||||
policy: plugin.policy,
|
||||
interface: plugin.interface,
|
||||
installed: installed_plugins.contains(&plugin_key),
|
||||
enabled: enabled_plugins.contains(&plugin_key),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(PluginReadOutcome {
|
||||
marketplace_name: if marketplace.name == OPENAI_CURATED_MARKETPLACE_NAME {
|
||||
@@ -984,7 +990,7 @@ impl PluginsManager {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn read_plugin_detail_for_marketplace_plugin(
|
||||
pub(crate) async fn read_plugin_detail_for_marketplace_plugin(
|
||||
&self,
|
||||
config: &Config,
|
||||
marketplace_name: &str,
|
||||
@@ -1026,12 +1032,17 @@ impl PluginsManager {
|
||||
self.restriction_product,
|
||||
&skill_config_rules,
|
||||
);
|
||||
let apps = load_plugin_apps(source_path.as_path());
|
||||
let apps = load_apps_from_paths(
|
||||
source_path.as_path(),
|
||||
plugin_app_config_paths(source_path.as_path(), manifest_paths),
|
||||
)
|
||||
.await;
|
||||
let mcp_config_paths = plugin_mcp_config_paths(source_path.as_path(), manifest_paths);
|
||||
let mut mcp_server_names = Vec::new();
|
||||
for mcp_config_path in mcp_config_paths {
|
||||
mcp_server_names.extend(
|
||||
load_mcp_servers_from_file(source_path.as_path(), &mcp_config_path)
|
||||
.await
|
||||
.mcp_servers
|
||||
.into_keys(),
|
||||
);
|
||||
@@ -1374,7 +1385,7 @@ struct PluginAppConfig {
|
||||
id: String,
|
||||
}
|
||||
|
||||
pub(crate) fn load_plugins_from_layer_stack(
|
||||
pub(crate) async fn load_plugins_from_layer_stack(
|
||||
config_layer_stack: &ConfigLayerStack,
|
||||
store: &PluginStore,
|
||||
restriction_product: Option<Product>,
|
||||
@@ -1394,7 +1405,8 @@ pub(crate) fn load_plugins_from_layer_stack(
|
||||
store,
|
||||
restriction_product,
|
||||
&skill_config_rules,
|
||||
);
|
||||
)
|
||||
.await;
|
||||
for name in loaded_plugin.mcp_servers.keys() {
|
||||
if let Some(previous_plugin) =
|
||||
seen_mcp_server_names.insert(name.clone(), configured_name.clone())
|
||||
@@ -1663,7 +1675,7 @@ fn non_curated_plugin_ids_from_config_keys(
|
||||
configured_non_curated_plugin_ids
|
||||
}
|
||||
|
||||
fn load_plugin(
|
||||
async fn load_plugin(
|
||||
config_name: String,
|
||||
plugin: &PluginConfig,
|
||||
store: &PluginStore,
|
||||
@@ -1745,7 +1757,7 @@ fn load_plugin(
|
||||
loaded_plugin.has_enabled_skills = has_enabled_skills;
|
||||
let mut mcp_servers = HashMap::new();
|
||||
for mcp_config_path in plugin_mcp_config_paths(plugin_root.as_path(), manifest_paths) {
|
||||
let plugin_mcp = load_mcp_servers_from_file(plugin_root.as_path(), &mcp_config_path);
|
||||
let plugin_mcp = load_mcp_servers_from_file(plugin_root.as_path(), &mcp_config_path).await;
|
||||
for (name, config) in plugin_mcp.mcp_servers {
|
||||
if mcp_servers.insert(name.clone(), config).is_some() {
|
||||
warn!(
|
||||
@@ -1758,7 +1770,11 @@ fn load_plugin(
|
||||
}
|
||||
}
|
||||
loaded_plugin.mcp_servers = mcp_servers;
|
||||
loaded_plugin.apps = load_plugin_apps(plugin_root.as_path());
|
||||
loaded_plugin.apps = load_apps_from_paths(
|
||||
plugin_root.as_path(),
|
||||
plugin_app_config_paths(plugin_root.as_path(), manifest_paths),
|
||||
)
|
||||
.await;
|
||||
loaded_plugin
|
||||
}
|
||||
|
||||
@@ -1853,14 +1869,15 @@ fn default_mcp_config_paths(plugin_root: &Path) -> Vec<AbsolutePathBuf> {
|
||||
paths
|
||||
}
|
||||
|
||||
pub fn load_plugin_apps(plugin_root: &Path) -> Vec<AppConnectorId> {
|
||||
pub async fn load_plugin_apps(plugin_root: &Path) -> Vec<AppConnectorId> {
|
||||
if let Some(manifest) = load_plugin_manifest(plugin_root) {
|
||||
return load_apps_from_paths(
|
||||
plugin_root,
|
||||
plugin_app_config_paths(plugin_root, &manifest.paths),
|
||||
);
|
||||
)
|
||||
.await;
|
||||
}
|
||||
load_apps_from_paths(plugin_root, default_app_config_paths(plugin_root))
|
||||
load_apps_from_paths(plugin_root, default_app_config_paths(plugin_root)).await
|
||||
}
|
||||
|
||||
fn plugin_app_config_paths(
|
||||
@@ -1886,13 +1903,13 @@ fn default_app_config_paths(plugin_root: &Path) -> Vec<AbsolutePathBuf> {
|
||||
paths
|
||||
}
|
||||
|
||||
fn load_apps_from_paths(
|
||||
async fn load_apps_from_paths(
|
||||
plugin_root: &Path,
|
||||
app_config_paths: Vec<AbsolutePathBuf>,
|
||||
) -> Vec<AppConnectorId> {
|
||||
let mut connector_ids = Vec::new();
|
||||
for app_config_path in app_config_paths {
|
||||
let Ok(contents) = fs::read_to_string(app_config_path.as_path()) else {
|
||||
let Ok(contents) = tokio::fs::read_to_string(app_config_path.as_path()).await else {
|
||||
continue;
|
||||
};
|
||||
let parsed = match serde_json::from_str::<PluginAppFile>(&contents) {
|
||||
@@ -1925,7 +1942,7 @@ fn load_apps_from_paths(
|
||||
connector_ids
|
||||
}
|
||||
|
||||
pub fn plugin_telemetry_metadata_from_root(
|
||||
pub async fn plugin_telemetry_metadata_from_root(
|
||||
plugin_id: &PluginId,
|
||||
plugin_root: &AbsolutePathBuf,
|
||||
) -> PluginTelemetryMetadata {
|
||||
@@ -1939,6 +1956,7 @@ pub fn plugin_telemetry_metadata_from_root(
|
||||
for path in plugin_mcp_config_paths(plugin_root.as_path(), manifest_paths) {
|
||||
mcp_server_names.extend(
|
||||
load_mcp_servers_from_file(plugin_root.as_path(), &path)
|
||||
.await
|
||||
.mcp_servers
|
||||
.into_keys(),
|
||||
);
|
||||
@@ -1954,19 +1972,23 @@ pub fn plugin_telemetry_metadata_from_root(
|
||||
description: None,
|
||||
has_skills,
|
||||
mcp_server_names,
|
||||
app_connector_ids: load_plugin_apps(plugin_root.as_path()),
|
||||
app_connector_ids: load_apps_from_paths(
|
||||
plugin_root.as_path(),
|
||||
plugin_app_config_paths(plugin_root.as_path(), manifest_paths),
|
||||
)
|
||||
.await,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_plugin_mcp_servers(plugin_root: &Path) -> HashMap<String, McpServerConfig> {
|
||||
pub async fn load_plugin_mcp_servers(plugin_root: &Path) -> HashMap<String, McpServerConfig> {
|
||||
let Some(manifest) = load_plugin_manifest(plugin_root) else {
|
||||
return HashMap::new();
|
||||
};
|
||||
|
||||
let mut mcp_servers = HashMap::new();
|
||||
for mcp_config_path in plugin_mcp_config_paths(plugin_root, &manifest.paths) {
|
||||
let plugin_mcp = load_mcp_servers_from_file(plugin_root, &mcp_config_path);
|
||||
let plugin_mcp = load_mcp_servers_from_file(plugin_root, &mcp_config_path).await;
|
||||
for (name, config) in plugin_mcp.mcp_servers {
|
||||
mcp_servers.entry(name).or_insert(config);
|
||||
}
|
||||
@@ -1975,7 +1997,7 @@ pub fn load_plugin_mcp_servers(plugin_root: &Path) -> HashMap<String, McpServerC
|
||||
mcp_servers
|
||||
}
|
||||
|
||||
pub fn installed_plugin_telemetry_metadata(
|
||||
pub async fn installed_plugin_telemetry_metadata(
|
||||
codex_home: &Path,
|
||||
plugin_id: &PluginId,
|
||||
) -> PluginTelemetryMetadata {
|
||||
@@ -1984,14 +2006,14 @@ pub fn installed_plugin_telemetry_metadata(
|
||||
return PluginTelemetryMetadata::from_plugin_id(plugin_id);
|
||||
};
|
||||
|
||||
plugin_telemetry_metadata_from_root(plugin_id, &plugin_root)
|
||||
plugin_telemetry_metadata_from_root(plugin_id, &plugin_root).await
|
||||
}
|
||||
|
||||
fn load_mcp_servers_from_file(
|
||||
async fn load_mcp_servers_from_file(
|
||||
plugin_root: &Path,
|
||||
mcp_config_path: &AbsolutePathBuf,
|
||||
) -> PluginMcpDiscovery {
|
||||
let Ok(contents) = fs::read_to_string(mcp_config_path.as_path()) else {
|
||||
let Ok(contents) = tokio::fs::read_to_string(mcp_config_path.as_path()).await else {
|
||||
return PluginMcpDiscovery::default();
|
||||
};
|
||||
let parsed = match serde_json::from_str::<PluginMcpFile>(&contents) {
|
||||
|
||||
@@ -82,10 +82,12 @@ fn plugin_config_toml(enabled: bool, plugins_feature_enabled: bool) -> String {
|
||||
toml::to_string(&Value::Table(root)).expect("plugin test config should serialize")
|
||||
}
|
||||
|
||||
fn load_plugins_from_config(config_toml: &str, codex_home: &Path) -> PluginLoadOutcome {
|
||||
async fn load_plugins_from_config(config_toml: &str, codex_home: &Path) -> PluginLoadOutcome {
|
||||
write_file(&codex_home.join(CONFIG_TOML_FILE), config_toml);
|
||||
let config = load_config_blocking(codex_home, codex_home);
|
||||
PluginsManager::new(codex_home.to_path_buf()).plugins_for_config(&config)
|
||||
let config = load_config(codex_home, codex_home).await;
|
||||
PluginsManager::new(codex_home.to_path_buf())
|
||||
.plugins_for_config(&config)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn load_config(codex_home: &Path, cwd: &Path) -> crate::config::Config {
|
||||
@@ -97,16 +99,8 @@ async fn load_config(codex_home: &Path, cwd: &Path) -> crate::config::Config {
|
||||
.expect("config should load")
|
||||
}
|
||||
|
||||
fn load_config_blocking(codex_home: &Path, cwd: &Path) -> crate::config::Config {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("tokio runtime should build")
|
||||
.block_on(load_config(codex_home, cwd))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_plugins_loads_default_skills_and_mcp_servers() {
|
||||
#[tokio::test]
|
||||
async fn load_plugins_loads_default_skills_and_mcp_servers() {
|
||||
let codex_home = TempDir::new().unwrap();
|
||||
let plugin_root = codex_home
|
||||
.path()
|
||||
@@ -153,7 +147,8 @@ fn load_plugins_loads_default_skills_and_mcp_servers() {
|
||||
let outcome = load_plugins_from_config(
|
||||
&plugin_config_toml(/*enabled*/ true, /*plugins_feature_enabled*/ true),
|
||||
codex_home.path(),
|
||||
);
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
outcome.plugins(),
|
||||
@@ -216,8 +211,8 @@ fn load_plugins_loads_default_skills_and_mcp_servers() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_plugins_resolves_disabled_skill_names_against_loaded_plugin_skills() {
|
||||
#[tokio::test]
|
||||
async fn load_plugins_resolves_disabled_skill_names_against_loaded_plugin_skills() {
|
||||
let codex_home = TempDir::new().unwrap();
|
||||
let plugin_root = codex_home
|
||||
.path()
|
||||
@@ -244,7 +239,7 @@ enabled = false
|
||||
[plugins."sample@test"]
|
||||
enabled = true
|
||||
"#;
|
||||
let outcome = load_plugins_from_config(config_toml, codex_home.path());
|
||||
let outcome = load_plugins_from_config(config_toml, codex_home.path()).await;
|
||||
let skill_path = dunce::canonicalize(skill_path)
|
||||
.expect("skill path should canonicalize")
|
||||
.abs();
|
||||
@@ -257,8 +252,8 @@ enabled = true
|
||||
assert!(outcome.capability_summaries().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_plugins_ignores_unknown_disabled_skill_names() {
|
||||
#[tokio::test]
|
||||
async fn load_plugins_ignores_unknown_disabled_skill_names() {
|
||||
let codex_home = TempDir::new().unwrap();
|
||||
let plugin_root = codex_home
|
||||
.path()
|
||||
@@ -284,7 +279,7 @@ enabled = false
|
||||
[plugins."sample@test"]
|
||||
enabled = true
|
||||
"#;
|
||||
let outcome = load_plugins_from_config(config_toml, codex_home.path());
|
||||
let outcome = load_plugins_from_config(config_toml, codex_home.path()).await;
|
||||
|
||||
assert!(outcome.plugins()[0].disabled_skill_paths.is_empty());
|
||||
assert!(outcome.plugins()[0].has_enabled_skills);
|
||||
@@ -301,8 +296,8 @@ enabled = true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plugin_telemetry_metadata_uses_default_mcp_config_path() {
|
||||
#[tokio::test]
|
||||
async fn plugin_telemetry_metadata_uses_default_mcp_config_path() {
|
||||
let codex_home = TempDir::new().unwrap();
|
||||
let plugin_root = codex_home
|
||||
.path()
|
||||
@@ -330,7 +325,8 @@ fn plugin_telemetry_metadata_uses_default_mcp_config_path() {
|
||||
let metadata = plugin_telemetry_metadata_from_root(
|
||||
&PluginId::parse("sample@test").expect("plugin id should parse"),
|
||||
&plugin_root.abs(),
|
||||
);
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
metadata.capability_summary,
|
||||
@@ -345,8 +341,8 @@ fn plugin_telemetry_metadata_uses_default_mcp_config_path() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn capability_summary_sanitizes_plugin_descriptions_to_one_line() {
|
||||
#[tokio::test]
|
||||
async fn capability_summary_sanitizes_plugin_descriptions_to_one_line() {
|
||||
let codex_home = TempDir::new().unwrap();
|
||||
let plugin_root = codex_home
|
||||
.path()
|
||||
@@ -368,7 +364,8 @@ fn capability_summary_sanitizes_plugin_descriptions_to_one_line() {
|
||||
let outcome = load_plugins_from_config(
|
||||
&plugin_config_toml(/*enabled*/ true, /*plugins_feature_enabled*/ true),
|
||||
codex_home.path(),
|
||||
);
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
outcome.plugins()[0].manifest_description.as_deref(),
|
||||
@@ -380,8 +377,8 @@ fn capability_summary_sanitizes_plugin_descriptions_to_one_line() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn capability_summary_truncates_overlong_plugin_descriptions() {
|
||||
#[tokio::test]
|
||||
async fn capability_summary_truncates_overlong_plugin_descriptions() {
|
||||
let codex_home = TempDir::new().unwrap();
|
||||
let plugin_root = codex_home
|
||||
.path()
|
||||
@@ -406,7 +403,8 @@ fn capability_summary_truncates_overlong_plugin_descriptions() {
|
||||
let outcome = load_plugins_from_config(
|
||||
&plugin_config_toml(/*enabled*/ true, /*plugins_feature_enabled*/ true),
|
||||
codex_home.path(),
|
||||
);
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
outcome.plugins()[0].manifest_description.as_deref(),
|
||||
@@ -418,8 +416,8 @@ fn capability_summary_truncates_overlong_plugin_descriptions() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_plugins_uses_manifest_configured_component_paths() {
|
||||
#[tokio::test]
|
||||
async fn load_plugins_uses_manifest_configured_component_paths() {
|
||||
let codex_home = TempDir::new().unwrap();
|
||||
let plugin_root = codex_home
|
||||
.path()
|
||||
@@ -489,7 +487,8 @@ fn load_plugins_uses_manifest_configured_component_paths() {
|
||||
let outcome = load_plugins_from_config(
|
||||
&plugin_config_toml(/*enabled*/ true, /*plugins_feature_enabled*/ true),
|
||||
codex_home.path(),
|
||||
);
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
outcome.plugins()[0].skill_roots,
|
||||
@@ -529,8 +528,8 @@ fn load_plugins_uses_manifest_configured_component_paths() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_plugins_ignores_manifest_component_paths_without_dot_slash() {
|
||||
#[tokio::test]
|
||||
async fn load_plugins_ignores_manifest_component_paths_without_dot_slash() {
|
||||
let codex_home = TempDir::new().unwrap();
|
||||
let plugin_root = codex_home
|
||||
.path()
|
||||
@@ -600,7 +599,8 @@ fn load_plugins_ignores_manifest_component_paths_without_dot_slash() {
|
||||
let outcome = load_plugins_from_config(
|
||||
&plugin_config_toml(/*enabled*/ true, /*plugins_feature_enabled*/ true),
|
||||
codex_home.path(),
|
||||
);
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
outcome.plugins()[0].skill_roots,
|
||||
@@ -637,8 +637,8 @@ fn load_plugins_ignores_manifest_component_paths_without_dot_slash() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_plugins_preserves_disabled_plugins_without_effective_contributions() {
|
||||
#[tokio::test]
|
||||
async fn load_plugins_preserves_disabled_plugins_without_effective_contributions() {
|
||||
let codex_home = TempDir::new().unwrap();
|
||||
let plugin_root = codex_home
|
||||
.path()
|
||||
@@ -666,7 +666,8 @@ fn load_plugins_preserves_disabled_plugins_without_effective_contributions() {
|
||||
/*enabled*/ false, /*plugins_feature_enabled*/ true,
|
||||
),
|
||||
codex_home.path(),
|
||||
);
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
outcome.plugins(),
|
||||
@@ -688,8 +689,8 @@ fn load_plugins_preserves_disabled_plugins_without_effective_contributions() {
|
||||
assert!(outcome.effective_mcp_servers().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn effective_apps_dedupes_connector_ids_across_plugins() {
|
||||
#[tokio::test]
|
||||
async fn effective_apps_dedupes_connector_ids_across_plugins() {
|
||||
let codex_home = TempDir::new().unwrap();
|
||||
let plugin_a_root = codex_home
|
||||
.path()
|
||||
@@ -751,7 +752,7 @@ fn effective_apps_dedupes_connector_ids_across_plugins() {
|
||||
let config_toml =
|
||||
toml::to_string(&Value::Table(root)).expect("plugin test config should serialize");
|
||||
|
||||
let outcome = load_plugins_from_config(&config_toml, codex_home.path());
|
||||
let outcome = load_plugins_from_config(&config_toml, codex_home.path()).await;
|
||||
|
||||
assert_eq!(
|
||||
outcome.effective_apps(),
|
||||
@@ -858,8 +859,8 @@ fn capability_index_filters_inactive_and_zero_capability_plugins() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_plugins_returns_empty_when_feature_disabled() {
|
||||
#[tokio::test]
|
||||
async fn load_plugins_returns_empty_when_feature_disabled() {
|
||||
let codex_home = TempDir::new().unwrap();
|
||||
let plugin_root = codex_home
|
||||
.path()
|
||||
@@ -881,14 +882,16 @@ fn load_plugins_returns_empty_when_feature_disabled() {
|
||||
),
|
||||
);
|
||||
|
||||
let config = load_config_blocking(codex_home.path(), codex_home.path());
|
||||
let outcome = PluginsManager::new(codex_home.path().to_path_buf()).plugins_for_config(&config);
|
||||
let config = load_config(codex_home.path(), codex_home.path()).await;
|
||||
let outcome = PluginsManager::new(codex_home.path().to_path_buf())
|
||||
.plugins_for_config(&config)
|
||||
.await;
|
||||
|
||||
assert_eq!(outcome, PluginLoadOutcome::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_plugins_rejects_invalid_plugin_keys() {
|
||||
#[tokio::test]
|
||||
async fn load_plugins_rejects_invalid_plugin_keys() {
|
||||
let codex_home = TempDir::new().unwrap();
|
||||
let plugin_root = codex_home
|
||||
.path()
|
||||
@@ -915,7 +918,8 @@ fn load_plugins_rejects_invalid_plugin_keys() {
|
||||
let outcome = load_plugins_from_config(
|
||||
&toml::to_string(&Value::Table(root)).expect("plugin test config should serialize"),
|
||||
codex_home.path(),
|
||||
);
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(outcome.plugins().len(), 1);
|
||||
assert_eq!(
|
||||
@@ -1346,6 +1350,7 @@ enabled = true
|
||||
marketplace_path,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert!(matches!(err, MarketplaceError::PluginsDisabled));
|
||||
@@ -1410,6 +1415,7 @@ enabled = false
|
||||
.unwrap(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(outcome.plugin.disabled_skill_paths.is_empty());
|
||||
@@ -2727,8 +2733,8 @@ enabled = true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_plugins_ignores_project_config_files() {
|
||||
#[tokio::test]
|
||||
async fn load_plugins_ignores_project_config_files() {
|
||||
let codex_home = TempDir::new().unwrap();
|
||||
let project_root = codex_home.path().join("project");
|
||||
let plugin_root = codex_home
|
||||
@@ -2764,7 +2770,8 @@ fn load_plugins_ignores_project_config_files() {
|
||||
&stack,
|
||||
&PluginStore::new(codex_home.path().to_path_buf()),
|
||||
Some(Product::Codex),
|
||||
);
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(outcome, PluginLoadOutcome::default());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user