mirror of
https://github.com/openai/codex.git
synced 2026-11-11 21:10:39 +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:
@@ -24,7 +24,7 @@ use crate::types::MemoriesToml;
|
||||
use crate::types::Notice;
|
||||
use crate::types::OAuthCredentialsStoreMode;
|
||||
use crate::types::OtelConfigToml;
|
||||
use crate::types::PluginConfig;
|
||||
use crate::types::PluginsConfigToml;
|
||||
use crate::types::SandboxWorkspaceWrite;
|
||||
use crate::types::ShellEnvironmentPolicyToml;
|
||||
use crate::types::SkillsConfig;
|
||||
@@ -496,9 +496,9 @@ pub struct ConfigToml {
|
||||
/// Lifecycle hooks configured inline in TOML plus user-level overrides.
|
||||
pub hooks: Option<HooksToml>,
|
||||
|
||||
/// User-level plugin config entries keyed by plugin name.
|
||||
/// Default and per-plugin settings keyed by exact `<plugin>@<marketplace>` IDs.
|
||||
#[serde(default)]
|
||||
pub plugins: HashMap<String, PluginConfig>,
|
||||
pub plugins: PluginsConfigToml,
|
||||
|
||||
/// User-level marketplace entries keyed by marketplace name.
|
||||
#[serde(default)]
|
||||
|
||||
@@ -1034,11 +1034,49 @@ pub use crate::skills_config::BundledSkillsConfig;
|
||||
pub use crate::skills_config::SkillConfig;
|
||||
pub use crate::skills_config::SkillsConfig;
|
||||
|
||||
/// Default activation settings for installed plugins.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct PluginsDefaultConfig {
|
||||
#[serde(default = "default_enabled")]
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
/// Plugin activation and capability settings loaded from configuration layers.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct PluginsConfigToml {
|
||||
/// Defaults for installed plugin activation. Omission preserves source enablement.
|
||||
/// Explicit plugin enablement settings override this default, but cannot enable
|
||||
/// an installation disabled by its source.
|
||||
#[serde(default, rename = "_default", skip_serializing_if = "Option::is_none")]
|
||||
pub default: Option<PluginsDefaultConfig>,
|
||||
|
||||
#[serde(default, flatten)]
|
||||
pub plugins: HashMap<String, PluginConfig>,
|
||||
}
|
||||
|
||||
impl PluginsConfigToml {
|
||||
/// Whether configuration permits a source-enabled plugin to remain active.
|
||||
/// Without `_default`, preserve legacy source and account enablement.
|
||||
/// Otherwise, an explicit enablement setting overrides the configured default.
|
||||
pub fn allows_plugin(&self, plugin_id: &str) -> bool {
|
||||
let Some(default) = &self.default else {
|
||||
return true;
|
||||
};
|
||||
self.plugins
|
||||
.get(plugin_id)
|
||||
.and_then(|plugin| plugin.enabled)
|
||||
.unwrap_or(default.enabled)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct PluginConfig {
|
||||
#[serde(default = "default_enabled")]
|
||||
pub enabled: bool,
|
||||
/// Explicit activation override. Omission inherits the installed-plugin default.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub enabled: Option<bool>,
|
||||
|
||||
/// Per-MCP-server policy overlays for MCP servers contributed by this plugin.
|
||||
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
||||
|
||||
@@ -1,6 +1,60 @@
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn plugin_activation_inherits_defaults_without_explicit_enablement() {
|
||||
for default_enabled in [None, Some(false), Some(true)] {
|
||||
let default_config = default_enabled.map_or_else(String::new, |enabled| {
|
||||
format!("[_default]\nenabled = {enabled}\n")
|
||||
});
|
||||
let config: PluginsConfigToml = toml::from_str(&format!(
|
||||
r#"{default_config}
|
||||
["empty@market"]
|
||||
["mcp@market".mcp_servers.example]
|
||||
enabled = false
|
||||
["approval@market".mcp_servers.example]
|
||||
default_tools_approval_mode = "approve"
|
||||
["enabled@market"]
|
||||
enabled = true
|
||||
["disabled@market"]
|
||||
enabled = false
|
||||
"#
|
||||
))
|
||||
.expect("plugin activation config");
|
||||
assert_eq!(
|
||||
[
|
||||
"empty@market",
|
||||
"mcp@market",
|
||||
"approval@market",
|
||||
"enabled@market",
|
||||
"disabled@market"
|
||||
]
|
||||
.map(|plugin_id| config.plugins[plugin_id].enabled),
|
||||
[None, None, None, Some(true), Some(false)],
|
||||
);
|
||||
assert_eq!(
|
||||
[
|
||||
"missing@market",
|
||||
"empty@market",
|
||||
"mcp@market",
|
||||
"approval@market",
|
||||
"enabled@market",
|
||||
"disabled@market"
|
||||
]
|
||||
.map(|plugin_id| config.allows_plugin(plugin_id)),
|
||||
match default_enabled {
|
||||
None => [true, true, true, true, true, true],
|
||||
Some(false) => [false, false, false, false, true, false],
|
||||
Some(true) => [true, true, true, true, true, false],
|
||||
},
|
||||
);
|
||||
let round_trip: PluginsConfigToml =
|
||||
toml::from_str(&toml::to_string(&config).expect("serialize plugin config"))
|
||||
.expect("deserialize plugin config");
|
||||
assert_eq!(round_trip, config);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_scroll_speed_accepts_integer_and_fractional_multipliers() {
|
||||
for (value, expected) in [("1", 1.0), ("0.5", 0.5), ("3.0", 3.0)] {
|
||||
|
||||
Reference in New Issue
Block a user