Skip to main content

mur_common/skill/
peers.rs

1//! Peer agent enumeration (M7a).
2//!
3//! Scans `<MUR_HOME>/agents/*/` for peer agent directories on the same host.
4//! Read-only — never writes to peer state.
5
6use std::path::{Path, PathBuf};
7
8#[derive(Debug, Clone, serde::Serialize)]
9pub struct PeerAgent {
10    pub name: String,
11    pub home_path: PathBuf,
12    pub skills_count: usize,
13}
14
15/// Enumerate peer agents on this host.
16///
17/// Skips hidden directories (names starting with `.`).
18pub fn list_peer_agents(mur_home: &Path) -> std::io::Result<Vec<PeerAgent>> {
19    let agents_dir = mur_home.join("agents");
20    if !agents_dir.exists() {
21        return Ok(vec![]);
22    }
23    let mut peers = Vec::new();
24    for entry in std::fs::read_dir(&agents_dir)? {
25        let entry = entry?;
26        if !entry.file_type()?.is_dir() {
27            continue;
28        }
29        let name = entry.file_name().to_string_lossy().into_owned();
30        if name.starts_with('.') {
31            continue;
32        }
33        let home_path = entry.path();
34        let skills_dir = home_path.join("skills");
35        let skills_count = if skills_dir.exists() {
36            std::fs::read_dir(&skills_dir)?
37                .filter_map(|e| e.ok())
38                .filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false))
39                .count()
40        } else {
41            0
42        };
43        peers.push(PeerAgent {
44            name,
45            home_path,
46            skills_count,
47        });
48    }
49    peers.sort_by(|a, b| a.name.cmp(&b.name));
50    Ok(peers)
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56    use tempfile::tempdir;
57
58    #[test]
59    fn lists_agent_directories() {
60        let dir = tempdir().unwrap();
61        let home = dir.path();
62        std::fs::create_dir_all(home.join("agents").join("alice").join("skills").join("s1"))
63            .unwrap();
64        std::fs::create_dir_all(home.join("agents").join("bob").join("skills")).unwrap();
65        std::fs::create_dir_all(home.join("agents").join(".hidden")).unwrap();
66
67        let peers = list_peer_agents(home).unwrap();
68        assert_eq!(peers.len(), 2);
69        assert_eq!(peers[0].name, "alice");
70        assert_eq!(peers[0].skills_count, 1);
71        assert_eq!(peers[1].name, "bob");
72        assert_eq!(peers[1].skills_count, 0);
73    }
74
75    #[test]
76    fn empty_when_agents_dir_missing() {
77        let dir = tempdir().unwrap();
78        assert!(list_peer_agents(dir.path()).unwrap().is_empty());
79    }
80}