Skip to main content

bamboo_engine/runtime/task_context/
prompt.rs

1use bamboo_domain::task::{
2    task_evidence_kind_label, task_phase_label, task_priority_label, TaskPhase, TaskPriority,
3};
4use bamboo_domain::TaskItemStatus;
5
6use super::TaskLoopContext;
7
8impl TaskLoopContext {
9    fn truncate_prompt_value(value: &str, max_chars: usize) -> String {
10        let trimmed = value.trim().replace('\n', " ");
11        let char_count = trimmed.chars().count();
12        if char_count <= max_chars {
13            return trimmed;
14        }
15
16        let truncated: String = trimmed.chars().take(max_chars).collect();
17        format!("{}…", truncated.trim_end())
18    }
19
20    /// Generate context for prompt injection
21    pub fn format_for_prompt(&self) -> String {
22        if self.items.is_empty() {
23            return String::new();
24        }
25
26        let mut output = format!(
27            "\n\n## Current Task List (Round {}/{})\n",
28            self.current_round + 1,
29            self.max_rounds
30        );
31
32        for item in &self.items {
33            let status_icon = match item.status {
34                TaskItemStatus::Pending => "[ ]",
35                TaskItemStatus::InProgress => "[/]",
36                TaskItemStatus::Completed => "[x]",
37                TaskItemStatus::Blocked => "[!]",
38            };
39
40            output.push_str(&format!(
41                "\n{} {}: {}",
42                status_icon, item.id, item.description
43            ));
44
45            if !item.tool_calls.is_empty() {
46                output.push_str(&format!(" ({} tool calls)", item.tool_calls.len()));
47            }
48
49            let mut tags = Vec::new();
50            if item.phase != TaskPhase::Execution {
51                tags.push(format!("phase={}", task_phase_label(&item.phase)));
52            }
53            if item.priority != TaskPriority::Medium {
54                tags.push(format!("priority={}", task_priority_label(&item.priority)));
55            }
56            if let Some(parent_id) = item.parent_id.as_deref().filter(|value| !value.is_empty()) {
57                tags.push(format!("parent={parent_id}"));
58            }
59            if !item.depends_on.is_empty() {
60                tags.push(format!("depends_on={}", item.depends_on.join(", ")));
61            }
62            if !tags.is_empty() {
63                output.push_str(&format!(" [{}]", tags.join(" | ")));
64            }
65
66            if matches!(item.status, TaskItemStatus::InProgress) {
67                if let Some(active_form) = item
68                    .active_form
69                    .as_deref()
70                    .filter(|value| !value.is_empty())
71                {
72                    output.push_str(&format!(
73                        "\n    Active: {}",
74                        Self::truncate_prompt_value(active_form, 140)
75                    ));
76                }
77            }
78
79            if !item.completion_criteria.is_empty() {
80                let criteria = item
81                    .completion_criteria
82                    .iter()
83                    .enumerate()
84                    .map(|(index, criterion)| format!("c{}: {}", index + 1, criterion.trim()))
85                    .collect::<Vec<_>>()
86                    .join(" | ");
87                output.push_str(&format!("\n    Completion Criteria: {criteria}"));
88            }
89
90            if let Some(blocker) = item.blockers.last() {
91                let mut blocker_line = Self::truncate_prompt_value(&blocker.summary, 140);
92                if let Some(waiting_on) = blocker
93                    .waiting_on
94                    .as_deref()
95                    .filter(|value| !value.is_empty())
96                {
97                    blocker_line.push_str(&format!(
98                        " (waiting_on: {})",
99                        Self::truncate_prompt_value(waiting_on, 60)
100                    ));
101                }
102                output.push_str(&format!("\n    Blocked by: {blocker_line}"));
103            }
104
105            if let Some(evidence) = item.evidence.last() {
106                let mut evidence_line = Self::truncate_prompt_value(&evidence.summary, 140);
107                if let Some(reference) = evidence
108                    .reference
109                    .as_deref()
110                    .filter(|value| !value.is_empty())
111                {
112                    evidence_line.push_str(&format!(
113                        " [ref: {}]",
114                        Self::truncate_prompt_value(reference, 60)
115                    ));
116                }
117                output.push_str(&format!(
118                    "\n    Latest evidence [{}]: {}",
119                    task_evidence_kind_label(&evidence.kind),
120                    evidence_line
121                ));
122            }
123
124            let notes = item.notes.trim();
125            let active_form = item.active_form.as_deref().map(str::trim);
126            if !notes.is_empty() && Some(notes) != active_form {
127                output.push_str(&format!(
128                    "\n    Notes: {}",
129                    Self::truncate_prompt_value(notes, 160)
130                ));
131            }
132        }
133
134        let completed = self
135            .items
136            .iter()
137            .filter(|item| matches!(item.status, TaskItemStatus::Completed))
138            .count();
139
140        output.push_str(&format!(
141            "\n\nProgress: {}/{} tasks completed",
142            completed,
143            self.items.len()
144        ));
145
146        output
147    }
148}