brainwires_agents/task_manager/
status_ops.rs1use anyhow::{Context, Result};
6
7use super::TaskManager;
8use brainwires_core::TaskStatus;
9
10impl TaskManager {
11 #[tracing::instrument(name = "agent.task.update_status", skip(self, summary), fields(status = ?status))]
13 pub async fn update_status(
14 &self,
15 task_id: &str,
16 status: TaskStatus,
17 summary: Option<String>,
18 ) -> Result<()> {
19 let parent_id = {
20 let mut tasks = self.tasks.write().await;
21 let task = tasks
22 .get_mut(task_id)
23 .context(format!("Task '{}' not found", task_id))?;
24
25 task.status = status.clone();
26 if let Some(s) = summary {
27 task.summary = Some(s);
28 }
29 task.updated_at = chrono::Utc::now().timestamp();
30
31 task.parent_id.clone()
32 };
33
34 if status == TaskStatus::Completed {
36 self.unblock_dependents(task_id).await?;
37 if let Some(ref parent_id) = parent_id {
38 self.check_parent_completion(parent_id).await?;
39 }
40 }
41
42 Ok(())
43 }
44
45 pub async fn start_task(&self, task_id: &str) -> Result<()> {
47 self.update_status(task_id, TaskStatus::InProgress, None)
48 .await
49 }
50
51 pub async fn complete_task(&self, task_id: &str, summary: String) -> Result<()> {
53 self.update_status(task_id, TaskStatus::Completed, Some(summary))
54 .await
55 }
56
57 pub async fn fail_task(&self, task_id: &str, error: String) -> Result<()> {
59 self.update_status(task_id, TaskStatus::Failed, Some(error))
60 .await
61 }
62
63 pub async fn skip_task(&self, task_id: &str, reason: Option<String>) -> Result<()> {
65 let parent_id = {
66 let mut tasks = self.tasks.write().await;
67 let task = tasks
68 .get_mut(task_id)
69 .context(format!("Task '{}' not found", task_id))?;
70
71 let now = chrono::Utc::now().timestamp();
72 task.status = TaskStatus::Skipped;
73 task.completed_at = Some(now);
74 task.updated_at = now;
75
76 if let Some(r) = reason {
77 task.summary = Some(r);
78 }
79
80 task.parent_id.clone()
81 };
82
83 if let Some(ref pid) = parent_id {
85 self.check_parent_completion(pid).await?;
86 }
87
88 self.unblock_dependents(task_id).await?;
90
91 Ok(())
92 }
93
94 pub async fn block_task(&self, task_id: &str, reason: Option<String>) -> Result<()> {
96 let mut tasks = self.tasks.write().await;
97 let task = tasks
98 .get_mut(task_id)
99 .context(format!("Task '{}' not found", task_id))?;
100
101 task.status = TaskStatus::Blocked;
102 task.updated_at = chrono::Utc::now().timestamp();
103
104 if let Some(r) = reason {
105 task.summary = Some(r);
106 }
107
108 Ok(())
109 }
110
111 pub(crate) async fn check_parent_completion(&self, parent_id: &str) -> Result<()> {
113 let tasks = self.tasks.read().await;
114
115 if let Some(parent) = tasks.get(parent_id) {
116 let all_complete = parent.children.iter().all(|child_id| {
118 tasks
119 .get(child_id)
120 .map(|t| t.status == TaskStatus::Completed)
121 .unwrap_or(false)
122 });
123
124 if all_complete && !parent.children.is_empty() {
125 let grandparent_id = parent.parent_id.clone();
126 drop(tasks);
127
128 let mut tasks = self.tasks.write().await;
130 if let Some(parent) = tasks.get_mut(parent_id)
131 && (parent.status == TaskStatus::InProgress
132 || parent.status == TaskStatus::Pending)
133 {
134 parent.status = TaskStatus::Completed;
135 parent.summary =
136 Some(format!("All {} subtasks completed", parent.children.len()));
137 parent.updated_at = chrono::Utc::now().timestamp();
138 }
139 drop(tasks);
140
141 if let Some(gp_id) = grandparent_id {
143 Box::pin(self.check_parent_completion(&gp_id)).await?;
144 }
145 }
146 }
147
148 Ok(())
149 }
150}