Support plugin agent roles

This commit is contained in:
charlesgong-openai
2026-07-07 22:54:32 -04:00
parent aaa30f79c2
commit 63d51ea592
17 changed files with 626 additions and 4 deletions
+47
View File
@@ -2,6 +2,7 @@ use std::collections::HashMap;
use std::collections::HashSet;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_plugins::PluginAgentRoot;
use codex_utils_plugins::PluginSkillRoot;
use crate::AppConnectorId;
@@ -21,6 +22,7 @@ pub struct LoadedPlugin<M> {
pub manifest_description: Option<String>,
pub root: AbsolutePathBuf,
pub enabled: bool,
pub agent_roots: Vec<AbsolutePathBuf>,
pub skill_roots: Vec<AbsolutePathBuf>,
pub disabled_skill_paths: HashSet<AbsolutePathBuf>,
pub has_enabled_skills: bool,
@@ -123,6 +125,25 @@ impl<M: Clone> PluginLoadOutcome<M> {
skill_roots
}
pub fn effective_plugin_agent_roots(&self) -> Vec<PluginAgentRoot> {
let mut agent_roots = Vec::new();
let mut seen_paths = HashSet::new();
for plugin in self.plugins.iter().filter(|plugin| plugin.is_active()) {
for path in &plugin.agent_roots {
if seen_paths.insert(path.clone()) {
agent_roots.push(PluginAgentRoot {
path: path.clone(),
plugin_id: plugin.config_name.clone(),
plugin_root: plugin.root.clone(),
});
}
}
}
agent_roots.sort_unstable_by(|a, b| a.path.cmp(&b.path));
agent_roots
}
pub fn effective_plugin_skill_roots(&self) -> Vec<PluginSkillRoot> {
let mut skill_roots = Vec::new();
let mut seen_paths = HashSet::new();
@@ -197,6 +218,8 @@ impl<M: Clone> PluginLoadOutcome<M> {
pub trait EffectiveSkillRoots {
fn effective_skill_roots(&self) -> Vec<AbsolutePathBuf>;
fn effective_plugin_agent_roots(&self) -> Vec<PluginAgentRoot>;
fn effective_plugin_skill_roots(&self) -> Vec<PluginSkillRoot>;
}
@@ -205,6 +228,10 @@ impl<M: Clone> EffectiveSkillRoots for PluginLoadOutcome<M> {
PluginLoadOutcome::effective_skill_roots(self)
}
fn effective_plugin_agent_roots(&self) -> Vec<PluginAgentRoot> {
PluginLoadOutcome::effective_plugin_agent_roots(self)
}
fn effective_plugin_skill_roots(&self) -> Vec<PluginSkillRoot> {
PluginLoadOutcome::effective_plugin_skill_roots(self)
}
@@ -232,6 +259,7 @@ mod tests {
manifest_description: None,
root: test_path(config_name),
enabled: true,
agent_roots: Vec::new(),
skill_roots,
disabled_skill_paths: HashSet::new(),
has_enabled_skills: true,
@@ -261,4 +289,23 @@ mod tests {
}]
);
}
#[test]
fn effective_plugin_agent_roots_preserves_first_plugin_for_shared_root() {
let shared_root = test_path("shared-agents");
let mut zeta = loaded_plugin("zeta@test", Vec::new());
zeta.agent_roots = vec![shared_root.clone()];
let mut alpha = loaded_plugin("alpha@test", Vec::new());
alpha.agent_roots = vec![shared_root.clone()];
let outcome = PluginLoadOutcome::from_plugins(vec![zeta, alpha]);
assert_eq!(
outcome.effective_plugin_agent_roots(),
vec![PluginAgentRoot {
path: shared_root,
plugin_id: "zeta@test".to_string(),
plugin_root: test_path("zeta@test"),
}]
);
}
}
+6
View File
@@ -17,6 +17,7 @@ pub struct PluginManifest<Resource> {
/// Component resources declared by a plugin manifest.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginManifestPaths<Resource> {
pub agents: Vec<Resource>,
pub skills: Vec<Resource>,
pub mcp_servers: Option<PluginManifestMcpServers<Resource>>,
pub apps: Option<Resource>,
@@ -104,6 +105,7 @@ impl<Resource> PluginManifest<Resource> {
interface,
} = self;
let PluginManifestPaths {
agents,
skills,
mcp_servers,
apps,
@@ -177,6 +179,10 @@ impl<Resource> PluginManifest<Resource> {
description,
keywords,
paths: PluginManifestPaths {
agents: agents
.into_iter()
.map(&mut map)
.collect::<Result<Vec<_>, _>>()?,
skills: skills
.into_iter()
.map(&mut map)
+4
View File
@@ -30,6 +30,7 @@ fn environment_descriptor_binds_every_manifest_resource() {
let root = absolute(std::env::current_dir().expect("cwd").join("plugin-root"));
let root_uri = path_uri(&root);
let manifest_path = root.join(".codex-plugin/plugin.json");
let agents = root.join("agents");
let skills = root.join("skills");
let mcp_servers = root.join(".mcp.json");
let apps = root.join(".app.json");
@@ -43,6 +44,7 @@ fn environment_descriptor_binds_every_manifest_resource() {
description: None,
keywords: Vec::new(),
paths: PluginManifestPaths {
agents: vec![path_uri(&agents)],
skills: vec![path_uri(&skills)],
mcp_servers: Some(PluginManifestMcpServers::Path(path_uri(&mcp_servers))),
apps: Some(path_uri(&apps)),
@@ -77,6 +79,7 @@ fn environment_descriptor_binds_every_manifest_resource() {
description: None,
keywords: Vec::new(),
paths: PluginManifestPaths {
agents: vec![resource("executor-1", &agents)],
skills: vec![resource("executor-1", &skills)],
mcp_servers: Some(PluginManifestMcpServers::Path(resource(
"executor-1",
@@ -109,6 +112,7 @@ fn environment_descriptor_rejects_resources_outside_package_root() {
description: None,
keywords: Vec::new(),
paths: PluginManifestPaths {
agents: Vec::new(),
skills: Vec::new(),
mcp_servers: Some(PluginManifestMcpServers::Path(path_uri(&outside))),
apps: None,