mirror of
https://github.com/openai/codex.git
synced 2026-10-23 18:08:45 +00:00
Add configurable default plugin activation policy (#52220)
## What changed Support `plugins._default.enabled` with explicit per-plugin `enabled` overrides. Omission preserves existing source and account enablement; configuration cannot activate a plugin disabled at its source. Plugin settings such as remembered tool approvals inherit the default unless `enabled` is explicitly set. Apply the policy to plugin skills, MCP servers, Apps, and hooks, including selected executor roots and runtime configuration refreshes. Preserve the latest plugin policy when adding MCP dependencies and rebuild hooks when concurrent configuration updates replace their inputs. Report effective enablement in `plugin/list`, `plugin/installed`, `plugin/read`, and `plugin/share/list`. Remote installation still installs disabled packages, but skips MCP and Apps authentication setup without writing an enablement override. ## Testing Add regression coverage for default inheritance and explicit overrides, source-disabled plugins, policy refreshes during active turns, disabled or malformed selected plugin roots, and remote installation authentication behavior. GitOrigin-RevId: 83b70c5b47fc95474fec292f248683f1f887221e
This commit is contained in:
@@ -8,6 +8,7 @@ use codex_app_server_protocol::PluginAvailability;
|
||||
use codex_app_server_protocol::PluginSharePrincipalRole;
|
||||
use codex_app_server_protocol::PluginShareTargetRole;
|
||||
use codex_config::types::McpServerConfig;
|
||||
use codex_config::types::PluginsConfigToml;
|
||||
use codex_core_plugins::OPENAI_CURATED_MARKETPLACE_NAME;
|
||||
use codex_core_plugins::PluginListBackgroundTaskOptions;
|
||||
use codex_core_plugins::PluginMarketplaceContext;
|
||||
@@ -599,6 +600,7 @@ impl PluginRequestProcessor {
|
||||
let use_remote_global_catalog =
|
||||
include_global_remote && auth_mode.is_some_and(DomainAuthMode::uses_codex_backend);
|
||||
let remote_plugin_service_config = remote_plugin_service_config(&config);
|
||||
let plugin_policy = &config.plugins;
|
||||
let remote_catalog_cache_mode = if force_refetch {
|
||||
RemotePluginCatalogCacheMode::ForceRefetch
|
||||
} else {
|
||||
@@ -687,7 +689,10 @@ impl PluginRequestProcessor {
|
||||
.map(|outcome| outcome.marketplace)
|
||||
{
|
||||
Ok(Some(remote_marketplace)) => {
|
||||
data.push(remote_marketplace_to_info(remote_marketplace));
|
||||
data.push(remote_marketplace_to_info(
|
||||
remote_marketplace,
|
||||
plugin_policy,
|
||||
));
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(RemotePluginCatalogError::UnsupportedAuthMode) => {}
|
||||
@@ -737,7 +742,7 @@ impl PluginRequestProcessor {
|
||||
for remote_marketplace in outcome
|
||||
.marketplaces
|
||||
.into_iter()
|
||||
.map(remote_marketplace_to_info)
|
||||
.map(|marketplace| remote_marketplace_to_info(marketplace, plugin_policy))
|
||||
{
|
||||
data.push(remote_marketplace);
|
||||
}
|
||||
@@ -965,6 +970,7 @@ impl PluginRequestProcessor {
|
||||
visible_marketplaces: &[&str],
|
||||
auth: Option<&CodexAuth>,
|
||||
) -> Vec<PluginMarketplaceEntry> {
|
||||
let plugin_policy = &plugins_input.plugins;
|
||||
let remote_marketplaces = if let Some(remote_marketplaces) = plugins_manager
|
||||
.build_remote_installed_plugin_marketplaces_from_cache(visible_marketplaces)
|
||||
{
|
||||
@@ -983,7 +989,7 @@ impl PluginRequestProcessor {
|
||||
match remote_marketplaces {
|
||||
Ok(remote_marketplaces) => remote_marketplaces
|
||||
.into_iter()
|
||||
.map(remote_marketplace_to_info)
|
||||
.map(|marketplace| remote_marketplace_to_info(marketplace, plugin_policy))
|
||||
.collect(),
|
||||
Err(
|
||||
RemotePluginCatalogError::AuthRequired
|
||||
@@ -1208,7 +1214,8 @@ impl PluginRequestProcessor {
|
||||
&app_category_by_id,
|
||||
)
|
||||
.await;
|
||||
remote_plugin_detail_to_info(remote_detail, app_summaries)
|
||||
let plugin_policy = &config.plugins;
|
||||
remote_plugin_detail_to_info(remote_detail, app_summaries, plugin_policy)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1373,6 +1380,7 @@ impl PluginRequestProcessor {
|
||||
) -> Result<PluginShareListResponse, JSONRPCErrorError> {
|
||||
let (config, auth) = self.load_plugin_share_config_and_auth().await?;
|
||||
let remote_plugin_service_config = remote_plugin_service_config(&config);
|
||||
let plugin_policy = &config.plugins;
|
||||
let data = codex_core_plugins::remote::list_remote_plugin_shares(
|
||||
&remote_plugin_service_config,
|
||||
auth.as_ref(),
|
||||
@@ -1386,7 +1394,7 @@ impl PluginRequestProcessor {
|
||||
summary,
|
||||
local_plugin_path,
|
||||
} = summary;
|
||||
let plugin = remote_plugin_summary_to_info(summary);
|
||||
let plugin = remote_plugin_summary_to_info(summary, plugin_policy);
|
||||
PluginShareListItem {
|
||||
plugin,
|
||||
local_plugin_path,
|
||||
@@ -1664,6 +1672,30 @@ impl PluginRequestProcessor {
|
||||
self.analytics_events_client
|
||||
.track_plugin_installed(plugin_metadata);
|
||||
|
||||
let config = match self.load_latest_config(/*fallback_cwd*/ None).await {
|
||||
Ok(mut reloaded_config) => {
|
||||
// Keep the policy captured with this installation's auth snapshot.
|
||||
reloaded_config.application_network_policy = config.application_network_policy;
|
||||
reloaded_config
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"failed to reload config after remote plugin install, skipping auth setup: {err:?}"
|
||||
);
|
||||
return Ok(PluginInstallResponse {
|
||||
auth_policy: remote_detail.summary.auth_policy,
|
||||
apps_needing_auth: Vec::new(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if !config.plugins.allows_plugin(&result.plugin_id.as_key()) {
|
||||
return Ok(PluginInstallResponse {
|
||||
auth_policy: remote_detail.summary.auth_policy,
|
||||
apps_needing_auth: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
let plugin_mcp_servers = load_configured_plugin_mcp_servers(
|
||||
result.installed_path.as_path(),
|
||||
auth.as_ref().map(CodexAuth::auth_mode),
|
||||
@@ -2174,7 +2206,10 @@ fn plugin_app_category_by_id_from_value(value: &serde_json::Value) -> HashMap<St
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn remote_marketplace_to_info(marketplace: RemoteMarketplace) -> PluginMarketplaceEntry {
|
||||
fn remote_marketplace_to_info(
|
||||
marketplace: RemoteMarketplace,
|
||||
plugin_policy: &PluginsConfigToml,
|
||||
) -> PluginMarketplaceEntry {
|
||||
PluginMarketplaceEntry {
|
||||
name: marketplace.name,
|
||||
path: None,
|
||||
@@ -2184,12 +2219,16 @@ fn remote_marketplace_to_info(marketplace: RemoteMarketplace) -> PluginMarketpla
|
||||
plugins: marketplace
|
||||
.plugins
|
||||
.into_iter()
|
||||
.map(remote_plugin_summary_to_info)
|
||||
.map(|summary| remote_plugin_summary_to_info(summary, plugin_policy))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn remote_plugin_summary_to_info(summary: RemoteCatalogPluginSummary) -> PluginSummary {
|
||||
fn remote_plugin_summary_to_info(
|
||||
summary: RemoteCatalogPluginSummary,
|
||||
plugin_policy: &PluginsConfigToml,
|
||||
) -> PluginSummary {
|
||||
let enabled = summary.enabled && plugin_policy.allows_plugin(&summary.id);
|
||||
PluginSummary {
|
||||
id: summary.id,
|
||||
remote_plugin_id: Some(summary.remote_plugin_id),
|
||||
@@ -2204,7 +2243,7 @@ fn remote_plugin_summary_to_info(summary: RemoteCatalogPluginSummary) -> PluginS
|
||||
installed_at: summary
|
||||
.installed_at
|
||||
.map(|installed_at| installed_at.timestamp()),
|
||||
enabled: summary.enabled,
|
||||
enabled,
|
||||
install_policy: summary.install_policy,
|
||||
install_policy_source: summary.install_policy_source,
|
||||
must_show_installation_interstitial: summary.must_show_installation_interstitial,
|
||||
@@ -2258,7 +2297,9 @@ fn remote_plugin_share_discoverability_to_info(
|
||||
fn remote_plugin_detail_to_info(
|
||||
detail: RemoteCatalogPluginDetail,
|
||||
apps: Vec<AppSummary>,
|
||||
plugin_policy: &PluginsConfigToml,
|
||||
) -> PluginDetail {
|
||||
let summary = remote_plugin_summary_to_info(detail.summary, plugin_policy);
|
||||
let app_templates = detail
|
||||
.app_templates
|
||||
.into_iter()
|
||||
@@ -2294,8 +2335,8 @@ fn remote_plugin_detail_to_info(
|
||||
enabled: skill.enabled,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let onboarding_skill = if detail.summary.enabled
|
||||
&& detail.summary.availability == PluginAvailability::Available
|
||||
let onboarding_skill = if summary.enabled
|
||||
&& summary.availability == PluginAvailability::Available
|
||||
&& let Some(name) = detail.onboarding_skill_name.as_ref()
|
||||
{
|
||||
skills
|
||||
@@ -2309,7 +2350,7 @@ fn remote_plugin_detail_to_info(
|
||||
PluginDetail {
|
||||
marketplace_name: detail.marketplace_name,
|
||||
marketplace_path: None,
|
||||
summary: remote_plugin_summary_to_info(detail.summary),
|
||||
summary,
|
||||
share_url: detail.share_url,
|
||||
description: detail.description,
|
||||
skills,
|
||||
|
||||
@@ -94,6 +94,7 @@ impl PluginRequestProcessor {
|
||||
|
||||
next_cursor = page.next_page_token;
|
||||
remote_results.reserve(page.plugins.len());
|
||||
let plugin_policy = &config.plugins;
|
||||
for plugin in page.plugins {
|
||||
let plugin_id = PluginId::parse(&plugin.id).map_err(|err| {
|
||||
internal_error(format!("invalid remote plugin search result id: {err}"))
|
||||
@@ -114,7 +115,7 @@ impl PluginRequestProcessor {
|
||||
continue;
|
||||
}
|
||||
remote_results.push(plugin_search_result(
|
||||
remote_plugin_summary_to_info(plugin),
|
||||
remote_plugin_summary_to_info(plugin, plugin_policy),
|
||||
plugin_id.marketplace_name,
|
||||
/*marketplace_path*/ None,
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user