use std::collections::HashMap;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
pub use pe_core::scope::TaskId;
#[must_use]
pub fn new_task_id() -> TaskId {
uuid::Uuid::new_v4().to_string()
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Task {
pub id: TaskId,
pub task_type: TaskType,
pub title: String,
pub description: String,
pub status: TaskStatus,
pub priority: TaskPriority,
pub tags: Vec<String>,
pub parent_task_id: Option<TaskId>,
pub agent_id: Option<String>,
pub created_by: String,
pub assignee: String,
pub result: Option<serde_json::Value>,
pub error: Option<String>,
pub metadata: HashMap<String, serde_json::Value>,
pub created_at: DateTime<Utc>,
pub updated_at: Option<DateTime<Utc>>,
pub completed_at: Option<DateTime<Utc>>,
pub deleted_at: Option<DateTime<Utc>>,
}
impl Task {
#[must_use]
pub fn new(title: impl Into<String>) -> Self {
Self {
id: new_task_id(),
task_type: TaskType::Human,
title: title.into(),
description: String::new(),
status: TaskStatus::Pending,
priority: TaskPriority::Medium,
tags: Vec::new(),
parent_task_id: None,
agent_id: None,
created_by: "user".into(),
assignee: "user".into(),
result: None,
error: None,
metadata: HashMap::new(),
created_at: Utc::now(),
updated_at: None,
completed_at: None,
deleted_at: None,
}
}
#[must_use]
pub fn agent_task(title: impl Into<String>, agent_id: impl Into<String>) -> Self {
let aid = agent_id.into();
Self {
task_type: TaskType::Agent,
agent_id: Some(aid.clone()),
created_by: aid.clone(),
assignee: aid,
..Self::new(title)
}
}
#[must_use]
pub fn plan(title: impl Into<String>) -> Self {
Self {
task_type: TaskType::Plan,
created_by: "system".into(),
assignee: "system".into(),
..Self::new(title)
}
}
#[must_use]
pub fn is_deleted(&self) -> bool {
self.deleted_at.is_some()
}
#[must_use]
pub fn is_terminal(&self) -> bool {
matches!(self.status, TaskStatus::Completed | TaskStatus::Cancelled)
}
#[must_use]
pub fn with_description(mut self, desc: impl Into<String>) -> Self {
self.description = desc.into();
self
}
#[must_use]
pub fn with_priority(mut self, priority: TaskPriority) -> Self {
self.priority = priority;
self
}
#[must_use]
pub fn with_parent(mut self, parent_id: impl Into<TaskId>) -> Self {
self.parent_task_id = Some(parent_id.into());
self
}
#[must_use]
pub fn with_tags(mut self, tags: Vec<String>) -> Self {
self.tags = tags;
self
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum TaskStatus {
Pending,
InProgress,
Completed,
Failed,
Blocked,
Cancelled,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum TaskType {
System,
Agent,
Human,
Plan,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
#[derive(Default)]
pub enum TaskPriority {
Urgent = 1,
High = 2,
#[default]
Medium = 3,
Low = 4,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_task_defaults() {
let t = Task::new("Build feature");
assert_eq!(t.title, "Build feature");
assert_eq!(t.status, TaskStatus::Pending);
assert_eq!(t.priority, TaskPriority::Medium);
assert_eq!(t.task_type, TaskType::Human);
assert_eq!(t.created_by, "user");
assert_eq!(t.assignee, "user");
assert!(t.parent_task_id.is_none());
assert!(!t.id.is_empty());
}
#[test]
fn test_agent_task() {
let t = Task::agent_task("Research APIs", "agent-1");
assert_eq!(t.task_type, TaskType::Agent);
assert_eq!(t.agent_id.as_deref(), Some("agent-1"));
assert_eq!(t.created_by, "agent-1");
assert_eq!(t.assignee, "agent-1");
}
#[test]
fn test_plan_task() {
let t = Task::plan("Deploy v2.0");
assert_eq!(t.task_type, TaskType::Plan);
assert_eq!(t.created_by, "system");
}
#[test]
fn test_builder_chain() {
let t = Task::new("Fix bug")
.with_description("The login form crashes")
.with_priority(TaskPriority::High)
.with_tags(vec!["bug".into(), "login".into()]);
assert_eq!(t.description, "The login form crashes");
assert_eq!(t.priority, TaskPriority::High);
assert_eq!(t.tags.len(), 2);
}
#[test]
fn test_subtask_with_parent() {
let parent = Task::plan("Big project");
let child = Task::new("Step 1").with_parent(&parent.id);
assert_eq!(child.parent_task_id.as_deref(), Some(parent.id.as_str()));
}
#[test]
fn test_serde_roundtrip() {
let t = Task::new("Test task");
let json = serde_json::to_string(&t).unwrap();
let t2: Task = serde_json::from_str(&json).unwrap();
assert_eq!(t.id, t2.id);
assert_eq!(t.title, t2.title);
}
#[test]
fn test_is_terminal() {
let mut t = Task::new("Done");
assert!(!t.is_terminal());
t.status = TaskStatus::Completed;
assert!(t.is_terminal());
t.status = TaskStatus::Cancelled;
assert!(t.is_terminal());
t.status = TaskStatus::Failed;
assert!(!t.is_terminal()); }
}