Skip to main content

brainwires_agents/task_manager/
status_ops.rs

1//! Status Operations
2//!
3//! Task status update operations: start, complete, fail, skip, block.
4
5use anyhow::{Context, Result};
6
7use super::TaskManager;
8use brainwires_core::TaskStatus;
9
10impl TaskManager {
11    /// Update task status
12    #[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 completing a task, unblock dependents and check parent
35        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    /// Mark a task as started
46    pub async fn start_task(&self, task_id: &str) -> Result<()> {
47        self.update_status(task_id, TaskStatus::InProgress, None)
48            .await
49    }
50
51    /// Mark a task as completed
52    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    /// Mark a task as failed
58    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    /// Mark a task as skipped
64    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        // Check if parent can be updated (lock released above)
84        if let Some(ref pid) = parent_id {
85            self.check_parent_completion(pid).await?;
86        }
87
88        // Unblock tasks that depend on this one
89        self.unblock_dependents(task_id).await?;
90
91        Ok(())
92    }
93
94    /// Mark a task as blocked with optional reason
95    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    /// Check if parent task should be auto-completed when all children are done
112    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            // Check if all children are complete
117            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                // Auto-complete the parent task
129                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                // Recursively check grandparent
142                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}