1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::path::{Path, PathBuf};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct Plan {
7 pub id: String,
8 pub task_id: String,
9 pub title: String,
10 pub steps: Vec<PlanStep>,
11 pub status: PlanStatus,
12 pub created_at: DateTime<Utc>,
13 pub updated_at: DateTime<Utc>,
14 #[serde(skip)]
15 pub file_path: PathBuf,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub(crate) struct PlanData {
20 id: String,
21 task_id: String,
22 title: String,
23 status: PlanStatus,
24 created_at: String,
25 updated_at: String,
26 steps: Vec<PlanStep>,
27}
28
29impl PlanData {
30 pub(crate) fn from_plan(plan: &Plan) -> Self {
31 Self {
32 id: plan.id.clone(),
33 task_id: plan.task_id.clone(),
34 title: plan.title.clone(),
35 status: plan.status.clone(),
36 created_at: plan.created_at.to_rfc3339(),
37 updated_at: plan.updated_at.to_rfc3339(),
38 steps: plan.steps.clone(),
39 }
40 }
41
42 pub(crate) fn to_plan(&self, file_path: &Path) -> crate::error::Result<Plan> {
43 Ok(Plan {
44 id: self.id.clone(),
45 task_id: self.task_id.clone(),
46 title: self.title.clone(),
47 status: self.status.clone(),
48 created_at: DateTime::parse_from_rfc3339(&self.created_at)
49 .map(|dt| dt.with_timezone(&Utc))
50 .map_err(|e| {
51 crate::error::AgentError::ConfigError(format!("Invalid created_at: {}", e))
52 })?,
53 updated_at: DateTime::parse_from_rfc3339(&self.updated_at)
54 .map(|dt| dt.with_timezone(&Utc))
55 .map_err(|e| {
56 crate::error::AgentError::ConfigError(format!("Invalid updated_at: {}", e))
57 })?,
58 steps: self.steps.clone(),
59 file_path: file_path.to_path_buf(),
60 })
61 }
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct PlanStep {
66 pub number: usize,
67 pub description: String,
68 pub status: StepStatus,
69 pub result: Option<String>,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
73pub enum PlanStatus {
74 Draft,
75 PendingApproval,
76 Approved,
77 InProgress,
78 Completed,
79 Cancelled,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Copy)]
83pub enum StepStatus {
84 Pending,
85 InProgress,
86 Completed,
87 Failed,
88 Skipped,
89}
90
91impl Plan {
92 pub fn new(task_id: &str, title: &str, step_descriptions: Vec<String>) -> Self {
94 let steps: Vec<PlanStep> = step_descriptions
95 .into_iter()
96 .enumerate()
97 .map(|(i, desc)| PlanStep {
98 number: i + 1,
99 description: desc,
100 status: StepStatus::Pending,
101 result: None,
102 })
103 .collect();
104
105 Plan {
106 id: uuid::Uuid::new_v4().to_string(),
107 task_id: task_id.to_string(),
108 title: title.to_string(),
109 steps,
110 status: PlanStatus::Draft,
111 created_at: Utc::now(),
112 updated_at: Utc::now(),
113 file_path: PathBuf::new(), }
115 }
116
117 pub fn mark_step_in_progress(&mut self, step_num: usize) -> bool {
118 if let Some(step) = self.steps.iter_mut().find(|s| s.number == step_num) {
119 step.status = StepStatus::InProgress;
120 self.updated_at = Utc::now();
121 true
122 } else {
123 false
124 }
125 }
126
127 pub fn mark_step_completed(&mut self, step_num: usize, result: Option<String>) -> bool {
128 if let Some(step) = self.steps.iter_mut().find(|s| s.number == step_num) {
129 step.status = StepStatus::Completed;
130 step.result = result;
131 self.updated_at = Utc::now();
132 if self
134 .steps
135 .iter()
136 .all(|s| s.status == StepStatus::Completed || s.status == StepStatus::Skipped)
137 {
138 self.status = PlanStatus::Completed;
139 }
140 true
141 } else {
142 false
143 }
144 }
145
146 pub fn mark_step_failed(&mut self, step_num: usize, error: String) -> bool {
147 if let Some(step) = self.steps.iter_mut().find(|s| s.number == step_num) {
148 step.status = StepStatus::Failed;
149 step.result = Some(error);
150 self.updated_at = Utc::now();
151 true
152 } else {
153 false
154 }
155 }
156}