mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
committed by
GitHub
Unverified
parent
ff584c5a4b
commit
3b24a9a532
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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()])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user