use std::path::{Path, PathBuf};
use super::PluginRegistry;
use super::activation::PluginActivationCapability;
use super::registry::verify_plugin_component_authority;
use super::types::{LoadedPlugin, PluginAuthority, PluginScope};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginComponentSource {
pub plugin_name: String,
pub path: PathBuf,
pub authority: PluginAuthority,
}
fn component_paths(plugin: &LoadedPlugin, capability: PluginActivationCapability) -> &[PathBuf] {
match capability {
PluginActivationCapability::Commands => &plugin.components.commands,
PluginActivationCapability::Agents => &plugin.components.agents,
PluginActivationCapability::Hooks => &plugin.components.hooks,
PluginActivationCapability::Skills
| PluginActivationCapability::McpStdio
| PluginActivationCapability::McpRemote
| PluginActivationCapability::Lsp
| PluginActivationCapability::Native
| PluginActivationCapability::FilesystemRoots
| PluginActivationCapability::LifecycleMutation => &[],
}
}
fn scope_precedence(scope: PluginScope) -> u8 {
match scope {
PluginScope::Workspace => 0,
PluginScope::User => 1,
PluginScope::Builtin => 2,
}
}
pub fn active_component_sources(
registry: &PluginRegistry,
capability: PluginActivationCapability,
) -> (Vec<PluginComponentSource>, Vec<String>) {
let mut plugins = registry
.active_plugins()
.into_iter()
.filter(|plugin| plugin.component_active(capability))
.collect::<Vec<_>>();
plugins.sort_by(|left, right| {
scope_precedence(left.scope)
.cmp(&scope_precedence(right.scope))
.then_with(|| left.name().cmp(right.name()))
.then_with(|| left.id.cmp(&right.id))
});
let mut sources = Vec::new();
let mut errors = Vec::new();
for plugin in plugins {
let Some(authority) = registry.authority_for(plugin.id.as_str()) else {
errors.push(format!(
"Plugin `{}` has no persisted runtime authority",
plugin.name()
));
continue;
};
if let Err(reason) = verify_plugin_component_authority(&authority, capability) {
errors.push(format!(
"Plugin `{}` {} adapter was denied: {reason}",
plugin.name(),
capability.as_str()
));
continue;
}
let Some(staged_root) = plugin.staged_root.as_deref() else {
errors.push(format!(
"Plugin `{}` has no immutable runtime snapshot",
plugin.name()
));
continue;
};
for source_path in component_paths(plugin, capability) {
match staged_component_path(&plugin.canonical_root, staged_root, source_path) {
Ok(path) => sources.push(PluginComponentSource {
plugin_name: plugin.name().to_string(),
path,
authority: authority.clone(),
}),
Err(reason) => errors.push(format!(
"Plugin `{}` {} component was denied: {reason}",
plugin.name(),
capability.as_str()
)),
}
}
}
(sources, errors)
}
fn staged_component_path(
canonical_root: &Path,
staged_root: &Path,
source_path: &Path,
) -> Result<PathBuf, String> {
let relative = source_path
.strip_prefix(canonical_root)
.map_err(|_| "reviewed component escaped the plugin root".to_string())?;
let staged_root = staged_root
.canonicalize()
.map_err(|_| "runtime snapshot is unavailable".to_string())?;
let candidate = staged_root.join(relative);
let candidate = candidate
.canonicalize()
.map_err(|_| "runtime component is unavailable".to_string())?;
if !candidate.starts_with(&staged_root) {
return Err("runtime component escaped the immutable snapshot".to_string());
}
Ok(candidate)
}