mirror of
https://github.com/openai/codex.git
synced 2026-10-22 17:58:46 +00:00
Address plugin refactor review comments
This commit is contained in:
Generated
+3
@@ -1941,6 +1941,7 @@ dependencies = [
|
||||
"codex-arg0",
|
||||
"codex-config",
|
||||
"codex-core",
|
||||
"codex-core-plugins",
|
||||
"codex-exec-server",
|
||||
"codex-feedback",
|
||||
"codex-protocol",
|
||||
@@ -2100,6 +2101,7 @@ dependencies = [
|
||||
"codex-app-server-protocol",
|
||||
"codex-connectors",
|
||||
"codex-core",
|
||||
"codex-core-plugins",
|
||||
"codex-features",
|
||||
"codex-git-utils",
|
||||
"codex-login",
|
||||
@@ -2489,6 +2491,7 @@ dependencies = [
|
||||
"codex-arg0",
|
||||
"codex-config",
|
||||
"codex-core",
|
||||
"codex-core-plugins",
|
||||
"codex-exec-server",
|
||||
"codex-features",
|
||||
"codex-login",
|
||||
|
||||
@@ -17,6 +17,7 @@ codex-app-server-protocol = { workspace = true }
|
||||
codex-arg0 = { workspace = true }
|
||||
codex-config = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-core-plugins = { workspace = true }
|
||||
codex-exec-server = { workspace = true }
|
||||
codex-feedback = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
|
||||
@@ -100,7 +100,7 @@ pub mod legacy_core {
|
||||
}
|
||||
|
||||
pub mod plugins {
|
||||
pub use codex_core::plugins::PluginsManager;
|
||||
pub use codex_core_plugins::PluginsManager;
|
||||
}
|
||||
|
||||
pub mod review_format {
|
||||
|
||||
@@ -271,10 +271,6 @@ use codex_core::find_thread_name_by_id;
|
||||
use codex_core::find_thread_names_by_ids;
|
||||
use codex_core::find_thread_path_by_id_str;
|
||||
use codex_core::path_utils;
|
||||
use codex_core::plugins::PluginInstallError as CorePluginInstallError;
|
||||
use codex_core::plugins::PluginInstallRequest;
|
||||
use codex_core::plugins::PluginReadRequest;
|
||||
use codex_core::plugins::PluginUninstallError as CorePluginUninstallError;
|
||||
use codex_core::read_head_for_summary;
|
||||
use codex_core::read_session_meta_line;
|
||||
use codex_core::sandboxing::SandboxPermissions;
|
||||
@@ -282,6 +278,10 @@ use codex_core::windows_sandbox::WindowsSandboxLevelExt;
|
||||
use codex_core::windows_sandbox::WindowsSandboxSetupMode as CoreWindowsSandboxSetupMode;
|
||||
use codex_core::windows_sandbox::WindowsSandboxSetupRequest;
|
||||
use codex_core_plugins::OPENAI_CURATED_MARKETPLACE_NAME;
|
||||
use codex_core_plugins::PluginInstallError as CorePluginInstallError;
|
||||
use codex_core_plugins::PluginInstallRequest;
|
||||
use codex_core_plugins::PluginReadRequest;
|
||||
use codex_core_plugins::PluginUninstallError as CorePluginUninstallError;
|
||||
use codex_core_plugins::loader::load_plugin_apps;
|
||||
use codex_core_plugins::loader::load_plugin_mcp_servers;
|
||||
use codex_core_plugins::manifest::PluginManifestInterface;
|
||||
@@ -6306,9 +6306,7 @@ impl CodexMessageProcessor {
|
||||
plugins_manager
|
||||
.effective_skill_roots_for_layer_stack(
|
||||
&config_layer_stack,
|
||||
config.features.enabled(Feature::Plugins),
|
||||
config.features.enabled(Feature::RemotePlugin),
|
||||
config.features.enabled(Feature::PluginHooks),
|
||||
config.plugin_feature_flags(),
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
@@ -6391,13 +6389,10 @@ impl CodexMessageProcessor {
|
||||
config.features.enabled(Feature::Plugins) && workspace_codex_plugins_enabled;
|
||||
let plugin_outcome = if plugins_enabled && config.features.enabled(Feature::PluginHooks)
|
||||
{
|
||||
let mut plugin_feature_flags = config.plugin_feature_flags();
|
||||
plugin_feature_flags.plugins_enabled = plugins_enabled;
|
||||
plugins_manager
|
||||
.plugins_for_layer_stack(
|
||||
&config.config_layer_stack,
|
||||
plugins_enabled,
|
||||
config.features.enabled(Feature::RemotePlugin),
|
||||
/*plugin_hooks_feature_enabled*/ true,
|
||||
)
|
||||
.plugins_for_layer_stack(&config.config_layer_stack, plugin_feature_flags)
|
||||
.await
|
||||
} else {
|
||||
codex_core::plugins::PluginLoadOutcome::default()
|
||||
|
||||
@@ -2,8 +2,8 @@ use codex_config::types::PluginConfig;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use codex_core::plugins::PluginId;
|
||||
use codex_core::plugins::PluginInstallRequest;
|
||||
use codex_core::plugins::PluginsManager;
|
||||
use codex_core_plugins::PluginInstallRequest;
|
||||
use codex_core_plugins::PluginsManager;
|
||||
use codex_core_plugins::marketplace::MarketplacePluginInstallPolicy;
|
||||
use codex_core_plugins::marketplace::find_marketplace_manifest_path;
|
||||
use codex_core_plugins::marketplace_add::MarketplaceAddRequest;
|
||||
|
||||
@@ -13,6 +13,7 @@ clap = { workspace = true, features = ["derive"] }
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-connectors = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-core-plugins = { workspace = true }
|
||||
codex-features = { workspace = true }
|
||||
codex-git-utils = { workspace = true }
|
||||
codex-login = { workspace = true }
|
||||
|
||||
@@ -17,8 +17,7 @@ pub use codex_core::connectors::list_accessible_connectors_from_mcp_tools_with_o
|
||||
pub use codex_core::connectors::list_cached_accessible_connectors_from_mcp_tools;
|
||||
pub use codex_core::connectors::with_app_enabled_state;
|
||||
use codex_core::plugins::AppConnectorId;
|
||||
use codex_core::plugins::PluginsManager;
|
||||
use codex_features::Feature;
|
||||
use codex_core_plugins::PluginsManager;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_login::default_client::originator;
|
||||
@@ -138,12 +137,7 @@ fn all_connectors_cache_key(config: &Config, auth: &CodexAuth) -> AllConnectorsC
|
||||
|
||||
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.config_layer_stack,
|
||||
config.features.enabled(Feature::Plugins),
|
||||
config.features.enabled(Feature::RemotePlugin),
|
||||
config.features.enabled(Feature::PluginHooks),
|
||||
)
|
||||
.plugins_for_config(&config.config_layer_stack, config.plugin_feature_flags())
|
||||
.await
|
||||
.effective_apps()
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@ use anyhow::bail;
|
||||
use clap::Parser;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::find_codex_home;
|
||||
use codex_core::plugins::PluginMarketplaceUpgradeOutcome;
|
||||
use codex_core::plugins::PluginsManager;
|
||||
use codex_core_plugins::PluginsManager;
|
||||
use codex_core_plugins::marketplace_add::MarketplaceAddRequest;
|
||||
use codex_core_plugins::marketplace_add::add_marketplace;
|
||||
use codex_core_plugins::marketplace_remove::MarketplaceRemoveRequest;
|
||||
use codex_core_plugins::marketplace_remove::remove_marketplace;
|
||||
use codex_core_plugins::marketplace_upgrade::ConfiguredMarketplaceUpgradeOutcome as PluginMarketplaceUpgradeOutcome;
|
||||
use codex_utils_cli::CliConfigOverrides;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
|
||||
@@ -14,7 +14,7 @@ use codex_core::config::Config;
|
||||
use codex_core::config::edit::ConfigEditsBuilder;
|
||||
use codex_core::config::find_codex_home;
|
||||
use codex_core::config::load_global_mcp_servers;
|
||||
use codex_core::plugins::PluginsManager;
|
||||
use codex_core_plugins::PluginsManager;
|
||||
use codex_mcp::McpOAuthLoginSupport;
|
||||
use codex_mcp::ResolvedMcpOAuthScopes;
|
||||
use codex_mcp::compute_auth_statuses;
|
||||
|
||||
@@ -14,6 +14,7 @@ mod mcp_types;
|
||||
mod merge;
|
||||
mod overrides;
|
||||
pub mod permissions_toml;
|
||||
mod plugin_edit;
|
||||
pub mod profile_toml;
|
||||
mod project_root_markers;
|
||||
mod requirements_exec_policy;
|
||||
|
||||
@@ -7,7 +7,6 @@ use std::path::PathBuf;
|
||||
use tokio::task;
|
||||
use toml::Value as TomlValue;
|
||||
use toml_edit::DocumentMut;
|
||||
use toml_edit::InlineTable;
|
||||
use toml_edit::Item as TomlItem;
|
||||
use toml_edit::Table as TomlTable;
|
||||
use toml_edit::value;
|
||||
@@ -20,6 +19,8 @@ use crate::CONFIG_TOML_FILE;
|
||||
use crate::McpServerConfig;
|
||||
use crate::McpServerEnvVar;
|
||||
use crate::McpServerTransportConfig;
|
||||
use crate::plugin_edit::PluginConfigEdit;
|
||||
use crate::plugin_edit::apply_plugin_config_edit;
|
||||
|
||||
pub async fn load_global_mcp_servers(
|
||||
codex_home: &Path,
|
||||
@@ -69,11 +70,6 @@ pub struct ConfigEditsBuilder {
|
||||
plugin_edits: Vec<PluginConfigEdit>,
|
||||
}
|
||||
|
||||
enum PluginConfigEdit {
|
||||
SetEnabled { plugin_id: String, enabled: bool },
|
||||
Clear { plugin_id: String },
|
||||
}
|
||||
|
||||
impl ConfigEditsBuilder {
|
||||
pub fn new(codex_home: &Path) -> Self {
|
||||
Self {
|
||||
@@ -89,17 +85,13 @@ impl ConfigEditsBuilder {
|
||||
}
|
||||
|
||||
pub fn set_plugin_enabled(mut self, plugin_id: &str, enabled: bool) -> Self {
|
||||
self.plugin_edits.push(PluginConfigEdit::SetEnabled {
|
||||
plugin_id: plugin_id.to_string(),
|
||||
enabled,
|
||||
});
|
||||
self.plugin_edits
|
||||
.push(PluginConfigEdit::set_enabled(plugin_id, enabled));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn clear_plugin(mut self, plugin_id: &str) -> Self {
|
||||
self.plugin_edits.push(PluginConfigEdit::Clear {
|
||||
plugin_id: plugin_id.to_string(),
|
||||
});
|
||||
self.plugin_edits.push(PluginConfigEdit::clear(plugin_id));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -158,97 +150,6 @@ fn replace_mcp_servers(doc: &mut DocumentMut, servers: &BTreeMap<String, McpServ
|
||||
root.insert("mcp_servers", TomlItem::Table(table));
|
||||
}
|
||||
|
||||
fn apply_plugin_config_edit(doc: &mut DocumentMut, edit: &PluginConfigEdit) -> bool {
|
||||
match edit {
|
||||
PluginConfigEdit::SetEnabled { plugin_id, enabled } => {
|
||||
set_plugin_enabled(doc, plugin_id, *enabled)
|
||||
}
|
||||
PluginConfigEdit::Clear { plugin_id } => clear_plugin(doc, plugin_id),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_plugin_enabled(doc: &mut DocumentMut, plugin_id: &str, enabled: bool) -> bool {
|
||||
let root = doc.as_table_mut();
|
||||
let plugins = ensure_table(root, "plugins", /*implicit*/ true);
|
||||
let plugin = ensure_table(plugins, plugin_id, /*implicit*/ false);
|
||||
let mut replacement = value(enabled);
|
||||
if let Some(existing) = plugin.get("enabled") {
|
||||
preserve_decor(existing, &mut replacement);
|
||||
}
|
||||
plugin["enabled"] = replacement;
|
||||
true
|
||||
}
|
||||
|
||||
fn clear_plugin(doc: &mut DocumentMut, plugin_id: &str) -> bool {
|
||||
let root = doc.as_table_mut();
|
||||
let Some(item) = root.get_mut("plugins") else {
|
||||
return false;
|
||||
};
|
||||
match item {
|
||||
TomlItem::Table(plugins) => plugins.remove(plugin_id).is_some(),
|
||||
TomlItem::Value(value) => {
|
||||
let Some(inline) = value.as_inline_table().cloned() else {
|
||||
return false;
|
||||
};
|
||||
*item = TomlItem::Table(table_from_inline(&inline, /*implicit*/ true));
|
||||
if let TomlItem::Table(plugins) = item {
|
||||
return plugins.remove(plugin_id).is_some();
|
||||
}
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn preserve_decor(existing: &TomlItem, replacement: &mut TomlItem) {
|
||||
if let (TomlItem::Value(existing_value), TomlItem::Value(replacement_value)) =
|
||||
(existing, replacement)
|
||||
{
|
||||
replacement_value
|
||||
.decor_mut()
|
||||
.clone_from(existing_value.decor());
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_table<'a>(parent: &'a mut TomlTable, key: &str, implicit: bool) -> &'a mut TomlTable {
|
||||
match parent.get_mut(key) {
|
||||
Some(TomlItem::Table(_)) => {}
|
||||
Some(item @ TomlItem::Value(_)) => {
|
||||
if let Some(inline) = item.as_value().and_then(toml_edit::Value::as_inline_table) {
|
||||
*item = TomlItem::Table(table_from_inline(inline, implicit));
|
||||
} else {
|
||||
*item = TomlItem::Table(new_table(implicit));
|
||||
}
|
||||
}
|
||||
Some(item) => {
|
||||
*item = TomlItem::Table(new_table(implicit));
|
||||
}
|
||||
None => {
|
||||
parent.insert(key, TomlItem::Table(new_table(implicit)));
|
||||
}
|
||||
}
|
||||
let Some(TomlItem::Table(table)) = parent.get_mut(key) else {
|
||||
unreachable!("inserted value should be a table");
|
||||
};
|
||||
table
|
||||
}
|
||||
|
||||
fn new_table(implicit: bool) -> TomlTable {
|
||||
let mut table = TomlTable::new();
|
||||
table.set_implicit(implicit);
|
||||
table
|
||||
}
|
||||
|
||||
fn table_from_inline(inline: &InlineTable, implicit: bool) -> TomlTable {
|
||||
let mut table = new_table(implicit);
|
||||
for (key, value) in inline.iter() {
|
||||
let mut value = value.clone();
|
||||
value.decor_mut().set_suffix("");
|
||||
table.insert(key, TomlItem::Value(value));
|
||||
}
|
||||
table
|
||||
}
|
||||
|
||||
fn serialize_mcp_server(config: &McpServerConfig) -> TomlItem {
|
||||
let mut entry = TomlTable::new();
|
||||
entry.set_implicit(false);
|
||||
|
||||
@@ -2,8 +2,6 @@ use super::*;
|
||||
use crate::McpServerToolConfig;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::HashMap;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::symlink;
|
||||
use std::time::SystemTime;
|
||||
use std::time::UNIX_EPOCH;
|
||||
|
||||
@@ -84,166 +82,3 @@ approval_mode = "approve"
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tokio::test]
|
||||
async fn plugin_edits_write_through_symlink_chain() -> anyhow::Result<()> {
|
||||
let unique_suffix = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos();
|
||||
let codex_home = std::env::temp_dir().join(format!(
|
||||
"codex-config-plugin-edit-symlink-test-{}-{unique_suffix}",
|
||||
std::process::id()
|
||||
));
|
||||
let target_home = std::env::temp_dir().join(format!(
|
||||
"codex-config-plugin-edit-symlink-target-{}-{unique_suffix}",
|
||||
std::process::id()
|
||||
));
|
||||
std::fs::create_dir_all(&codex_home)?;
|
||||
std::fs::create_dir_all(&target_home)?;
|
||||
let target_path = target_home.join(CONFIG_TOML_FILE);
|
||||
let link_path = codex_home.join("config-link.toml");
|
||||
let config_path = codex_home.join(CONFIG_TOML_FILE);
|
||||
std::fs::write(
|
||||
&target_path,
|
||||
r#"[plugins."linear@openai-curated"]
|
||||
enabled = false
|
||||
"#,
|
||||
)?;
|
||||
symlink(&target_path, &link_path)?;
|
||||
symlink("config-link.toml", &config_path)?;
|
||||
|
||||
ConfigEditsBuilder::new(&codex_home)
|
||||
.set_plugin_enabled("linear@openai-curated", /*enabled*/ true)
|
||||
.apply()
|
||||
.await?;
|
||||
|
||||
let meta = std::fs::symlink_metadata(&config_path)?;
|
||||
assert!(meta.file_type().is_symlink());
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(target_path)?,
|
||||
r#"[plugins."linear@openai-curated"]
|
||||
enabled = true
|
||||
"#
|
||||
);
|
||||
|
||||
std::fs::remove_dir_all(&codex_home)?;
|
||||
std::fs::remove_dir_all(&target_home)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_edits_set_and_clear_enabled_entries() -> anyhow::Result<()> {
|
||||
let unique_suffix = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos();
|
||||
let codex_home = std::env::temp_dir().join(format!(
|
||||
"codex-config-plugin-edit-test-{}-{unique_suffix}",
|
||||
std::process::id()
|
||||
));
|
||||
std::fs::create_dir_all(&codex_home)?;
|
||||
std::fs::write(
|
||||
codex_home.join(CONFIG_TOML_FILE),
|
||||
r#"[plugins."linear@openai-curated"]
|
||||
enabled = false
|
||||
|
||||
[plugins."gmail@openai-curated"]
|
||||
enabled = true
|
||||
"#,
|
||||
)?;
|
||||
|
||||
ConfigEditsBuilder::new(&codex_home)
|
||||
.set_plugin_enabled("linear@openai-curated", /*enabled*/ true)
|
||||
.clear_plugin("gmail@openai-curated")
|
||||
.apply()
|
||||
.await?;
|
||||
|
||||
let serialized = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE))?;
|
||||
assert_eq!(
|
||||
serialized,
|
||||
r#"[plugins."linear@openai-curated"]
|
||||
enabled = true
|
||||
"#
|
||||
);
|
||||
|
||||
std::fs::remove_dir_all(&codex_home)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_clear_leaves_malformed_plugins_scalar_unchanged() -> anyhow::Result<()> {
|
||||
let unique_suffix = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos();
|
||||
let codex_home = std::env::temp_dir().join(format!(
|
||||
"codex-config-plugin-clear-malformed-test-{}-{unique_suffix}",
|
||||
std::process::id()
|
||||
));
|
||||
std::fs::create_dir_all(&codex_home)?;
|
||||
let config_path = codex_home.join(CONFIG_TOML_FILE);
|
||||
let config = r#"plugins = "bad"
|
||||
"#;
|
||||
std::fs::write(&config_path, config)?;
|
||||
|
||||
ConfigEditsBuilder::new(&codex_home)
|
||||
.clear_plugin("linear@openai-curated")
|
||||
.apply()
|
||||
.await?;
|
||||
|
||||
assert_eq!(std::fs::read_to_string(&config_path)?, config);
|
||||
|
||||
std::fs::remove_dir_all(&codex_home)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_clear_missing_entry_does_not_create_config() -> anyhow::Result<()> {
|
||||
let unique_suffix = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos();
|
||||
let codex_home = std::env::temp_dir().join(format!(
|
||||
"codex-config-plugin-clear-missing-test-{}-{unique_suffix}",
|
||||
std::process::id()
|
||||
));
|
||||
std::fs::create_dir_all(&codex_home)?;
|
||||
let config_path = codex_home.join(CONFIG_TOML_FILE);
|
||||
|
||||
ConfigEditsBuilder::new(&codex_home)
|
||||
.clear_plugin("linear@openai-curated")
|
||||
.apply()
|
||||
.await?;
|
||||
|
||||
assert!(!config_path.exists());
|
||||
|
||||
std::fs::remove_dir_all(&codex_home)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_enabled_update_preserves_existing_value_decor() -> anyhow::Result<()> {
|
||||
let unique_suffix = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos();
|
||||
let codex_home = std::env::temp_dir().join(format!(
|
||||
"codex-config-plugin-edit-decor-test-{}-{unique_suffix}",
|
||||
std::process::id()
|
||||
));
|
||||
std::fs::create_dir_all(&codex_home)?;
|
||||
let config_path = codex_home.join(CONFIG_TOML_FILE);
|
||||
std::fs::write(
|
||||
&config_path,
|
||||
r#"[plugins."linear@openai-curated"]
|
||||
enabled = false # keep
|
||||
"#,
|
||||
)?;
|
||||
|
||||
ConfigEditsBuilder::new(&codex_home)
|
||||
.set_plugin_enabled("linear@openai-curated", /*enabled*/ true)
|
||||
.apply()
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(&config_path)?,
|
||||
r#"[plugins."linear@openai-curated"]
|
||||
enabled = true # keep
|
||||
"#
|
||||
);
|
||||
|
||||
std::fs::remove_dir_all(&codex_home)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
use toml_edit::DocumentMut;
|
||||
use toml_edit::InlineTable;
|
||||
use toml_edit::Item as TomlItem;
|
||||
use toml_edit::Table as TomlTable;
|
||||
use toml_edit::value;
|
||||
|
||||
pub(crate) enum PluginConfigEdit {
|
||||
SetEnabled { plugin_id: String, enabled: bool },
|
||||
Clear { plugin_id: String },
|
||||
}
|
||||
|
||||
impl PluginConfigEdit {
|
||||
pub(crate) fn set_enabled(plugin_id: &str, enabled: bool) -> Self {
|
||||
Self::SetEnabled {
|
||||
plugin_id: plugin_id.to_string(),
|
||||
enabled,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn clear(plugin_id: &str) -> Self {
|
||||
Self::Clear {
|
||||
plugin_id: plugin_id.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn apply_plugin_config_edit(doc: &mut DocumentMut, edit: &PluginConfigEdit) -> bool {
|
||||
match edit {
|
||||
PluginConfigEdit::SetEnabled { plugin_id, enabled } => {
|
||||
set_plugin_enabled(doc, plugin_id, *enabled)
|
||||
}
|
||||
PluginConfigEdit::Clear { plugin_id } => clear_plugin(doc, plugin_id),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_plugin_enabled(doc: &mut DocumentMut, plugin_id: &str, enabled: bool) -> bool {
|
||||
let root = doc.as_table_mut();
|
||||
let plugins = ensure_table(root, "plugins", /*implicit*/ true);
|
||||
let plugin = ensure_table(plugins, plugin_id, /*implicit*/ false);
|
||||
let mut replacement = value(enabled);
|
||||
if let Some(existing) = plugin.get("enabled") {
|
||||
preserve_decor(existing, &mut replacement);
|
||||
}
|
||||
plugin["enabled"] = replacement;
|
||||
true
|
||||
}
|
||||
|
||||
fn clear_plugin(doc: &mut DocumentMut, plugin_id: &str) -> bool {
|
||||
let root = doc.as_table_mut();
|
||||
let Some(item) = root.get_mut("plugins") else {
|
||||
return false;
|
||||
};
|
||||
match item {
|
||||
TomlItem::Table(plugins) => plugins.remove(plugin_id).is_some(),
|
||||
TomlItem::Value(value) => {
|
||||
let Some(inline) = value.as_inline_table().cloned() else {
|
||||
return false;
|
||||
};
|
||||
*item = TomlItem::Table(table_from_inline(&inline, /*implicit*/ true));
|
||||
if let TomlItem::Table(plugins) = item {
|
||||
return plugins.remove(plugin_id).is_some();
|
||||
}
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn preserve_decor(existing: &TomlItem, replacement: &mut TomlItem) {
|
||||
if let (TomlItem::Value(existing_value), TomlItem::Value(replacement_value)) =
|
||||
(existing, replacement)
|
||||
{
|
||||
replacement_value
|
||||
.decor_mut()
|
||||
.clone_from(existing_value.decor());
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_table<'a>(parent: &'a mut TomlTable, key: &str, implicit: bool) -> &'a mut TomlTable {
|
||||
match parent.get_mut(key) {
|
||||
Some(TomlItem::Table(_)) => {}
|
||||
Some(item @ TomlItem::Value(_)) => {
|
||||
if let Some(inline) = item.as_value().and_then(toml_edit::Value::as_inline_table) {
|
||||
*item = TomlItem::Table(table_from_inline(inline, implicit));
|
||||
} else {
|
||||
*item = TomlItem::Table(new_table(implicit));
|
||||
}
|
||||
}
|
||||
Some(item) => {
|
||||
*item = TomlItem::Table(new_table(implicit));
|
||||
}
|
||||
None => {
|
||||
parent.insert(key, TomlItem::Table(new_table(implicit)));
|
||||
}
|
||||
}
|
||||
let Some(TomlItem::Table(table)) = parent.get_mut(key) else {
|
||||
unreachable!("inserted value should be a table");
|
||||
};
|
||||
table
|
||||
}
|
||||
|
||||
fn new_table(implicit: bool) -> TomlTable {
|
||||
let mut table = TomlTable::new();
|
||||
table.set_implicit(implicit);
|
||||
table
|
||||
}
|
||||
|
||||
fn table_from_inline(inline: &InlineTable, implicit: bool) -> TomlTable {
|
||||
let mut table = new_table(implicit);
|
||||
for (key, value) in inline.iter() {
|
||||
let mut value = value.clone();
|
||||
value.decor_mut().set_suffix("");
|
||||
table.insert(key, TomlItem::Value(value));
|
||||
}
|
||||
table
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "plugin_edit_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,170 @@
|
||||
use crate::CONFIG_TOML_FILE;
|
||||
use crate::ConfigEditsBuilder;
|
||||
use pretty_assertions::assert_eq;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::symlink;
|
||||
use std::time::SystemTime;
|
||||
use std::time::UNIX_EPOCH;
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tokio::test]
|
||||
async fn plugin_edits_write_through_symlink_chain() -> anyhow::Result<()> {
|
||||
let unique_suffix = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos();
|
||||
let codex_home = std::env::temp_dir().join(format!(
|
||||
"codex-config-plugin-edit-symlink-test-{}-{unique_suffix}",
|
||||
std::process::id()
|
||||
));
|
||||
let target_home = std::env::temp_dir().join(format!(
|
||||
"codex-config-plugin-edit-symlink-target-{}-{unique_suffix}",
|
||||
std::process::id()
|
||||
));
|
||||
std::fs::create_dir_all(&codex_home)?;
|
||||
std::fs::create_dir_all(&target_home)?;
|
||||
let target_path = target_home.join(CONFIG_TOML_FILE);
|
||||
let link_path = codex_home.join("config-link.toml");
|
||||
let config_path = codex_home.join(CONFIG_TOML_FILE);
|
||||
std::fs::write(
|
||||
&target_path,
|
||||
r#"[plugins."linear@openai-curated"]
|
||||
enabled = false
|
||||
"#,
|
||||
)?;
|
||||
symlink(&target_path, &link_path)?;
|
||||
symlink("config-link.toml", &config_path)?;
|
||||
|
||||
ConfigEditsBuilder::new(&codex_home)
|
||||
.set_plugin_enabled("linear@openai-curated", /*enabled*/ true)
|
||||
.apply()
|
||||
.await?;
|
||||
|
||||
let meta = std::fs::symlink_metadata(&config_path)?;
|
||||
assert!(meta.file_type().is_symlink());
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(target_path)?,
|
||||
r#"[plugins."linear@openai-curated"]
|
||||
enabled = true
|
||||
"#
|
||||
);
|
||||
|
||||
std::fs::remove_dir_all(&codex_home)?;
|
||||
std::fs::remove_dir_all(&target_home)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_edits_set_and_clear_enabled_entries() -> anyhow::Result<()> {
|
||||
let unique_suffix = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos();
|
||||
let codex_home = std::env::temp_dir().join(format!(
|
||||
"codex-config-plugin-edit-test-{}-{unique_suffix}",
|
||||
std::process::id()
|
||||
));
|
||||
std::fs::create_dir_all(&codex_home)?;
|
||||
std::fs::write(
|
||||
codex_home.join(CONFIG_TOML_FILE),
|
||||
r#"[plugins."linear@openai-curated"]
|
||||
enabled = false
|
||||
|
||||
[plugins."gmail@openai-curated"]
|
||||
enabled = true
|
||||
"#,
|
||||
)?;
|
||||
|
||||
ConfigEditsBuilder::new(&codex_home)
|
||||
.set_plugin_enabled("linear@openai-curated", /*enabled*/ true)
|
||||
.clear_plugin("gmail@openai-curated")
|
||||
.apply()
|
||||
.await?;
|
||||
|
||||
let serialized = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE))?;
|
||||
assert_eq!(
|
||||
serialized,
|
||||
r#"[plugins."linear@openai-curated"]
|
||||
enabled = true
|
||||
"#
|
||||
);
|
||||
|
||||
std::fs::remove_dir_all(&codex_home)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_clear_leaves_malformed_plugins_scalar_unchanged() -> anyhow::Result<()> {
|
||||
let unique_suffix = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos();
|
||||
let codex_home = std::env::temp_dir().join(format!(
|
||||
"codex-config-plugin-clear-malformed-test-{}-{unique_suffix}",
|
||||
std::process::id()
|
||||
));
|
||||
std::fs::create_dir_all(&codex_home)?;
|
||||
let config_path = codex_home.join(CONFIG_TOML_FILE);
|
||||
let config = r#"plugins = "bad"
|
||||
"#;
|
||||
std::fs::write(&config_path, config)?;
|
||||
|
||||
ConfigEditsBuilder::new(&codex_home)
|
||||
.clear_plugin("linear@openai-curated")
|
||||
.apply()
|
||||
.await?;
|
||||
|
||||
assert_eq!(std::fs::read_to_string(&config_path)?, config);
|
||||
|
||||
std::fs::remove_dir_all(&codex_home)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_clear_missing_entry_does_not_create_config() -> anyhow::Result<()> {
|
||||
let unique_suffix = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos();
|
||||
let codex_home = std::env::temp_dir().join(format!(
|
||||
"codex-config-plugin-clear-missing-test-{}-{unique_suffix}",
|
||||
std::process::id()
|
||||
));
|
||||
std::fs::create_dir_all(&codex_home)?;
|
||||
let config_path = codex_home.join(CONFIG_TOML_FILE);
|
||||
|
||||
ConfigEditsBuilder::new(&codex_home)
|
||||
.clear_plugin("linear@openai-curated")
|
||||
.apply()
|
||||
.await?;
|
||||
|
||||
assert!(!config_path.exists());
|
||||
|
||||
std::fs::remove_dir_all(&codex_home)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_enabled_update_preserves_existing_value_decor() -> anyhow::Result<()> {
|
||||
let unique_suffix = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos();
|
||||
let codex_home = std::env::temp_dir().join(format!(
|
||||
"codex-config-plugin-edit-decor-test-{}-{unique_suffix}",
|
||||
std::process::id()
|
||||
));
|
||||
std::fs::create_dir_all(&codex_home)?;
|
||||
let config_path = codex_home.join(CONFIG_TOML_FILE);
|
||||
std::fs::write(
|
||||
&config_path,
|
||||
r#"[plugins."linear@openai-curated"]
|
||||
enabled = false # keep
|
||||
"#,
|
||||
)?;
|
||||
|
||||
ConfigEditsBuilder::new(&codex_home)
|
||||
.set_plugin_enabled("linear@openai-curated", /*enabled*/ true)
|
||||
.apply()
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(&config_path)?,
|
||||
r#"[plugins."linear@openai-curated"]
|
||||
enabled = true # keep
|
||||
"#
|
||||
);
|
||||
|
||||
std::fs::remove_dir_all(&codex_home)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -17,6 +17,7 @@ codex-arg0 = { workspace = true }
|
||||
codex-analytics = { workspace = true }
|
||||
codex-config = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-core-plugins = { workspace = true }
|
||||
codex-exec-server = { workspace = true }
|
||||
codex-features = { workspace = true }
|
||||
codex-login = { workspace = true }
|
||||
|
||||
@@ -35,9 +35,9 @@ pub use codex_core::config::Permissions;
|
||||
pub use codex_core::config::TerminalResizeReflowConfig;
|
||||
pub use codex_core::config::ThreadStoreConfig;
|
||||
pub use codex_core::config::find_codex_home;
|
||||
pub use codex_core::plugins::PluginsManager;
|
||||
pub use codex_core::skills::SkillsManager;
|
||||
pub use codex_core::thread_store_from_config;
|
||||
pub use codex_core_plugins::PluginsManager;
|
||||
pub use codex_exec_server::EnvironmentManager;
|
||||
pub use codex_exec_server::EnvironmentManagerArgs;
|
||||
pub use codex_exec_server::ExecServerRuntimePaths;
|
||||
|
||||
@@ -45,6 +45,7 @@ pub use manager::ConfiguredMarketplaceListOutcome;
|
||||
pub use manager::ConfiguredMarketplacePlugin;
|
||||
pub use manager::PluginDetail;
|
||||
pub use manager::PluginDetailsUnavailableReason;
|
||||
pub use manager::PluginFeatureFlags;
|
||||
pub use manager::PluginInstallError;
|
||||
pub use manager::PluginInstallOutcome;
|
||||
pub use manager::PluginInstallRequest;
|
||||
|
||||
@@ -77,6 +77,16 @@ static CURATED_REPO_SYNC_STARTED: AtomicBool = AtomicBool::new(false);
|
||||
const FEATURED_PLUGIN_IDS_CACHE_TTL: std::time::Duration =
|
||||
std::time::Duration::from_secs(60 * 60 * 3);
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct PluginFeatureFlags {
|
||||
/// Whether plugin loading is enabled at all.
|
||||
pub plugins_enabled: bool,
|
||||
/// Whether remotely installed plugins should be included in local plugin loading.
|
||||
pub remote_plugins_enabled: bool,
|
||||
/// Whether plugin-provided hooks should be loaded.
|
||||
pub plugin_hooks_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
struct FeaturedPluginIdsCacheKey {
|
||||
chatgpt_base_url: String,
|
||||
@@ -370,8 +380,7 @@ pub struct PluginsManager {
|
||||
#[derive(Clone)]
|
||||
struct CachedPluginLoadOutcome {
|
||||
config_version: String,
|
||||
remote_plugins_enabled: bool,
|
||||
plugin_hooks_enabled: bool,
|
||||
feature_flags: PluginFeatureFlags,
|
||||
outcome: PluginLoadOutcome,
|
||||
}
|
||||
|
||||
@@ -431,15 +440,11 @@ impl PluginsManager {
|
||||
pub async fn plugins_for_config(
|
||||
&self,
|
||||
config_layer_stack: &ConfigLayerStack,
|
||||
plugins_enabled: bool,
|
||||
remote_plugins_enabled: bool,
|
||||
plugin_hooks_enabled: bool,
|
||||
feature_flags: PluginFeatureFlags,
|
||||
) -> PluginLoadOutcome {
|
||||
self.plugins_for_config_with_force_reload(
|
||||
config_layer_stack,
|
||||
plugins_enabled,
|
||||
remote_plugins_enabled,
|
||||
plugin_hooks_enabled,
|
||||
feature_flags,
|
||||
/*force_reload*/ false,
|
||||
)
|
||||
.await
|
||||
@@ -448,32 +453,26 @@ impl PluginsManager {
|
||||
pub(crate) async fn plugins_for_config_with_force_reload(
|
||||
&self,
|
||||
config_layer_stack: &ConfigLayerStack,
|
||||
plugins_enabled: bool,
|
||||
remote_plugins_enabled: bool,
|
||||
plugin_hooks_enabled: bool,
|
||||
feature_flags: PluginFeatureFlags,
|
||||
force_reload: bool,
|
||||
) -> PluginLoadOutcome {
|
||||
if !plugins_enabled {
|
||||
if !feature_flags.plugins_enabled {
|
||||
return PluginLoadOutcome::default();
|
||||
}
|
||||
|
||||
let config_version = version_for_toml(&config_layer_stack.effective_config());
|
||||
if !force_reload
|
||||
&& let Some(outcome) = self.cached_enabled_outcome(
|
||||
&config_version,
|
||||
remote_plugins_enabled,
|
||||
plugin_hooks_enabled,
|
||||
)
|
||||
&& let Some(outcome) = self.cached_enabled_outcome(&config_version, feature_flags)
|
||||
{
|
||||
return outcome;
|
||||
}
|
||||
|
||||
let outcome = load_plugins_from_layer_stack(
|
||||
config_layer_stack,
|
||||
self.remote_installed_plugin_configs(remote_plugins_enabled),
|
||||
self.remote_installed_plugin_configs(feature_flags.remote_plugins_enabled),
|
||||
&self.store,
|
||||
self.restriction_product,
|
||||
plugin_hooks_enabled,
|
||||
feature_flags.plugin_hooks_enabled,
|
||||
)
|
||||
.await;
|
||||
log_plugin_load_errors(&outcome);
|
||||
@@ -483,8 +482,7 @@ impl PluginsManager {
|
||||
};
|
||||
*cache = Some(CachedPluginLoadOutcome {
|
||||
config_version,
|
||||
remote_plugins_enabled,
|
||||
plugin_hooks_enabled,
|
||||
feature_flags,
|
||||
outcome: outcome.clone(),
|
||||
});
|
||||
outcome
|
||||
@@ -511,19 +509,17 @@ impl PluginsManager {
|
||||
pub async fn plugins_for_layer_stack(
|
||||
&self,
|
||||
config_layer_stack: &ConfigLayerStack,
|
||||
plugins_enabled: bool,
|
||||
remote_plugins_enabled: bool,
|
||||
plugin_hooks_feature_enabled: bool,
|
||||
feature_flags: PluginFeatureFlags,
|
||||
) -> PluginLoadOutcome {
|
||||
if !plugins_enabled {
|
||||
if !feature_flags.plugins_enabled {
|
||||
return PluginLoadOutcome::default();
|
||||
}
|
||||
load_plugins_from_layer_stack(
|
||||
config_layer_stack,
|
||||
self.remote_installed_plugin_configs(remote_plugins_enabled),
|
||||
self.remote_installed_plugin_configs(feature_flags.remote_plugins_enabled),
|
||||
&self.store,
|
||||
self.restriction_product,
|
||||
plugin_hooks_feature_enabled,
|
||||
feature_flags.plugin_hooks_enabled,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -532,42 +528,30 @@ impl PluginsManager {
|
||||
pub async fn effective_skill_roots_for_layer_stack(
|
||||
&self,
|
||||
config_layer_stack: &ConfigLayerStack,
|
||||
plugins_enabled: bool,
|
||||
remote_plugins_enabled: bool,
|
||||
plugin_hooks_enabled: bool,
|
||||
feature_flags: PluginFeatureFlags,
|
||||
) -> Vec<AbsolutePathBuf> {
|
||||
self.plugins_for_layer_stack(
|
||||
config_layer_stack,
|
||||
plugins_enabled,
|
||||
remote_plugins_enabled,
|
||||
plugin_hooks_enabled,
|
||||
)
|
||||
.await
|
||||
.effective_skill_roots()
|
||||
self.plugins_for_layer_stack(config_layer_stack, feature_flags)
|
||||
.await
|
||||
.effective_skill_roots()
|
||||
}
|
||||
|
||||
fn cached_enabled_outcome(
|
||||
&self,
|
||||
config_version: &str,
|
||||
remote_plugins_enabled: bool,
|
||||
plugin_hooks_enabled: bool,
|
||||
feature_flags: PluginFeatureFlags,
|
||||
) -> Option<PluginLoadOutcome> {
|
||||
match self.cached_enabled_outcome.read() {
|
||||
Ok(cache) => cache
|
||||
.as_ref()
|
||||
.filter(|cached| {
|
||||
cached.config_version == config_version
|
||||
&& cached.remote_plugins_enabled == remote_plugins_enabled
|
||||
&& cached.plugin_hooks_enabled == plugin_hooks_enabled
|
||||
cached.config_version == config_version && cached.feature_flags == feature_flags
|
||||
})
|
||||
.map(|cached| cached.outcome.clone()),
|
||||
Err(err) => err
|
||||
.into_inner()
|
||||
.as_ref()
|
||||
.filter(|cached| {
|
||||
cached.config_version == config_version
|
||||
&& cached.remote_plugins_enabled == remote_plugins_enabled
|
||||
&& cached.plugin_hooks_enabled == plugin_hooks_enabled
|
||||
cached.config_version == config_version && cached.feature_flags == feature_flags
|
||||
})
|
||||
.map(|cached| cached.outcome.clone()),
|
||||
}
|
||||
|
||||
@@ -264,6 +264,14 @@ impl TestPluginConfig {
|
||||
chatgpt_base_url: chatgpt_base_url(&effective_config),
|
||||
}
|
||||
}
|
||||
|
||||
fn plugin_feature_flags(&self) -> PluginFeatureFlags {
|
||||
PluginFeatureFlags {
|
||||
plugins_enabled: self.plugins_enabled,
|
||||
remote_plugins_enabled: self.remote_plugins_enabled,
|
||||
plugin_hooks_enabled: self.plugin_hooks_enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn feature_enabled(config: &TomlValue, feature_name: &str) -> bool {
|
||||
@@ -327,9 +335,7 @@ impl PluginsManagerTestConfigExt for PluginsManager {
|
||||
PluginsManager::plugins_for_config(
|
||||
self,
|
||||
&config.config_layer_stack,
|
||||
config.plugins_enabled,
|
||||
config.remote_plugins_enabled,
|
||||
config.plugin_hooks_enabled,
|
||||
config.plugin_feature_flags(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -620,14 +626,11 @@ remote_plugin = true
|
||||
let enabled_outcome = manager.plugins_for_test_config(&config).await;
|
||||
assert_eq!(enabled_outcome.plugins().len(), 1);
|
||||
|
||||
let disabled_outcome = PluginsManager::plugins_for_config(
|
||||
&manager,
|
||||
&config.config_layer_stack,
|
||||
config.plugins_enabled,
|
||||
/*remote_plugins_enabled*/ false,
|
||||
config.plugin_hooks_enabled,
|
||||
)
|
||||
.await;
|
||||
let mut feature_flags = config.plugin_feature_flags();
|
||||
feature_flags.remote_plugins_enabled = false;
|
||||
let disabled_outcome =
|
||||
PluginsManager::plugins_for_config(&manager, &config.config_layer_stack, feature_flags)
|
||||
.await;
|
||||
assert_eq!(disabled_outcome, PluginLoadOutcome::default());
|
||||
}
|
||||
|
||||
@@ -730,9 +733,11 @@ async fn remote_installed_plugins_cache_refresh_clears_stale_cache_when_auth_is_
|
||||
let outcome = manager
|
||||
.plugins_for_config(
|
||||
&ConfigLayerStack::default(),
|
||||
/*plugins_enabled*/ true,
|
||||
/*remote_plugins_enabled*/ true,
|
||||
/*plugin_hooks_enabled*/ true,
|
||||
PluginFeatureFlags {
|
||||
plugins_enabled: true,
|
||||
remote_plugins_enabled: true,
|
||||
plugin_hooks_enabled: true,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
assert_eq!(outcome, PluginLoadOutcome::default());
|
||||
|
||||
@@ -2,10 +2,9 @@ use super::*;
|
||||
use crate::SkillsManager;
|
||||
use crate::config::CONFIG_TOML_FILE;
|
||||
use crate::config::ConfigBuilder;
|
||||
use crate::plugins::PluginsManager;
|
||||
use crate::skills_load_input_from_config;
|
||||
use codex_config::ConfigLayerStackOrdering;
|
||||
use codex_features::Feature;
|
||||
use codex_core_plugins::PluginsManager;
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::config_types::Verbosity;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
@@ -657,12 +656,7 @@ enabled = false
|
||||
let skills_manager =
|
||||
SkillsManager::new(home.path().abs(), /*bundled_skills_enabled*/ true);
|
||||
let plugin_outcome = plugins_manager
|
||||
.plugins_for_config(
|
||||
&config.config_layer_stack,
|
||||
config.features.enabled(Feature::Plugins),
|
||||
config.features.enabled(Feature::RemotePlugin),
|
||||
config.features.enabled(Feature::PluginHooks),
|
||||
)
|
||||
.plugins_for_config(&config.config_layer_stack, config.plugin_feature_flags())
|
||||
.await;
|
||||
let effective_skill_roots = plugin_outcome.effective_skill_roots();
|
||||
let skills_input = skills_load_input_from_config(&config, effective_skill_roots);
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::config::ThreadStoreConfig;
|
||||
use crate::config::edit::ConfigEdit;
|
||||
use crate::config::edit::ConfigEditsBuilder;
|
||||
use crate::config::edit::apply_blocking;
|
||||
use crate::plugins::PluginsManager;
|
||||
use assert_matches::assert_matches;
|
||||
use codex_config::CONFIG_TOML_FILE;
|
||||
use codex_config::RequirementSource;
|
||||
@@ -53,6 +52,7 @@ use codex_config::types::TuiKeymap;
|
||||
use codex_config::types::TuiNotificationSettings;
|
||||
use codex_config::types::WindowsSandboxModeToml;
|
||||
use codex_config::types::WindowsToml;
|
||||
use codex_core_plugins::PluginsManager;
|
||||
use codex_exec_server::LOCAL_FS;
|
||||
use codex_features::Feature;
|
||||
use codex_features::FeaturesToml;
|
||||
|
||||
@@ -996,17 +996,20 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin_feature_flags(&self) -> codex_core_plugins::PluginFeatureFlags {
|
||||
codex_core_plugins::PluginFeatureFlags {
|
||||
plugins_enabled: self.features.enabled(Feature::Plugins),
|
||||
remote_plugins_enabled: self.features.enabled(Feature::RemotePlugin),
|
||||
plugin_hooks_enabled: self.features.enabled(Feature::PluginHooks),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn to_mcp_config(
|
||||
&self,
|
||||
plugins_manager: &crate::plugins::PluginsManager,
|
||||
plugins_manager: &codex_core_plugins::PluginsManager,
|
||||
) -> McpConfig {
|
||||
let loaded_plugins = plugins_manager
|
||||
.plugins_for_config(
|
||||
&self.config_layer_stack,
|
||||
self.features.enabled(Feature::Plugins),
|
||||
self.features.enabled(Feature::RemotePlugin),
|
||||
self.features.enabled(Feature::PluginHooks),
|
||||
)
|
||||
.plugins_for_config(&self.config_layer_stack, self.plugin_feature_flags())
|
||||
.await;
|
||||
let mut configured_mcp_servers = self.mcp_servers.get().clone();
|
||||
for plugin in loaded_plugins
|
||||
|
||||
@@ -27,12 +27,12 @@ use tracing::warn;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::mcp::McpManager;
|
||||
use crate::plugins::PluginsManager;
|
||||
use crate::session::INITIAL_SUBMIT_ID;
|
||||
use codex_config::AppsRequirementsToml;
|
||||
use codex_config::types::AppToolApproval;
|
||||
use codex_config::types::AppsConfigToml;
|
||||
use codex_config::types::ToolSuggestDiscoverableType;
|
||||
use codex_core_plugins::PluginsManager;
|
||||
use codex_features::Feature;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
@@ -409,12 +409,7 @@ fn write_cached_accessible_connectors(
|
||||
|
||||
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.config_layer_stack,
|
||||
config.features.enabled(Feature::Plugins),
|
||||
config.features.enabled(Feature::RemotePlugin),
|
||||
config.features.enabled(Feature::PluginHooks),
|
||||
)
|
||||
.plugins_for_config(&config.config_layer_stack, config.plugin_feature_flags())
|
||||
.await
|
||||
.capability_summaries()
|
||||
.iter()
|
||||
|
||||
@@ -2,8 +2,8 @@ use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::plugins::PluginsManager;
|
||||
use codex_config::McpServerConfig;
|
||||
use codex_core_plugins::PluginsManager;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_mcp::ToolPluginProvenance;
|
||||
use codex_mcp::configured_mcp_servers;
|
||||
|
||||
@@ -758,9 +758,7 @@ async fn custom_mcp_tool_approval_mode(
|
||||
.plugins_manager
|
||||
.plugins_for_config(
|
||||
&turn_context.config.config_layer_stack,
|
||||
turn_context.config.features.enabled(Feature::Plugins),
|
||||
turn_context.config.features.enabled(Feature::RemotePlugin),
|
||||
turn_context.config.features.enabled(Feature::PluginHooks),
|
||||
turn_context.config.plugin_feature_flags(),
|
||||
)
|
||||
.await
|
||||
.plugins()
|
||||
@@ -1810,12 +1808,7 @@ async fn persist_non_app_mcp_tool_approval(
|
||||
let plugin_config_name = sess
|
||||
.services
|
||||
.plugins_manager
|
||||
.plugins_for_config(
|
||||
&config.config_layer_stack,
|
||||
config.features.enabled(Feature::Plugins),
|
||||
config.features.enabled(Feature::RemotePlugin),
|
||||
config.features.enabled(Feature::PluginHooks),
|
||||
)
|
||||
.plugins_for_config(&config.config_layer_stack, config.plugin_feature_flags())
|
||||
.await
|
||||
.plugins()
|
||||
.iter()
|
||||
|
||||
@@ -13,7 +13,6 @@ use codex_config::types::ApprovalsReviewer;
|
||||
use codex_config::types::AppsConfigToml;
|
||||
use codex_config::types::McpServerConfig;
|
||||
use codex_config::types::McpServerToolConfig;
|
||||
use codex_features::Feature;
|
||||
use codex_hooks::Hooks;
|
||||
use codex_hooks::HooksConfig;
|
||||
use codex_model_provider::create_model_provider;
|
||||
@@ -1597,9 +1596,7 @@ enabled = true
|
||||
.plugins_manager
|
||||
.plugins_for_config(
|
||||
&initial_config.config_layer_stack,
|
||||
initial_config.features.enabled(Feature::Plugins),
|
||||
initial_config.features.enabled(Feature::RemotePlugin),
|
||||
initial_config.features.enabled(Feature::PluginHooks),
|
||||
initial_config.plugin_feature_flags(),
|
||||
)
|
||||
.await;
|
||||
std::fs::write(
|
||||
|
||||
@@ -6,22 +6,6 @@ pub(crate) mod test_support;
|
||||
|
||||
use codex_config::types::McpServerConfig;
|
||||
|
||||
pub use codex_core_plugins::ConfiguredMarketplace;
|
||||
pub use codex_core_plugins::ConfiguredMarketplaceListOutcome;
|
||||
pub use codex_core_plugins::ConfiguredMarketplacePlugin;
|
||||
pub use codex_core_plugins::PluginDetail;
|
||||
pub use codex_core_plugins::PluginDetailsUnavailableReason;
|
||||
pub use codex_core_plugins::PluginInstallError;
|
||||
pub use codex_core_plugins::PluginInstallOutcome;
|
||||
pub use codex_core_plugins::PluginInstallRequest;
|
||||
pub use codex_core_plugins::PluginReadOutcome;
|
||||
pub use codex_core_plugins::PluginReadRequest;
|
||||
pub use codex_core_plugins::PluginRemoteSyncError;
|
||||
pub use codex_core_plugins::PluginUninstallError;
|
||||
pub use codex_core_plugins::PluginsManager;
|
||||
pub use codex_core_plugins::RemotePluginSyncResult;
|
||||
pub use codex_core_plugins::marketplace_upgrade::ConfiguredMarketplaceUpgradeError as PluginMarketplaceUpgradeError;
|
||||
pub use codex_core_plugins::marketplace_upgrade::ConfiguredMarketplaceUpgradeOutcome as PluginMarketplaceUpgradeOutcome;
|
||||
pub use codex_plugin::AppConnectorId;
|
||||
pub use codex_plugin::EffectiveSkillRoots;
|
||||
pub use codex_plugin::PluginCapabilitySummary;
|
||||
|
||||
@@ -23,7 +23,6 @@ use codex_config::CloudRequirementsLoader;
|
||||
use codex_config::LoaderOverrides;
|
||||
use codex_config::loader::load_config_layers_state;
|
||||
use codex_exec_server::LOCAL_FS;
|
||||
use codex_features::Feature;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
use crate::review_prompts::resolve_review_request;
|
||||
@@ -624,9 +623,7 @@ pub async fn list_skills(sess: &Session, sub_id: String, cwds: Vec<PathBuf>, for
|
||||
let effective_skill_roots = plugins_manager
|
||||
.effective_skill_roots_for_layer_stack(
|
||||
&config_layer_stack,
|
||||
config.features.enabled(Feature::Plugins),
|
||||
config.features.enabled(Feature::RemotePlugin),
|
||||
config.features.enabled(Feature::PluginHooks),
|
||||
config.plugin_feature_flags(),
|
||||
)
|
||||
.await;
|
||||
let skills_input = crate::SkillsLoadInput::new(
|
||||
|
||||
@@ -274,7 +274,6 @@ use crate::exec_policy::ExecPolicyUpdateError;
|
||||
use crate::guardian::GuardianReviewSessionManager;
|
||||
use crate::mcp::McpManager;
|
||||
use crate::network_policy_decision::execpolicy_network_rule_amendment;
|
||||
use crate::plugins::PluginsManager;
|
||||
use crate::rollout::map_session_init_error;
|
||||
use crate::session_startup_prewarm::SessionStartupPrewarmHandle;
|
||||
use crate::shell;
|
||||
@@ -301,6 +300,7 @@ use crate::turn_timing::TurnTimingState;
|
||||
use crate::turn_timing::record_turn_ttfm_metric;
|
||||
use crate::unified_exec::UnifiedExecProcessManager;
|
||||
use crate::windows_sandbox::WindowsSandboxLevelExt;
|
||||
use codex_core_plugins::PluginsManager;
|
||||
use codex_git_utils::get_git_repo_root;
|
||||
use codex_mcp::compute_auth_statuses;
|
||||
use codex_mcp::with_codex_apps_mcp;
|
||||
@@ -476,12 +476,7 @@ impl Codex {
|
||||
.as_ref()
|
||||
.map(|environment| environment.get_filesystem());
|
||||
let plugin_outcome = plugins_manager
|
||||
.plugins_for_config(
|
||||
&config.config_layer_stack,
|
||||
config.features.enabled(Feature::Plugins),
|
||||
config.features.enabled(Feature::RemotePlugin),
|
||||
config.features.enabled(Feature::PluginHooks),
|
||||
)
|
||||
.plugins_for_config(&config.config_layer_stack, config.plugin_feature_flags())
|
||||
.await;
|
||||
let effective_skill_roots = plugin_outcome.effective_skill_roots();
|
||||
let skills_input = skills_load_input_from_config(&config, effective_skill_roots);
|
||||
@@ -2675,9 +2670,7 @@ impl Session {
|
||||
.plugins_manager
|
||||
.plugins_for_config(
|
||||
&turn_context.config.config_layer_stack,
|
||||
turn_context.config.features.enabled(Feature::Plugins),
|
||||
turn_context.config.features.enabled(Feature::RemotePlugin),
|
||||
turn_context.config.features.enabled(Feature::PluginHooks),
|
||||
turn_context.config.plugin_feature_flags(),
|
||||
)
|
||||
.await;
|
||||
if let Some(plugin_instructions) =
|
||||
@@ -3375,12 +3368,7 @@ async fn build_hooks_for_config(
|
||||
let plugin_hooks_enabled = config.features.enabled(Feature::PluginHooks);
|
||||
let (plugin_hook_sources, plugin_hook_load_warnings) = if plugin_hooks_enabled {
|
||||
let plugin_outcome = plugins_manager
|
||||
.plugins_for_config(
|
||||
&config.config_layer_stack,
|
||||
config.features.enabled(Feature::Plugins),
|
||||
config.features.enabled(Feature::RemotePlugin),
|
||||
config.features.enabled(Feature::PluginHooks),
|
||||
)
|
||||
.plugins_for_config(&config.config_layer_stack, config.plugin_feature_flags())
|
||||
.await;
|
||||
(
|
||||
plugin_outcome.effective_plugin_hook_sources(),
|
||||
|
||||
@@ -3560,9 +3560,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
.plugins_manager
|
||||
.plugins_for_config(
|
||||
&per_turn_config.config_layer_stack,
|
||||
per_turn_config.features.enabled(Feature::Plugins),
|
||||
per_turn_config.features.enabled(Feature::RemotePlugin),
|
||||
per_turn_config.features.enabled(Feature::PluginHooks),
|
||||
per_turn_config.plugin_feature_flags(),
|
||||
)
|
||||
.await;
|
||||
let effective_skill_roots = plugin_outcome.effective_skill_roots();
|
||||
@@ -4994,9 +4992,7 @@ where
|
||||
.plugins_manager
|
||||
.plugins_for_config(
|
||||
&per_turn_config.config_layer_stack,
|
||||
per_turn_config.features.enabled(Feature::Plugins),
|
||||
per_turn_config.features.enabled(Feature::RemotePlugin),
|
||||
per_turn_config.features.enabled(Feature::PluginHooks),
|
||||
per_turn_config.plugin_feature_flags(),
|
||||
)
|
||||
.await;
|
||||
let effective_skill_roots = plugin_outcome.effective_skill_roots();
|
||||
|
||||
@@ -172,9 +172,7 @@ pub(crate) async fn run_turn(
|
||||
.plugins_manager
|
||||
.plugins_for_config(
|
||||
&turn_context.config.config_layer_stack,
|
||||
turn_context.config.features.enabled(Feature::Plugins),
|
||||
turn_context.config.features.enabled(Feature::RemotePlugin),
|
||||
turn_context.config.features.enabled(Feature::PluginHooks),
|
||||
turn_context.config.plugin_feature_flags(),
|
||||
)
|
||||
.await;
|
||||
// Structured plugin:// mentions are resolved from the current session's
|
||||
@@ -1131,9 +1129,7 @@ pub(crate) async fn built_tools(
|
||||
.plugins_manager
|
||||
.plugins_for_config(
|
||||
&turn_context.config.config_layer_stack,
|
||||
turn_context.config.features.enabled(Feature::Plugins),
|
||||
turn_context.config.features.enabled(Feature::RemotePlugin),
|
||||
turn_context.config.features.enabled(Feature::PluginHooks),
|
||||
turn_context.config.plugin_feature_flags(),
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -698,9 +698,7 @@ impl Session {
|
||||
.plugins_manager
|
||||
.plugins_for_config(
|
||||
&per_turn_config.config_layer_stack,
|
||||
per_turn_config.features.enabled(Feature::Plugins),
|
||||
per_turn_config.features.enabled(Feature::RemotePlugin),
|
||||
per_turn_config.features.enabled(Feature::PluginHooks),
|
||||
per_turn_config.plugin_feature_flags(),
|
||||
)
|
||||
.await;
|
||||
let effective_skill_roots = plugin_outcome.effective_skill_roots();
|
||||
|
||||
@@ -8,8 +8,6 @@ use tokio::runtime::Handle;
|
||||
use tokio::sync::broadcast;
|
||||
use tracing::warn;
|
||||
|
||||
use codex_features::Feature;
|
||||
|
||||
use crate::SkillsManager;
|
||||
use crate::config::Config;
|
||||
use crate::file_watcher::FileWatcher;
|
||||
@@ -18,8 +16,8 @@ use crate::file_watcher::Receiver;
|
||||
use crate::file_watcher::ThrottledWatchReceiver;
|
||||
use crate::file_watcher::WatchPath;
|
||||
use crate::file_watcher::WatchRegistration;
|
||||
use crate::plugins::PluginsManager;
|
||||
use crate::skills_load_input_from_config;
|
||||
use codex_core_plugins::PluginsManager;
|
||||
|
||||
#[cfg(not(test))]
|
||||
const WATCHER_THROTTLE_INTERVAL: Duration = Duration::from_secs(10);
|
||||
@@ -64,12 +62,7 @@ impl SkillsWatcher {
|
||||
fs: Option<Arc<dyn codex_exec_server::ExecutorFileSystem>>,
|
||||
) -> WatchRegistration {
|
||||
let plugin_outcome = plugins_manager
|
||||
.plugins_for_config(
|
||||
&config.config_layer_stack,
|
||||
config.features.enabled(Feature::Plugins),
|
||||
config.features.enabled(Feature::RemotePlugin),
|
||||
config.features.enabled(Feature::PluginHooks),
|
||||
)
|
||||
.plugins_for_config(&config.config_layer_stack, config.plugin_feature_flags())
|
||||
.await;
|
||||
let effective_skill_roots = plugin_outcome.effective_skill_roots();
|
||||
let skills_input = skills_load_input_from_config(config, effective_skill_roots);
|
||||
|
||||
@@ -9,7 +9,6 @@ use crate::exec_policy::ExecPolicyManager;
|
||||
use crate::guardian::GuardianRejection;
|
||||
use crate::guardian::GuardianRejectionCircuitBreaker;
|
||||
use crate::mcp::McpManager;
|
||||
use crate::plugins::PluginsManager;
|
||||
use crate::skills_watcher::SkillsWatcher;
|
||||
use crate::tools::code_mode::CodeModeService;
|
||||
use crate::tools::network_approval::NetworkApprovalService;
|
||||
@@ -17,6 +16,7 @@ use crate::tools::sandboxing::ApprovalStore;
|
||||
use crate::unified_exec::UnifiedExecProcessManager;
|
||||
use arc_swap::ArcSwap;
|
||||
use codex_analytics::AnalyticsEventsClient;
|
||||
use codex_core_plugins::PluginsManager;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_hooks::Hooks;
|
||||
use codex_login::AuthManager;
|
||||
|
||||
@@ -8,7 +8,6 @@ use crate::environment_selection::selected_primary_environment;
|
||||
use crate::environment_selection::validate_environment_selections;
|
||||
use crate::file_watcher::FileWatcher;
|
||||
use crate::mcp::McpManager;
|
||||
use crate::plugins::PluginsManager;
|
||||
use crate::rollout::RolloutRecorder;
|
||||
use crate::rollout::truncation;
|
||||
use crate::session::Codex;
|
||||
@@ -23,6 +22,7 @@ use crate::tasks::interrupted_turn_history_marker;
|
||||
use codex_analytics::AnalyticsEventsClient;
|
||||
use codex_app_server_protocol::ThreadHistoryBuilder;
|
||||
use codex_app_server_protocol::TurnStatus;
|
||||
use codex_core_plugins::PluginsManager;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
|
||||
@@ -315,7 +315,7 @@ async fn refresh_missing_suggested_connectors(
|
||||
fn verified_plugin_suggestion_completed(
|
||||
tool_id: &str,
|
||||
config: &crate::config::Config,
|
||||
plugins_manager: &crate::plugins::PluginsManager,
|
||||
plugins_manager: &codex_core_plugins::PluginsManager,
|
||||
) -> bool {
|
||||
plugins_manager
|
||||
.list_marketplaces_for_config(
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use super::*;
|
||||
use crate::config::edit::ConfigEditsBuilder;
|
||||
use crate::plugins::PluginInstallRequest;
|
||||
use crate::plugins::PluginsManager;
|
||||
use crate::plugins::test_support::load_plugins_config;
|
||||
use crate::plugins::test_support::write_curated_plugin_sha;
|
||||
use crate::plugins::test_support::write_openai_curated_marketplace;
|
||||
@@ -12,6 +10,8 @@ use codex_config::types::ToolSuggestConfig;
|
||||
use codex_config::types::ToolSuggestDisabledTool;
|
||||
use codex_config::types::ToolSuggestDiscoverable;
|
||||
use codex_config::types::ToolSuggestDiscoverableType;
|
||||
use codex_core_plugins::PluginInstallRequest;
|
||||
use codex_core_plugins::PluginsManager;
|
||||
use codex_core_plugins::startup_sync::curated_plugins_repo_path;
|
||||
use codex_rmcp_client::ElicitationResponse;
|
||||
use codex_tools::DiscoverablePluginInfo;
|
||||
|
||||
@@ -233,12 +233,7 @@ impl App {
|
||||
|
||||
tokio::spawn(async move {
|
||||
let plugins = PluginsManager::new(config.codex_home.to_path_buf())
|
||||
.plugins_for_config(
|
||||
&config.config_layer_stack,
|
||||
config.features.enabled(Feature::Plugins),
|
||||
config.features.enabled(Feature::RemotePlugin),
|
||||
config.features.enabled(Feature::PluginHooks),
|
||||
)
|
||||
.plugins_for_config(&config.config_layer_stack, config.plugin_feature_flags())
|
||||
.await
|
||||
.capability_summaries()
|
||||
.to_vec();
|
||||
|
||||
Reference in New Issue
Block a user