mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
Support plugin agent roles
This commit is contained in:
@@ -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"),
|
||||
}]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user