driven/agents/
registry.rs1use super::Agent;
6use crate::{DrivenError, Result};
7use std::collections::HashMap;
8use std::path::Path;
9
10#[derive(Debug, Default)]
12pub struct AgentRegistry {
13 agents: HashMap<String, Agent>,
14}
15
16impl AgentRegistry {
17 pub fn new() -> Self {
19 Self {
20 agents: HashMap::new(),
21 }
22 }
23
24 pub fn load_builtin(&mut self) {
26 let builtin_agents = super::builtin::all_agents();
27 for agent in builtin_agents {
28 self.agents.insert(agent.id.clone(), agent);
29 }
30 }
31
32 pub fn load_custom(&mut self, path: &Path) -> Result<()> {
34 let content = std::fs::read_to_string(path)?;
35 let agents: Vec<Agent> = serde_yaml::from_str(&content)
36 .map_err(|e| DrivenError::Config(format!("Failed to parse agent YAML: {}", e)))?;
37
38 for agent in agents {
39 self.register(agent)?;
40 }
41 Ok(())
42 }
43
44 pub fn load_custom_dir(&mut self, dir: &Path) -> Result<()> {
46 if !dir.exists() {
47 return Ok(());
48 }
49
50 for entry in std::fs::read_dir(dir)? {
51 let entry = entry?;
52 let path = entry.path();
53 if path
54 .extension()
55 .is_some_and(|ext| ext == "yaml" || ext == "yml")
56 {
57 self.load_custom(&path)?;
58 }
59 }
60 Ok(())
61 }
62
63 pub fn register(&mut self, agent: Agent) -> Result<()> {
65 if self.agents.contains_key(&agent.id) {
66 return Err(DrivenError::Config(format!(
67 "Agent with id '{}' already registered",
68 agent.id
69 )));
70 }
71 self.agents.insert(agent.id.clone(), agent);
72 Ok(())
73 }
74
75 pub fn get(&self, id: &str) -> Option<&Agent> {
77 self.agents.get(id)
78 }
79
80 pub fn get_mut(&mut self, id: &str) -> Option<&mut Agent> {
82 self.agents.get_mut(id)
83 }
84
85 pub fn list(&self) -> Vec<&Agent> {
87 self.agents.values().collect()
88 }
89
90 pub fn list_by_capability(&self, capability: &str) -> Vec<&Agent> {
92 self.agents
93 .values()
94 .filter(|a| a.has_capability(capability))
95 .collect()
96 }
97
98 pub fn list_by_workflow(&self, workflow: &str) -> Vec<&Agent> {
100 self.agents
101 .values()
102 .filter(|a| a.workflows.iter().any(|w| w == workflow))
103 .collect()
104 }
105
106 pub fn list_builtin(&self) -> Vec<&Agent> {
108 self.agents.values().filter(|a| a.builtin).collect()
109 }
110
111 pub fn list_custom(&self) -> Vec<&Agent> {
113 self.agents.values().filter(|a| !a.builtin).collect()
114 }
115
116 pub fn remove(&mut self, id: &str) -> Option<Agent> {
118 self.agents.remove(id)
119 }
120
121 pub fn contains(&self, id: &str) -> bool {
123 self.agents.contains_key(id)
124 }
125
126 pub fn len(&self) -> usize {
128 self.agents.len()
129 }
130
131 pub fn is_empty(&self) -> bool {
133 self.agents.is_empty()
134 }
135
136 pub fn delegate(&self, from_agent_id: &str, to_agent_id: &str) -> Result<&Agent> {
138 let from_agent = self.get(from_agent_id).ok_or_else(|| {
139 DrivenError::Config(format!("Source agent '{}' not found", from_agent_id))
140 })?;
141
142 if !from_agent.can_delegate_to(to_agent_id) {
143 return Err(DrivenError::Config(format!(
144 "Agent '{}' cannot delegate to '{}'",
145 from_agent_id, to_agent_id
146 )));
147 }
148
149 self.get(to_agent_id)
150 .ok_or_else(|| DrivenError::Config(format!("Target agent '{}' not found", to_agent_id)))
151 }
152
153 pub fn process_delegation(
155 &self,
156 request: &super::DelegationRequest,
157 ) -> Result<super::DelegationResult> {
158 let target_agent = self.delegate(&request.from_agent, &request.to_agent)?;
160
161 Ok(super::DelegationResult::success(
163 &target_agent.id,
164 Some(format!(
165 "Delegated task '{}' from {} to {}",
166 request.task, request.from_agent, request.to_agent
167 )),
168 ))
169 }
170
171 pub fn get_delegates(&self, agent_id: &str) -> Vec<&Agent> {
173 let agent = match self.get(agent_id) {
174 Some(a) => a,
175 None => return Vec::new(),
176 };
177
178 agent
179 .delegates_to
180 .iter()
181 .filter_map(|id| self.get(id))
182 .collect()
183 }
184
185 pub fn find_by_capability(&self, capability: &str) -> Option<&Agent> {
187 self.agents.values().find(|a| a.has_capability(capability))
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn test_registry_new() {
197 let registry = AgentRegistry::new();
198 assert!(registry.is_empty());
199 }
200
201 #[test]
202 fn test_load_builtin() {
203 let mut registry = AgentRegistry::new();
204 registry.load_builtin();
205 assert!(!registry.is_empty());
206 assert!(registry.len() >= 15);
207 }
208
209 #[test]
210 fn test_get_agent() {
211 let mut registry = AgentRegistry::new();
212 registry.load_builtin();
213
214 let pm = registry.get("pm");
215 assert!(pm.is_some());
216 assert_eq!(pm.unwrap().name, "Product Manager");
217 }
218
219 #[test]
220 fn test_list_by_capability() {
221 let mut registry = AgentRegistry::new();
222 registry.load_builtin();
223
224 let architects = registry.list_by_capability("architecture");
225 assert!(!architects.is_empty());
226 }
227
228 #[test]
229 fn test_delegation() {
230 let mut registry = AgentRegistry::new();
231 registry.load_builtin();
232
233 let result = registry.delegate("bmad-master", "architect");
235 assert!(result.is_ok());
236 }
237}
238
239#[test]
240fn test_custom_agent_yaml_parsing() {
241 use super::*;
242
243 let yaml = r#"
244- id: custom-agent
245 name: Custom Agent
246 title: Custom Title
247 icon: "🔧"
248 persona:
249 role: Custom Role
250 identity: Custom identity description
251 communication_style: Custom style
252 principles:
253 - Principle 1
254 - Principle 2
255 traits:
256 - Trait 1
257 - Trait 2
258 capabilities:
259 - custom-capability
260 workflows:
261 - custom-workflow
262 delegates_to:
263 - developer
264 builtin: false
265"#;
266
267 let agents: Vec<Agent> = serde_yaml::from_str(yaml).unwrap();
268 assert_eq!(agents.len(), 1);
269
270 let agent = &agents[0];
271 assert_eq!(agent.id, "custom-agent");
272 assert_eq!(agent.name, "Custom Agent");
273 assert!(agent.has_capability("custom-capability"));
274 assert!(agent.can_delegate_to("developer"));
275}