1use anyhow::{anyhow, Result};
6use chrono::{DateTime, Utc};
7use std::collections::HashMap;
8use tokio::sync::broadcast;
9use tokio::task::JoinHandle;
10use uuid::Uuid;
11
12use crate::orchestrator::DxTool;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub struct ToolId(Uuid);
17
18impl ToolId {
19 pub fn new() -> Self {
21 Self(Uuid::new_v4())
22 }
23}
24
25impl Default for ToolId {
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum ToolStatus {
34 Stopped,
36
37 Starting,
39
40 Running,
42
43 Stopping,
45
46 Failed(String),
48
49 Completed,
51}
52
53#[derive(Debug, Clone)]
55pub enum LifecycleEvent {
56 ToolStarting {
58 id: ToolId,
59 name: String,
60 },
61
62 ToolStarted {
64 id: ToolId,
65 name: String,
66 },
67
68 ToolStopping {
70 id: ToolId,
71 name: String,
72 },
73
74 ToolStopped {
76 id: ToolId,
77 name: String,
78 },
79
80 ToolFailed {
82 id: ToolId,
83 name: String,
84 error: String,
85 },
86
87 ToolCompleted {
89 id: ToolId,
90 name: String,
91 },
92}
93
94pub struct ToolState {
96 pub id: ToolId,
97 pub status: ToolStatus,
98 pub tool: Box<dyn DxTool>,
99 pub started_at: Option<DateTime<Utc>>,
100 pub stopped_at: Option<DateTime<Utc>>,
101 pub handle: Option<JoinHandle<()>>,
102}
103
104impl ToolState {
105 fn new(id: ToolId, tool: Box<dyn DxTool>) -> Self {
106 Self {
107 id,
108 status: ToolStatus::Stopped,
109 tool,
110 started_at: None,
111 stopped_at: None,
112 handle: None,
113 }
114 }
115}
116
117pub struct LifecycleManager {
119 tools: HashMap<ToolId, ToolState>,
120 event_bus: broadcast::Sender<LifecycleEvent>,
121}
122
123impl LifecycleManager {
124 pub fn new() -> Self {
126 let (event_bus, _) = broadcast::channel(1000);
127
128 Self {
129 tools: HashMap::new(),
130 event_bus,
131 }
132 }
133
134 pub fn register_tool(&mut self, tool: Box<dyn DxTool>) -> Result<ToolId> {
136 let id = ToolId::new();
137 let state = ToolState::new(id, tool);
138
139 self.tools.insert(id, state);
140
141 tracing::debug!("Registered tool with id: {:?}", id);
142 Ok(id)
143 }
144
145 pub async fn start_tool(&mut self, id: ToolId) -> Result<()> {
147 let state = self.tools.get_mut(&id)
148 .ok_or_else(|| anyhow!("Tool not found: {:?}", id))?;
149
150 if state.status == ToolStatus::Running {
151 return Err(anyhow!("Tool is already running: {:?}", id));
152 }
153
154 let tool_name = state.tool.name().to_string();
155
156 state.status = ToolStatus::Starting;
158 let _ = self.event_bus.send(LifecycleEvent::ToolStarting {
159 id,
160 name: tool_name.clone(),
161 });
162
163 state.status = ToolStatus::Running;
165 state.started_at = Some(Utc::now());
166
167 let _ = self.event_bus.send(LifecycleEvent::ToolStarted {
169 id,
170 name: tool_name,
171 });
172
173 tracing::info!("Started tool: {:?}", id);
174 Ok(())
175 }
176
177 pub async fn stop_tool(&mut self, id: ToolId) -> Result<()> {
179 let state = self.tools.get_mut(&id)
180 .ok_or_else(|| anyhow!("Tool not found: {:?}", id))?;
181
182 if state.status == ToolStatus::Stopped {
183 return Ok(());
184 }
185
186 let tool_name = state.tool.name().to_string();
187
188 state.status = ToolStatus::Stopping;
190 let _ = self.event_bus.send(LifecycleEvent::ToolStopping {
191 id,
192 name: tool_name.clone(),
193 });
194
195 if let Some(handle) = state.handle.take() {
197 handle.abort();
198 }
199
200 state.status = ToolStatus::Stopped;
202 state.stopped_at = Some(Utc::now());
203
204 let _ = self.event_bus.send(LifecycleEvent::ToolStopped {
206 id,
207 name: tool_name,
208 });
209
210 tracing::info!("Stopped tool: {:?}", id);
211 Ok(())
212 }
213
214 pub fn get_status(&self, id: ToolId) -> Option<ToolStatus> {
216 self.tools.get(&id).map(|state| state.status.clone())
217 }
218
219 pub fn list_tool_ids(&self) -> Vec<ToolId> {
221 self.tools.keys().copied().collect()
222 }
223
224 pub fn stop_all(&mut self) -> Result<()> {
226 let tool_ids: Vec<ToolId> = self.tools.keys().copied().collect();
227
228 for id in tool_ids {
229 if let Some(state) = self.tools.get(&id) {
230 if state.status == ToolStatus::Running {
231 let rt = tokio::runtime::Handle::try_current();
233 if let Ok(handle) = rt {
234 handle.block_on(async {
235 let _ = self.stop_tool(id).await;
236 });
237 }
238 }
239 }
240 }
241
242 Ok(())
243 }
244
245 pub fn subscribe(&self) -> broadcast::Receiver<LifecycleEvent> {
247 self.event_bus.subscribe()
248 }
249
250 pub fn running_count(&self) -> usize {
252 self.tools
253 .values()
254 .filter(|state| state.status == ToolStatus::Running)
255 .count()
256 }
257
258 pub fn total_count(&self) -> usize {
260 self.tools.len()
261 }
262}
263
264impl Default for LifecycleManager {
265 fn default() -> Self {
266 Self::new()
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273 use crate::orchestrator::{ExecutionContext, ToolOutput};
274
275 struct TestTool {
276 name: String,
277 }
278
279 impl DxTool for TestTool {
280 fn name(&self) -> &str {
281 &self.name
282 }
283
284 fn version(&self) -> &str {
285 "1.0.0"
286 }
287
288 fn priority(&self) -> u32 {
289 50
290 }
291
292 fn execute(&mut self, _ctx: &ExecutionContext) -> Result<ToolOutput> {
293 Ok(ToolOutput::success())
294 }
295 }
296
297 #[tokio::test]
298 async fn test_register_tool() {
299 let mut manager = LifecycleManager::new();
300 let tool = Box::new(TestTool {
301 name: "test-tool".to_string(),
302 });
303
304 let id = manager.register_tool(tool).unwrap();
305 assert_eq!(manager.total_count(), 1);
306 assert_eq!(manager.get_status(id), Some(ToolStatus::Stopped));
307 }
308
309 #[tokio::test]
310 async fn test_start_stop_tool() {
311 let mut manager = LifecycleManager::new();
312 let tool = Box::new(TestTool {
313 name: "test-tool".to_string(),
314 });
315
316 let id = manager.register_tool(tool).unwrap();
317
318 manager.start_tool(id).await.unwrap();
319 assert_eq!(manager.get_status(id), Some(ToolStatus::Running));
320 assert_eq!(manager.running_count(), 1);
321
322 manager.stop_tool(id).await.unwrap();
323 assert_eq!(manager.get_status(id), Some(ToolStatus::Stopped));
324 assert_eq!(manager.running_count(), 0);
325 }
326
327 #[tokio::test]
328 async fn test_lifecycle_events() {
329 let mut manager = LifecycleManager::new();
330 let mut rx = manager.subscribe();
331
332 let tool = Box::new(TestTool {
333 name: "test-tool".to_string(),
334 });
335
336 let id = manager.register_tool(tool).unwrap();
337
338 manager.start_tool(id).await.unwrap();
340
341 if let Ok(event) = rx.try_recv() {
342 match event {
343 LifecycleEvent::ToolStarting { id: _, name } => {
344 assert_eq!(name, "test-tool");
345 }
346 _ => panic!("Expected ToolStarting event"),
347 }
348 }
349 }
350}