Skip to main content

brainwires_core/
task.rs

1use serde::{Deserialize, Serialize};
2
3/// Task status
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5#[serde(rename_all = "lowercase")]
6pub enum TaskStatus {
7    /// Task is waiting to be started.
8    Pending,
9    /// Task is currently being executed.
10    InProgress,
11    /// Task completed successfully.
12    Completed,
13    /// Task failed.
14    Failed,
15    /// Task is blocked by dependencies.
16    Blocked,
17    /// Task was skipped.
18    Skipped,
19}
20
21/// Task priority levels
22#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
23#[serde(rename_all = "lowercase")]
24#[derive(Default)]
25pub enum TaskPriority {
26    /// Low priority.
27    Low = 0,
28    /// Normal (default) priority.
29    #[default]
30    Normal = 1,
31    /// High priority.
32    High = 2,
33    /// Urgent priority.
34    Urgent = 3,
35}
36
37/// A task being executed by an agent (supports tree structure)
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct Task {
40    /// Unique task ID
41    pub id: String,
42    /// Task description
43    pub description: String,
44    /// Current status
45    pub status: TaskStatus,
46    /// Associated plan ID (links task to a plan)
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub plan_id: Option<String>,
49    /// Parent task ID (for tree structure)
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub parent_id: Option<String>,
52    /// Child task IDs
53    #[serde(default)]
54    pub children: Vec<String>,
55    /// Task IDs this task depends on (must complete before this can start)
56    #[serde(default)]
57    pub depends_on: Vec<String>,
58    /// Task priority
59    #[serde(default)]
60    pub priority: TaskPriority,
61    /// Assigned agent (if any)
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub assigned_to: Option<String>,
64    /// Number of iterations executed
65    #[serde(default)]
66    pub iterations: u32,
67    /// Result summary (when completed or failed)
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub summary: Option<String>,
70    /// Creation timestamp
71    #[serde(default = "default_timestamp")]
72    pub created_at: i64,
73    /// Last update timestamp
74    #[serde(default = "default_timestamp")]
75    pub updated_at: i64,
76    /// When the task was started (for time tracking)
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub started_at: Option<i64>,
79    /// When the task was completed (for time tracking)
80    #[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    /// Create a new pending task
90    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    /// Create a new task associated with a plan
112    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    /// Create a new task with a parent (subtask)
119    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    /// Mark task as in progress
126    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    /// Mark task as completed
134    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    /// Get task duration in seconds (if started and completed)
143    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    /// Get elapsed time since task started (in seconds)
151    pub fn elapsed_secs(&self) -> Option<i64> {
152        self.started_at
153            .map(|start| chrono::Utc::now().timestamp() - start)
154    }
155
156    /// Mark task as failed
157    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    /// Mark task as blocked (waiting on dependencies)
164    pub fn block(&mut self) {
165        self.status = TaskStatus::Blocked;
166        self.updated_at = chrono::Utc::now().timestamp();
167    }
168
169    /// Mark task as skipped
170    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    /// Increment iterations
181    pub fn increment_iteration(&mut self) {
182        self.iterations += 1;
183        self.updated_at = chrono::Utc::now().timestamp();
184    }
185
186    /// Add a child task ID
187    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    /// Add a dependency
195    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    /// Check if task has any incomplete dependencies
203    pub fn has_dependencies(&self) -> bool {
204        !self.depends_on.is_empty()
205    }
206
207    /// Check if task has children
208    pub fn has_children(&self) -> bool {
209        !self.children.is_empty()
210    }
211
212    /// Check if task is a root task (no parent)
213    pub fn is_root(&self) -> bool {
214        self.parent_id.is_none()
215    }
216
217    /// Set priority
218    pub fn set_priority(&mut self, priority: TaskPriority) {
219        self.priority = priority;
220        self.updated_at = chrono::Utc::now().timestamp();
221    }
222}
223
224/// Agent response after processing
225#[derive(Debug, Clone)]
226pub struct AgentResponse {
227    /// The response message
228    pub message: String,
229    /// Whether the task is complete
230    pub is_complete: bool,
231    /// Tasks created or updated
232    pub tasks: Vec<Task>,
233    /// Number of iterations executed
234    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}