1use super::core::TmaiCore;
7use super::types::{AgentSnapshot, ApiError, TeamSummary, TeamTaskInfo};
8
9impl TmaiCore {
10 pub fn list_agents(&self) -> Vec<AgentSnapshot> {
16 let state = self.state().read();
17 state
18 .agent_order
19 .iter()
20 .filter_map(|id| state.agents.get(id))
21 .map(AgentSnapshot::from_agent)
22 .collect()
23 }
24
25 pub fn get_agent(&self, target: &str) -> Result<AgentSnapshot, ApiError> {
27 let state = self.state().read();
28 state
29 .agents
30 .get(target)
31 .map(AgentSnapshot::from_agent)
32 .ok_or_else(|| ApiError::AgentNotFound {
33 target: target.to_string(),
34 })
35 }
36
37 pub fn selected_agent(&self) -> Result<AgentSnapshot, ApiError> {
39 let state = self.state().read();
40 state
41 .selected_agent()
42 .map(AgentSnapshot::from_agent)
43 .ok_or(ApiError::NoSelection)
44 }
45
46 pub fn attention_count(&self) -> usize {
48 let state = self.state().read();
49 state.attention_count()
50 }
51
52 pub fn agent_count(&self) -> usize {
54 let state = self.state().read();
55 state.agents.len()
56 }
57
58 pub fn agents_needing_attention(&self) -> Vec<AgentSnapshot> {
60 let state = self.state().read();
61 state
62 .agent_order
63 .iter()
64 .filter_map(|id| state.agents.get(id))
65 .filter(|a| a.status.needs_attention())
66 .map(AgentSnapshot::from_agent)
67 .collect()
68 }
69
70 pub fn get_preview(&self, target: &str) -> Result<String, ApiError> {
76 let state = self.state().read();
77 state
78 .agents
79 .get(target)
80 .map(|a| a.last_content_ansi.clone())
81 .ok_or_else(|| ApiError::AgentNotFound {
82 target: target.to_string(),
83 })
84 }
85
86 pub fn get_content(&self, target: &str) -> Result<String, ApiError> {
88 let state = self.state().read();
89 state
90 .agents
91 .get(target)
92 .map(|a| a.last_content.clone())
93 .ok_or_else(|| ApiError::AgentNotFound {
94 target: target.to_string(),
95 })
96 }
97
98 pub fn list_teams(&self) -> Vec<TeamSummary> {
104 let state = self.state().read();
105 let mut teams: Vec<TeamSummary> = state
106 .teams
107 .values()
108 .map(TeamSummary::from_snapshot)
109 .collect();
110 teams.sort_by(|a, b| a.name.cmp(&b.name));
111 teams
112 }
113
114 pub fn get_team(&self, name: &str) -> Result<TeamSummary, ApiError> {
116 let state = self.state().read();
117 state
118 .teams
119 .get(name)
120 .map(TeamSummary::from_snapshot)
121 .ok_or_else(|| ApiError::TeamNotFound {
122 name: name.to_string(),
123 })
124 }
125
126 pub fn get_team_tasks(&self, name: &str) -> Result<Vec<TeamTaskInfo>, ApiError> {
128 let state = self.state().read();
129 state
130 .teams
131 .get(name)
132 .map(|ts| ts.tasks.iter().map(TeamTaskInfo::from_task).collect())
133 .ok_or_else(|| ApiError::TeamNotFound {
134 name: name.to_string(),
135 })
136 }
137
138 pub fn is_running(&self) -> bool {
144 let state = self.state().read();
145 state.running
146 }
147
148 pub fn last_poll(&self) -> Option<chrono::DateTime<chrono::Utc>> {
150 let state = self.state().read();
151 state.last_poll
152 }
153
154 pub fn known_directories(&self) -> Vec<String> {
156 let state = self.state().read();
157 state.get_known_directories()
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164 use crate::agents::{AgentStatus, AgentType, MonitoredAgent};
165 use crate::api::builder::TmaiCoreBuilder;
166 use crate::config::Settings;
167 use crate::state::AppState;
168
169 fn make_core_with_agents(agents: Vec<MonitoredAgent>) -> TmaiCore {
170 let state = AppState::shared();
171 {
172 let mut s = state.write();
173 s.update_agents(agents);
174 }
175 TmaiCoreBuilder::new(Settings::default())
176 .with_state(state)
177 .build()
178 }
179
180 fn test_agent(id: &str, status: AgentStatus) -> MonitoredAgent {
181 let mut agent = MonitoredAgent::new(
182 id.to_string(),
183 AgentType::ClaudeCode,
184 "Title".to_string(),
185 "/home/user".to_string(),
186 100,
187 "main".to_string(),
188 "win".to_string(),
189 0,
190 0,
191 );
192 agent.status = status;
193 agent
194 }
195
196 #[test]
197 fn test_list_agents_empty() {
198 let core = TmaiCoreBuilder::new(Settings::default()).build();
199 assert!(core.list_agents().is_empty());
200 }
201
202 #[test]
203 fn test_list_agents() {
204 let core = make_core_with_agents(vec![
205 test_agent("main:0.0", AgentStatus::Idle),
206 test_agent(
207 "main:0.1",
208 AgentStatus::Processing {
209 activity: "Bash".to_string(),
210 },
211 ),
212 ]);
213
214 let agents = core.list_agents();
215 assert_eq!(agents.len(), 2);
216 }
217
218 #[test]
219 fn test_get_agent_found() {
220 let core = make_core_with_agents(vec![test_agent("main:0.0", AgentStatus::Idle)]);
221
222 let result = core.get_agent("main:0.0");
223 assert!(result.is_ok());
224 assert_eq!(result.unwrap().id, "main:0.0");
225 }
226
227 #[test]
228 fn test_get_agent_not_found() {
229 let core = TmaiCoreBuilder::new(Settings::default()).build();
230 let result = core.get_agent("nonexistent");
231 assert!(matches!(result, Err(ApiError::AgentNotFound { .. })));
232 }
233
234 #[test]
235 fn test_attention_count() {
236 let core = make_core_with_agents(vec![
237 test_agent("main:0.0", AgentStatus::Idle),
238 test_agent(
239 "main:0.1",
240 AgentStatus::AwaitingApproval {
241 approval_type: crate::agents::ApprovalType::ShellCommand,
242 details: "rm -rf".to_string(),
243 },
244 ),
245 test_agent(
246 "main:0.2",
247 AgentStatus::Error {
248 message: "oops".to_string(),
249 },
250 ),
251 ]);
252
253 assert_eq!(core.attention_count(), 2);
254 assert_eq!(core.agent_count(), 3);
255 }
256
257 #[test]
258 fn test_agents_needing_attention() {
259 let core = make_core_with_agents(vec![
260 test_agent("main:0.0", AgentStatus::Idle),
261 test_agent(
262 "main:0.1",
263 AgentStatus::AwaitingApproval {
264 approval_type: crate::agents::ApprovalType::FileEdit,
265 details: String::new(),
266 },
267 ),
268 ]);
269
270 let attention = core.agents_needing_attention();
271 assert_eq!(attention.len(), 1);
272 assert_eq!(attention[0].id, "main:0.1");
273 }
274
275 #[test]
276 fn test_get_preview() {
277 let mut agent = test_agent("main:0.0", AgentStatus::Idle);
278 agent.last_content_ansi = "\x1b[32mHello\x1b[0m".to_string();
279 agent.last_content = "Hello".to_string();
280
281 let core = make_core_with_agents(vec![agent]);
282
283 let preview = core.get_preview("main:0.0").unwrap();
284 assert!(preview.contains("Hello"));
285
286 let content = core.get_content("main:0.0").unwrap();
287 assert_eq!(content, "Hello");
288 }
289
290 #[test]
291 fn test_list_teams_empty() {
292 let core = TmaiCoreBuilder::new(Settings::default()).build();
293 assert!(core.list_teams().is_empty());
294 }
295
296 #[test]
297 fn test_is_running() {
298 let core = TmaiCoreBuilder::new(Settings::default()).build();
299 assert!(core.is_running());
300 }
301}