1pub mod llm_planner;
9
10pub use llm_planner::{AchievementResult, LlmPlanner, PreAnalysis};
11
12use serde::{Deserialize, Serialize};
13use std::fmt;
14use std::str::FromStr;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
22#[serde(rename_all = "snake_case")]
23pub enum TaskStatus {
24 #[default]
26 Pending,
27 InProgress,
29 Completed,
31 Failed,
33 Skipped,
35 Cancelled,
37}
38
39impl fmt::Display for TaskStatus {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 match self {
42 TaskStatus::Pending => write!(f, "pending"),
43 TaskStatus::InProgress => write!(f, "in_progress"),
44 TaskStatus::Completed => write!(f, "completed"),
45 TaskStatus::Failed => write!(f, "failed"),
46 TaskStatus::Skipped => write!(f, "skipped"),
47 TaskStatus::Cancelled => write!(f, "cancelled"),
48 }
49 }
50}
51
52impl FromStr for TaskStatus {
53 type Err = std::convert::Infallible;
54
55 fn from_str(s: &str) -> Result<Self, Self::Err> {
56 Ok(match s.to_lowercase().as_str() {
57 "pending" => TaskStatus::Pending,
58 "in_progress" | "inprogress" => TaskStatus::InProgress,
59 "completed" | "done" => TaskStatus::Completed,
60 "failed" => TaskStatus::Failed,
61 "skipped" => TaskStatus::Skipped,
62 "cancelled" | "canceled" => TaskStatus::Cancelled,
63 _ => TaskStatus::Pending,
64 })
65 }
66}
67
68impl TaskStatus {
69 pub fn is_active(&self) -> bool {
71 matches!(self, TaskStatus::Pending | TaskStatus::InProgress)
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
81#[serde(rename_all = "snake_case")]
82pub enum TaskPriority {
83 High,
85 #[default]
87 Medium,
88 Low,
90}
91
92impl fmt::Display for TaskPriority {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 match self {
95 TaskPriority::High => write!(f, "high"),
96 TaskPriority::Medium => write!(f, "medium"),
97 TaskPriority::Low => write!(f, "low"),
98 }
99 }
100}
101
102impl FromStr for TaskPriority {
103 type Err = std::convert::Infallible;
104
105 fn from_str(s: &str) -> Result<Self, Self::Err> {
106 Ok(match s.to_lowercase().as_str() {
107 "high" | "h" | "1" => TaskPriority::High,
108 "medium" | "med" | "m" | "2" => TaskPriority::Medium,
109 "low" | "l" | "3" => TaskPriority::Low,
110 _ => TaskPriority::Medium,
111 })
112 }
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct Task {
122 pub id: String,
124 pub content: String,
126 pub status: TaskStatus,
128 #[serde(default)]
130 pub priority: TaskPriority,
131 #[serde(default, skip_serializing_if = "Option::is_none")]
133 pub tool: Option<String>,
134 #[serde(default, skip_serializing_if = "Vec::is_empty")]
136 pub dependencies: Vec<String>,
137 #[serde(default, skip_serializing_if = "Option::is_none")]
139 pub success_criteria: Option<String>,
140}
141
142impl Task {
143 pub fn new(id: impl Into<String>, content: impl Into<String>) -> Self {
145 Self {
146 id: id.into(),
147 content: content.into(),
148 status: TaskStatus::Pending,
149 priority: TaskPriority::Medium,
150 tool: None,
151 dependencies: Vec::new(),
152 success_criteria: None,
153 }
154 }
155
156 pub fn with_priority(mut self, priority: TaskPriority) -> Self {
158 self.priority = priority;
159 self
160 }
161
162 pub fn with_status(mut self, status: TaskStatus) -> Self {
164 self.status = status;
165 self
166 }
167
168 pub fn with_tool(mut self, tool: impl Into<String>) -> Self {
170 self.tool = Some(tool.into());
171 self
172 }
173
174 pub fn with_dependencies(mut self, deps: Vec<String>) -> Self {
176 self.dependencies = deps;
177 self
178 }
179
180 pub fn with_success_criteria(mut self, criteria: impl Into<String>) -> Self {
182 self.success_criteria = Some(criteria.into());
183 self
184 }
185
186 pub fn is_active(&self) -> bool {
188 self.status.is_active()
189 }
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
198pub enum Complexity {
199 Simple,
201 Medium,
203 Complex,
205 VeryComplex,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct ExecutionPlan {
212 pub goal: String,
214 #[serde(default, skip_serializing_if = "Option::is_none")]
218 pub execution_context: Option<String>,
219 pub steps: Vec<Task>,
221 pub complexity: Complexity,
223 pub required_tools: Vec<String>,
225 pub estimated_steps: usize,
227}
228
229impl ExecutionPlan {
230 pub fn new(goal: impl Into<String>, complexity: Complexity) -> Self {
231 Self {
232 goal: goal.into(),
233 execution_context: None,
234 steps: Vec::new(),
235 complexity,
236 required_tools: Vec::new(),
237 estimated_steps: 0,
238 }
239 }
240
241 pub fn wire_goal(&self) -> &str {
243 self.execution_context
244 .as_deref()
245 .map(str::trim)
246 .filter(|text| !text.is_empty())
247 .unwrap_or(self.goal.as_str())
248 }
249
250 pub fn product_goal(&self) -> String {
252 let human = crate::transcript::product_user_text(self.wire_goal());
253 if !human.trim().is_empty() {
254 return human;
255 }
256 let short = self.goal.trim();
257 if !short.is_empty() {
258 return short.to_string();
259 }
260 self.wire_goal()
261 .lines()
262 .next()
263 .unwrap_or("Task plan")
264 .trim()
265 .to_string()
266 }
267
268 pub fn add_step(&mut self, step: Task) {
269 self.steps.push(step);
270 self.estimated_steps = self.steps.len();
271 }
272
273 pub fn upsert_step(&mut self, step: Task) {
280 let tool = step.tool.clone();
281 if let Some(existing) = self.steps.iter_mut().find(|item| item.id == step.id) {
282 existing.status = merge_status(existing.status, step.status);
286 existing.content = step.content;
287 existing.priority = step.priority;
288 existing.tool = step.tool.clone();
289 existing.dependencies = step.dependencies;
290 existing.success_criteria = step.success_criteria;
291 } else {
292 self.steps.push(step);
293 }
294 self.estimated_steps = self.steps.len();
295 if let Some(tool) = tool {
296 self.add_required_tool(tool);
297 }
298 }
299
300 pub fn add_required_tool(&mut self, tool: impl Into<String>) {
301 let tool_str = tool.into();
302 if !self.required_tools.contains(&tool_str) {
303 self.required_tools.push(tool_str);
304 }
305 }
306
307 pub fn definition_identity(
315 &self,
316 ) -> Result<
317 crate::execution_identity::ExecutionIdentityV1,
318 crate::execution_identity::ExecutionIdentityError,
319 > {
320 let steps = self
321 .steps
322 .iter()
323 .map(|step| {
324 serde_json::json!({
325 "id": step.id,
326 "content": step.content,
327 "priority": step.priority,
328 "tool": step.tool,
329 "dependencies": step.dependencies,
330 "success_criteria": step.success_criteria,
331 })
332 })
333 .collect::<Vec<_>>();
334 let mut required_tools = self.required_tools.clone();
335 required_tools.sort();
336 required_tools.dedup();
337 crate::execution_identity::ExecutionIdentityV1::derive(
338 crate::execution_identity::EXECUTION_PLAN_IDENTITY_DOMAIN_V1,
339 &serde_json::json!({
340 "goal": self.goal,
341 "execution_context": self.execution_context,
342 "complexity": self.complexity,
343 "required_tools": required_tools,
344 "steps": steps,
345 }),
346 )
347 }
348
349 pub fn get_ready_steps(&self) -> Vec<&Task> {
351 self.steps
352 .iter()
353 .filter(|step| {
354 step.status == TaskStatus::Pending
355 && step.dependencies.iter().all(|dep_id| {
356 self.steps
357 .iter()
358 .find(|s| &s.id == dep_id)
359 .map(|s| s.status == TaskStatus::Completed)
360 .unwrap_or(false)
361 })
362 })
363 .collect()
364 }
365
366 pub fn mark_status(&mut self, step_id: &str, status: TaskStatus) {
368 if let Some(step) = self.steps.iter_mut().find(|s| s.id == step_id) {
369 step.status = merge_status(step.status, status);
370 }
371 }
372
373 pub fn pending_count(&self) -> usize {
375 self.steps
376 .iter()
377 .filter(|s| s.status == TaskStatus::Pending)
378 .count()
379 }
380
381 pub fn has_deadlock(&self) -> bool {
386 self.pending_count() > 0 && self.get_ready_steps().is_empty()
387 }
388
389 pub fn progress(&self) -> f32 {
391 if self.steps.is_empty() {
392 return 0.0;
393 }
394 let completed = self
395 .steps
396 .iter()
397 .filter(|s| s.status == TaskStatus::Completed)
398 .count();
399 completed as f32 / self.steps.len() as f32
400 }
401}
402
403fn merge_status(current: TaskStatus, incoming: TaskStatus) -> TaskStatus {
406 if !current.is_active() {
407 return current;
408 }
409 if current == TaskStatus::InProgress && incoming == TaskStatus::Pending {
410 return current;
411 }
412 incoming
413}
414
415#[derive(Debug, Clone, Serialize, Deserialize)]
421pub struct AgentGoal {
422 pub description: String,
424 pub success_criteria: Vec<String>,
426 pub progress: f32,
428 pub achieved: bool,
430 pub created_at: i64,
432 pub achieved_at: Option<i64>,
434}
435
436impl AgentGoal {
437 pub fn new(description: impl Into<String>) -> Self {
438 Self {
439 description: description.into(),
440 success_criteria: Vec::new(),
441 progress: 0.0,
442 achieved: false,
443 created_at: chrono::Utc::now().timestamp(),
444 achieved_at: None,
445 }
446 }
447
448 pub fn with_criteria(mut self, criteria: Vec<String>) -> Self {
449 self.success_criteria = criteria;
450 self
451 }
452
453 pub fn update_progress(&mut self, progress: f32) {
454 self.progress = progress.clamp(0.0, 1.0);
455 }
456
457 pub fn mark_achieved(&mut self) {
458 self.achieved = true;
459 self.progress = 1.0;
460 self.achieved_at = Some(chrono::Utc::now().timestamp());
461 }
462}
463
464#[cfg(test)]
469mod tests {
470 use super::*;
471
472 #[test]
477 fn test_task_status_display() {
478 assert_eq!(TaskStatus::Pending.to_string(), "pending");
479 assert_eq!(TaskStatus::InProgress.to_string(), "in_progress");
480 assert_eq!(TaskStatus::Completed.to_string(), "completed");
481 assert_eq!(TaskStatus::Failed.to_string(), "failed");
482 assert_eq!(TaskStatus::Skipped.to_string(), "skipped");
483 assert_eq!(TaskStatus::Cancelled.to_string(), "cancelled");
484 }
485
486 #[test]
487 fn test_task_status_from_str() {
488 assert_eq!(
489 TaskStatus::from_str("pending").unwrap(),
490 TaskStatus::Pending
491 );
492 assert_eq!(
493 TaskStatus::from_str("in_progress").unwrap(),
494 TaskStatus::InProgress
495 );
496 assert_eq!(
497 TaskStatus::from_str("inprogress").unwrap(),
498 TaskStatus::InProgress
499 );
500 assert_eq!(
501 TaskStatus::from_str("completed").unwrap(),
502 TaskStatus::Completed
503 );
504 assert_eq!(TaskStatus::from_str("done").unwrap(), TaskStatus::Completed);
505 assert_eq!(TaskStatus::from_str("failed").unwrap(), TaskStatus::Failed);
506 assert_eq!(
507 TaskStatus::from_str("skipped").unwrap(),
508 TaskStatus::Skipped
509 );
510 assert_eq!(
511 TaskStatus::from_str("cancelled").unwrap(),
512 TaskStatus::Cancelled
513 );
514 assert_eq!(
515 TaskStatus::from_str("canceled").unwrap(),
516 TaskStatus::Cancelled
517 );
518 assert_eq!(
519 TaskStatus::from_str("unknown").unwrap(),
520 TaskStatus::Pending
521 );
522 }
523
524 #[test]
525 fn test_task_status_is_active() {
526 assert!(TaskStatus::Pending.is_active());
527 assert!(TaskStatus::InProgress.is_active());
528 assert!(!TaskStatus::Completed.is_active());
529 assert!(!TaskStatus::Failed.is_active());
530 assert!(!TaskStatus::Skipped.is_active());
531 assert!(!TaskStatus::Cancelled.is_active());
532 }
533
534 #[test]
535 fn test_task_status_serialization() {
536 assert_eq!(
537 serde_json::to_string(&TaskStatus::InProgress).unwrap(),
538 "\"in_progress\""
539 );
540 assert_eq!(
541 serde_json::to_string(&TaskStatus::Failed).unwrap(),
542 "\"failed\""
543 );
544 }
545
546 #[test]
551 fn test_task_priority_display() {
552 assert_eq!(TaskPriority::High.to_string(), "high");
553 assert_eq!(TaskPriority::Medium.to_string(), "medium");
554 assert_eq!(TaskPriority::Low.to_string(), "low");
555 }
556
557 #[test]
558 fn test_task_priority_from_str() {
559 assert_eq!(TaskPriority::from_str("high").unwrap(), TaskPriority::High);
560 assert_eq!(TaskPriority::from_str("h").unwrap(), TaskPriority::High);
561 assert_eq!(
562 TaskPriority::from_str("medium").unwrap(),
563 TaskPriority::Medium
564 );
565 assert_eq!(TaskPriority::from_str("med").unwrap(), TaskPriority::Medium);
566 assert_eq!(TaskPriority::from_str("low").unwrap(), TaskPriority::Low);
567 assert_eq!(TaskPriority::from_str("l").unwrap(), TaskPriority::Low);
568 assert_eq!(
569 TaskPriority::from_str("unknown").unwrap(),
570 TaskPriority::Medium
571 );
572 }
573
574 #[test]
579 fn test_task_new() {
580 let task = Task::new("1", "Test task");
581 assert_eq!(task.id, "1");
582 assert_eq!(task.content, "Test task");
583 assert_eq!(task.status, TaskStatus::Pending);
584 assert_eq!(task.priority, TaskPriority::Medium);
585 assert!(task.tool.is_none());
586 assert!(task.dependencies.is_empty());
587 assert!(task.success_criteria.is_none());
588 }
589
590 #[test]
591 fn test_task_builder() {
592 let task = Task::new("1", "Test task")
593 .with_priority(TaskPriority::High)
594 .with_status(TaskStatus::InProgress)
595 .with_tool("bash")
596 .with_dependencies(vec!["step-0".to_string()])
597 .with_success_criteria("Command exits with 0");
598
599 assert_eq!(task.priority, TaskPriority::High);
600 assert_eq!(task.status, TaskStatus::InProgress);
601 assert_eq!(task.tool, Some("bash".to_string()));
602 assert_eq!(task.dependencies, vec!["step-0".to_string()]);
603 assert_eq!(
604 task.success_criteria,
605 Some("Command exits with 0".to_string())
606 );
607 }
608
609 #[test]
610 fn test_task_is_active() {
611 let pending = Task::new("1", "Pending task");
612 let in_progress = Task::new("2", "In progress").with_status(TaskStatus::InProgress);
613 let completed = Task::new("3", "Completed").with_status(TaskStatus::Completed);
614 let failed = Task::new("4", "Failed").with_status(TaskStatus::Failed);
615 let cancelled = Task::new("5", "Cancelled").with_status(TaskStatus::Cancelled);
616
617 assert!(pending.is_active());
618 assert!(in_progress.is_active());
619 assert!(!completed.is_active());
620 assert!(!failed.is_active());
621 assert!(!cancelled.is_active());
622 }
623
624 #[test]
625 fn test_task_serialization() {
626 let task = Task::new("1", "Test task")
627 .with_priority(TaskPriority::High)
628 .with_status(TaskStatus::InProgress);
629
630 let json = serde_json::to_string(&task).unwrap();
631 let parsed: Task = serde_json::from_str(&json).unwrap();
632
633 assert_eq!(parsed.id, task.id);
634 assert_eq!(parsed.content, task.content);
635 assert_eq!(parsed.status, task.status);
636 assert_eq!(parsed.priority, task.priority);
637 }
638
639 #[test]
644 fn test_execution_plan() {
645 let mut plan = ExecutionPlan::new("Test goal", Complexity::Medium);
646
647 plan.add_step(Task::new("step-1", "First step"));
648 plan.add_step(
649 Task::new("step-2", "Second step").with_dependencies(vec!["step-1".to_string()]),
650 );
651
652 assert_eq!(plan.steps.len(), 2);
653 assert_eq!(plan.estimated_steps, 2);
654 assert_eq!(plan.progress(), 0.0);
655
656 plan.steps[0].status = TaskStatus::Completed;
658 assert_eq!(plan.progress(), 0.5);
659
660 let ready = plan.get_ready_steps();
662 assert_eq!(ready.len(), 1);
663 assert_eq!(ready[0].id, "step-2");
664 }
665
666 #[test]
671 fn test_mark_status() {
672 let mut plan = ExecutionPlan::new("Test", Complexity::Simple);
673 plan.add_step(Task::new("s1", "Step 1"));
674 plan.add_step(Task::new("s2", "Step 2"));
675
676 assert_eq!(plan.steps[0].status, TaskStatus::Pending);
677 plan.mark_status("s1", TaskStatus::InProgress);
678 assert_eq!(plan.steps[0].status, TaskStatus::InProgress);
679 plan.mark_status("s1", TaskStatus::Completed);
680 assert_eq!(plan.steps[0].status, TaskStatus::Completed);
681 plan.mark_status("s999", TaskStatus::Failed);
683 assert_eq!(plan.steps[1].status, TaskStatus::Pending);
684 }
685
686 #[test]
687 fn test_pending_count() {
688 let mut plan = ExecutionPlan::new("Test", Complexity::Simple);
689 plan.add_step(Task::new("s1", "Step 1"));
690 plan.add_step(Task::new("s2", "Step 2"));
691 plan.add_step(Task::new("s3", "Step 3"));
692
693 assert_eq!(plan.pending_count(), 3);
694 plan.mark_status("s1", TaskStatus::Completed);
695 assert_eq!(plan.pending_count(), 2);
696 plan.mark_status("s2", TaskStatus::Failed);
697 assert_eq!(plan.pending_count(), 1);
698 plan.mark_status("s3", TaskStatus::InProgress);
699 assert_eq!(plan.pending_count(), 0);
700 }
701
702 #[test]
703 fn test_has_deadlock() {
704 let mut plan = ExecutionPlan::new("Test", Complexity::Simple);
706 plan.add_step(Task::new("s1", "Step 1").with_dependencies(vec!["s2".to_string()]));
707 plan.add_step(Task::new("s2", "Step 2").with_dependencies(vec!["s1".to_string()]));
708
709 assert!(plan.has_deadlock());
710
711 let mut plan2 = ExecutionPlan::new("Test", Complexity::Simple);
713 plan2.add_step(Task::new("s1", "Step 1"));
714 assert!(!plan2.has_deadlock());
715
716 let mut plan3 = ExecutionPlan::new("Test", Complexity::Simple);
718 plan3.add_step(Task::new("s1", "Step 1"));
719 plan3.add_step(Task::new("s2", "Step 2").with_dependencies(vec!["s1".to_string()]));
720 plan3.mark_status("s1", TaskStatus::Failed);
721 assert!(plan3.has_deadlock()); }
723
724 #[test]
725 fn test_get_ready_steps_parallel() {
726 let mut plan = ExecutionPlan::new("Test", Complexity::Medium);
728 plan.add_step(Task::new("s1", "Step 1"));
729 plan.add_step(Task::new("s2", "Step 2"));
730 plan.add_step(Task::new("s3", "Step 3"));
731
732 let ready = plan.get_ready_steps();
733 assert_eq!(ready.len(), 3);
734 }
735
736 #[test]
737 fn test_get_ready_steps_wave() {
738 let mut plan = ExecutionPlan::new("Test", Complexity::Medium);
740 plan.add_step(Task::new("s1", "Step 1"));
741 plan.add_step(Task::new("s2", "Step 2"));
742 plan.add_step(
743 Task::new("s3", "Step 3").with_dependencies(vec!["s1".to_string(), "s2".to_string()]),
744 );
745
746 let ready = plan.get_ready_steps();
748 assert_eq!(ready.len(), 2);
749 let ids: Vec<&str> = ready.iter().map(|s| s.id.as_str()).collect();
750 assert!(ids.contains(&"s1"));
751 assert!(ids.contains(&"s2"));
752
753 plan.mark_status("s1", TaskStatus::Completed);
755 plan.mark_status("s2", TaskStatus::Completed);
756
757 let ready = plan.get_ready_steps();
759 assert_eq!(ready.len(), 1);
760 assert_eq!(ready[0].id, "s3");
761 }
762
763 #[test]
764 fn upsert_preserves_order_and_does_not_regress_status() {
765 let mut plan = ExecutionPlan::new("Test", Complexity::Simple);
766 plan.upsert_step(
767 Task::new("step", "First")
768 .with_tool("read")
769 .with_status(TaskStatus::InProgress),
770 );
771 plan.upsert_step(
772 Task::new("step", "Updated")
773 .with_tool("read")
774 .with_status(TaskStatus::Pending),
775 );
776 assert_eq!(plan.steps.len(), 1);
777 assert_eq!(plan.steps[0].content, "Updated");
778 assert_eq!(plan.steps[0].status, TaskStatus::InProgress);
779 assert_eq!(plan.required_tools, vec!["read"]);
780 }
781
782 #[test]
783 fn definition_identity_ignores_progress_status() {
784 let mut plan = ExecutionPlan::new("Test", Complexity::Simple);
785 plan.add_step(Task::new("step", "First").with_tool("read"));
786 let before = plan.definition_identity().unwrap();
787 plan.mark_status("step", TaskStatus::Completed);
788 let after = plan.definition_identity().unwrap();
789 assert_eq!(before, after);
790 }
791
792 #[test]
797 fn test_agent_goal() {
798 let mut goal = AgentGoal::new("Complete task")
799 .with_criteria(vec!["Criterion 1".to_string(), "Criterion 2".to_string()]);
800
801 assert_eq!(goal.description, "Complete task");
802 assert_eq!(goal.success_criteria.len(), 2);
803 assert_eq!(goal.progress, 0.0);
804 assert!(!goal.achieved);
805
806 goal.update_progress(0.5);
807 assert_eq!(goal.progress, 0.5);
808
809 goal.mark_achieved();
810 assert!(goal.achieved);
811 assert_eq!(goal.progress, 1.0);
812 assert!(goal.achieved_at.is_some());
813 }
814
815 #[test]
816 fn test_complexity_levels() {
817 assert_eq!(
818 serde_json::to_string(&Complexity::Simple).unwrap(),
819 "\"Simple\""
820 );
821 assert_eq!(
822 serde_json::to_string(&Complexity::Complex).unwrap(),
823 "\"Complex\""
824 );
825 }
826}