use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum TaskStatus {
Pending,
InProgress,
Completed,
Cancelled,
Failed(String),
Blocked(String),
TimedOut { error: String },
Retrying { attempt: u32, last_error: String },
}
impl TaskStatus {
pub fn is_terminal(&self) -> bool {
matches!(
self,
TaskStatus::Completed
| TaskStatus::Cancelled
| TaskStatus::Failed(_)
| TaskStatus::TimedOut { .. }
)
}
pub fn can_transition_to(&self, target: &TaskStatus) -> bool {
match self {
TaskStatus::Pending => {
matches!(target, TaskStatus::InProgress | TaskStatus::Cancelled)
}
TaskStatus::InProgress => matches!(
target,
TaskStatus::Completed
| TaskStatus::Cancelled
| TaskStatus::Failed(_)
| TaskStatus::TimedOut { .. }
| TaskStatus::Retrying { .. }
),
TaskStatus::Retrying { .. } => matches!(
target,
TaskStatus::Completed
| TaskStatus::Cancelled
| TaskStatus::Failed(_)
| TaskStatus::TimedOut { .. }
| TaskStatus::Retrying { .. }
),
TaskStatus::Blocked(_) => matches!(target, TaskStatus::Pending | TaskStatus::Cancelled),
_ => false,
}
}
pub fn transition_to(&self, target: TaskStatus) -> Result<TaskStatus, String> {
if !self.can_transition_to(&target) {
return Err(format!(
"Invalid task state transition: {:?} → {:?}",
self, target
));
}
Ok(target)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Task {
pub id: String,
pub description: String,
pub status: TaskStatus,
pub dependencies: Vec<String>,
pub priority: u8,
pub result: Option<String>,
pub reasoning: Option<String>,
pub assigned_agent: Option<String>,
pub tags: Vec<String>,
pub parent_id: Option<String>,
pub created_at: u64,
pub updated_at: u64,
pub subject: String,
pub timeout_secs: u64,
pub max_retries: u32,
pub retry_count: u32,
}
impl Task {
pub fn new(id: impl Into<String>, description: impl Into<String>) -> Self {
let description = description.into();
Self {
id: id.into(),
subject: description.clone(),
description,
status: TaskStatus::Pending,
dependencies: Vec::new(),
priority: 5,
result: None,
reasoning: None,
assigned_agent: None,
tags: Vec::new(),
parent_id: None,
created_at: 0,
updated_at: 0,
timeout_secs: 0,
max_retries: 0,
retry_count: 0,
}
}
pub fn with_dependencies(mut self, deps: Vec<String>) -> Self {
self.dependencies = deps;
self
}
pub fn add_dependency(&mut self, dep: String) {
self.dependencies.push(dep);
}
pub fn with_priority(mut self, priority: u8) -> Self {
self.priority = priority.min(10);
self
}
pub fn with_subject(mut self, subject: impl Into<String>) -> Self {
self.subject = subject.into();
self
}
pub fn with_timeout(mut self, secs: u64) -> Self {
self.timeout_secs = secs;
self
}
pub fn with_max_retries(mut self, retries: u32) -> Self {
self.max_retries = retries;
self
}
pub fn with_assigned_agent(mut self, agent: impl Into<String>) -> Self {
self.assigned_agent = Some(agent.into());
self
}
pub fn with_tags(mut self, tags: Vec<String>) -> Self {
self.tags = tags;
self
}
pub fn add_tag(&mut self, tag: impl Into<String>) {
self.tags.push(tag.into());
}
pub fn is_cancelled(&self) -> bool {
self.status == TaskStatus::Cancelled
}
pub fn cancel(&mut self) -> bool {
match self.status.transition_to(TaskStatus::Cancelled) {
Ok(new_status) => {
self.status = new_status;
true
}
Err(_) => false,
}
}
pub fn record_execution(
&mut self,
attempt: u32,
error: Option<String>,
duration_secs: Option<u64>,
result: Option<String>,
) {
self.retry_count = attempt.saturating_sub(1);
self.updated_at = super::time::now_secs();
if let Some(r) = result {
self.result = Some(r);
}
if let Some(dur) = duration_secs {
let _ = dur; }
if let Some(err) = error {
self.reasoning = Some(format!("Attempt {} failed: {}", attempt, err));
}
}
}