Skip to main content

lean_ctx/tools/
ctx_agent.rs

1use crate::core::agents::{AgentRegistry, AgentStatus};
2
3#[allow(clippy::too_many_arguments)]
4pub fn handle(
5    action: &str,
6    agent_type: Option<&str>,
7    role: Option<&str>,
8    project_root: &str,
9    current_agent_id: Option<&str>,
10    message: Option<&str>,
11    category: Option<&str>,
12    to_agent: Option<&str>,
13    status: Option<&str>,
14) -> String {
15    match action {
16        "register" => {
17            let atype = agent_type.unwrap_or("unknown");
18            let mut registry = AgentRegistry::load_or_create();
19            registry.cleanup_stale(24);
20            let agent_id = registry.register(atype, role, project_root);
21            match registry.save() {
22                Ok(()) => format!(
23                    "Agent registered: {agent_id} (type: {atype}, role: {})",
24                    role.unwrap_or("none")
25                ),
26                Err(e) => format!("Registered as {agent_id} but save failed: {e}"),
27            }
28        }
29
30        "list" => {
31            let mut registry = AgentRegistry::load_or_create();
32            registry.cleanup_stale(24);
33            let _ = registry.save();
34
35            let agents = registry.list_active(Some(project_root));
36            if agents.is_empty() {
37                return "No active agents for this project.".to_string();
38            }
39
40            let mut out = format!("Active agents ({}):\n", agents.len());
41            for a in agents {
42                let role_str = a.role.as_deref().unwrap_or("-");
43                let status_msg = a
44                    .status_message
45                    .as_deref()
46                    .map(|m| format!(" — {m}"))
47                    .unwrap_or_default();
48                let age = (chrono::Utc::now() - a.last_active).num_minutes();
49                out.push_str(&format!(
50                    "  {} [{}] role={} status={}{} (last active: {}m ago, pid: {})\n",
51                    a.agent_id, a.agent_type, role_str, a.status, status_msg, age, a.pid
52                ));
53            }
54            out
55        }
56
57        "post" => {
58            let msg = match message {
59                Some(m) => m,
60                None => return "Error: message is required for post".to_string(),
61            };
62            let cat = category.unwrap_or("status");
63            let from = current_agent_id.unwrap_or("anonymous");
64            let mut registry = AgentRegistry::load_or_create();
65            let msg_id = registry.post_message(from, to_agent, cat, msg);
66            match registry.save() {
67                Ok(()) => {
68                    let target = to_agent.unwrap_or("all agents (broadcast)");
69                    format!("Posted [{cat}] to {target}: {msg} (id: {msg_id})")
70                }
71                Err(e) => format!("Posted but save failed: {e}"),
72            }
73        }
74
75        "read" => {
76            let agent_id = match current_agent_id {
77                Some(id) => id,
78                None => {
79                    return "Error: agent must be registered first (use action=register)"
80                        .to_string()
81                }
82            };
83            let mut registry = AgentRegistry::load_or_create();
84            let messages = registry.read_unread(agent_id);
85
86            if messages.is_empty() {
87                let _ = registry.save();
88                return "No new messages.".to_string();
89            }
90
91            let mut out = format!("New messages ({}):\n", messages.len());
92            for m in &messages {
93                let age = (chrono::Utc::now() - m.timestamp).num_minutes();
94                out.push_str(&format!(
95                    "  [{}] from {} ({}m ago): {}\n",
96                    m.category, m.from_agent, age, m.message
97                ));
98            }
99            let _ = registry.save();
100            out
101        }
102
103        "status" => {
104            let agent_id = match current_agent_id {
105                Some(id) => id,
106                None => return "Error: agent must be registered first".to_string(),
107            };
108            let new_status = match status {
109                Some("active") => AgentStatus::Active,
110                Some("idle") => AgentStatus::Idle,
111                Some("finished") => AgentStatus::Finished,
112                Some(other) => {
113                    return format!("Unknown status: {other}. Use: active, idle, finished")
114                }
115                None => return "Error: status value is required".to_string(),
116            };
117            let status_msg = message;
118
119            let mut registry = AgentRegistry::load_or_create();
120            registry.set_status(agent_id, new_status.clone(), status_msg);
121            match registry.save() {
122                Ok(()) => format!(
123                    "Status updated: {} → {}{}",
124                    agent_id,
125                    new_status,
126                    status_msg.map(|m| format!(" ({m})")).unwrap_or_default()
127                ),
128                Err(e) => format!("Status set but save failed: {e}"),
129            }
130        }
131
132        "info" => {
133            let registry = AgentRegistry::load_or_create();
134            let total = registry.agents.len();
135            let active = registry
136                .agents
137                .iter()
138                .filter(|a| a.status == AgentStatus::Active)
139                .count();
140            let messages = registry.scratchpad.len();
141            format!(
142                "Agent Registry: {total} total, {active} active, {messages} scratchpad entries\nLast updated: {}",
143                registry.updated_at.format("%Y-%m-%d %H:%M UTC")
144            )
145        }
146
147        _ => format!("Unknown action: {action}. Use: register, list, post, read, status, info"),
148    }
149}