Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion codex-rs/app-server/src/skills_watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ impl SkillsWatcher {
.await
.into_iter()
// Plugin roots are invalidated by plugin lifecycle operations.
.filter(|root| root.plugin_id.is_none())
.filter(|root| root.plugin_identity.is_none())
.map(|root| WatchPath {
path: root.path.into_path_buf(),
recursive: true,
Expand Down
1 change: 1 addition & 0 deletions codex-rs/core-plugins/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod provider;
pub mod remote;
pub mod remote_bundle;
pub mod remote_legacy;
mod remote_plugin_id_resolver;
mod script_attribution;
pub mod startup_sync;
pub mod store;
Expand Down
77 changes: 64 additions & 13 deletions codex-rs/core-plugins/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ use crate::marketplace_policy::configured_plugins_from_stack;
use crate::npm_source::materialize_npm_plugin_source;
use crate::remote::REMOTE_GLOBAL_MARKETPLACE_NAME;
use crate::remote::RemoteInstalledPlugin;
use crate::remote_plugin_id_resolver::RemoteInstalledPluginsSnapshot;
use crate::remote_plugin_id_resolver::RemotePluginIdResolver;
use crate::store::PluginStore;
use crate::store::plugin_version_for_source;
use crate::store::plugin_version_for_source_with_fallback_manifest;
Expand Down Expand Up @@ -45,6 +47,7 @@ use codex_protocol::protocol::SkillScope;
use codex_skills::SkillConfigRules;
use codex_skills::SkillMetadata;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_plugins::PluginIdentity;
use codex_utils_plugins::SkillDiscoveryMode;
use codex_utils_plugins::find_plugin_manifest_path;
use serde_json::Value as JsonValue;
Expand Down Expand Up @@ -78,6 +81,7 @@ enum PluginLoadScope<'a> {
restriction_product: Option<Product>,
skill_config_rules: &'a SkillConfigRules,
plugin_skill_snapshots: Option<&'a PluginSkillSnapshots>,
remote_plugin_id_resolver: &'a RemotePluginIdResolver,
root_scan_slots: Arc<Semaphore>,
},
HooksOnly,
Expand Down Expand Up @@ -117,14 +121,18 @@ pub(crate) fn log_plugin_load_errors(plugins: &[LoadedPlugin<McpServerConfig>])
#[instrument(level = "trace", skip_all)]
pub(crate) async fn load_plugins_from_layer_stack(
config_layer_stack: &ConfigLayerStack,
extra_plugins: HashMap<String, PluginConfig>,
remote_installed_plugins_snapshot: RemoteInstalledPluginsSnapshot,
store: &PluginStore,
plugin_skill_snapshots: Option<&PluginSkillSnapshots>,
restriction_product: Option<Product>,
remote_global_catalog_active: bool,
root_scan_slots: Arc<Semaphore>,
) -> Vec<LoadedPlugin<McpServerConfig>> {
let skill_config_rules = skill_config_rules_from_stack(config_layer_stack);
let RemoteInstalledPluginsSnapshot {
configs: extra_plugins,
remote_plugin_id_resolver,
} = remote_installed_plugins_snapshot;
load_plugins_from_layer_stack_with_scope(
config_layer_stack,
extra_plugins,
Expand All @@ -134,6 +142,7 @@ pub(crate) async fn load_plugins_from_layer_stack(
restriction_product,
skill_config_rules: &skill_config_rules,
plugin_skill_snapshots,
remote_plugin_id_resolver: &remote_plugin_id_resolver,
root_scan_slots,
},
)
Expand Down Expand Up @@ -753,18 +762,20 @@ async fn load_plugin(
scope: &PluginLoadScope<'_>,
) -> LoadedPlugin<McpServerConfig> {
let plugin_id = PluginId::parse(&config_name);
let active_plugin_root = plugin_id
let active_plugin_installation = plugin_id
.as_ref()
.ok()
.and_then(|plugin_id| store.active_plugin_root(plugin_id));
let root = active_plugin_root
.clone()
.and_then(|plugin_id| store.active_plugin_installation(plugin_id));
let root = active_plugin_installation
.as_ref()
.map(|installation| installation.root.clone())
.unwrap_or_else(|| match &plugin_id {
Ok(plugin_id) => store.plugin_base_root(plugin_id),
Err(_) => store.root().clone(),
});
let mut loaded_plugin = LoadedPlugin {
config_name,
remote_plugin_id: None,
manifest_name: None,
plugin_namespace: None,
manifest_description: None,
Expand All @@ -784,20 +795,30 @@ async fn load_plugin(
return loaded_plugin;
}

let (loaded_plugin_id, plugin_root) = match plugin_id {
let (loaded_plugin_id, installation) = match plugin_id {
Ok(plugin_id) => {
let Some(plugin_root) = active_plugin_root else {
let Some(installation) = active_plugin_installation else {
loaded_plugin.error = Some("plugin is not installed".to_string());
return loaded_plugin;
};
(plugin_id, plugin_root)
(plugin_id, installation)
}
Err(err) => {
loaded_plugin.error = Some(err.to_string());
return loaded_plugin;
}
};

loaded_plugin.remote_plugin_id = match scope {
PluginLoadScope::AllCapabilities {
remote_plugin_id_resolver,
..
} => remote_plugin_id_resolver.remote_plugin_id_for_installation(&installation),
PluginLoadScope::HooksOnly => None,
};

let plugin_root = installation.root;

if !plugin_root.as_path().is_dir() {
loaded_plugin.error = Some("path does not exist or is not a directory".to_string());
return loaded_plugin;
Expand All @@ -815,14 +836,19 @@ async fn load_plugin(
restriction_product,
skill_config_rules,
plugin_skill_snapshots,
remote_plugin_id_resolver: _,
root_scan_slots,
} => {
loaded_plugin.manifest_name = Some(manifest.display_name().to_string());
loaded_plugin.manifest_description = manifest.description.clone();
loaded_plugin.skill_roots = plugin_skill_roots(&plugin_root, manifest_paths);
let resolved_skills = load_plugin_skills(
let plugin_identity = PluginIdentity {
plugin_id: loaded_plugin_id.as_key(),
remote_plugin_id: loaded_plugin.remote_plugin_id.clone(),
};
let resolved_skills = load_plugin_skills_with_identity(
&plugin_root,
&loaded_plugin_id,
&plugin_identity,
&manifest,
*restriction_product,
skill_config_rules,
Expand Down Expand Up @@ -926,10 +952,35 @@ pub async fn load_plugin_skills(
skill_config_rules: &SkillConfigRules,
plugin_skill_snapshots: Option<&PluginSkillSnapshots>,
root_scan_slots: Arc<Semaphore>,
) -> ResolvedPluginSkills {
let plugin_identity = PluginIdentity {
plugin_id: plugin_id.as_key(),
remote_plugin_id: None,
};
load_plugin_skills_with_identity(
plugin_root,
&plugin_identity,
manifest,
restriction_product,
skill_config_rules,
plugin_skill_snapshots,
root_scan_slots,
)
.await
}

pub(crate) async fn load_plugin_skills_with_identity(
plugin_root: &AbsolutePathBuf,
plugin_identity: &PluginIdentity,
manifest: &PluginManifest,
restriction_product: Option<Product>,
skill_config_rules: &SkillConfigRules,
plugin_skill_snapshots: Option<&PluginSkillSnapshots>,
root_scan_slots: Arc<Semaphore>,
) -> ResolvedPluginSkills {
load_plugin_skill_inventory(
plugin_root,
plugin_id,
plugin_identity,
manifest,
restriction_product,
plugin_skill_snapshots,
Expand All @@ -941,7 +992,7 @@ pub async fn load_plugin_skills(

pub(crate) async fn load_plugin_skill_inventory(
plugin_root: &AbsolutePathBuf,
plugin_id: &PluginId,
plugin_identity: &PluginIdentity,
manifest: &PluginManifest,
restriction_product: Option<Product>,
plugin_skill_snapshots: Option<&PluginSkillSnapshots>,
Expand All @@ -953,7 +1004,7 @@ pub(crate) async fn load_plugin_skill_inventory(
path,
scope: SkillScope::User,
file_system: Arc::clone(&LOCAL_FS),
plugin_id: Some(plugin_id.as_key()),
plugin_identity: Some(plugin_identity.clone()),
plugin_namespace: Some(manifest.name.clone()),
plugin_root: Some(plugin_root.clone()),
discovery_mode: SkillDiscoveryMode::Recursive,
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core-plugins/src/loader_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ enabled = true

let full = load_plugins_from_layer_stack(
&stack,
HashMap::new(),
RemoteInstalledPluginsSnapshot::default(),
&store,
/*plugin_skill_snapshots*/ None,
Some(Product::Codex),
Expand Down
70 changes: 40 additions & 30 deletions codex-rs/core-plugins/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::loader::load_plugin_apps_from_manifest;
use crate::loader::load_plugin_hooks;
use crate::loader::load_plugin_hooks_from_layer_stack;
use crate::loader::load_plugin_mcp_servers_from_manifest;
use crate::loader::load_plugin_skills;
use crate::loader::load_plugin_skills_with_identity;
use crate::loader::load_plugins_from_layer_stack;
use crate::loader::log_plugin_load_errors;
use crate::loader::materialize_marketplace_plugin_source;
Expand Down Expand Up @@ -51,6 +51,9 @@ use crate::remote::RemotePluginScope;
use crate::remote::RemotePluginServiceConfig;
use crate::remote_legacy::RemotePluginFetchError;
use crate::remote_legacy::RemotePluginMutationError;
use crate::remote_plugin_id_resolver::RemoteInstalledPluginsSnapshot;
use crate::remote_plugin_id_resolver::RemotePluginIdResolver;
use crate::remote_plugin_id_resolver::persisted_remote_plugin_id_for_installation;
use crate::startup_sync::curated_plugins_api_marketplace_path;
use crate::startup_sync::curated_plugins_repo_path;
use crate::startup_sync::read_curated_plugins_sha;
Expand Down Expand Up @@ -91,6 +94,7 @@ use codex_tools::DiscoverablePluginInfo;
use codex_tools::DiscoverableTool;
use codex_tools::filter_request_plugin_install_discoverable_tools_for_client;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_plugins::PluginIdentity;
use codex_utils_plugins::PluginSkillRoot;
use std::collections::BTreeSet;
use std::collections::HashMap;
Expand Down Expand Up @@ -643,7 +647,7 @@ impl PluginsManager {
let plugin_skill_snapshots = PluginSkillSnapshots::for_plugin_load();
let plugins = load_plugins_from_layer_stack(
&config.config_layer_stack,
self.remote_installed_plugin_configs(),
self.remote_installed_plugins_snapshot(),
&self.store,
Some(&plugin_skill_snapshots),
self.restriction_product,
Expand Down Expand Up @@ -729,7 +733,7 @@ impl PluginsManager {
}
let plugins = load_plugins_from_layer_stack(
config_layer_stack,
self.remote_installed_plugin_configs(),
self.remote_installed_plugins_snapshot(),
&self.store,
/*plugin_skill_snapshots*/ None,
self.restriction_product,
Expand Down Expand Up @@ -818,35 +822,37 @@ impl PluginsManager {
remote_installed_plugins_to_config(plugins, &self.store)
}

fn remote_plugin_id_for(&self, plugin_id: &PluginId) -> Option<String> {
let cached_remote_plugin_id = {
let cache = match self.remote_installed_plugins_cache.read() {
Ok(cache) => cache,
Err(err) => err.into_inner(),
};
cache.as_ref().and_then(|plugins| {
plugins.iter().find_map(|plugin| {
(plugin.name == plugin_id.plugin_name
&& plugin.marketplace_name == plugin_id.marketplace_name)
.then(|| plugin.id.clone())
})
})
fn remote_installed_plugins_snapshot(&self) -> RemoteInstalledPluginsSnapshot {
let cache = match self.remote_installed_plugins_cache.read() {
Ok(cache) => cache,
Err(err) => err.into_inner(),
};
if cached_remote_plugin_id.is_some() {
return cached_remote_plugin_id;
let Some(plugins) = cache.as_ref() else {
return RemoteInstalledPluginsSnapshot::default();
};

RemoteInstalledPluginsSnapshot {
configs: remote_installed_plugins_to_config(plugins, &self.store),
remote_plugin_id_resolver: RemotePluginIdResolver::new(plugins),
}
}

match self.store.remote_plugin_id(plugin_id) {
Ok(remote_plugin_id) => remote_plugin_id,
Err(err) => {
tracing::warn!(
plugin_id = %plugin_id.as_key(),
error = %err,
"failed to read persisted remote plugin identity"
);
None
}
fn remote_plugin_id_for(&self, plugin_id: &PluginId) -> Option<String> {
let cache = match self.remote_installed_plugins_cache.read() {
Ok(cache) => cache,
Err(err) => err.into_inner(),
};
if let Some(plugins) = cache.as_ref() {
return plugins.iter().find_map(|plugin| {
(plugin.name == plugin_id.plugin_name
&& plugin.marketplace_name == plugin_id.marketplace_name)
.then(|| plugin.id.clone())
});
}
drop(cache);

let installation = self.store.active_plugin_installation(plugin_id)?;
persisted_remote_plugin_id_for_installation(&installation)
}

pub async fn telemetry_metadata_for_installed_plugin(
Expand Down Expand Up @@ -1958,9 +1964,13 @@ impl PluginsManager {
manifest.interface.clone(),
marketplace_category,
);
let resolved_skills = load_plugin_skills(
let plugin_identity = PluginIdentity {
plugin_id: plugin_id.as_key(),
remote_plugin_id: self.remote_plugin_id_for(&plugin_id),
};
let resolved_skills = load_plugin_skills_with_identity(
&source_path,
&plugin_id,
&plugin_identity,
&manifest,
self.restriction_product,
&codex_core_skills::config_rules::skill_config_rules_from_stack(
Expand Down
Loading
Loading