Refactor plugin loading to async (#17747)

Simplifies skills migration.
This commit is contained in:
pakrym-oai
2026-04-13 21:52:56 -07:00
committed by GitHub
parent ff584c5a4b
commit 3b24a9a532
23 changed files with 308 additions and 209 deletions
+5 -6
View File
@@ -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");
+68 -46
View File
@@ -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) {
+58 -51
View File
@@ -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());
}