1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5#[serde(rename_all = "lowercase")]
6pub enum TaskStatus {
7 Pending,
9 InProgress,
11 Completed,
13 Failed,
15 Blocked,
17 Skipped,
19}
20
21#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
23#[serde(rename_all = "lowercase")]
24#[derive(Default)]
25pub enum TaskPriority {
26 Low = 0,
28 #[default]
30 Normal = 1,
31 High = 2,
33 Urgent = 3,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct Task {
40 pub id: String,
42 pub description: String,
44 pub status: TaskStatus,
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub plan_id: Option<String>,
49 #[serde(skip_serializing_if = "Option::is_none")]
51 pub parent_id: Option<String>,
52 #[serde(default)]
54 pub children: Vec<String>,
55 #[serde(default)]
57 pub depends_on: Vec<String>,
58 #[serde(default)]
60 pub priority: TaskPriority,
61 #[serde(skip_serializing_if = "Option::is_none")]
63 pub assigned_to: Option<String>,
64 #[serde(default)]
66 pub iterations: u32,
67 #[serde(skip_serializing_if = "Option::is_none")]
69 pub summary: Option<String>,
70 #[serde(default = "default_timestamp")]
72 pub created_at: i64,
73 #[serde(default = "default_timestamp")]
75 pub updated_at: i64,
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub started_at: Option<i64>,
79 #[serde(skip_serializing_if = "Option::is_none")]
81 pub completed_at: Option<i64>,
82}
83
84fn default_timestamp() -> i64 {
85 chrono::Utc::now().timestamp()
86}
87
88impl Task {
89 pub fn new<S: Into<String>>(id: S, description: S) -> Self {
91 let now = chrono::Utc::now().timestamp();
92 Self {
93 id: id.into(),
94 description: description.into(),
95 status: TaskStatus::Pending,
96 plan_id: None,
97 parent_id: None,
98 children: Vec::new(),
99 depends_on: Vec::new(),
100 priority: TaskPriority::Normal,
101 assigned_to: None,
102 iterations: 0,
103 summary: None,
104 created_at: now,
105 updated_at: now,
106 started_at: None,
107 completed_at: None,
108 }
109 }
110
111 pub fn new_for_plan<S: Into<String>>(id: S, description: S, plan_id: S) -> Self {
113 let mut task = Self::new(id, description);
114 task.plan_id = Some(plan_id.into());
115 task
116 }
117
118 pub fn new_subtask<S: Into<String>>(id: S, description: S, parent_id: S) -> Self {
120 let mut task = Self::new(id, description);
121 task.parent_id = Some(parent_id.into());
122 task
123 }
124
125 pub fn start(&mut self) {
127 let now = chrono::Utc::now().timestamp();
128 self.status = TaskStatus::InProgress;
129 self.started_at = Some(now);
130 self.updated_at = now;
131 }
132
133 pub fn complete<S: Into<String>>(&mut self, summary: S) {
135 let now = chrono::Utc::now().timestamp();
136 self.status = TaskStatus::Completed;
137 self.summary = Some(summary.into());
138 self.completed_at = Some(now);
139 self.updated_at = now;
140 }
141
142 pub fn duration_secs(&self) -> Option<i64> {
144 match (self.started_at, self.completed_at) {
145 (Some(start), Some(end)) => Some(end - start),
146 _ => None,
147 }
148 }
149
150 pub fn elapsed_secs(&self) -> Option<i64> {
152 self.started_at
153 .map(|start| chrono::Utc::now().timestamp() - start)
154 }
155
156 pub fn fail<S: Into<String>>(&mut self, error: S) {
158 self.status = TaskStatus::Failed;
159 self.summary = Some(error.into());
160 self.updated_at = chrono::Utc::now().timestamp();
161 }
162
163 pub fn block(&mut self) {
165 self.status = TaskStatus::Blocked;
166 self.updated_at = chrono::Utc::now().timestamp();
167 }
168
169 pub fn skip<S: Into<String>>(&mut self, reason: Option<S>) {
171 let now = chrono::Utc::now().timestamp();
172 self.status = TaskStatus::Skipped;
173 if let Some(r) = reason {
174 self.summary = Some(r.into());
175 }
176 self.completed_at = Some(now);
177 self.updated_at = now;
178 }
179
180 pub fn increment_iteration(&mut self) {
182 self.iterations += 1;
183 self.updated_at = chrono::Utc::now().timestamp();
184 }
185
186 pub fn add_child(&mut self, child_id: String) {
188 if !self.children.contains(&child_id) {
189 self.children.push(child_id);
190 self.updated_at = chrono::Utc::now().timestamp();
191 }
192 }
193
194 pub fn add_dependency(&mut self, task_id: String) {
196 if !self.depends_on.contains(&task_id) {
197 self.depends_on.push(task_id);
198 self.updated_at = chrono::Utc::now().timestamp();
199 }
200 }
201
202 pub fn has_dependencies(&self) -> bool {
204 !self.depends_on.is_empty()
205 }
206
207 pub fn has_children(&self) -> bool {
209 !self.children.is_empty()
210 }
211
212 pub fn is_root(&self) -> bool {
214 self.parent_id.is_none()
215 }
216
217 pub fn set_priority(&mut self, priority: TaskPriority) {
219 self.priority = priority;
220 self.updated_at = chrono::Utc::now().timestamp();
221 }
222}
223
224#[derive(Debug, Clone)]
226pub struct AgentResponse {
227 pub message: String,
229 pub is_complete: bool,
231 pub tasks: Vec<Task>,
233 pub iterations: u32,
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 fn test_task_lifecycle() {
243 let mut task = Task::new("task-1", "Test task");
244 assert_eq!(task.status, TaskStatus::Pending);
245 task.start();
246 assert_eq!(task.status, TaskStatus::InProgress);
247 task.complete("Done!");
248 assert_eq!(task.status, TaskStatus::Completed);
249 }
250
251 #[test]
252 fn test_task_failure() {
253 let mut task = Task::new("task-2", "Failing task");
254 task.start();
255 task.fail("Error occurred");
256 assert_eq!(task.status, TaskStatus::Failed);
257 }
258}