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