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 pub steps: Vec<Task>,
216 pub complexity: Complexity,
218 pub required_tools: Vec<String>,
220 pub estimated_steps: usize,
222}
223
224impl ExecutionPlan {
225 pub fn new(goal: impl Into<String>, complexity: Complexity) -> Self {
226 Self {
227 goal: goal.into(),
228 steps: Vec::new(),
229 complexity,
230 required_tools: Vec::new(),
231 estimated_steps: 0,
232 }
233 }
234
235 pub fn add_step(&mut self, step: Task) {
236 self.steps.push(step);
237 self.estimated_steps = self.steps.len();
238 }
239
240 pub fn upsert_step(&mut self, step: Task) {
247 let tool = step.tool.clone();
248 if let Some(existing) = self.steps.iter_mut().find(|item| item.id == step.id) {
249 existing.status = merge_status(existing.status, step.status);
253 existing.content = step.content;
254 existing.priority = step.priority;
255 existing.tool = step.tool.clone();
256 existing.dependencies = step.dependencies;
257 existing.success_criteria = step.success_criteria;
258 } else {
259 self.steps.push(step);
260 }
261 self.estimated_steps = self.steps.len();
262 if let Some(tool) = tool {
263 self.add_required_tool(tool);
264 }
265 }
266
267 pub fn add_required_tool(&mut self, tool: impl Into<String>) {
268 let tool_str = tool.into();
269 if !self.required_tools.contains(&tool_str) {
270 self.required_tools.push(tool_str);
271 }
272 }
273
274 pub fn definition_identity(
282 &self,
283 ) -> Result<
284 crate::execution_identity::ExecutionIdentityV1,
285 crate::execution_identity::ExecutionIdentityError,
286 > {
287 let steps = self
288 .steps
289 .iter()
290 .map(|step| {
291 serde_json::json!({
292 "id": step.id,
293 "content": step.content,
294 "priority": step.priority,
295 "tool": step.tool,
296 "dependencies": step.dependencies,
297 "success_criteria": step.success_criteria,
298 })
299 })
300 .collect::<Vec<_>>();
301 let mut required_tools = self.required_tools.clone();
302 required_tools.sort();
303 required_tools.dedup();
304 crate::execution_identity::ExecutionIdentityV1::derive(
305 crate::execution_identity::EXECUTION_PLAN_IDENTITY_DOMAIN_V1,
306 &serde_json::json!({
307 "goal": self.goal,
308 "complexity": self.complexity,
309 "required_tools": required_tools,
310 "steps": steps,
311 }),
312 )
313 }
314
315 pub fn get_ready_steps(&self) -> Vec<&Task> {
317 self.steps
318 .iter()
319 .filter(|step| {
320 step.status == TaskStatus::Pending
321 && step.dependencies.iter().all(|dep_id| {
322 self.steps
323 .iter()
324 .find(|s| &s.id == dep_id)
325 .map(|s| s.status == TaskStatus::Completed)
326 .unwrap_or(false)
327 })
328 })
329 .collect()
330 }
331
332 pub fn mark_status(&mut self, step_id: &str, status: TaskStatus) {
334 if let Some(step) = self.steps.iter_mut().find(|s| s.id == step_id) {
335 step.status = merge_status(step.status, status);
336 }
337 }
338
339 pub fn pending_count(&self) -> usize {
341 self.steps
342 .iter()
343 .filter(|s| s.status == TaskStatus::Pending)
344 .count()
345 }
346
347 pub fn has_deadlock(&self) -> bool {
352 self.pending_count() > 0 && self.get_ready_steps().is_empty()
353 }
354
355 pub fn progress(&self) -> f32 {
357 if self.steps.is_empty() {
358 return 0.0;
359 }
360 let completed = self
361 .steps
362 .iter()
363 .filter(|s| s.status == TaskStatus::Completed)
364 .count();
365 completed as f32 / self.steps.len() as f32
366 }
367}
368
369fn merge_status(current: TaskStatus, incoming: TaskStatus) -> TaskStatus {
372 if !current.is_active() {
373 return current;
374 }
375 if current == TaskStatus::InProgress && incoming == TaskStatus::Pending {
376 return current;
377 }
378 incoming
379}
380
381#[derive(Debug, Clone, Serialize, Deserialize)]
387pub struct AgentGoal {
388 pub description: String,
390 pub success_criteria: Vec<String>,
392 pub progress: f32,
394 pub achieved: bool,
396 pub created_at: i64,
398 pub achieved_at: Option<i64>,
400}
401
402impl AgentGoal {
403 pub fn new(description: impl Into<String>) -> Self {
404 Self {
405 description: description.into(),
406 success_criteria: Vec::new(),
407 progress: 0.0,
408 achieved: false,
409 created_at: chrono::Utc::now().timestamp(),
410 achieved_at: None,
411 }
412 }
413
414 pub fn with_criteria(mut self, criteria: Vec<String>) -> Self {
415 self.success_criteria = criteria;
416 self
417 }
418
419 pub fn update_progress(&mut self, progress: f32) {
420 self.progress = progress.clamp(0.0, 1.0);
421 }
422
423 pub fn mark_achieved(&mut self) {
424 self.achieved = true;
425 self.progress = 1.0;
426 self.achieved_at = Some(chrono::Utc::now().timestamp());
427 }
428}
429
430#[cfg(test)]
435mod tests {
436 use super::*;
437
438 #[test]
443 fn test_task_status_display() {
444 assert_eq!(TaskStatus::Pending.to_string(), "pending");
445 assert_eq!(TaskStatus::InProgress.to_string(), "in_progress");
446 assert_eq!(TaskStatus::Completed.to_string(), "completed");
447 assert_eq!(TaskStatus::Failed.to_string(), "failed");
448 assert_eq!(TaskStatus::Skipped.to_string(), "skipped");
449 assert_eq!(TaskStatus::Cancelled.to_string(), "cancelled");
450 }
451
452 #[test]
453 fn test_task_status_from_str() {
454 assert_eq!(
455 TaskStatus::from_str("pending").unwrap(),
456 TaskStatus::Pending
457 );
458 assert_eq!(
459 TaskStatus::from_str("in_progress").unwrap(),
460 TaskStatus::InProgress
461 );
462 assert_eq!(
463 TaskStatus::from_str("inprogress").unwrap(),
464 TaskStatus::InProgress
465 );
466 assert_eq!(
467 TaskStatus::from_str("completed").unwrap(),
468 TaskStatus::Completed
469 );
470 assert_eq!(TaskStatus::from_str("done").unwrap(), TaskStatus::Completed);
471 assert_eq!(TaskStatus::from_str("failed").unwrap(), TaskStatus::Failed);
472 assert_eq!(
473 TaskStatus::from_str("skipped").unwrap(),
474 TaskStatus::Skipped
475 );
476 assert_eq!(
477 TaskStatus::from_str("cancelled").unwrap(),
478 TaskStatus::Cancelled
479 );
480 assert_eq!(
481 TaskStatus::from_str("canceled").unwrap(),
482 TaskStatus::Cancelled
483 );
484 assert_eq!(
485 TaskStatus::from_str("unknown").unwrap(),
486 TaskStatus::Pending
487 );
488 }
489
490 #[test]
491 fn test_task_status_is_active() {
492 assert!(TaskStatus::Pending.is_active());
493 assert!(TaskStatus::InProgress.is_active());
494 assert!(!TaskStatus::Completed.is_active());
495 assert!(!TaskStatus::Failed.is_active());
496 assert!(!TaskStatus::Skipped.is_active());
497 assert!(!TaskStatus::Cancelled.is_active());
498 }
499
500 #[test]
501 fn test_task_status_serialization() {
502 assert_eq!(
503 serde_json::to_string(&TaskStatus::InProgress).unwrap(),
504 "\"in_progress\""
505 );
506 assert_eq!(
507 serde_json::to_string(&TaskStatus::Failed).unwrap(),
508 "\"failed\""
509 );
510 }
511
512 #[test]
517 fn test_task_priority_display() {
518 assert_eq!(TaskPriority::High.to_string(), "high");
519 assert_eq!(TaskPriority::Medium.to_string(), "medium");
520 assert_eq!(TaskPriority::Low.to_string(), "low");
521 }
522
523 #[test]
524 fn test_task_priority_from_str() {
525 assert_eq!(TaskPriority::from_str("high").unwrap(), TaskPriority::High);
526 assert_eq!(TaskPriority::from_str("h").unwrap(), TaskPriority::High);
527 assert_eq!(
528 TaskPriority::from_str("medium").unwrap(),
529 TaskPriority::Medium
530 );
531 assert_eq!(TaskPriority::from_str("med").unwrap(), TaskPriority::Medium);
532 assert_eq!(TaskPriority::from_str("low").unwrap(), TaskPriority::Low);
533 assert_eq!(TaskPriority::from_str("l").unwrap(), TaskPriority::Low);
534 assert_eq!(
535 TaskPriority::from_str("unknown").unwrap(),
536 TaskPriority::Medium
537 );
538 }
539
540 #[test]
545 fn test_task_new() {
546 let task = Task::new("1", "Test task");
547 assert_eq!(task.id, "1");
548 assert_eq!(task.content, "Test task");
549 assert_eq!(task.status, TaskStatus::Pending);
550 assert_eq!(task.priority, TaskPriority::Medium);
551 assert!(task.tool.is_none());
552 assert!(task.dependencies.is_empty());
553 assert!(task.success_criteria.is_none());
554 }
555
556 #[test]
557 fn test_task_builder() {
558 let task = Task::new("1", "Test task")
559 .with_priority(TaskPriority::High)
560 .with_status(TaskStatus::InProgress)
561 .with_tool("bash")
562 .with_dependencies(vec!["step-0".to_string()])
563 .with_success_criteria("Command exits with 0");
564
565 assert_eq!(task.priority, TaskPriority::High);
566 assert_eq!(task.status, TaskStatus::InProgress);
567 assert_eq!(task.tool, Some("bash".to_string()));
568 assert_eq!(task.dependencies, vec!["step-0".to_string()]);
569 assert_eq!(
570 task.success_criteria,
571 Some("Command exits with 0".to_string())
572 );
573 }
574
575 #[test]
576 fn test_task_is_active() {
577 let pending = Task::new("1", "Pending task");
578 let in_progress = Task::new("2", "In progress").with_status(TaskStatus::InProgress);
579 let completed = Task::new("3", "Completed").with_status(TaskStatus::Completed);
580 let failed = Task::new("4", "Failed").with_status(TaskStatus::Failed);
581 let cancelled = Task::new("5", "Cancelled").with_status(TaskStatus::Cancelled);
582
583 assert!(pending.is_active());
584 assert!(in_progress.is_active());
585 assert!(!completed.is_active());
586 assert!(!failed.is_active());
587 assert!(!cancelled.is_active());
588 }
589
590 #[test]
591 fn test_task_serialization() {
592 let task = Task::new("1", "Test task")
593 .with_priority(TaskPriority::High)
594 .with_status(TaskStatus::InProgress);
595
596 let json = serde_json::to_string(&task).unwrap();
597 let parsed: Task = serde_json::from_str(&json).unwrap();
598
599 assert_eq!(parsed.id, task.id);
600 assert_eq!(parsed.content, task.content);
601 assert_eq!(parsed.status, task.status);
602 assert_eq!(parsed.priority, task.priority);
603 }
604
605 #[test]
610 fn test_execution_plan() {
611 let mut plan = ExecutionPlan::new("Test goal", Complexity::Medium);
612
613 plan.add_step(Task::new("step-1", "First step"));
614 plan.add_step(
615 Task::new("step-2", "Second step").with_dependencies(vec!["step-1".to_string()]),
616 );
617
618 assert_eq!(plan.steps.len(), 2);
619 assert_eq!(plan.estimated_steps, 2);
620 assert_eq!(plan.progress(), 0.0);
621
622 plan.steps[0].status = TaskStatus::Completed;
624 assert_eq!(plan.progress(), 0.5);
625
626 let ready = plan.get_ready_steps();
628 assert_eq!(ready.len(), 1);
629 assert_eq!(ready[0].id, "step-2");
630 }
631
632 #[test]
637 fn test_mark_status() {
638 let mut plan = ExecutionPlan::new("Test", Complexity::Simple);
639 plan.add_step(Task::new("s1", "Step 1"));
640 plan.add_step(Task::new("s2", "Step 2"));
641
642 assert_eq!(plan.steps[0].status, TaskStatus::Pending);
643 plan.mark_status("s1", TaskStatus::InProgress);
644 assert_eq!(plan.steps[0].status, TaskStatus::InProgress);
645 plan.mark_status("s1", TaskStatus::Completed);
646 assert_eq!(plan.steps[0].status, TaskStatus::Completed);
647 plan.mark_status("s999", TaskStatus::Failed);
649 assert_eq!(plan.steps[1].status, TaskStatus::Pending);
650 }
651
652 #[test]
653 fn test_pending_count() {
654 let mut plan = ExecutionPlan::new("Test", Complexity::Simple);
655 plan.add_step(Task::new("s1", "Step 1"));
656 plan.add_step(Task::new("s2", "Step 2"));
657 plan.add_step(Task::new("s3", "Step 3"));
658
659 assert_eq!(plan.pending_count(), 3);
660 plan.mark_status("s1", TaskStatus::Completed);
661 assert_eq!(plan.pending_count(), 2);
662 plan.mark_status("s2", TaskStatus::Failed);
663 assert_eq!(plan.pending_count(), 1);
664 plan.mark_status("s3", TaskStatus::InProgress);
665 assert_eq!(plan.pending_count(), 0);
666 }
667
668 #[test]
669 fn test_has_deadlock() {
670 let mut plan = ExecutionPlan::new("Test", Complexity::Simple);
672 plan.add_step(Task::new("s1", "Step 1").with_dependencies(vec!["s2".to_string()]));
673 plan.add_step(Task::new("s2", "Step 2").with_dependencies(vec!["s1".to_string()]));
674
675 assert!(plan.has_deadlock());
676
677 let mut plan2 = ExecutionPlan::new("Test", Complexity::Simple);
679 plan2.add_step(Task::new("s1", "Step 1"));
680 assert!(!plan2.has_deadlock());
681
682 let mut plan3 = ExecutionPlan::new("Test", Complexity::Simple);
684 plan3.add_step(Task::new("s1", "Step 1"));
685 plan3.add_step(Task::new("s2", "Step 2").with_dependencies(vec!["s1".to_string()]));
686 plan3.mark_status("s1", TaskStatus::Failed);
687 assert!(plan3.has_deadlock()); }
689
690 #[test]
691 fn test_get_ready_steps_parallel() {
692 let mut plan = ExecutionPlan::new("Test", Complexity::Medium);
694 plan.add_step(Task::new("s1", "Step 1"));
695 plan.add_step(Task::new("s2", "Step 2"));
696 plan.add_step(Task::new("s3", "Step 3"));
697
698 let ready = plan.get_ready_steps();
699 assert_eq!(ready.len(), 3);
700 }
701
702 #[test]
703 fn test_get_ready_steps_wave() {
704 let mut plan = ExecutionPlan::new("Test", Complexity::Medium);
706 plan.add_step(Task::new("s1", "Step 1"));
707 plan.add_step(Task::new("s2", "Step 2"));
708 plan.add_step(
709 Task::new("s3", "Step 3").with_dependencies(vec!["s1".to_string(), "s2".to_string()]),
710 );
711
712 let ready = plan.get_ready_steps();
714 assert_eq!(ready.len(), 2);
715 let ids: Vec<&str> = ready.iter().map(|s| s.id.as_str()).collect();
716 assert!(ids.contains(&"s1"));
717 assert!(ids.contains(&"s2"));
718
719 plan.mark_status("s1", TaskStatus::Completed);
721 plan.mark_status("s2", TaskStatus::Completed);
722
723 let ready = plan.get_ready_steps();
725 assert_eq!(ready.len(), 1);
726 assert_eq!(ready[0].id, "s3");
727 }
728
729 #[test]
730 fn upsert_preserves_order_and_does_not_regress_status() {
731 let mut plan = ExecutionPlan::new("Test", Complexity::Simple);
732 plan.upsert_step(
733 Task::new("step", "First")
734 .with_tool("read")
735 .with_status(TaskStatus::InProgress),
736 );
737 plan.upsert_step(
738 Task::new("step", "Updated")
739 .with_tool("read")
740 .with_status(TaskStatus::Pending),
741 );
742 assert_eq!(plan.steps.len(), 1);
743 assert_eq!(plan.steps[0].content, "Updated");
744 assert_eq!(plan.steps[0].status, TaskStatus::InProgress);
745 assert_eq!(plan.required_tools, vec!["read"]);
746 }
747
748 #[test]
749 fn definition_identity_ignores_progress_status() {
750 let mut plan = ExecutionPlan::new("Test", Complexity::Simple);
751 plan.add_step(Task::new("step", "First").with_tool("read"));
752 let before = plan.definition_identity().unwrap();
753 plan.mark_status("step", TaskStatus::Completed);
754 let after = plan.definition_identity().unwrap();
755 assert_eq!(before, after);
756 }
757
758 #[test]
763 fn test_agent_goal() {
764 let mut goal = AgentGoal::new("Complete task")
765 .with_criteria(vec!["Criterion 1".to_string(), "Criterion 2".to_string()]);
766
767 assert_eq!(goal.description, "Complete task");
768 assert_eq!(goal.success_criteria.len(), 2);
769 assert_eq!(goal.progress, 0.0);
770 assert!(!goal.achieved);
771
772 goal.update_progress(0.5);
773 assert_eq!(goal.progress, 0.5);
774
775 goal.mark_achieved();
776 assert!(goal.achieved);
777 assert_eq!(goal.progress, 1.0);
778 assert!(goal.achieved_at.is_some());
779 }
780
781 #[test]
782 fn test_complexity_levels() {
783 assert_eq!(
784 serde_json::to_string(&Complexity::Simple).unwrap(),
785 "\"Simple\""
786 );
787 assert_eq!(
788 serde_json::to_string(&Complexity::Complex).unwrap(),
789 "\"Complex\""
790 );
791 }
792}