mojentic/llm/tools/ephemeral_task_manager/
task_list.rs

1use super::task::{Task, TaskStatus};
2use crate::error::{MojenticError, Result};
3
4/// Manages a list of tasks for the ephemeral task manager
5///
6/// This structure provides methods for adding, starting, completing, and listing tasks.
7/// Tasks follow a state machine that transitions from Pending through InProgress to Completed.
8#[derive(Debug, Clone)]
9pub struct TaskList {
10    tasks: Vec<Task>,
11    next_id: usize,
12}
13
14impl TaskList {
15    /// Creates a new empty task list
16    pub fn new() -> Self {
17        Self {
18            tasks: Vec::new(),
19            next_id: 1,
20        }
21    }
22
23    /// Claims the next available ID and increments the counter
24    fn claim_next_id(&mut self) -> usize {
25        let id = self.next_id;
26        self.next_id += 1;
27        id
28    }
29
30    /// Appends a new task to the end of the list
31    ///
32    /// Returns the newly created task with Pending status
33    pub fn append_task(&mut self, description: String) -> Task {
34        let id = self.claim_next_id();
35        let task = Task::new(id, description);
36        self.tasks.push(task.clone());
37        task
38    }
39
40    /// Prepends a new task to the beginning of the list
41    ///
42    /// Returns the newly created task with Pending status
43    pub fn prepend_task(&mut self, description: String) -> Task {
44        let id = self.claim_next_id();
45        let task = Task::new(id, description);
46        self.tasks.insert(0, task.clone());
47        task
48    }
49
50    /// Inserts a new task after an existing task with the given ID
51    ///
52    /// Returns the newly created task or an error if the existing task is not found
53    pub fn insert_task_after(
54        &mut self,
55        existing_task_id: usize,
56        description: String,
57    ) -> Result<Task> {
58        let position =
59            self.tasks.iter().position(|t| t.id == existing_task_id).ok_or_else(|| {
60                MojenticError::ToolError(format!("No task with ID '{}' exists", existing_task_id))
61            })?;
62
63        let id = self.claim_next_id();
64        let task = Task::new(id, description);
65        self.tasks.insert(position + 1, task.clone());
66        Ok(task)
67    }
68
69    /// Starts a task by changing its status from Pending to InProgress
70    ///
71    /// Returns an error if the task is not found or not in Pending status
72    pub fn start_task(&mut self, task_id: usize) -> Result<Task> {
73        let task = self.tasks.iter_mut().find(|t| t.id == task_id).ok_or_else(|| {
74            MojenticError::ToolError(format!("No task with ID '{}' exists", task_id))
75        })?;
76
77        if task.status != TaskStatus::Pending {
78            return Err(MojenticError::ToolError(format!(
79                "Task '{}' cannot be started because it is not in PENDING status",
80                task_id
81            )));
82        }
83
84        task.status = TaskStatus::InProgress;
85        Ok(task.clone())
86    }
87
88    /// Completes a task by changing its status from InProgress to Completed
89    ///
90    /// Returns an error if the task is not found or not in InProgress status
91    pub fn complete_task(&mut self, task_id: usize) -> Result<Task> {
92        let task = self.tasks.iter_mut().find(|t| t.id == task_id).ok_or_else(|| {
93            MojenticError::ToolError(format!("No task with ID '{}' exists", task_id))
94        })?;
95
96        if task.status != TaskStatus::InProgress {
97            return Err(MojenticError::ToolError(format!(
98                "Task '{}' cannot be completed because it is not in IN_PROGRESS status",
99                task_id
100            )));
101        }
102
103        task.status = TaskStatus::Completed;
104        Ok(task.clone())
105    }
106
107    /// Returns all tasks in the list
108    pub fn list_tasks(&self) -> Vec<Task> {
109        self.tasks.clone()
110    }
111
112    /// Clears all tasks from the list
113    ///
114    /// Returns the number of tasks that were cleared
115    pub fn clear_tasks(&mut self) -> usize {
116        let count = self.tasks.len();
117        self.tasks.clear();
118        count
119    }
120}
121
122impl Default for TaskList {
123    fn default() -> Self {
124        Self::new()
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn test_new_task_list() {
134        let task_list = TaskList::new();
135        assert_eq!(task_list.list_tasks().len(), 0);
136    }
137
138    #[test]
139    fn test_append_task() {
140        let mut task_list = TaskList::new();
141        let task = task_list.append_task("Test task".to_string());
142
143        assert_eq!(task.id, 1);
144        assert_eq!(task.description, "Test task");
145        assert_eq!(task.status, TaskStatus::Pending);
146
147        let tasks = task_list.list_tasks();
148        assert_eq!(tasks.len(), 1);
149        assert_eq!(tasks[0].id, task.id);
150    }
151
152    #[test]
153    fn test_prepend_task() {
154        let mut task_list = TaskList::new();
155        task_list.append_task("Existing task".to_string());
156        let task = task_list.prepend_task("New task".to_string());
157
158        let tasks = task_list.list_tasks();
159        assert_eq!(tasks.len(), 2);
160        assert_eq!(tasks[0].id, task.id);
161        assert_eq!(tasks[0].description, "New task");
162    }
163
164    #[test]
165    fn test_insert_task_after() {
166        let mut task_list = TaskList::new();
167        let task1 = task_list.append_task("Task 1".to_string());
168        task_list.append_task("Task 3".to_string());
169
170        let task2 = task_list.insert_task_after(task1.id, "Task 2".to_string()).unwrap();
171
172        let tasks = task_list.list_tasks();
173        assert_eq!(tasks.len(), 3);
174        assert_eq!(tasks[1].id, task2.id);
175        assert_eq!(tasks[1].description, "Task 2");
176    }
177
178    #[test]
179    fn test_insert_task_after_nonexistent() {
180        let mut task_list = TaskList::new();
181        let result = task_list.insert_task_after(999, "Task".to_string());
182        assert!(result.is_err());
183    }
184
185    #[test]
186    fn test_start_task() {
187        let mut task_list = TaskList::new();
188        let task = task_list.append_task("Task 1".to_string());
189
190        let started = task_list.start_task(task.id).unwrap();
191        assert_eq!(started.status, TaskStatus::InProgress);
192    }
193
194    #[test]
195    fn test_start_non_pending_task() {
196        let mut task_list = TaskList::new();
197        let task = task_list.append_task("Task 1".to_string());
198        task_list.start_task(task.id).unwrap();
199
200        let result = task_list.start_task(task.id);
201        assert!(result.is_err());
202    }
203
204    #[test]
205    fn test_complete_task() {
206        let mut task_list = TaskList::new();
207        let task = task_list.append_task("Task 1".to_string());
208        task_list.start_task(task.id).unwrap();
209
210        let completed = task_list.complete_task(task.id).unwrap();
211        assert_eq!(completed.status, TaskStatus::Completed);
212    }
213
214    #[test]
215    fn test_complete_non_in_progress_task() {
216        let mut task_list = TaskList::new();
217        let task = task_list.append_task("Task 1".to_string());
218
219        let result = task_list.complete_task(task.id);
220        assert!(result.is_err());
221    }
222
223    #[test]
224    fn test_clear_tasks() {
225        let mut task_list = TaskList::new();
226        task_list.append_task("Task 1".to_string());
227        task_list.append_task("Task 2".to_string());
228
229        let count = task_list.clear_tasks();
230        assert_eq!(count, 2);
231        assert_eq!(task_list.list_tasks().len(), 0);
232    }
233
234    #[test]
235    fn test_maintain_task_ids() {
236        let mut task_list = TaskList::new();
237        let task1 = task_list.append_task("Task 1".to_string());
238        let task2 = task_list.append_task("Task 2".to_string());
239
240        task_list.start_task(task1.id).unwrap();
241        task_list.complete_task(task1.id).unwrap();
242
243        let task3 = task_list.append_task("Task 3".to_string());
244
245        let tasks = task_list.list_tasks();
246        assert_eq!(tasks.len(), 3);
247        assert!(tasks.iter().any(|t| t.id == task1.id && t.status == TaskStatus::Completed));
248        assert!(tasks.iter().any(|t| t.id == task2.id && t.status == TaskStatus::Pending));
249        assert!(tasks.iter().any(|t| t.id == task3.id && t.status == TaskStatus::Pending));
250    }
251}