Skip to main content

agent_procs/
config.rs

1use serde::Deserialize;
2use std::collections::HashMap;
3use std::path::{Path, PathBuf};
4
5#[derive(Debug, Deserialize)]
6pub struct ProjectConfig {
7    pub processes: HashMap<String, ProcessDef>,
8}
9
10#[derive(Debug, Deserialize)]
11pub struct ProcessDef {
12    pub cmd: String,
13    pub cwd: Option<String>,
14    #[serde(default)]
15    pub env: HashMap<String, String>,
16    pub ready: Option<String>,
17    #[serde(default)]
18    pub depends_on: Vec<String>,
19}
20
21impl ProjectConfig {
22    pub fn startup_order(&self) -> Result<Vec<Vec<String>>, String> {
23        let mut in_degree: HashMap<&str, usize> = HashMap::new();
24        let mut dependents: HashMap<&str, Vec<&str>> = HashMap::new();
25
26        for name in self.processes.keys() {
27            in_degree.entry(name.as_str()).or_insert(0);
28        }
29        for (name, def) in &self.processes {
30            for dep in &def.depends_on {
31                if !self.processes.contains_key(dep) {
32                    return Err(format!("unknown dependency: {} depends on {}", name, dep));
33                }
34                dependents.entry(dep.as_str()).or_default().push(name.as_str());
35                *in_degree.entry(name.as_str()).or_insert(0) += 1;
36            }
37        }
38
39        let mut groups = Vec::new();
40        let mut remaining = in_degree.clone();
41
42        loop {
43            let mut ready: Vec<String> = remaining.iter()
44                .filter(|(_, &deg)| deg == 0)
45                .map(|(&name, _)| name.to_string())
46                .collect();
47
48            if ready.is_empty() {
49                if remaining.is_empty() { break; }
50                else { return Err("dependency cycle detected".into()); }
51            }
52
53            for name in &ready {
54                remaining.remove(name.as_str());
55                if let Some(deps) = dependents.get(name.as_str()) {
56                    for dep in deps {
57                        if let Some(deg) = remaining.get_mut(dep) { *deg -= 1; }
58                    }
59                }
60            }
61            ready.sort();
62            groups.push(ready);
63        }
64        Ok(groups)
65    }
66}
67
68pub fn discover_config(start: &Path) -> Option<PathBuf> {
69    let mut dir = start.to_path_buf();
70    loop {
71        let candidate = dir.join("agent-procs.yaml");
72        if candidate.exists() { return Some(candidate); }
73        if !dir.pop() { return None; }
74    }
75}