Move plugin out of core. (#20348)

This commit is contained in:
xl-openai
2026-04-30 14:26:14 -07:00
committed by GitHub
Unverified
parent 127be0612c
commit 7b3de63041
47 changed files with 786 additions and 295 deletions
+4
View File
@@ -1882,6 +1882,7 @@ dependencies = [
"codex-model-provider-info",
"codex-models-manager",
"codex-otel",
"codex-plugin",
"codex-protocol",
"codex-rmcp-client",
"codex-rollout",
@@ -2100,9 +2101,11 @@ dependencies = [
"codex-app-server-protocol",
"codex-connectors",
"codex-core",
"codex-core-plugins",
"codex-git-utils",
"codex-login",
"codex-model-provider",
"codex-plugin",
"codex-utils-cargo-bin",
"codex-utils-cli",
"pretty_assertions",
@@ -2504,6 +2507,7 @@ version = "0.0.0"
dependencies = [
"anyhow",
"chrono",
"codex-analytics",
"codex-app-server-protocol",
"codex-config",
"codex-core-skills",
-4
View File
@@ -99,10 +99,6 @@ pub mod legacy_core {
pub use codex_core::personality_migration::*;
}
pub mod plugins {
pub use codex_core::plugins::PluginsManager;
}
pub mod review_format {
pub use codex_core::review_format::*;
}
+1
View File
@@ -44,6 +44,7 @@ codex-features = { workspace = true }
codex-git-utils = { workspace = true }
codex-hooks = { workspace = true }
codex-otel = { workspace = true }
codex-plugin = { workspace = true }
codex-shell-command = { workspace = true }
codex-utils-cli = { workspace = true }
codex-utils-pty = { workspace = true }
@@ -270,10 +270,6 @@ use codex_core::find_archived_thread_path_by_id_str;
use codex_core::find_thread_name_by_id;
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;
@@ -281,6 +277,11 @@ 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::PluginLoadOutcome;
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;
@@ -6321,8 +6322,9 @@ impl CodexMessageProcessor {
.get(&cwd)
.map_or(&[][..], std::vec::Vec::as_slice);
let effective_skill_roots = if workspace_codex_plugins_enabled {
let plugins_input = config.plugins_config_input();
plugins_manager
.effective_skill_roots_for_layer_stack(&config_layer_stack, &config)
.effective_skill_roots_for_layer_stack(&config_layer_stack, &plugins_input)
.await
} else {
Vec::new()
@@ -6404,15 +6406,16 @@ impl CodexMessageProcessor {
config.features.enabled(Feature::Plugins) && workspace_codex_plugins_enabled;
let plugin_outcome = if plugins_enabled && config.features.enabled(Feature::PluginHooks)
{
let plugins_input = config.plugins_config_input();
plugins_manager
.plugins_for_layer_stack(
&config.config_layer_stack,
&config,
&plugins_input,
/*plugin_hooks_feature_enabled*/ true,
)
.await
} else {
codex_core::plugins::PluginLoadOutcome::default()
PluginLoadOutcome::default()
};
let hooks = codex_hooks::list_hooks(codex_hooks::HooksConfig {
feature_enabled: config.features.enabled(Feature::CodexHooks),
@@ -6470,10 +6473,13 @@ impl CodexMessageProcessor {
let config = self.load_latest_config(/*fallback_cwd*/ None).await?;
let plugins_manager = self.thread_manager.plugins_manager();
let MarketplaceUpgradeParams { marketplace_name } = params;
let plugins_input = config.plugins_config_input();
let outcome = tokio::task::spawn_blocking(move || {
plugins_manager
.upgrade_configured_marketplaces_for_config(&config, marketplace_name.as_deref())
plugins_manager.upgrade_configured_marketplaces_for_config(
&plugins_input,
marketplace_name.as_deref(),
)
})
.await
.map_err(|err| internal_error(format!("failed to upgrade marketplaces: {err}")))?
@@ -4,8 +4,8 @@ use codex_app_server_protocol::AppInfo;
use codex_app_server_protocol::AppSummary;
use codex_chatgpt::connectors;
use codex_core::config::Config;
use codex_core::plugins::AppConnectorId;
use codex_exec_server::EnvironmentManager;
use codex_plugin::AppConnectorId;
use tracing::warn;
pub(super) async fn load_plugin_app_summaries(
@@ -113,7 +113,7 @@ pub(super) fn plugin_apps_needing_auth(
#[cfg(test)]
mod tests {
use codex_app_server_protocol::AppInfo;
use codex_core::plugins::AppConnectorId;
use codex_plugin::AppConnectorId;
use pretty_assertions::assert_eq;
use super::plugin_apps_needing_auth;
@@ -37,14 +37,15 @@ impl CodexMessageProcessor {
{
return Ok(empty_response());
}
let plugins_input = config.plugins_config_input();
plugins_manager.maybe_start_plugin_list_background_tasks_for_config(
&config,
&plugins_input,
auth.clone(),
&roots,
Some(self.effective_plugins_changed_callback(config.clone())),
);
let config_for_marketplace_listing = config.clone();
let config_for_marketplace_listing = plugins_input.clone();
let plugins_manager_for_marketplace_listing = plugins_manager.clone();
let (mut data, marketplace_load_errors) = match tokio::task::spawn_blocking(move || {
let outcome = plugins_manager_for_marketplace_listing
@@ -145,7 +146,7 @@ impl CodexMessageProcessor {
.any(|marketplace| marketplace.name == OPENAI_CURATED_MARKETPLACE_NAME)
{
match plugins_manager
.featured_plugin_ids_for_config(&config, auth.as_ref())
.featured_plugin_ids_for_config(&plugins_input, auth.as_ref())
.await
{
Ok(featured_plugin_ids) => featured_plugin_ids,
@@ -201,6 +202,7 @@ impl CodexMessageProcessor {
});
let config = self.load_latest_config(config_cwd).await?;
let plugins_input = config.plugins_config_input();
let plugin = match read_source {
Ok(marketplace_path) => {
@@ -209,7 +211,7 @@ impl CodexMessageProcessor {
marketplace_path,
};
let outcome = plugins_manager
.read_plugin_for_config(&config, &request)
.read_plugin_for_config(&plugins_input, &request)
.await
.map_err(|err| Self::marketplace_error(err, "read plugin details"))?;
let environment_manager = self.thread_manager.environment_manager();
@@ -279,7 +281,7 @@ impl CodexMessageProcessor {
.app_ids
.iter()
.cloned()
.map(codex_core::plugins::AppConnectorId)
.map(codex_plugin::AppConnectorId)
.collect::<Vec<_>>();
let environment_manager = self.thread_manager.environment_manager();
let app_summaries = plugin_app_helpers::load_plugin_app_summaries(
@@ -570,7 +572,7 @@ impl CodexMessageProcessor {
self.thread_manager
.plugins_manager()
.maybe_start_remote_installed_plugins_cache_refresh_after_mutation(
&config,
&config.plugins_config_input(),
auth.clone(),
Some(self.effective_plugins_changed_callback(config.clone())),
);
@@ -602,7 +604,7 @@ impl CodexMessageProcessor {
config: &Config,
is_chatgpt_auth: bool,
plugin_id: &str,
plugin_apps: &[codex_core::plugins::AppConnectorId],
plugin_apps: &[codex_plugin::AppConnectorId],
) -> Vec<AppSummary> {
if plugin_apps.is_empty() || !config.features.apps_enabled_for_auth(is_chatgpt_auth) {
return Vec::new();
@@ -675,7 +677,7 @@ impl CodexMessageProcessor {
params: PluginUninstallParams,
) -> Result<PluginUninstallResponse, JSONRPCErrorError> {
let PluginUninstallParams { plugin_id } = params;
if codex_core::plugins::PluginId::parse(&plugin_id).is_err()
if codex_plugin::PluginId::parse(&plugin_id).is_err()
&& (plugin_id.is_empty() || !is_valid_remote_plugin_id(&plugin_id))
{
return Err(invalid_request(
@@ -798,7 +800,7 @@ impl CodexMessageProcessor {
self.on_effective_plugins_changed(config.clone());
}
plugins_manager.maybe_start_remote_installed_plugins_cache_refresh_after_mutation(
&config,
&config.plugins_config_input(),
auth.clone(),
Some(self.effective_plugins_changed_callback(config.clone())),
);
@@ -1,9 +1,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;
@@ -20,6 +19,7 @@ use codex_external_agent_migration::missing_command_names;
use codex_external_agent_migration::missing_subagent_names;
use codex_external_agent_sessions::ExternalAgentSessionMigration;
use codex_external_agent_sessions::detect_recent_sessions;
use codex_plugin::PluginId;
use codex_protocol::protocol::Product;
use serde_json::Value as JsonValue;
use std::collections::BTreeMap;
@@ -1146,8 +1146,9 @@ fn configured_marketplace_plugins(
config: &Config,
plugins_manager: &PluginsManager,
) -> io::Result<BTreeMap<String, HashSet<String>>> {
let plugins_input = config.plugins_config_input();
let marketplaces = plugins_manager
.list_marketplaces_for_config(config, &[])
.list_marketplaces_for_config(&plugins_input, &[])
.map_err(|err| {
invalid_data_error(format!("failed to list configured marketplaces: {err}"))
})?;
+1 -1
View File
@@ -32,11 +32,11 @@ use codex_config::ResidencyRequirement as CoreResidencyRequirement;
use codex_config::SandboxModeRequirement as CoreSandboxModeRequirement;
use codex_core::ThreadManager;
use codex_core::config::Config;
use codex_core::plugins::PluginId;
use codex_core_plugins::loader::installed_plugin_telemetry_metadata;
use codex_core_plugins::toggles::collect_plugin_enabled_candidates;
use codex_features::canonical_feature_for_key;
use codex_features::feature_for_key;
use codex_plugin::PluginId;
use codex_protocol::config_types::WebSearchMode;
use codex_protocol::protocol::Op;
use serde_json::json;
+1 -1
View File
@@ -314,7 +314,7 @@ impl MessageProcessor {
thread_manager
.plugins_manager()
.maybe_start_plugin_startup_tasks_for_config(
&config,
&config.plugins_config_input(),
auth_manager.clone(),
Some(on_effective_plugins_changed),
);
+2
View File
@@ -13,9 +13,11 @@ 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-git-utils = { workspace = true }
codex-login = { workspace = true }
codex-model-provider = { workspace = true }
codex-plugin = { workspace = true }
codex-utils-cli = { workspace = true }
serde = { workspace = true, features = ["derive"] }
tokio = { workspace = true, features = ["full"] }
+6 -5
View File
@@ -16,11 +16,11 @@ pub use codex_core::connectors::list_accessible_connectors_from_mcp_tools_with_o
pub use codex_core::connectors::list_accessible_connectors_from_mcp_tools_with_options_and_status;
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_core_plugins::PluginsManager;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_login::default_client::originator;
use codex_plugin::AppConnectorId;
const DIRECTORY_CONNECTORS_TIMEOUT: Duration = Duration::from_secs(60);
@@ -135,9 +135,10 @@ fn all_connectors_cache_key(config: &Config, auth: &CodexAuth) -> AllConnectorsC
)
}
async fn plugin_apps_for_config(config: &Config) -> Vec<codex_core::plugins::AppConnectorId> {
async fn plugin_apps_for_config(config: &Config) -> Vec<AppConnectorId> {
let plugins_input = config.plugins_config_input();
PluginsManager::new(config.codex_home.to_path_buf())
.plugins_for_config(config)
.plugins_for_config(&plugins_input)
.await
.effective_apps()
}
@@ -188,7 +189,7 @@ pub fn merge_connectors_with_accessible(
mod tests {
use super::*;
use codex_connectors::metadata::connector_install_url;
use codex_core::plugins::AppConnectorId;
use codex_plugin::AppConnectorId;
use pretty_assertions::assert_eq;
fn app(id: &str) -> AppInfo {
+4 -3
View File
@@ -4,8 +4,8 @@ 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::PluginMarketplaceUpgradeOutcome;
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;
@@ -128,8 +128,9 @@ async fn run_upgrade(
.context("failed to load configuration")?;
let codex_home = find_codex_home().context("failed to resolve CODEX_HOME")?;
let manager = PluginsManager::new(codex_home.to_path_buf());
let plugins_input = config.plugins_config_input();
let outcome = manager
.upgrade_configured_marketplaces_for_config(&config, marketplace_name.as_deref())
.upgrade_configured_marketplaces_for_config(&plugins_input, marketplace_name.as_deref())
.map_err(anyhow::Error::msg)?;
print_upgrade_outcome(&outcome, marketplace_name.as_deref())
}
+1 -1
View File
@@ -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;
+5
View File
@@ -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;
@@ -95,6 +96,10 @@ pub use mcp_types::McpServerTransportConfig;
pub use mcp_types::RawMcpServerConfig;
pub use merge::merge_toml_values;
pub use overrides::build_cli_overrides_layer;
pub use plugin_edit::PluginConfigEdit;
pub use plugin_edit::apply_user_plugin_config_edits;
pub use plugin_edit::clear_user_plugin;
pub use plugin_edit::set_user_plugin_enabled;
pub use project_root_markers::default_project_root_markers;
pub use project_root_markers::project_root_markers_from_config;
pub use requirements_exec_policy::RequirementsExecPolicy;
+307
View File
@@ -0,0 +1,307 @@
use std::fs;
use std::io::ErrorKind;
use std::path::Path;
use codex_utils_path::resolve_symlink_write_paths;
use codex_utils_path::write_atomically;
use tokio::task;
use toml_edit::DocumentMut;
use toml_edit::Item as TomlItem;
use toml_edit::Table as TomlTable;
use toml_edit::value;
use crate::CONFIG_TOML_FILE;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PluginConfigEdit {
SetEnabled { plugin_key: String, enabled: bool },
Clear { plugin_key: String },
}
pub async fn set_user_plugin_enabled(
codex_home: &Path,
plugin_key: String,
enabled: bool,
) -> std::io::Result<()> {
apply_user_plugin_config_edits(
codex_home,
vec![PluginConfigEdit::SetEnabled {
plugin_key,
enabled,
}],
)
.await
}
pub async fn clear_user_plugin(codex_home: &Path, plugin_key: String) -> std::io::Result<()> {
apply_user_plugin_config_edits(codex_home, vec![PluginConfigEdit::Clear { plugin_key }]).await
}
pub async fn apply_user_plugin_config_edits(
codex_home: &Path,
edits: Vec<PluginConfigEdit>,
) -> std::io::Result<()> {
let codex_home = codex_home.to_path_buf();
task::spawn_blocking(move || apply_user_plugin_config_edits_blocking(&codex_home, edits))
.await
.map_err(|err| std::io::Error::other(format!("config persistence task panicked: {err}")))?
}
fn apply_user_plugin_config_edits_blocking(
codex_home: &Path,
edits: Vec<PluginConfigEdit>,
) -> std::io::Result<()> {
if edits.is_empty() {
return Ok(());
}
let config_path = codex_home.join(CONFIG_TOML_FILE);
let write_paths = resolve_symlink_write_paths(&config_path)?;
let mut doc = read_or_create_document(write_paths.read_path.as_deref())?;
let mut mutated = false;
for edit in edits {
mutated |= match edit {
PluginConfigEdit::SetEnabled {
plugin_key,
enabled,
} => set_plugin_enabled(&mut doc, &plugin_key, enabled),
PluginConfigEdit::Clear { plugin_key } => clear_plugin(&mut doc, &plugin_key),
};
}
if !mutated {
return Ok(());
}
write_atomically(&write_paths.write_path, &doc.to_string())
}
fn read_or_create_document(config_path: Option<&Path>) -> std::io::Result<DocumentMut> {
let Some(config_path) = config_path else {
return Ok(DocumentMut::new());
};
match fs::read_to_string(config_path) {
Ok(raw) => raw
.parse::<DocumentMut>()
.map_err(|err| std::io::Error::new(ErrorKind::InvalidData, err)),
Err(err) if err.kind() == ErrorKind::NotFound => Ok(DocumentMut::new()),
Err(err) => Err(err),
}
}
fn set_plugin_enabled(doc: &mut DocumentMut, plugin_key: &str, enabled: bool) -> bool {
let Some(plugins) = ensure_plugins_table(doc) else {
return false;
};
let Some(plugin) = ensure_table_for_write(&mut plugins[plugin_key]) else {
return 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_key: &str) -> bool {
let root = doc.as_table_mut();
let Some(plugins_item) = root.get_mut("plugins") else {
return false;
};
let Some(plugins) = ensure_table_for_read(plugins_item) else {
return false;
};
plugins.remove(plugin_key).is_some()
}
fn ensure_plugins_table(doc: &mut DocumentMut) -> Option<&mut TomlTable> {
let root = doc.as_table_mut();
if !root.contains_key("plugins") {
root.insert("plugins", TomlItem::Table(new_implicit_table()));
}
ensure_table_for_write(root.get_mut("plugins")?)
}
fn ensure_table_for_write(item: &mut TomlItem) -> Option<&mut TomlTable> {
match item {
TomlItem::Table(table) => Some(table),
TomlItem::Value(value) => {
let table = value
.as_inline_table()
.map_or_else(new_implicit_table, table_from_inline);
*item = TomlItem::Table(table);
item.as_table_mut()
}
TomlItem::None => {
*item = TomlItem::Table(new_implicit_table());
item.as_table_mut()
}
_ => None,
}
}
fn ensure_table_for_read(item: &mut TomlItem) -> Option<&mut TomlTable> {
match item {
TomlItem::Table(_) => {}
TomlItem::Value(value) => {
let inline = value.as_inline_table()?.clone();
*item = TomlItem::Table(table_from_inline(&inline));
}
_ => return None,
}
item.as_table_mut()
}
fn table_from_inline(inline: &toml_edit::InlineTable) -> TomlTable {
let mut table = new_implicit_table();
for (key, value) in inline.iter() {
let mut value = value.clone();
value.decor_mut().set_suffix("");
table.insert(key, TomlItem::Value(value));
}
table
}
fn new_implicit_table() -> TomlTable {
let mut table = TomlTable::new();
table.set_implicit(true);
table
}
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());
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use tempfile::TempDir;
#[tokio::test]
async fn set_user_plugin_enabled_writes_plugin_entry() {
let codex_home = TempDir::new().unwrap();
set_user_plugin_enabled(
codex_home.path(),
"demo@market".to_string(),
/*enabled*/ true,
)
.await
.unwrap();
let config = read_config(codex_home.path());
let expected: toml::Value = toml::from_str(
r#"
[plugins."demo@market"]
enabled = true
"#,
)
.unwrap();
assert_eq!(config, expected);
}
#[tokio::test]
async fn set_user_plugin_enabled_preserves_existing_plugin_fields() {
let codex_home = TempDir::new().unwrap();
fs::write(
codex_home.path().join(CONFIG_TOML_FILE),
r#"
[plugins."demo@market"]
enabled = false
source = "/tmp/plugin"
"#,
)
.unwrap();
set_user_plugin_enabled(
codex_home.path(),
"demo@market".to_string(),
/*enabled*/ true,
)
.await
.unwrap();
let config = read_config(codex_home.path());
let expected: toml::Value = toml::from_str(
r#"
[plugins."demo@market"]
enabled = true
source = "/tmp/plugin"
"#,
)
.unwrap();
assert_eq!(config, expected);
}
#[tokio::test]
async fn clear_user_plugin_removes_empty_plugins_table() {
let codex_home = TempDir::new().unwrap();
fs::write(
codex_home.path().join(CONFIG_TOML_FILE),
r#"
[plugins."demo@market"]
enabled = true
"#,
)
.unwrap();
clear_user_plugin(codex_home.path(), "demo@market".to_string())
.await
.unwrap();
assert_eq!(
fs::read_to_string(codex_home.path().join(CONFIG_TOML_FILE)).unwrap(),
""
);
}
#[tokio::test]
async fn clear_user_plugin_missing_entry_does_not_create_config() {
let codex_home = TempDir::new().unwrap();
clear_user_plugin(codex_home.path(), "demo@market".to_string())
.await
.unwrap();
assert!(!codex_home.path().join(CONFIG_TOML_FILE).exists());
}
#[tokio::test]
#[cfg(unix)]
async fn set_user_plugin_enabled_follows_config_symlink() {
use std::os::unix::fs::symlink;
let codex_home = TempDir::new().unwrap();
let target_path = codex_home.path().join("target_config.toml");
symlink(&target_path, codex_home.path().join(CONFIG_TOML_FILE)).unwrap();
set_user_plugin_enabled(
codex_home.path(),
"demo@market".to_string(),
/*enabled*/ true,
)
.await
.unwrap();
let config =
toml::from_str::<toml::Value>(&fs::read_to_string(target_path).unwrap()).unwrap();
let expected: toml::Value = toml::from_str(
r#"
[plugins."demo@market"]
enabled = true
"#,
)
.unwrap();
assert_eq!(config, expected);
}
fn read_config(codex_home: &Path) -> toml::Value {
toml::from_str(&fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).unwrap()).unwrap()
}
}
-1
View File
@@ -37,7 +37,6 @@ 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_exec_server::EnvironmentManager;
+2 -1
View File
@@ -13,6 +13,8 @@ path = "src/lib.rs"
workspace = true
[dependencies]
anyhow = { workspace = true }
codex-analytics = { workspace = true }
codex-app-server-protocol = { workspace = true }
codex-config = { workspace = true }
codex-core-skills = { workspace = true }
@@ -41,7 +43,6 @@ url = { workspace = true }
zip = { workspace = true }
[dev-dependencies]
anyhow = { workspace = true }
libc = { workspace = true }
pretty_assertions = { workspace = true }
tempfile = { workspace = true }
+25
View File
@@ -1,5 +1,6 @@
pub mod installed_marketplaces;
pub mod loader;
mod manager;
pub mod manifest;
pub mod marketplace;
pub mod marketplace_add;
@@ -8,8 +9,11 @@ pub mod marketplace_upgrade;
pub mod remote;
pub mod remote_bundle;
pub mod remote_legacy;
pub(crate) mod startup_remote_sync;
pub mod startup_sync;
pub mod store;
#[cfg(test)]
mod test_support;
pub mod toggles;
pub const OPENAI_CURATED_MARKETPLACE_NAME: &str = "openai-curated";
@@ -32,3 +36,24 @@ pub const TOOL_SUGGEST_DISCOVERABLE_PLUGIN_ALLOWLIST: &[&str] = &[
"chrome@openai-bundled",
"computer-use@openai-bundled",
];
pub type LoadedPlugin = codex_plugin::LoadedPlugin<codex_config::McpServerConfig>;
pub type PluginLoadOutcome = codex_plugin::PluginLoadOutcome<codex_config::McpServerConfig>;
pub use manager::ConfiguredMarketplace;
pub use manager::ConfiguredMarketplaceListOutcome;
pub use manager::ConfiguredMarketplacePlugin;
pub use manager::PluginDetail;
pub use manager::PluginDetailsUnavailableReason;
pub use manager::PluginInstallError;
pub use manager::PluginInstallOutcome;
pub use manager::PluginInstallRequest;
pub use manager::PluginReadOutcome;
pub use manager::PluginReadRequest;
pub use manager::PluginRemoteSyncError;
pub use manager::PluginUninstallError;
pub use manager::PluginsConfigInput;
pub use manager::PluginsManager;
pub use manager::RemotePluginSyncResult;
pub use marketplace_upgrade::ConfiguredMarketplaceUpgradeError as PluginMarketplaceUpgradeError;
pub use marketplace_upgrade::ConfiguredMarketplaceUpgradeOutcome as PluginMarketplaceUpgradeOutcome;
@@ -1,59 +1,59 @@
use super::PluginLoadOutcome;
use super::startup_sync::start_startup_remote_plugin_sync_once;
use crate::SkillMetadata;
use crate::config::Config;
use crate::config::edit::ConfigEdit;
use crate::config::edit::ConfigEditsBuilder;
use super::startup_remote_sync::start_startup_remote_plugin_sync_once;
use crate::OPENAI_CURATED_MARKETPLACE_NAME;
use crate::installed_marketplaces::installed_marketplace_roots_from_layer_stack;
use crate::loader::configured_curated_plugin_ids_from_codex_home;
use crate::loader::curated_plugin_cache_version;
use crate::loader::installed_plugin_telemetry_metadata;
use crate::loader::load_plugin_apps;
use crate::loader::load_plugin_mcp_servers;
use crate::loader::load_plugin_skills;
use crate::loader::load_plugins_from_layer_stack;
use crate::loader::log_plugin_load_errors;
use crate::loader::materialize_marketplace_plugin_source;
use crate::loader::plugin_telemetry_metadata_from_root;
use crate::loader::refresh_curated_plugin_cache;
use crate::loader::refresh_non_curated_plugin_cache;
use crate::loader::refresh_non_curated_plugin_cache_force_reinstall;
use crate::loader::remote_installed_plugins_to_config;
use crate::manifest::PluginManifestInterface;
use crate::manifest::load_plugin_manifest;
use crate::marketplace::MarketplaceError;
use crate::marketplace::MarketplaceInterface;
use crate::marketplace::MarketplaceListError;
use crate::marketplace::MarketplacePluginAuthPolicy;
use crate::marketplace::MarketplacePluginPolicy;
use crate::marketplace::MarketplacePluginSource;
use crate::marketplace::ResolvedMarketplacePlugin;
use crate::marketplace::find_installable_marketplace_plugin;
use crate::marketplace::find_marketplace_plugin;
use crate::marketplace::list_marketplaces;
use crate::marketplace::load_marketplace;
use crate::marketplace::plugin_interface_with_marketplace_category;
use crate::marketplace_upgrade::ConfiguredMarketplaceUpgradeError;
use crate::marketplace_upgrade::ConfiguredMarketplaceUpgradeOutcome;
use crate::marketplace_upgrade::configured_git_marketplace_names;
use crate::marketplace_upgrade::upgrade_configured_git_marketplaces;
use crate::remote::RemoteInstalledPlugin;
use crate::remote::RemotePluginCatalogError;
use crate::remote::RemotePluginServiceConfig;
use crate::remote_legacy::RemotePluginFetchError;
use crate::remote_legacy::RemotePluginMutationError;
use crate::startup_sync::curated_plugins_repo_path;
use crate::startup_sync::read_curated_plugins_sha;
use crate::startup_sync::sync_openai_plugins_repo;
use crate::store::PluginInstallResult as StorePluginInstallResult;
use crate::store::PluginStore;
use crate::store::PluginStoreError;
use codex_analytics::AnalyticsEventsClient;
use codex_config::ConfigLayerStack;
use codex_config::PluginConfigEdit;
use codex_config::apply_user_plugin_config_edits;
use codex_config::clear_user_plugin;
use codex_config::set_user_plugin_enabled;
use codex_config::types::PluginConfig;
use codex_config::version_for_toml;
use codex_core_plugins::OPENAI_CURATED_MARKETPLACE_NAME;
use codex_core_plugins::installed_marketplaces::installed_marketplace_roots_from_layer_stack;
use codex_core_plugins::loader::configured_curated_plugin_ids_from_codex_home;
use codex_core_plugins::loader::curated_plugin_cache_version;
use codex_core_plugins::loader::installed_plugin_telemetry_metadata;
use codex_core_plugins::loader::load_plugin_apps;
use codex_core_plugins::loader::load_plugin_mcp_servers;
use codex_core_plugins::loader::load_plugin_skills;
use codex_core_plugins::loader::load_plugins_from_layer_stack;
use codex_core_plugins::loader::log_plugin_load_errors;
use codex_core_plugins::loader::materialize_marketplace_plugin_source;
use codex_core_plugins::loader::plugin_telemetry_metadata_from_root;
use codex_core_plugins::loader::refresh_curated_plugin_cache;
use codex_core_plugins::loader::refresh_non_curated_plugin_cache;
use codex_core_plugins::loader::refresh_non_curated_plugin_cache_force_reinstall;
use codex_core_plugins::loader::remote_installed_plugins_to_config;
use codex_core_plugins::manifest::PluginManifestInterface;
use codex_core_plugins::manifest::load_plugin_manifest;
use codex_core_plugins::marketplace::MarketplaceError;
use codex_core_plugins::marketplace::MarketplaceInterface;
use codex_core_plugins::marketplace::MarketplaceListError;
use codex_core_plugins::marketplace::MarketplacePluginAuthPolicy;
use codex_core_plugins::marketplace::MarketplacePluginPolicy;
use codex_core_plugins::marketplace::MarketplacePluginSource;
use codex_core_plugins::marketplace::ResolvedMarketplacePlugin;
use codex_core_plugins::marketplace::find_installable_marketplace_plugin;
use codex_core_plugins::marketplace::find_marketplace_plugin;
use codex_core_plugins::marketplace::list_marketplaces;
use codex_core_plugins::marketplace::load_marketplace;
use codex_core_plugins::marketplace::plugin_interface_with_marketplace_category;
use codex_core_plugins::marketplace_upgrade::ConfiguredMarketplaceUpgradeError;
use codex_core_plugins::marketplace_upgrade::ConfiguredMarketplaceUpgradeOutcome;
use codex_core_plugins::marketplace_upgrade::configured_git_marketplace_names;
use codex_core_plugins::marketplace_upgrade::upgrade_configured_git_marketplaces;
use codex_core_plugins::remote::RemoteInstalledPlugin;
use codex_core_plugins::remote::RemotePluginCatalogError;
use codex_core_plugins::remote::RemotePluginServiceConfig;
use codex_core_plugins::remote_legacy::RemotePluginFetchError;
use codex_core_plugins::remote_legacy::RemotePluginMutationError;
use codex_core_plugins::startup_sync::curated_plugins_repo_path;
use codex_core_plugins::startup_sync::read_curated_plugins_sha;
use codex_core_plugins::startup_sync::sync_openai_plugins_repo;
use codex_core_plugins::store::PluginInstallResult as StorePluginInstallResult;
use codex_core_plugins::store::PluginStore;
use codex_core_plugins::store::PluginStoreError;
use codex_features::Feature;
use codex_core_skills::SkillMetadata;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_plugin::AppConnectorId;
@@ -72,7 +72,6 @@ use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::time::Instant;
use tokio::sync::Semaphore;
use toml_edit::value;
use tracing::info;
use tracing::warn;
@@ -80,6 +79,33 @@ 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(Debug, Clone)]
pub struct PluginsConfigInput {
pub config_layer_stack: ConfigLayerStack,
pub plugins_enabled: bool,
pub remote_plugin_enabled: bool,
pub plugin_hooks_enabled: bool,
pub chatgpt_base_url: String,
}
impl PluginsConfigInput {
pub fn new(
config_layer_stack: ConfigLayerStack,
plugins_enabled: bool,
remote_plugin_enabled: bool,
plugin_hooks_enabled: bool,
chatgpt_base_url: String,
) -> Self {
Self {
config_layer_stack,
plugins_enabled,
remote_plugin_enabled,
plugin_hooks_enabled,
chatgpt_base_url,
}
}
}
#[derive(Clone, PartialEq, Eq)]
struct FeaturedPluginIdsCacheKey {
chatgpt_base_url: String,
@@ -143,14 +169,14 @@ struct ConfiguredMarketplaceUpgradeState {
in_flight: bool,
}
fn remote_plugin_service_config(config: &Config) -> RemotePluginServiceConfig {
fn remote_plugin_service_config(config: &PluginsConfigInput) -> RemotePluginServiceConfig {
RemotePluginServiceConfig {
chatgpt_base_url: config.chatgpt_base_url.clone(),
}
}
fn featured_plugin_ids_cache_key(
config: &Config,
config: &PluginsConfigInput,
auth: Option<&CodexAuth>,
) -> FeaturedPluginIdsCacheKey {
FeaturedPluginIdsCacheKey {
@@ -430,21 +456,21 @@ impl PluginsManager {
}
}
pub async fn plugins_for_config(&self, config: &Config) -> PluginLoadOutcome {
pub async fn plugins_for_config(&self, config: &PluginsConfigInput) -> PluginLoadOutcome {
self.plugins_for_config_with_force_reload(config, /*force_reload*/ false)
.await
}
pub(crate) async fn plugins_for_config_with_force_reload(
&self,
config: &Config,
config: &PluginsConfigInput,
force_reload: bool,
) -> PluginLoadOutcome {
if !config.features.enabled(Feature::Plugins) {
if !config.plugins_enabled {
return PluginLoadOutcome::default();
}
let plugin_hooks_enabled = config.features.enabled(Feature::PluginHooks);
let plugin_hooks_enabled = config.plugin_hooks_enabled;
let config_version = version_for_toml(&config.config_layer_stack.effective_config());
if !force_reload
&& let Some(outcome) =
@@ -495,10 +521,10 @@ impl PluginsManager {
pub async fn plugins_for_layer_stack(
&self,
config_layer_stack: &ConfigLayerStack,
config: &Config,
config: &PluginsConfigInput,
plugin_hooks_feature_enabled: bool,
) -> PluginLoadOutcome {
if !config.features.enabled(Feature::Plugins) {
if !config.plugins_enabled {
return PluginLoadOutcome::default();
}
load_plugins_from_layer_stack(
@@ -515,15 +541,11 @@ impl PluginsManager {
pub async fn effective_skill_roots_for_layer_stack(
&self,
config_layer_stack: &ConfigLayerStack,
config: &Config,
config: &PluginsConfigInput,
) -> Vec<AbsolutePathBuf> {
self.plugins_for_layer_stack(
config_layer_stack,
config,
config.features.enabled(Feature::PluginHooks),
)
.await
.effective_skill_roots()
self.plugins_for_layer_stack(config_layer_stack, config, config.plugin_hooks_enabled)
.await
.effective_skill_roots()
}
fn cached_enabled_outcome(
@@ -550,8 +572,11 @@ impl PluginsManager {
}
}
fn remote_installed_plugin_configs(&self, config: &Config) -> HashMap<String, PluginConfig> {
if !config.features.enabled(Feature::RemotePlugin) {
fn remote_installed_plugin_configs(
&self,
config: &PluginsConfigInput,
) -> HashMap<String, PluginConfig> {
if !config.remote_plugin_enabled {
return HashMap::new();
}
@@ -596,7 +621,7 @@ impl PluginsManager {
pub fn maybe_start_remote_installed_plugins_cache_refresh(
self: &Arc<Self>,
config: &Config,
config: &PluginsConfigInput,
auth: Option<CodexAuth>,
on_effective_plugins_changed: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
) {
@@ -610,7 +635,7 @@ impl PluginsManager {
pub fn maybe_start_remote_installed_plugins_cache_refresh_after_mutation(
self: &Arc<Self>,
config: &Config,
config: &PluginsConfigInput,
auth: Option<CodexAuth>,
on_effective_plugins_changed: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
) {
@@ -624,14 +649,12 @@ impl PluginsManager {
fn maybe_start_remote_installed_plugins_cache_refresh_with_notify(
self: &Arc<Self>,
config: &Config,
config: &PluginsConfigInput,
auth: Option<CodexAuth>,
notify: RemoteInstalledPluginsCacheRefreshNotify,
on_effective_plugins_changed: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
) {
if !config.features.enabled(Feature::Plugins)
|| !config.features.enabled(Feature::RemotePlugin)
{
if !config.plugins_enabled || !config.remote_plugin_enabled {
return;
}
@@ -647,7 +670,7 @@ impl PluginsManager {
pub fn maybe_start_plugin_list_background_tasks_for_config(
self: &Arc<Self>,
config: &Config,
config: &PluginsConfigInput,
auth: Option<CodexAuth>,
roots: &[AbsolutePathBuf],
on_effective_plugins_changed: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
@@ -710,10 +733,10 @@ impl PluginsManager {
pub async fn featured_plugin_ids_for_config(
&self,
config: &Config,
config: &PluginsConfigInput,
auth: Option<&CodexAuth>,
) -> Result<Vec<String>, RemotePluginFetchError> {
if !config.features.enabled(Feature::Plugins) {
if !config.plugins_enabled {
return Ok(Vec::new());
}
@@ -721,13 +744,12 @@ impl PluginsManager {
if let Some(featured_plugin_ids) = self.cached_featured_plugin_ids(&cache_key) {
return Ok(featured_plugin_ids);
}
let featured_plugin_ids =
codex_core_plugins::remote_legacy::fetch_remote_featured_plugin_ids(
&remote_plugin_service_config(config),
auth,
self.restriction_product,
)
.await?;
let featured_plugin_ids = crate::remote_legacy::fetch_remote_featured_plugin_ids(
&remote_plugin_service_config(config),
auth,
self.restriction_product,
)
.await?;
self.write_featured_plugin_ids_cache(cache_key, &featured_plugin_ids);
Ok(featured_plugin_ids)
}
@@ -746,7 +768,7 @@ impl PluginsManager {
pub async fn install_plugin_with_remote_sync(
&self,
config: &Config,
config: &PluginsConfigInput,
auth: Option<&CodexAuth>,
request: PluginInstallRequest,
) -> Result<PluginInstallOutcome, PluginInstallError> {
@@ -757,7 +779,7 @@ impl PluginsManager {
)?;
let plugin_id = resolved.plugin_id.as_key();
// This only forwards the backend mutation before the local install flow.
codex_core_plugins::remote_legacy::enable_remote_plugin(
crate::remote_legacy::enable_remote_plugin(
&remote_plugin_service_config(config),
auth,
&plugin_id,
@@ -800,18 +822,13 @@ impl PluginsManager {
.await
.map_err(PluginInstallError::join)??;
ConfigEditsBuilder::new(&self.codex_home)
.with_edits([ConfigEdit::SetPath {
segments: vec![
"plugins".to_string(),
result.plugin_id.as_key(),
"enabled".to_string(),
],
value: value(true),
}])
.apply()
.await
.map_err(PluginInstallError::from)?;
set_user_plugin_enabled(
&self.codex_home,
result.plugin_id.as_key(),
/*enabled*/ true,
)
.await
.map_err(anyhow::Error::from)?;
let analytics_events_client = match self.analytics_events_client.read() {
Ok(client) => client.clone(),
@@ -839,7 +856,7 @@ impl PluginsManager {
pub async fn uninstall_plugin_with_remote_sync(
&self,
config: &Config,
config: &PluginsConfigInput,
auth: Option<&CodexAuth>,
plugin_id: String,
) -> Result<(), PluginUninstallError> {
@@ -848,7 +865,7 @@ impl PluginsManager {
let plugin_id = PluginId::parse(&plugin_id)?;
let plugin_key = plugin_id.as_key();
// This only forwards the backend mutation before the local uninstall flow.
codex_core_plugins::remote_legacy::uninstall_remote_plugin(
crate::remote_legacy::uninstall_remote_plugin(
&remote_plugin_service_config(config),
auth,
&plugin_key,
@@ -870,12 +887,9 @@ impl PluginsManager {
.await
.map_err(PluginUninstallError::join)??;
ConfigEditsBuilder::new(&self.codex_home)
.with_edits([ConfigEdit::ClearPath {
segments: vec!["plugins".to_string(), plugin_id.as_key()],
}])
.apply()
.await?;
clear_user_plugin(&self.codex_home, plugin_id.as_key())
.await
.map_err(anyhow::Error::from)?;
let analytics_events_client = match self.analytics_events_client.read() {
Ok(client) => client.clone(),
@@ -892,7 +906,7 @@ impl PluginsManager {
pub async fn sync_plugins_from_remote(
&self,
config: &Config,
config: &PluginsConfigInput,
auth: Option<&CodexAuth>,
additive_only: bool,
) -> Result<RemotePluginSyncResult, PluginRemoteSyncError> {
@@ -900,12 +914,12 @@ impl PluginsManager {
PluginRemoteSyncError::Config(anyhow::anyhow!("remote plugin sync semaphore closed"))
})?;
if !config.features.enabled(Feature::Plugins) {
if !config.plugins_enabled {
return Ok(RemotePluginSyncResult::default());
}
info!("starting remote plugin sync");
let remote_plugins = codex_core_plugins::remote_legacy::fetch_remote_plugin_status(
let remote_plugins = crate::remote_legacy::fetch_remote_plugin_status(
&remote_plugin_service_config(config),
auth,
)
@@ -1051,9 +1065,9 @@ impl PluginsManager {
if current_enabled != Some(true) {
result.enabled_plugin_ids.push(plugin_key.clone());
config_edits.push(ConfigEdit::SetPath {
segments: vec!["plugins".to_string(), plugin_key, "enabled".to_string()],
value: value(true),
config_edits.push(PluginConfigEdit::SetEnabled {
plugin_key,
enabled: true,
});
}
} else if !additive_only {
@@ -1064,9 +1078,7 @@ impl PluginsManager {
result.uninstalled_plugin_ids.push(plugin_key.clone());
}
if current_enabled.is_some() {
config_edits.push(ConfigEdit::ClearPath {
segments: vec!["plugins".to_string(), plugin_key],
});
config_edits.push(PluginConfigEdit::Clear { plugin_key });
}
}
}
@@ -1091,13 +1103,10 @@ impl PluginsManager {
let config_result = if config_edits.is_empty() {
Ok(())
} else {
ConfigEditsBuilder::new(&self.codex_home)
.with_edits(config_edits)
.apply()
.await
apply_user_plugin_config_edits(&self.codex_home, config_edits).await
};
self.clear_cache();
config_result?;
config_result.map_err(anyhow::Error::from)?;
info!(
marketplace = %marketplace_name,
@@ -1115,10 +1124,10 @@ impl PluginsManager {
pub fn list_marketplaces_for_config(
&self,
config: &Config,
config: &PluginsConfigInput,
additional_roots: &[AbsolutePathBuf],
) -> Result<ConfiguredMarketplaceListOutcome, MarketplaceError> {
if !config.features.enabled(Feature::Plugins) {
if !config.plugins_enabled {
return Ok(ConfiguredMarketplaceListOutcome::default());
}
@@ -1175,10 +1184,10 @@ impl PluginsManager {
pub async fn read_plugin_for_config(
&self,
config: &Config,
config: &PluginsConfigInput,
request: &PluginReadRequest,
) -> Result<PluginReadOutcome, MarketplaceError> {
if !config.features.enabled(Feature::Plugins) {
if !config.plugins_enabled {
return Err(MarketplaceError::PluginsDisabled);
}
@@ -1216,9 +1225,9 @@ impl PluginsManager {
})
}
pub(crate) async fn read_plugin_detail_for_marketplace_plugin(
pub async fn read_plugin_detail_for_marketplace_plugin(
&self,
config: &Config,
config: &PluginsConfigInput,
marketplace_name: &str,
plugin: ConfiguredMarketplacePlugin,
) -> Result<PluginDetail, MarketplaceError> {
@@ -1332,11 +1341,11 @@ impl PluginsManager {
pub fn maybe_start_plugin_startup_tasks_for_config(
self: &Arc<Self>,
config: &Config,
config: &PluginsConfigInput,
auth_manager: Arc<AuthManager>,
on_effective_plugins_changed: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
) {
if config.features.enabled(Feature::Plugins) {
if config.plugins_enabled {
self.start_curated_repo_sync();
let should_spawn_marketplace_auto_upgrade = {
let mut state = match self.configured_marketplace_upgrade_state.write() {
@@ -1396,7 +1405,7 @@ impl PluginsManager {
auth_manager.clone(),
);
if config.features.enabled(Feature::RemotePlugin) {
if config.remote_plugin_enabled {
let config = config.clone();
let manager = Arc::clone(self);
let auth_manager = auth_manager.clone();
@@ -1430,7 +1439,7 @@ impl PluginsManager {
pub fn upgrade_configured_marketplaces_for_config(
&self,
config: &Config,
config: &PluginsConfigInput,
marketplace_name: Option<&str>,
) -> Result<ConfiguredMarketplaceUpgradeOutcome, String> {
if let Some(marketplace_name) = marketplace_name
@@ -1648,7 +1657,7 @@ impl PluginsManager {
}
};
let installed_plugins = codex_core_plugins::remote::fetch_remote_installed_plugins(
let installed_plugins = crate::remote::fetch_remote_installed_plugins(
&request.service_config,
request.auth.as_ref(),
)
@@ -1751,7 +1760,10 @@ impl PluginsManager {
}
}
fn configured_plugin_states(&self, config: &Config) -> (HashSet<String>, HashSet<String>) {
fn configured_plugin_states(
&self,
config: &PluginsConfigInput,
) -> (HashSet<String>, HashSet<String>) {
let configured_plugins = configured_plugins_from_stack(&config.config_layer_stack);
let installed_plugins = configured_plugins
.keys()
@@ -1771,7 +1783,7 @@ impl PluginsManager {
fn marketplace_roots(
&self,
config: &Config,
config: &PluginsConfigInput,
additional_roots: &[AbsolutePathBuf],
) -> Vec<AbsolutePathBuf> {
// Treat the curated catalog as an extra marketplace root so plugin listing can surface it
@@ -1,15 +1,22 @@
use super::*;
use crate::config::CONFIG_TOML_FILE;
use crate::config::ConfigBuilder;
use crate::plugins::LoadedPlugin;
use crate::plugins::PluginLoadOutcome;
use crate::plugins::test_support::TEST_CURATED_PLUGIN_CACHE_VERSION;
use crate::plugins::test_support::TEST_CURATED_PLUGIN_SHA;
use crate::plugins::test_support::write_curated_plugin_sha_with as write_curated_plugin_sha;
use crate::plugins::test_support::write_file;
use crate::plugins::test_support::write_openai_curated_marketplace;
use crate::LoadedPlugin;
use crate::PluginLoadOutcome;
use crate::installed_marketplaces::marketplace_install_root;
use crate::loader::load_plugins_from_layer_stack;
use crate::loader::refresh_non_curated_plugin_cache;
use crate::loader::refresh_non_curated_plugin_cache_force_reinstall;
use crate::marketplace::MarketplacePluginInstallPolicy;
use crate::remote::RemoteInstalledPlugin;
use crate::startup_sync::curated_plugins_repo_path;
use crate::test_support::TEST_CURATED_PLUGIN_CACHE_VERSION;
use crate::test_support::TEST_CURATED_PLUGIN_SHA;
use crate::test_support::load_plugins_config as load_plugins_config_input;
use crate::test_support::write_curated_plugin_sha_with as write_curated_plugin_sha;
use crate::test_support::write_file;
use crate::test_support::write_openai_curated_marketplace;
use codex_app_server_protocol::ConfigLayerSource;
use codex_config::AppToolApproval;
use codex_config::CONFIG_TOML_FILE;
use codex_config::ConfigLayerEntry;
use codex_config::ConfigLayerStack;
use codex_config::ConfigRequirements;
@@ -17,12 +24,6 @@ use codex_config::ConfigRequirementsToml;
use codex_config::McpServerConfig;
use codex_config::McpServerToolConfig;
use codex_config::types::McpServerTransportConfig;
use codex_core_plugins::installed_marketplaces::marketplace_install_root;
use codex_core_plugins::loader::load_plugins_from_layer_stack;
use codex_core_plugins::loader::refresh_non_curated_plugin_cache;
use codex_core_plugins::loader::refresh_non_curated_plugin_cache_force_reinstall;
use codex_core_plugins::marketplace::MarketplacePluginInstallPolicy;
use codex_core_plugins::startup_sync::curated_plugins_repo_path;
use codex_login::CodexAuth;
use codex_protocol::protocol::Product;
use codex_utils_absolute_path::test_support::PathBufExt;
@@ -140,13 +141,8 @@ async fn load_plugins_from_config(config_toml: &str, codex_home: &Path) -> Plugi
.await
}
async fn load_config(codex_home: &Path, cwd: &Path) -> crate::config::Config {
ConfigBuilder::default()
.codex_home(codex_home.to_path_buf())
.fallback_cwd(Some(cwd.to_path_buf()))
.build()
.await
.expect("config should load")
async fn load_config(codex_home: &Path, cwd: &Path) -> PluginsConfigInput {
load_plugins_config_input(codex_home, cwd).await
}
#[tokio::test]
@@ -350,14 +346,12 @@ remote_plugin = true
let config = load_config(codex_home.path(), codex_home.path()).await;
let manager = PluginsManager::new(codex_home.path().to_path_buf());
manager.write_remote_installed_plugins_cache(vec![
codex_core_plugins::remote::RemoteInstalledPlugin {
marketplace_name: "chatgpt-global".to_string(),
id: "plugins~Plugin_linear".to_string(),
name: "linear".to_string(),
enabled: true,
},
]);
manager.write_remote_installed_plugins_cache(vec![RemoteInstalledPlugin {
marketplace_name: "chatgpt-global".to_string(),
id: "plugins~Plugin_linear".to_string(),
name: "linear".to_string(),
enabled: true,
}]);
let outcome = manager.plugins_for_config(&config).await;
assert_eq!(
@@ -381,14 +375,12 @@ remote_plugin = true
let config = load_config(codex_home.path(), codex_home.path()).await;
let manager = PluginsManager::new(codex_home.path().to_path_buf());
manager.write_remote_installed_plugins_cache(vec![
codex_core_plugins::remote::RemoteInstalledPlugin {
marketplace_name: "chatgpt-global".to_string(),
id: "plugins~Plugin_linear".to_string(),
name: "linear".to_string(),
enabled: true,
},
]);
manager.write_remote_installed_plugins_cache(vec![RemoteInstalledPlugin {
marketplace_name: "chatgpt-global".to_string(),
id: "plugins~Plugin_linear".to_string(),
name: "linear".to_string(),
enabled: true,
}]);
let outcome = manager.plugins_for_config(&config).await;
assert_eq!(outcome, PluginLoadOutcome::default());
@@ -423,7 +415,7 @@ enabled = false
enabled = true
"#;
let outcome = load_plugins_from_config(config_toml, codex_home.path()).await;
let skill_path = dunce::canonicalize(skill_path)
let skill_path = std::fs::canonicalize(skill_path)
.expect("skill path should canonicalize")
.abs();
@@ -3,9 +3,9 @@ use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use crate::config::Config;
use crate::plugins::PluginsManager;
use codex_core_plugins::startup_sync::has_local_curated_plugins_snapshot;
use crate::manager::PluginsConfigInput;
use crate::manager::PluginsManager;
use crate::startup_sync::has_local_curated_plugins_snapshot;
use codex_login::AuthManager;
use tracing::info;
use tracing::warn;
@@ -16,7 +16,7 @@ const STARTUP_REMOTE_PLUGIN_SYNC_PREREQUISITE_TIMEOUT: Duration = Duration::from
pub(crate) fn start_startup_remote_plugin_sync_once(
manager: Arc<PluginsManager>,
codex_home: PathBuf,
config: Config,
config: PluginsConfigInput,
auth_manager: Arc<AuthManager>,
) {
let marker_path = startup_remote_plugin_sync_marker_path(codex_home.as_path());
@@ -96,5 +96,5 @@ async fn write_startup_remote_plugin_sync_marker(codex_home: &Path) -> std::io::
}
#[cfg(test)]
#[path = "startup_sync_tests.rs"]
#[path = "startup_remote_sync_tests.rs"]
mod tests;
@@ -1,11 +1,12 @@
use super::*;
use crate::config::CONFIG_TOML_FILE;
use crate::plugins::PluginsManager;
use crate::plugins::test_support::TEST_CURATED_PLUGIN_CACHE_VERSION;
use crate::plugins::test_support::write_curated_plugin_sha;
use crate::plugins::test_support::write_file;
use crate::plugins::test_support::write_openai_curated_marketplace;
use codex_core_plugins::startup_sync::curated_plugins_repo_path;
use crate::PluginsManager;
use crate::startup_sync::curated_plugins_repo_path;
use crate::test_support::TEST_CURATED_PLUGIN_CACHE_VERSION;
use crate::test_support::load_plugins_config;
use crate::test_support::write_curated_plugin_sha;
use crate::test_support::write_file;
use crate::test_support::write_openai_curated_marketplace;
use codex_config::CONFIG_TOML_FILE;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use pretty_assertions::assert_eq;
@@ -48,7 +49,7 @@ enabled = false
.mount(&server)
.await;
let mut config = crate::plugins::test_support::load_plugins_config(tmp.path()).await;
let mut config = load_plugins_config(tmp.path(), tmp.path()).await;
config.chatgpt_base_url = format!("{}/backend-api/", server.uri());
let manager = Arc::new(PluginsManager::new(tmp.path().to_path_buf()));
let auth_manager =
+139
View File
@@ -0,0 +1,139 @@
use std::fs;
use std::path::Path;
use crate::OPENAI_CURATED_MARKETPLACE_NAME;
use crate::PluginsConfigInput;
use codex_config::CloudRequirementsLoader;
use codex_config::LoaderOverrides;
use codex_config::NoopThreadConfigLoader;
use codex_config::loader::load_config_layers_state;
use codex_exec_server::LOCAL_FS;
use codex_utils_absolute_path::AbsolutePathBuf;
use toml::Value;
pub(crate) const TEST_CURATED_PLUGIN_SHA: &str = "0123456789abcdef0123456789abcdef01234567";
pub(crate) const TEST_CURATED_PLUGIN_CACHE_VERSION: &str = "01234567";
pub(crate) fn write_file(path: &Path, contents: &str) {
fs::create_dir_all(path.parent().expect("file should have a parent")).unwrap();
fs::write(path, contents).unwrap();
}
pub(crate) fn write_curated_plugin(root: &Path, plugin_name: &str) {
let plugin_root = root.join("plugins").join(plugin_name);
write_file(
&plugin_root.join(".codex-plugin/plugin.json"),
&format!(
r#"{{
"name": "{plugin_name}",
"description": "Plugin that includes skills, MCP servers, and app connectors"
}}"#
),
);
write_file(
&plugin_root.join("skills/SKILL.md"),
"---\nname: sample\ndescription: sample\n---\n",
);
write_file(
&plugin_root.join(".mcp.json"),
r#"{
"mcpServers": {
"sample-docs": {
"type": "http",
"url": "https://sample.example/mcp"
}
}
}"#,
);
write_file(
&plugin_root.join(".app.json"),
r#"{
"apps": {
"calendar": {
"id": "connector_calendar"
}
}
}"#,
);
}
pub(crate) fn write_openai_curated_marketplace(root: &Path, plugin_names: &[&str]) {
let plugins = plugin_names
.iter()
.map(|plugin_name| {
format!(
r#"{{
"name": "{plugin_name}",
"source": {{
"source": "local",
"path": "./plugins/{plugin_name}"
}}
}}"#
)
})
.collect::<Vec<_>>()
.join(",\n");
write_file(
&root.join(".agents/plugins/marketplace.json"),
&format!(
r#"{{
"name": "{OPENAI_CURATED_MARKETPLACE_NAME}",
"plugins": [
{plugins}
]
}}"#
),
);
for plugin_name in plugin_names {
write_curated_plugin(root, plugin_name);
}
}
pub(crate) fn write_curated_plugin_sha(codex_home: &Path) {
write_curated_plugin_sha_with(codex_home, TEST_CURATED_PLUGIN_SHA);
}
pub(crate) fn write_curated_plugin_sha_with(codex_home: &Path, sha: &str) {
write_file(&codex_home.join(".tmp/plugins.sha"), &format!("{sha}\n"));
}
pub(crate) async fn load_plugins_config(codex_home: &Path, cwd: &Path) -> PluginsConfigInput {
let codex_home = AbsolutePathBuf::try_from(codex_home).expect("codex home should be absolute");
let cwd = AbsolutePathBuf::try_from(cwd).expect("cwd should be absolute");
let config_layer_stack = load_config_layers_state(
LOCAL_FS.as_ref(),
codex_home.as_path(),
Some(cwd),
&[],
LoaderOverrides::without_managed_config_for_tests(),
CloudRequirementsLoader::default(),
&NoopThreadConfigLoader,
)
.await
.expect("config should load");
let effective_config = config_layer_stack.effective_config();
PluginsConfigInput::new(
config_layer_stack,
feature_enabled(&effective_config, "plugins", /*default_enabled*/ true),
feature_enabled(
&effective_config,
"remote_plugin",
/*default_enabled*/ false,
),
feature_enabled(
&effective_config,
"plugin_hooks",
/*default_enabled*/ false,
),
"https://chatgpt.com/backend-api/".to_string(),
)
}
fn feature_enabled(config: &Value, key: &str, default_enabled: bool) -> bool {
config
.get("features")
.and_then(Value::as_table)
.and_then(|features| features.get(key))
.and_then(Value::as_bool)
.unwrap_or(default_enabled)
}
+3 -2
View File
@@ -2,9 +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_core_plugins::PluginsManager;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::config_types::Verbosity;
use codex_protocol::openai_models::ReasoningEffort;
@@ -655,7 +655,8 @@ 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).await;
let plugins_input = config.plugins_config_input();
let plugin_outcome = plugins_manager.plugins_for_config(&plugins_input).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
+1 -1
View File
@@ -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;
+15 -2
View File
@@ -54,6 +54,7 @@ use codex_config::types::TuiKeymap;
use codex_config::types::TuiNotificationSettings;
use codex_config::types::UriBasedFileOpener;
use codex_config::types::WindowsSandboxModeToml;
use codex_core_plugins::PluginsConfigInput;
use codex_exec_server::ExecutorFileSystem;
use codex_exec_server::LOCAL_FS;
use codex_features::AppsMcpPathOverrideConfigToml;
@@ -995,11 +996,23 @@ impl Config {
}
}
/// Build the plugin-manager input from the effective config.
pub fn plugins_config_input(&self) -> PluginsConfigInput {
PluginsConfigInput::new(
self.config_layer_stack.clone(),
self.features.enabled(Feature::Plugins),
self.features.enabled(Feature::RemotePlugin),
self.features.enabled(Feature::PluginHooks),
self.chatgpt_base_url.clone(),
)
}
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).await;
let plugins_input = self.plugins_config_input();
let loaded_plugins = plugins_manager.plugins_for_config(&plugins_input).await;
let mut configured_mcp_servers = self.mcp_servers.get().clone();
for plugin in loaded_plugins
.plugins()
+3 -2
View File
@@ -26,13 +26,13 @@ use tracing::warn;
use crate::config::Config;
use crate::mcp::McpManager;
use crate::plugins::PluginsManager;
use crate::plugins::list_tool_suggest_discoverable_plugins;
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;
@@ -403,8 +403,9 @@ fn write_cached_accessible_connectors(
}
async fn tool_suggest_connector_ids(config: &Config) -> HashSet<String> {
let plugins_input = config.plugins_config_input();
let mut connector_ids = PluginsManager::new(config.codex_home.to_path_buf())
.plugins_for_config(config)
.plugins_for_config(&plugins_input)
.await
.capability_summaries()
.iter()
+1 -1
View File
@@ -67,7 +67,7 @@ pub use message_history::history_metadata as message_history_metadata;
pub use message_history::lookup as lookup_message_history_entry;
pub use utils::path_utils;
pub mod personality_migration;
pub mod plugins;
pub(crate) mod plugins;
#[doc(hidden)]
pub(crate) mod prompt_debug;
#[doc(hidden)]
+1 -1
View File
@@ -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;
+2 -2
View File
@@ -804,7 +804,7 @@ async fn custom_mcp_tool_approval_mode(
sess.services
.plugins_manager
.plugins_for_config(turn_context.config.as_ref())
.plugins_for_config(&turn_context.config.plugins_config_input())
.await
.plugins()
.iter()
@@ -1853,7 +1853,7 @@ async fn persist_non_app_mcp_tool_approval(
let plugin_config_name = sess
.services
.plugins_manager
.plugins_for_config(config)
.plugins_for_config(&config.plugins_config_input())
.await
.plugins()
.iter()
+1 -1
View File
@@ -1660,7 +1660,7 @@ enabled = true
session
.services
.plugins_manager
.plugins_for_config(&initial_config)
.plugins_for_config(&initial_config.plugins_config_input())
.await;
std::fs::write(
codex_home.join(CONFIG_TOML_FILE),
+8 -3
View File
@@ -3,11 +3,11 @@ use std::collections::HashSet;
use tracing::warn;
use super::PluginCapabilitySummary;
use super::PluginsManager;
use crate::config::Config;
use codex_config::types::ToolSuggestDiscoverableType;
use codex_core_plugins::OPENAI_BUNDLED_MARKETPLACE_NAME;
use codex_core_plugins::OPENAI_CURATED_MARKETPLACE_NAME;
use codex_core_plugins::PluginsManager;
use codex_core_plugins::TOOL_SUGGEST_DISCOVERABLE_PLUGIN_ALLOWLIST;
use codex_features::Feature;
use codex_tools::DiscoverablePluginInfo;
@@ -25,6 +25,7 @@ pub(crate) async fn list_tool_suggest_discoverable_plugins(
}
let plugins_manager = PluginsManager::new(config.codex_home.to_path_buf());
let plugins_input = config.plugins_config_input();
let configured_plugin_ids = config
.tool_suggest
.discoverables
@@ -40,7 +41,7 @@ pub(crate) async fn list_tool_suggest_discoverable_plugins(
.map(|disabled_tool| disabled_tool.id.as_str())
.collect::<HashSet<_>>();
let marketplaces = plugins_manager
.list_marketplaces_for_config(config, &[])
.list_marketplaces_for_config(&plugins_input, &[])
.context("failed to list plugin marketplaces for tool suggestions")?
.marketplaces;
let mut discoverable_plugins = Vec::<DiscoverablePluginInfo>::new();
@@ -62,7 +63,11 @@ pub(crate) async fn list_tool_suggest_discoverable_plugins(
let plugin_id = plugin.id.clone();
match plugins_manager
.read_plugin_detail_for_marketplace_plugin(config, &marketplace_name, plugin)
.read_plugin_detail_for_marketplace_plugin(
&plugins_input,
&marketplace_name,
plugin,
)
.await
{
Ok(plugin) => {
@@ -1,11 +1,11 @@
use super::*;
use crate::plugins::PluginInstallRequest;
use crate::plugins::test_support::load_plugins_config;
use crate::plugins::test_support::write_curated_plugin;
use crate::plugins::test_support::write_curated_plugin_sha;
use crate::plugins::test_support::write_file;
use crate::plugins::test_support::write_openai_curated_marketplace;
use crate::plugins::test_support::write_plugins_feature_config;
use codex_core_plugins::PluginInstallRequest;
use codex_core_plugins::startup_sync::curated_plugins_repo_path;
use codex_tools::DiscoverablePluginInfo;
use codex_utils_absolute_path::AbsolutePathBuf;
+1 -30
View File
@@ -1,43 +1,14 @@
use codex_config::types::McpServerConfig;
mod discoverable;
mod injection;
mod manager;
mod mentions;
mod render;
mod startup_sync;
#[cfg(test)]
pub(crate) mod test_support;
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;
pub use codex_plugin::PluginId;
pub use codex_plugin::PluginIdError;
pub use codex_plugin::PluginTelemetryMetadata;
pub use codex_plugin::validate_plugin_segment;
pub type LoadedPlugin = codex_plugin::LoadedPlugin<McpServerConfig>;
pub type PluginLoadOutcome = codex_plugin::PluginLoadOutcome<McpServerConfig>;
pub(crate) use codex_plugin::PluginCapabilitySummary;
pub(crate) use discoverable::list_tool_suggest_discoverable_plugins;
pub(crate) use injection::build_plugin_injections;
pub use manager::ConfiguredMarketplace;
pub use manager::ConfiguredMarketplaceListOutcome;
pub use manager::ConfiguredMarketplacePlugin;
pub use manager::PluginDetail;
pub use manager::PluginDetailsUnavailableReason;
pub use manager::PluginInstallError;
pub use manager::PluginInstallOutcome;
pub use manager::PluginInstallRequest;
pub use manager::PluginReadOutcome;
pub use manager::PluginReadRequest;
pub use manager::PluginRemoteSyncError;
pub use manager::PluginUninstallError;
pub use manager::PluginsManager;
pub use manager::RemotePluginSyncResult;
pub(crate) use render::render_explicit_plugin_instructions;
pub(crate) use mentions::build_connector_slug_counts;
@@ -6,7 +6,6 @@ use std::path::Path;
use codex_core_plugins::OPENAI_CURATED_MARKETPLACE_NAME;
pub(crate) const TEST_CURATED_PLUGIN_SHA: &str = "0123456789abcdef0123456789abcdef01234567";
pub(crate) const TEST_CURATED_PLUGIN_CACHE_VERSION: &str = "01234567";
pub(crate) fn write_file(path: &Path, contents: &str) {
fs::create_dir_all(path.parent().expect("file should have a parent")).unwrap();
+2 -1
View File
@@ -620,8 +620,9 @@ pub async fn list_skills(sess: &Session, sub_id: String, cwds: Vec<PathBuf>, for
continue;
}
};
let plugins_input = config.plugins_config_input();
let effective_skill_roots = plugins_manager
.effective_skill_roots_for_layer_stack(&config_layer_stack, &config)
.effective_skill_roots_for_layer_stack(&config_layer_stack, &plugins_input)
.await;
let skills_input = crate::SkillsLoadInput::new(
cwd_abs.clone(),
+6 -4
View File
@@ -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;
@@ -475,7 +475,8 @@ impl Codex {
let fs = environment
.as_ref()
.map(|environment| environment.get_filesystem());
let plugin_outcome = plugins_manager.plugins_for_config(&config).await;
let plugins_input = config.plugins_config_input();
let plugin_outcome = plugins_manager.plugins_for_config(&plugins_input).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, fs).await;
@@ -2666,7 +2667,7 @@ impl Session {
let loaded_plugins = self
.services
.plugins_manager
.plugins_for_config(&turn_context.config)
.plugins_for_config(&turn_context.config.plugins_config_input())
.await;
if let Some(plugin_instructions) =
AvailablePluginsInstructions::from_plugins(loaded_plugins.capability_summaries())
@@ -3362,7 +3363,8 @@ async fn build_hooks_for_config(
let _ = hook_shell_argv.pop();
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).await;
let plugins_input = config.plugins_config_input();
let plugin_outcome = plugins_manager.plugins_for_config(&plugins_input).await;
(
plugin_outcome.effective_plugin_hook_sources(),
plugin_outcome.effective_plugin_hook_warnings(),
+2 -2
View File
@@ -3558,7 +3558,7 @@ 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.plugins_config_input())
.await;
let effective_skill_roots = plugin_outcome.effective_skill_roots();
let skills_input =
@@ -4987,7 +4987,7 @@ where
let plugin_outcome = services
.plugins_manager
.plugins_for_config(&per_turn_config)
.plugins_for_config(&per_turn_config.plugins_config_input())
.await;
let effective_skill_roots = plugin_outcome.effective_skill_roots();
let skills_input =
+2 -2
View File
@@ -170,7 +170,7 @@ 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.plugins_config_input())
.await;
// Structured plugin:// mentions are resolved from the current session's
// enabled plugins, then converted into turn-scoped guidance below.
@@ -1124,7 +1124,7 @@ 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.plugins_config_input())
.await;
let mut effective_explicitly_enabled_connectors = explicitly_enabled_connectors.clone();
+1 -1
View File
@@ -696,7 +696,7 @@ impl Session {
let plugin_outcome = self
.services
.plugins_manager
.plugins_for_config(&per_turn_config)
.plugins_for_config(&per_turn_config.plugins_config_input())
.await;
let effective_skill_roots = plugin_outcome.effective_skill_roots();
let skills_input = skills_load_input_from_config(&per_turn_config, effective_skill_roots);
+3 -2
View File
@@ -16,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);
@@ -61,7 +61,8 @@ impl SkillsWatcher {
plugins_manager: &PluginsManager,
fs: Option<Arc<dyn codex_exec_server::ExecutorFileSystem>>,
) -> WatchRegistration {
let plugin_outcome = plugins_manager.plugins_for_config(config).await;
let plugins_input = config.plugins_config_input();
let plugin_outcome = plugins_manager.plugins_for_config(&plugins_input).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
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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;
@@ -314,10 +314,11 @@ 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 {
let plugins_input = config.plugins_config_input();
plugins_manager
.list_marketplaces_for_config(config, &[])
.list_marketplaces_for_config(&plugins_input, &[])
.ok()
.into_iter()
.flat_map(|outcome| outcome.marketplaces)
@@ -1,6 +1,4 @@
use super::*;
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;
@@ -11,6 +9,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;
+1 -1
View File
@@ -48,7 +48,6 @@ use crate::legacy_core::config::ConfigOverrides;
use crate::legacy_core::config::edit::ConfigEdit;
use crate::legacy_core::config::edit::ConfigEditsBuilder;
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;
@@ -126,6 +125,7 @@ use codex_app_server_protocol::TurnStatus;
use codex_config::ConfigLayerStackOrdering;
use codex_config::types::ApprovalsReviewer;
use codex_config::types::ModelAvailabilityNuxConfig;
use codex_core_plugins::PluginsManager;
use codex_exec_server::EnvironmentManager;
use codex_features::Feature;
use codex_model_provider::create_model_provider;
+2 -1
View File
@@ -307,8 +307,9 @@ impl App {
}
tokio::spawn(async move {
let plugins_input = config.plugins_config_input();
let plugins = PluginsManager::new(config.codex_home.to_path_buf())
.plugins_for_config(&config)
.plugins_for_config(&plugins_input)
.await
.capability_summaries()
.to_vec();