use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum TaskStatus {
Pending,
InProgress,
Completed,
Failed,
Blocked,
Skipped,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum TaskPriority {
Low = 0,
#[default]
Normal = 1,
High = 2,
Urgent = 3,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Task {
pub id: String,
pub description: String,
pub status: TaskStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub plan_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_id: Option<String>,
#[serde(default)]
pub children: Vec<String>,
#[serde(default)]
pub depends_on: Vec<String>,
#[serde(default)]
pub priority: TaskPriority,
#[serde(skip_serializing_if = "Option::is_none")]
pub assigned_to: Option<String>,
#[serde(default)]
pub iterations: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(default = "default_timestamp")]
pub created_at: i64,
#[serde(default = "default_timestamp")]
pub updated_at: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub started_at: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub completed_at: Option<i64>,
}
fn default_timestamp() -> i64 {
chrono::Utc::now().timestamp()
}
impl Task {
pub fn new<S: Into<String>>(id: S, description: S) -> Self {
let now = chrono::Utc::now().timestamp();
Self {
id: id.into(),
description: description.into(),
status: TaskStatus::Pending,
plan_id: None,
parent_id: None,
children: Vec::new(),
depends_on: Vec::new(),
priority: TaskPriority::Normal,
assigned_to: None,
iterations: 0,
summary: None,
created_at: now,
updated_at: now,
started_at: None,
completed_at: None,
}
}
pub fn new_for_plan<S: Into<String>>(id: S, description: S, plan_id: S) -> Self {
let mut task = Self::new(id, description);
task.plan_id = Some(plan_id.into());
task
}
pub fn new_subtask<S: Into<String>>(id: S, description: S, parent_id: S) -> Self {
let mut task = Self::new(id, description);
task.parent_id = Some(parent_id.into());
task
}
pub fn start(&mut self) {
let now = chrono::Utc::now().timestamp();
self.status = TaskStatus::InProgress;
self.started_at = Some(now);
self.updated_at = now;
}
pub fn complete<S: Into<String>>(&mut self, summary: S) {
let now = chrono::Utc::now().timestamp();
self.status = TaskStatus::Completed;
self.summary = Some(summary.into());
self.completed_at = Some(now);
self.updated_at = now;
}
pub fn duration_secs(&self) -> Option<i64> {
match (self.started_at, self.completed_at) {
(Some(start), Some(end)) => Some(end - start),
_ => None,
}
}
pub fn elapsed_secs(&self) -> Option<i64> {
self.started_at
.map(|start| chrono::Utc::now().timestamp() - start)
}
pub fn fail<S: Into<String>>(&mut self, error: S) {
self.status = TaskStatus::Failed;
self.summary = Some(error.into());
self.updated_at = chrono::Utc::now().timestamp();
}
pub fn block(&mut self) {
self.status = TaskStatus::Blocked;
self.updated_at = chrono::Utc::now().timestamp();
}
pub fn skip<S: Into<String>>(&mut self, reason: Option<S>) {
let now = chrono::Utc::now().timestamp();
self.status = TaskStatus::Skipped;
if let Some(r) = reason {
self.summary = Some(r.into());
}
self.completed_at = Some(now);
self.updated_at = now;
}
pub fn increment_iteration(&mut self) {
self.iterations += 1;
self.updated_at = chrono::Utc::now().timestamp();
}
pub fn add_child(&mut self, child_id: String) {
if !self.children.contains(&child_id) {
self.children.push(child_id);
self.updated_at = chrono::Utc::now().timestamp();
}
}
pub fn add_dependency(&mut self, task_id: String) {
if !self.depends_on.contains(&task_id) {
self.depends_on.push(task_id);
self.updated_at = chrono::Utc::now().timestamp();
}
}
pub fn has_dependencies(&self) -> bool {
!self.depends_on.is_empty()
}
pub fn has_children(&self) -> bool {
!self.children.is_empty()
}
pub fn is_root(&self) -> bool {
self.parent_id.is_none()
}
pub fn set_priority(&mut self, priority: TaskPriority) {
self.priority = priority;
self.updated_at = chrono::Utc::now().timestamp();
}
}
#[derive(Debug, Clone)]
pub struct AgentResponse {
pub message: String,
pub is_complete: bool,
pub tasks: Vec<Task>,
pub iterations: u32,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_task_lifecycle() {
let mut task = Task::new("task-1", "Test task");
assert_eq!(task.status, TaskStatus::Pending);
task.start();
assert_eq!(task.status, TaskStatus::InProgress);
task.complete("Done!");
assert_eq!(task.status, TaskStatus::Completed);
}
#[test]
fn test_task_failure() {
let mut task = Task::new("task-2", "Failing task");
task.start();
task.fail("Error occurred");
assert_eq!(task.status, TaskStatus::Failed);
}
}