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
Unverified
parent ff584c5a4b
commit 3b24a9a532
23 changed files with 308 additions and 209 deletions
@@ -5328,7 +5328,11 @@ impl CodexMessageProcessor {
&self,
config: &Config,
) -> Result<(), JSONRPCErrorError> {
let configured_servers = self.thread_manager.mcp_manager().configured_servers(config);
let configured_servers = self
.thread_manager
.mcp_manager()
.configured_servers(config)
.await;
let mcp_servers = match serde_json::to_value(configured_servers) {
Ok(value) => value,
Err(err) => {
@@ -5388,7 +5392,8 @@ impl CodexMessageProcessor {
let configured_servers = self
.thread_manager
.mcp_manager()
.configured_servers(&config);
.configured_servers(&config)
.await;
let Some(server) = configured_servers.get(&name) else {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
@@ -5490,7 +5495,9 @@ impl CodexMessageProcessor {
return;
}
};
let mcp_config = config.to_mcp_config(self.thread_manager.plugins_manager().as_ref());
let mcp_config = config
.to_mcp_config(self.thread_manager.plugins_manager().as_ref())
.await;
let auth = self.auth_manager.auth().await;
tokio::spawn(async move {
@@ -6315,10 +6322,12 @@ impl CodexMessageProcessor {
continue;
}
};
let effective_skill_roots = plugins_manager.effective_skill_roots_for_layer_stack(
&config_layer_stack,
config.features.enabled(Feature::Plugins),
);
let effective_skill_roots = plugins_manager
.effective_skill_roots_for_layer_stack(
&config_layer_stack,
config.features.enabled(Feature::Plugins),
)
.await;
let skills_input = codex_core::skills::SkillsLoadInput::new(
cwd_abs,
effective_skill_roots,
@@ -6544,26 +6553,16 @@ impl CodexMessageProcessor {
plugin_name,
marketplace_path,
};
let config_for_read = config.clone();
let outcome = match tokio::task::spawn_blocking(move || {
plugins_manager.read_plugin_for_config(&config_for_read, &request)
})
.await
let outcome = match plugins_manager
.read_plugin_for_config(&config, &request)
.await
{
Ok(Ok(outcome)) => outcome,
Ok(Err(err)) => {
Ok(outcome) => outcome,
Err(err) => {
self.send_marketplace_error(request_id, err, "read plugin details")
.await;
return;
}
Err(err) => {
self.send_internal_error(
request_id,
format!("failed to read plugin details: {err}"),
)
.await;
return;
}
};
let app_summaries =
plugin_app_helpers::load_plugin_app_summaries(&config, &outcome.plugin.apps).await;
@@ -6704,7 +6703,8 @@ impl CodexMessageProcessor {
self.clear_plugin_related_caches();
let plugin_mcp_servers = load_plugin_mcp_servers(result.installed_path.as_path());
let plugin_mcp_servers =
load_plugin_mcp_servers(result.installed_path.as_path()).await;
if !plugin_mcp_servers.is_empty() {
if let Err(err) = self.queue_mcp_server_refresh_for_config(&config).await {
@@ -6717,7 +6717,7 @@ impl CodexMessageProcessor {
.await;
}
let plugin_apps = load_plugin_apps(result.installed_path.as_path());
let plugin_apps = load_plugin_apps(result.installed_path.as_path()).await;
let auth = self.auth_manager.auth().await;
let apps_needing_auth = if plugin_apps.is_empty()
|| !config.features.apps_enabled_for_auth(
+7 -4
View File
@@ -210,7 +210,7 @@ impl ConfigApi {
.write_value(params)
.await
.map_err(map_error)?;
self.emit_plugin_toggle_events(pending_changes);
self.emit_plugin_toggle_events(pending_changes).await;
Ok(response)
}
@@ -230,7 +230,7 @@ impl ConfigApi {
.batch_write(params)
.await
.map_err(map_error)?;
self.emit_plugin_toggle_events(pending_changes);
self.emit_plugin_toggle_events(pending_changes).await;
if reload_user_config {
self.user_config_reloader.reload_user_config().await;
}
@@ -299,13 +299,16 @@ impl ConfigApi {
Ok(ExperimentalFeatureEnablementSetResponse { enablement })
}
fn emit_plugin_toggle_events(&self, pending_changes: std::collections::BTreeMap<String, bool>) {
async fn emit_plugin_toggle_events(
&self,
pending_changes: std::collections::BTreeMap<String, bool>,
) {
for (plugin_id, enabled) in pending_changes {
let Ok(plugin_id) = PluginId::parse(&plugin_id) else {
continue;
};
let metadata =
installed_plugin_telemetry_metadata(self.codex_home.as_path(), &plugin_id);
installed_plugin_telemetry_metadata(self.codex_home.as_path(), &plugin_id).await;
if enabled {
self.analytics_events_client.track_plugin_enabled(metadata);
} else {
+6 -6
View File
@@ -73,10 +73,9 @@ pub async fn list_cached_all_connectors(config: &Config) -> Option<Vec<AppInfo>>
}
let token_data = get_chatgpt_token_data()?;
let cache_key = all_connectors_cache_key(config, &token_data);
codex_connectors::cached_all_connectors(&cache_key).map(|connectors| {
let connectors = merge_plugin_apps(connectors, plugin_apps_for_config(config));
filter_disallowed_connectors(connectors)
})
let connectors = codex_connectors::cached_all_connectors(&cache_key)?;
let connectors = merge_plugin_apps(connectors, plugin_apps_for_config(config).await);
Some(filter_disallowed_connectors(connectors))
}
pub async fn list_all_connectors_with_options(
@@ -106,7 +105,7 @@ pub async fn list_all_connectors_with_options(
},
)
.await?;
let connectors = merge_plugin_apps(connectors, plugin_apps_for_config(config));
let connectors = merge_plugin_apps(connectors, plugin_apps_for_config(config).await);
Ok(filter_disallowed_connectors(connectors))
}
@@ -119,9 +118,10 @@ fn all_connectors_cache_key(config: &Config, token_data: &TokenData) -> AllConne
)
}
fn plugin_apps_for_config(config: &Config) -> Vec<codex_core::plugins::AppConnectorId> {
async fn plugin_apps_for_config(config: &Config) -> Vec<codex_core::plugins::AppConnectorId> {
PluginsManager::new(config.codex_home.to_path_buf())
.plugins_for_config(config)
.await
.effective_apps()
}
+4 -4
View File
@@ -394,7 +394,7 @@ async fn run_login(config_overrides: &CliConfigOverrides, login_args: LoginArgs)
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(
config.codex_home.to_path_buf(),
)));
let mcp_servers = mcp_manager.effective_servers(&config, /*auth*/ None);
let mcp_servers = mcp_manager.effective_servers(&config, /*auth*/ None).await;
let LoginArgs { name, scopes } = login_args;
@@ -447,7 +447,7 @@ async fn run_logout(config_overrides: &CliConfigOverrides, logout_args: LogoutAr
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(
config.codex_home.to_path_buf(),
)));
let mcp_servers = mcp_manager.effective_servers(&config, /*auth*/ None);
let mcp_servers = mcp_manager.effective_servers(&config, /*auth*/ None).await;
let LogoutArgs { name } = logout_args;
@@ -479,7 +479,7 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) ->
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(
config.codex_home.to_path_buf(),
)));
let mcp_servers = mcp_manager.effective_servers(&config, /*auth*/ None);
let mcp_servers = mcp_manager.effective_servers(&config, /*auth*/ None).await;
let mut entries: Vec<_> = mcp_servers.iter().collect();
entries.sort_by(|(a, _), (b, _)| a.cmp(b));
@@ -730,7 +730,7 @@ async fn run_get(config_overrides: &CliConfigOverrides, get_args: GetArgs) -> Re
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(
config.codex_home.to_path_buf(),
)));
let mcp_servers = mcp_manager.effective_servers(&config, /*auth*/ None);
let mcp_servers = mcp_manager.effective_servers(&config, /*auth*/ None).await;
let Some(server) = mcp_servers.get(&get_args.name) else {
bail!("No MCP server named '{name}' found.", name = get_args.name);
+1 -1
View File
@@ -655,7 +655,7 @@ enabled = false
let plugins_manager = Arc::new(PluginsManager::new(home.path().to_path_buf()));
let skills_manager =
SkillsManager::new(home.path().abs(), /*bundled_skills_enabled*/ true);
let plugin_outcome = plugins_manager.plugins_for_config(&config);
let plugin_outcome = plugins_manager.plugins_for_config(&config).await;
let effective_skill_roots = plugin_outcome.effective_skill_roots();
let skills_input = skills_load_input_from_config(&config, effective_skill_roots);
let outcome = skills_manager.skills_for_config(&skills_input);
+26 -14
View File
@@ -496,7 +496,7 @@ impl Codex {
let (tx_sub, rx_sub) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY);
let (tx_event, rx_event) = async_channel::unbounded();
let plugin_outcome = plugins_manager.plugins_for_config(&config);
let plugin_outcome = plugins_manager.plugins_for_config(&config).await;
let effective_skill_roots = plugin_outcome.effective_skill_roots();
let skills_input = skills_load_input_from_config(&config, effective_skill_roots);
let loaded_skills = skills_manager.skills_for_config(&skills_input);
@@ -1746,7 +1746,9 @@ impl Session {
let mcp_manager_for_mcp = Arc::clone(&mcp_manager);
let auth_and_mcp_fut = async move {
let auth = auth_manager_clone.auth().await;
let mcp_servers = mcp_manager_for_mcp.effective_servers(&config_for_mcp, auth.as_ref());
let mcp_servers = mcp_manager_for_mcp
.effective_servers(&config_for_mcp, auth.as_ref())
.await;
let auth_statuses = compute_auth_statuses(
mcp_servers.iter(),
config_for_mcp.mcp_oauth_credentials_store_mode,
@@ -2161,7 +2163,7 @@ impl Session {
required_mcp_servers.sort();
let enabled_mcp_server_count = mcp_servers.values().filter(|server| server.enabled).count();
let required_mcp_server_count = required_mcp_servers.len();
let tool_plugin_provenance = mcp_manager.tool_plugin_provenance(config.as_ref());
let tool_plugin_provenance = mcp_manager.tool_plugin_provenance(config.as_ref()).await;
{
let mut cancel_guard = sess.services.mcp_startup_cancellation_token.lock().await;
cancel_guard.cancel();
@@ -2658,7 +2660,8 @@ impl Session {
let plugin_outcome = self
.services
.plugins_manager
.plugins_for_config(&per_turn_config);
.plugins_for_config(&per_turn_config)
.await;
let effective_skill_roots = plugin_outcome.effective_skill_roots();
let skills_input = skills_load_input_from_config(&per_turn_config, effective_skill_roots);
let skills_outcome = Arc::new(
@@ -3852,7 +3855,8 @@ impl Session {
let loaded_plugins = self
.services
.plugins_manager
.plugins_for_config(&turn_context.config);
.plugins_for_config(&turn_context.config)
.await;
if let Some(plugin_section) = render_plugins_section(loaded_plugins.capability_summaries())
{
developer_sections.push(plugin_section);
@@ -4501,11 +4505,14 @@ impl Session {
) {
let auth = self.services.auth_manager.auth().await;
let config = self.get_config().await;
let mcp_config = config.to_mcp_config(self.services.plugins_manager.as_ref());
let mcp_config = config
.to_mcp_config(self.services.plugins_manager.as_ref())
.await;
let tool_plugin_provenance = self
.services
.mcp_manager
.tool_plugin_provenance(config.as_ref());
.tool_plugin_provenance(config.as_ref())
.await;
let mcp_servers = with_codex_apps_mcp(mcp_servers, auth.as_ref(), &mcp_config);
let auth_statuses = compute_auth_statuses(mcp_servers.iter(), store_mode).await;
let sandbox_state = SandboxState {
@@ -5360,7 +5367,8 @@ mod handlers {
let mcp_servers = sess
.services
.mcp_manager
.effective_servers(config, auth.as_ref());
.effective_servers(config, auth.as_ref())
.await;
let snapshot = collect_mcp_snapshot_from_manager(
&mcp_connection_manager,
compute_auth_statuses(mcp_servers.iter(), config.mcp_oauth_credentials_store_mode)
@@ -5434,10 +5442,12 @@ mod handlers {
continue;
}
};
let effective_skill_roots = plugins_manager.effective_skill_roots_for_layer_stack(
&config_layer_stack,
config.features.enabled(Feature::Plugins),
);
let effective_skill_roots = plugins_manager
.effective_skill_roots_for_layer_stack(
&config_layer_stack,
config.features.enabled(Feature::Plugins),
)
.await;
let skills_input = crate::SkillsLoadInput::new(
cwd_abs,
effective_skill_roots,
@@ -6144,7 +6154,8 @@ pub(crate) async fn run_turn(
let loaded_plugins = sess
.services
.plugins_manager
.plugins_for_config(&turn_context.config);
.plugins_for_config(&turn_context.config)
.await;
// Structured plugin:// mentions are resolved from the current session's
// enabled plugins, then converted into turn-scoped guidance below.
let mentioned_plugins =
@@ -7057,7 +7068,8 @@ pub(crate) async fn built_tools(
let loaded_plugins = sess
.services
.plugins_manager
.plugins_for_config(&turn_context.config);
.plugins_for_config(&turn_context.config)
.await;
let mut effective_explicitly_enabled_connectors = explicitly_enabled_connectors.clone();
effective_explicitly_enabled_connectors.extend(sess.get_connector_selection().await);
+4 -2
View File
@@ -2906,7 +2906,8 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
let plugin_outcome = services
.plugins_manager
.plugins_for_config(&per_turn_config);
.plugins_for_config(&per_turn_config)
.await;
let effective_skill_roots = plugin_outcome.effective_skill_roots();
let skills_input =
crate::skills_load_input_from_config(&per_turn_config, effective_skill_roots);
@@ -3751,7 +3752,8 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
let plugin_outcome = services
.plugins_manager
.plugins_for_config(&per_turn_config);
.plugins_for_config(&per_turn_config)
.await;
let effective_skill_roots = plugin_outcome.effective_skill_roots();
let skills_input =
crate::skills_load_input_from_config(&per_turn_config, effective_skill_roots);
+5 -5
View File
@@ -2192,8 +2192,8 @@ approval_mode = "approve"
);
}
#[test]
fn to_mcp_config_preserves_apps_feature_from_config() -> std::io::Result<()> {
#[tokio::test]
async fn to_mcp_config_preserves_apps_feature_from_config() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let mut config = Config::load_from_base_config_with_overrides(
ConfigToml::default(),
@@ -2202,15 +2202,15 @@ fn to_mcp_config_preserves_apps_feature_from_config() -> std::io::Result<()> {
)?;
let plugins_manager = PluginsManager::new(codex_home.path().to_path_buf());
let mcp_config = config.to_mcp_config(&plugins_manager);
let mcp_config = config.to_mcp_config(&plugins_manager).await;
assert!(mcp_config.apps_enabled);
let _ = config.features.disable(Feature::Apps);
let mcp_config = config.to_mcp_config(&plugins_manager);
let mcp_config = config.to_mcp_config(&plugins_manager).await;
assert!(!mcp_config.apps_enabled);
let _ = config.features.enable(Feature::Apps);
let mcp_config = config.to_mcp_config(&plugins_manager);
let mcp_config = config.to_mcp_config(&plugins_manager).await;
assert!(mcp_config.apps_enabled);
Ok(())
+5 -2
View File
@@ -747,8 +747,11 @@ impl Config {
}
}
pub fn to_mcp_config(&self, plugins_manager: &crate::plugins::PluginsManager) -> McpConfig {
let loaded_plugins = plugins_manager.plugins_for_config(self);
pub async fn to_mcp_config(
&self,
plugins_manager: &crate::plugins::PluginsManager,
) -> McpConfig {
let loaded_plugins = plugins_manager.plugins_for_config(self).await;
let mut configured_mcp_servers = self.mcp_servers.get().clone();
for (name, plugin_server) in loaded_plugins.effective_mcp_servers() {
configured_mcp_servers.entry(name).or_insert(plugin_server);
+7 -5
View File
@@ -124,7 +124,7 @@ pub(crate) async fn list_tool_suggest_discoverable_tools_with_auth(
) -> anyhow::Result<Vec<DiscoverableTool>> {
let directory_connectors =
list_directory_connectors_for_tool_suggest_with_auth(config, auth).await?;
let connector_ids = tool_suggest_connector_ids(config);
let connector_ids = tool_suggest_connector_ids(config).await;
let discoverable_connectors = filter_tool_suggest_discoverable_connectors(
directory_connectors,
accessible_connectors,
@@ -132,7 +132,8 @@ pub(crate) async fn list_tool_suggest_discoverable_tools_with_auth(
)
.into_iter()
.map(DiscoverableTool::from);
let discoverable_plugins = list_tool_suggest_discoverable_plugins(config)?
let discoverable_plugins = list_tool_suggest_discoverable_plugins(config)
.await?
.into_iter()
.map(DiscoverableTool::from);
Ok(discoverable_connectors
@@ -201,7 +202,7 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_options_and_status(
let cache_key = accessible_connectors_cache_key(config, auth.as_ref());
let plugins_manager = Arc::new(PluginsManager::new(config.codex_home.to_path_buf()));
let mcp_manager = McpManager::new(Arc::clone(&plugins_manager));
let tool_plugin_provenance = mcp_manager.tool_plugin_provenance(config);
let tool_plugin_provenance = mcp_manager.tool_plugin_provenance(config).await;
if !force_refetch && let Some(cached_connectors) = read_cached_accessible_connectors(&cache_key)
{
let cached_connectors = filter_disallowed_connectors(cached_connectors);
@@ -212,7 +213,7 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_options_and_status(
});
}
let mcp_config = config.to_mcp_config(plugins_manager.as_ref());
let mcp_config = config.to_mcp_config(plugins_manager.as_ref()).await;
let mcp_servers = with_codex_apps_mcp(HashMap::new(), auth.as_ref(), &mcp_config);
if mcp_servers.is_empty() {
return Ok(AccessibleConnectorsStatus {
@@ -395,9 +396,10 @@ fn filter_tool_suggest_discoverable_connectors(
connectors
}
fn tool_suggest_connector_ids(config: &Config) -> HashSet<String> {
async fn tool_suggest_connector_ids(config: &Config) -> HashSet<String> {
let mut connector_ids = PluginsManager::new(config.codex_home.to_path_buf())
.plugins_for_config(config)
.await
.capability_summaries()
.iter()
.flat_map(|plugin| plugin.app_connector_ids.iter())
+1 -1
View File
@@ -1037,7 +1037,7 @@ discoverables = [
.expect("config should load");
assert_eq!(
tool_suggest_connector_ids(&config),
tool_suggest_connector_ids(&config).await,
HashSet::from(["connector_2128aebfecb84f64a069897515042a44".to_string()])
);
}
+6 -6
View File
@@ -20,22 +20,22 @@ impl McpManager {
Self { plugins_manager }
}
pub fn configured_servers(&self, config: &Config) -> HashMap<String, McpServerConfig> {
let mcp_config = config.to_mcp_config(self.plugins_manager.as_ref());
pub async fn configured_servers(&self, config: &Config) -> HashMap<String, McpServerConfig> {
let mcp_config = config.to_mcp_config(self.plugins_manager.as_ref()).await;
configured_mcp_servers(&mcp_config)
}
pub fn effective_servers(
pub async fn effective_servers(
&self,
config: &Config,
auth: Option<&CodexAuth>,
) -> HashMap<String, McpServerConfig> {
let mcp_config = config.to_mcp_config(self.plugins_manager.as_ref());
let mcp_config = config.to_mcp_config(self.plugins_manager.as_ref()).await;
effective_mcp_servers(&mcp_config, auth)
}
pub fn tool_plugin_provenance(&self, config: &Config) -> ToolPluginProvenance {
let mcp_config = config.to_mcp_config(self.plugins_manager.as_ref());
pub async fn tool_plugin_provenance(&self, config: &Config) -> ToolPluginProvenance {
let mcp_config = config.to_mcp_config(self.plugins_manager.as_ref()).await;
collect_tool_plugin_provenance(&mcp_config)
}
}
+5 -3
View File
@@ -53,7 +53,8 @@ pub(crate) async fn maybe_prompt_and_install_mcp_dependencies(
let installed = sess
.services
.mcp_manager
.configured_servers(config.as_ref());
.configured_servers(config.as_ref())
.await;
let missing = collect_missing_mcp_dependencies(mentioned_skills, &installed);
if missing.is_empty() {
return;
@@ -86,7 +87,7 @@ pub(crate) async fn maybe_install_mcp_dependencies(
}
let codex_home = config.codex_home.clone();
let installed = sess.services.mcp_manager.configured_servers(config);
let installed = sess.services.mcp_manager.configured_servers(config).await;
let missing = collect_missing_mcp_dependencies(mentioned_skills, &installed);
if missing.is_empty() {
return;
@@ -197,7 +198,8 @@ pub(crate) async fn maybe_install_mcp_dependencies(
let mut refresh_servers = sess
.services
.mcp_manager
.effective_servers(config, auth.as_ref());
.effective_servers(config, auth.as_ref())
.await;
for (name, server_config) in &servers {
refresh_servers
.entry(name.clone())
+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());
}
+2 -2
View File
@@ -54,13 +54,13 @@ impl SkillsWatcher {
self.tx.subscribe()
}
pub(crate) fn register_config(
pub(crate) async fn register_config(
&self,
config: &Config,
skills_manager: &SkillsManager,
plugins_manager: &PluginsManager,
) -> WatchRegistration {
let plugin_outcome = plugins_manager.plugins_for_config(config);
let plugin_outcome = plugins_manager.plugins_for_config(config).await;
let effective_skill_roots = plugin_outcome.effective_skill_roots();
let skills_input = skills_load_input_from_config(config, effective_skill_roots);
let roots = skills_manager
+8 -5
View File
@@ -903,11 +903,14 @@ impl ThreadManagerState {
parent_trace: Option<W3cTraceContext>,
user_shell_override: Option<crate::shell::Shell>,
) -> CodexResult<NewThread> {
let watch_registration = self.skills_watcher.register_config(
&config,
self.skills_manager.as_ref(),
self.plugins_manager.as_ref(),
);
let watch_registration = self
.skills_watcher
.register_config(
&config,
self.skills_manager.as_ref(),
self.plugins_manager.as_ref(),
)
.await;
let CodexSpawnOk {
codex, thread_id, ..
} = Codex::spawn(CodexSpawnArgs {
+30
View File
@@ -42,6 +42,7 @@ use crate::legacy_core::config::edit::ConfigEdit;
use crate::legacy_core::config::edit::ConfigEditsBuilder;
use crate::legacy_core::config_loader::ConfigLayerStackOrdering;
use crate::legacy_core::lookup_message_history_entry;
use crate::legacy_core::plugins::PluginsManager;
#[cfg(target_os = "windows")]
use crate::legacy_core::windows_sandbox::WindowsSandboxLevelExt;
use crate::model_catalog::ModelCatalog;
@@ -2017,6 +2018,26 @@ impl App {
});
}
fn refresh_plugin_mentions(&mut self) {
let config = self.config.clone();
let app_event_tx = self.app_event_tx.clone();
if !config.features.enabled(Feature::Plugins) {
app_event_tx.send(AppEvent::PluginMentionsLoaded { plugins: None });
return;
}
tokio::spawn(async move {
let plugins = PluginsManager::new(config.codex_home.to_path_buf())
.plugins_for_config(&config)
.await
.capability_summaries()
.to_vec();
app_event_tx.send(AppEvent::PluginMentionsLoaded {
plugins: Some(plugins),
});
});
}
fn submit_feedback(
&mut self,
app_server: &AppServerSession,
@@ -5044,6 +5065,15 @@ impl App {
self.fetch_plugins_list(app_server, cwd);
}
}
AppEvent::RefreshPluginMentions => {
self.refresh_plugin_mentions();
}
AppEvent::PluginMentionsLoaded { mut plugins } => {
if !self.config.features.enabled(Feature::Plugins) {
plugins = None;
}
self.chat_widget.on_plugin_mentions_loaded(plugins);
}
AppEvent::PersistPersonalitySelection { personality } => {
let profile = self.active_profile.as_deref();
match ConfigEditsBuilder::new(&self.config.codex_home)
+9
View File
@@ -30,6 +30,7 @@ use crate::bottom_pane::ApprovalRequest;
use crate::bottom_pane::StatusLineItem;
use crate::bottom_pane::TerminalTitleItem;
use crate::history_cell::HistoryCell;
use crate::legacy_core::plugins::PluginCapabilitySummary;
use codex_config::types::ApprovalsReviewer;
use codex_features::Feature;
@@ -270,6 +271,14 @@ pub(crate) enum AppEvent {
result: Result<PluginUninstallResponse, String>,
},
/// Refresh plugin mention bindings from the current config.
RefreshPluginMentions,
/// Result of refreshing plugin mention bindings.
PluginMentionsLoaded {
plugins: Option<Vec<PluginCapabilitySummary>>,
},
/// Advance the post-install plugin app-auth flow.
PluginInstallAuthAdvance {
refresh_connectors: bool,
+8 -6
View File
@@ -62,7 +62,6 @@ use crate::legacy_core::config::Constrained;
use crate::legacy_core::config::ConstraintResult;
use crate::legacy_core::config_loader::ConfigLayerStackOrdering;
use crate::legacy_core::find_thread_name_by_id;
use crate::legacy_core::plugins::PluginsManager;
use crate::legacy_core::skills::model::SkillMetadata;
#[cfg(target_os = "windows")]
use crate::legacy_core::windows_sandbox::WindowsSandboxLevelExt;
@@ -10369,11 +10368,14 @@ impl ChatWidget {
return;
}
let plugins = PluginsManager::new(self.config.codex_home.to_path_buf())
.plugins_for_config(&self.config)
.capability_summaries()
.to_vec();
self.bottom_pane.set_plugin_mentions(Some(plugins));
self.app_event_tx.send(AppEvent::RefreshPluginMentions);
}
pub(crate) fn on_plugin_mentions_loaded(
&mut self,
plugins: Option<Vec<crate::legacy_core::plugins::PluginCapabilitySummary>>,
) {
self.bottom_pane.set_plugin_mentions(plugins);
}
pub(crate) fn sync_plugin_mentions_config(&mut self, config: &Config) {
+1 -10
View File
@@ -19,11 +19,7 @@ use crate::exec_cell::output_lines;
use crate::exec_cell::spinner;
use crate::exec_command::relativize_to_home;
use crate::exec_command::strip_bash_lc_and_escape;
#[cfg(test)]
use crate::legacy_core::McpManager;
use crate::legacy_core::config::Config;
#[cfg(test)]
use crate::legacy_core::plugins::PluginsManager;
use crate::legacy_core::web_search_detail;
use crate::live_wrap::take_prefix_by_width;
use crate::markdown::append_markdown;
@@ -88,8 +84,6 @@ use std::collections::HashMap;
use std::io::Cursor;
use std::path::Path;
use std::path::PathBuf;
#[cfg(test)]
use std::sync::Arc;
use std::time::Duration;
use std::time::Instant;
use tracing::error;
@@ -1877,10 +1871,7 @@ pub(crate) fn new_mcp_tools_output(
lines.push("".into());
}
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(
config.codex_home.to_path_buf(),
)));
let effective_servers = mcp_manager.effective_servers(config, /*auth*/ None);
let effective_servers = config.mcp_servers.get().clone();
let mut servers: Vec<_> = effective_servers.iter().collect();
servers.sort_by(|(a, _), (b, _)| a.cmp(b));