ricecoder_execution/
manager.rs

1//! Central execution manager for coordinating plan execution
2
3use crate::error::{ExecutionError, ExecutionResult};
4use crate::models::{ExecutionMode, ExecutionPlan, ExecutionState};
5use crate::progress_tracker::ProgressTracker;
6use chrono::Utc;
7use std::collections::HashMap;
8use uuid::Uuid;
9
10/// Central coordinator for execution plan execution
11///
12/// Manages execution lifecycle (start, pause, resume, cancel) and tracks
13/// active executions. Wraps the WorkflowEngine and provides high-level
14/// execution plan management.
15pub struct ExecutionManager {
16    /// Active execution states
17    active_executions: HashMap<String, ExecutionState>,
18    /// Execution plans
19    plans: HashMap<String, ExecutionPlan>,
20    /// Progress trackers for active executions
21    progress_trackers: HashMap<String, ProgressTracker>,
22}
23
24impl Default for ExecutionManager {
25    fn default() -> Self {
26        Self::new()
27    }
28}
29
30impl ExecutionManager {
31    /// Create a new execution manager
32    pub fn new() -> Self {
33        ExecutionManager {
34            active_executions: HashMap::new(),
35            plans: HashMap::new(),
36            progress_trackers: HashMap::new(),
37        }
38    }
39
40    /// Register an execution plan
41    ///
42    /// Stores the plan for later execution. Returns the plan ID.
43    pub fn register_plan(&mut self, plan: ExecutionPlan) -> ExecutionResult<String> {
44        let plan_id = plan.id.clone();
45        self.plans.insert(plan_id.clone(), plan);
46        Ok(plan_id)
47    }
48
49    /// Get a registered plan
50    pub fn get_plan(&self, plan_id: &str) -> ExecutionResult<ExecutionPlan> {
51        self.plans
52            .get(plan_id)
53            .cloned()
54            .ok_or_else(|| ExecutionError::PlanError(format!("Plan not found: {}", plan_id)))
55    }
56
57    /// Start execution of a plan
58    ///
59    /// Creates a new execution state and begins execution in the specified mode.
60    /// Also creates a progress tracker for the execution.
61    pub fn start_execution(
62        &mut self,
63        plan_id: &str,
64        mode: ExecutionMode,
65    ) -> ExecutionResult<String> {
66        let plan = self.get_plan(plan_id)?;
67
68        let execution_id = Uuid::new_v4().to_string();
69        let state = ExecutionState {
70            execution_id: execution_id.clone(),
71            current_step_index: 0,
72            completed_steps: Vec::new(),
73            mode,
74            paused_at: Utc::now(),
75        };
76
77        // Create progress tracker for this execution
78        let progress_tracker = ProgressTracker::new(&plan);
79
80        self.active_executions.insert(execution_id.clone(), state);
81        self.progress_trackers
82            .insert(execution_id.clone(), progress_tracker);
83
84        tracing::info!(
85            execution_id = %execution_id,
86            plan_id = %plan_id,
87            mode = ?mode,
88            "Execution started"
89        );
90
91        Ok(execution_id)
92    }
93
94    /// Pause an active execution
95    ///
96    /// Saves the current execution state for later resumption.
97    pub fn pause_execution(&mut self, execution_id: &str) -> ExecutionResult<()> {
98        let state = self
99            .active_executions
100            .get_mut(execution_id)
101            .ok_or_else(|| {
102                ExecutionError::ValidationError(format!("Execution not found: {}", execution_id))
103            })?;
104
105        state.paused_at = Utc::now();
106
107        tracing::info!(
108            execution_id = %execution_id,
109            "Execution paused"
110        );
111
112        Ok(())
113    }
114
115    /// Resume a paused execution
116    ///
117    /// Continues execution from where it was paused.
118    pub fn resume_execution(&mut self, execution_id: &str) -> ExecutionResult<()> {
119        let state = self
120            .active_executions
121            .get_mut(execution_id)
122            .ok_or_else(|| {
123                ExecutionError::ValidationError(format!("Execution not found: {}", execution_id))
124            })?;
125
126        // Update pause time to now (for tracking pause duration)
127        state.paused_at = Utc::now();
128
129        tracing::info!(
130            execution_id = %execution_id,
131            "Execution resumed"
132        );
133
134        Ok(())
135    }
136
137    /// Cancel an active execution
138    ///
139    /// Stops execution and removes the execution state and progress tracker.
140    pub fn cancel_execution(&mut self, execution_id: &str) -> ExecutionResult<()> {
141        self.active_executions.remove(execution_id).ok_or_else(|| {
142            ExecutionError::ValidationError(format!("Execution not found: {}", execution_id))
143        })?;
144
145        // Clean up progress tracker
146        self.progress_trackers.remove(execution_id);
147
148        tracing::info!(
149            execution_id = %execution_id,
150            "Execution cancelled"
151        );
152
153        Ok(())
154    }
155
156    /// Get the current state of an execution
157    pub fn get_execution_state(&self, execution_id: &str) -> ExecutionResult<ExecutionState> {
158        self.active_executions
159            .get(execution_id)
160            .cloned()
161            .ok_or_else(|| {
162                ExecutionError::ValidationError(format!("Execution not found: {}", execution_id))
163            })
164    }
165
166    /// Get all active executions
167    pub fn get_active_executions(&self) -> Vec<ExecutionState> {
168        self.active_executions.values().cloned().collect()
169    }
170
171    /// Check if an execution is active
172    pub fn is_active(&self, execution_id: &str) -> bool {
173        self.active_executions.contains_key(execution_id)
174    }
175
176    /// Update execution state (internal use)
177    #[allow(dead_code)]
178    pub(crate) fn update_execution_state(
179        &mut self,
180        execution_id: &str,
181        state: ExecutionState,
182    ) -> ExecutionResult<()> {
183        self.active_executions
184            .insert(execution_id.to_string(), state);
185        Ok(())
186    }
187
188    /// Get the progress tracker for an execution
189    ///
190    /// Returns a mutable reference to the progress tracker for the given execution.
191    pub fn get_progress_tracker_mut(
192        &mut self,
193        execution_id: &str,
194    ) -> ExecutionResult<&mut ProgressTracker> {
195        self.progress_trackers.get_mut(execution_id).ok_or_else(|| {
196            ExecutionError::ValidationError(format!(
197                "Progress tracker not found for execution: {}",
198                execution_id
199            ))
200        })
201    }
202
203    /// Get the progress tracker for an execution (read-only)
204    ///
205    /// Returns a reference to the progress tracker for the given execution.
206    pub fn get_progress_tracker(&self, execution_id: &str) -> ExecutionResult<&ProgressTracker> {
207        self.progress_trackers.get(execution_id).ok_or_else(|| {
208            ExecutionError::ValidationError(format!(
209                "Progress tracker not found for execution: {}",
210                execution_id
211            ))
212        })
213    }
214
215    /// Register a progress callback for an execution
216    ///
217    /// Callbacks are called whenever progress is updated during execution.
218    pub fn on_progress<F>(&mut self, execution_id: &str, callback: F) -> ExecutionResult<()>
219    where
220        F: Fn(crate::progress_tracker::ProgressUpdate) + Send + Sync + 'static,
221    {
222        let tracker = self.get_progress_tracker_mut(execution_id)?;
223        tracker.on_progress(callback);
224        Ok(())
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231
232    #[test]
233    fn test_create_manager() {
234        let manager = ExecutionManager::new();
235        assert_eq!(manager.active_executions.len(), 0);
236        assert_eq!(manager.plans.len(), 0);
237    }
238
239    #[test]
240    fn test_register_plan() {
241        let mut manager = ExecutionManager::new();
242        let plan = ExecutionPlan::new("test".to_string(), vec![]);
243
244        let plan_id = manager.register_plan(plan.clone()).unwrap();
245        assert_eq!(plan_id, plan.id);
246        assert!(manager.get_plan(&plan_id).is_ok());
247    }
248
249    #[test]
250    fn test_start_execution() {
251        let mut manager = ExecutionManager::new();
252        let plan = ExecutionPlan::new("test".to_string(), vec![]);
253        let plan_id = manager.register_plan(plan).unwrap();
254
255        let execution_id = manager
256            .start_execution(&plan_id, ExecutionMode::Automatic)
257            .unwrap();
258
259        assert!(manager.is_active(&execution_id));
260    }
261
262    #[test]
263    fn test_pause_resume_execution() {
264        let mut manager = ExecutionManager::new();
265        let plan = ExecutionPlan::new("test".to_string(), vec![]);
266        let plan_id = manager.register_plan(plan).unwrap();
267
268        let execution_id = manager
269            .start_execution(&plan_id, ExecutionMode::Automatic)
270            .unwrap();
271
272        manager.pause_execution(&execution_id).unwrap();
273        assert!(manager.is_active(&execution_id));
274
275        manager.resume_execution(&execution_id).unwrap();
276        assert!(manager.is_active(&execution_id));
277    }
278
279    #[test]
280    fn test_cancel_execution() {
281        let mut manager = ExecutionManager::new();
282        let plan = ExecutionPlan::new("test".to_string(), vec![]);
283        let plan_id = manager.register_plan(plan).unwrap();
284
285        let execution_id = manager
286            .start_execution(&plan_id, ExecutionMode::Automatic)
287            .unwrap();
288
289        assert!(manager.is_active(&execution_id));
290        manager.cancel_execution(&execution_id).unwrap();
291        assert!(!manager.is_active(&execution_id));
292    }
293
294    #[test]
295    fn test_get_execution_state() {
296        let mut manager = ExecutionManager::new();
297        let plan = ExecutionPlan::new("test".to_string(), vec![]);
298        let plan_id = manager.register_plan(plan).unwrap();
299
300        let execution_id = manager
301            .start_execution(&plan_id, ExecutionMode::StepByStep)
302            .unwrap();
303
304        let state = manager.get_execution_state(&execution_id).unwrap();
305        assert_eq!(state.execution_id, execution_id);
306        assert_eq!(state.mode, ExecutionMode::StepByStep);
307        assert_eq!(state.current_step_index, 0);
308    }
309
310    #[test]
311    fn test_get_active_executions() {
312        let mut manager = ExecutionManager::new();
313        let plan1 = ExecutionPlan::new("test1".to_string(), vec![]);
314        let plan2 = ExecutionPlan::new("test2".to_string(), vec![]);
315
316        let plan_id1 = manager.register_plan(plan1).unwrap();
317        let plan_id2 = manager.register_plan(plan2).unwrap();
318
319        let _exec_id1 = manager
320            .start_execution(&plan_id1, ExecutionMode::Automatic)
321            .unwrap();
322        let _exec_id2 = manager
323            .start_execution(&plan_id2, ExecutionMode::DryRun)
324            .unwrap();
325
326        let active = manager.get_active_executions();
327        assert_eq!(active.len(), 2);
328    }
329
330    #[test]
331    fn test_nonexistent_plan() {
332        let manager = ExecutionManager::new();
333        assert!(manager.get_plan("nonexistent").is_err());
334    }
335
336    #[test]
337    fn test_nonexistent_execution() {
338        let manager = ExecutionManager::new();
339        assert!(manager.get_execution_state("nonexistent").is_err());
340    }
341
342    #[test]
343    fn test_progress_tracker_created() {
344        let mut manager = ExecutionManager::new();
345        let plan = ExecutionPlan::new("test".to_string(), vec![]);
346        let plan_id = manager.register_plan(plan).unwrap();
347
348        let execution_id = manager
349            .start_execution(&plan_id, ExecutionMode::Automatic)
350            .unwrap();
351
352        let tracker = manager.get_progress_tracker(&execution_id);
353        assert!(tracker.is_ok());
354    }
355
356    #[test]
357    fn test_progress_tracker_cleanup() {
358        let mut manager = ExecutionManager::new();
359        let plan = ExecutionPlan::new("test".to_string(), vec![]);
360        let plan_id = manager.register_plan(plan).unwrap();
361
362        let execution_id = manager
363            .start_execution(&plan_id, ExecutionMode::Automatic)
364            .unwrap();
365
366        assert!(manager.get_progress_tracker(&execution_id).is_ok());
367
368        manager.cancel_execution(&execution_id).unwrap();
369
370        assert!(manager.get_progress_tracker(&execution_id).is_err());
371    }
372
373    #[test]
374    fn test_progress_callback_registration() {
375        let mut manager = ExecutionManager::new();
376        let plan = ExecutionPlan::new("test".to_string(), vec![]);
377        let plan_id = manager.register_plan(plan).unwrap();
378
379        let execution_id = manager
380            .start_execution(&plan_id, ExecutionMode::Automatic)
381            .unwrap();
382
383        let result = manager.on_progress(&execution_id, |_progress| {
384            // Callback
385        });
386
387        assert!(result.is_ok());
388    }
389}