Skip to main content

bamboo_engine/runtime/task_context/
auto_status.rs

1use bamboo_agent_core::tools::ToolResult;
2use bamboo_domain::task::{TaskBlocker, TaskBlockerKind};
3use bamboo_domain::TaskItemStatus;
4use chrono::Utc;
5
6use super::{TaskLoopContext, TaskLoopItem};
7
8impl TaskLoopContext {
9    /// Auto-match tool to task item based on keywords
10    pub fn auto_match_tool_to_item(&mut self, tool_name: &str) {
11        if self.active_item_id.is_some() {
12            return;
13        }
14
15        let tool_lower = tool_name.to_lowercase();
16        let matching_item_id = self
17            .items
18            .iter()
19            .find(|item| {
20                if matches!(item.status, TaskItemStatus::Completed) {
21                    return false;
22                }
23                if !self.unresolved_dependencies(&item.depends_on).is_empty() {
24                    return false;
25                }
26                let desc_lower = item.description.to_lowercase();
27                desc_lower.contains(&tool_lower)
28                    || (tool_lower.contains("file") && desc_lower.contains("file"))
29                    || (tool_lower.contains("command")
30                        && (desc_lower.contains("run") || desc_lower.contains("execute")))
31            })
32            .map(|item| item.id.clone());
33
34        if let Some(item_id) = matching_item_id {
35            self.set_active_item(&item_id);
36        }
37    }
38
39    /// Auto-update status based on tool execution result
40    pub fn auto_update_status(&mut self, tool_name: &str, result: &ToolResult) {
41        if self.active_item_id.is_none() {
42            self.auto_match_tool_to_item(tool_name);
43        }
44
45        if let Some(ref active_id) = self.active_item_id.clone() {
46            let action = self
47                .items
48                .iter()
49                .find(|item| &item.id == active_id)
50                .and_then(|item| {
51                    if result.success {
52                        if self.should_mark_completed(item) {
53                            Some(TaskItemStatus::Completed)
54                        } else {
55                            None
56                        }
57                    } else if self.should_mark_blocked(item) {
58                        Some(TaskItemStatus::Blocked)
59                    } else {
60                        None
61                    }
62                });
63
64            if let Some(new_status) = action {
65                if let Some(item) = self.items.iter_mut().find(|item| &item.id == active_id) {
66                    if matches!(new_status, TaskItemStatus::Blocked) {
67                        let mut summary = format!("Tool `{}` failed repeatedly", tool_name);
68                        let detail = result.result.trim();
69                        if !detail.is_empty() {
70                            let clipped: String = detail.chars().take(120).collect();
71                            if detail.chars().count() > 120 {
72                                summary.push_str(&format!(": {}…", clipped.trim_end()));
73                            } else {
74                                summary.push_str(&format!(": {clipped}"));
75                            }
76                        }
77                        TaskLoopContext::add_item_blocker(
78                            item,
79                            TaskBlocker {
80                                kind: TaskBlockerKind::ToolFailure,
81                                summary,
82                                waiting_on: None,
83                            },
84                        );
85                    }
86
87                    let reason = if result.success {
88                        Some("Auto-updated after sufficient successful tool calls")
89                    } else {
90                        Some("Auto-updated after repeated tool failures")
91                    };
92                    let changed = TaskLoopContext::transition_item(
93                        item,
94                        new_status.clone(),
95                        reason,
96                        Some(self.current_round),
97                    );
98
99                    if changed && matches!(new_status, TaskItemStatus::Completed) {
100                        item.completed_at_round = Some(self.current_round);
101                        self.active_item_id = None;
102                    }
103                    if changed {
104                        self.version += 1;
105                        self.updated_at = Utc::now();
106                    }
107                }
108            }
109        }
110    }
111
112    /// Determine if item should be marked as completed.
113    fn should_mark_completed(&self, item: &TaskLoopItem) -> bool {
114        if !item.completion_criteria.is_empty() {
115            return false;
116        }
117        let success_count = item
118            .tool_calls
119            .iter()
120            .filter(|record| record.success)
121            .count();
122        success_count >= 3
123    }
124
125    /// Determine if item should be marked as blocked.
126    fn should_mark_blocked(&self, item: &TaskLoopItem) -> bool {
127        let recent_failures = item
128            .tool_calls
129            .iter()
130            .rev()
131            .take(2)
132            .filter(|record| !record.success)
133            .count();
134        recent_failures >= 2
135    }
136}