Skip to main content

bamboo_engine/runtime/task_context/
conversion.rs

1use bamboo_domain::{TaskItem, TaskItemStatus, TaskList};
2
3use super::{TaskLoopContext, TaskLoopItem};
4
5fn clone_loop_item_into_task_item(loop_item: &TaskLoopItem) -> TaskItem {
6    TaskItem {
7        id: loop_item.id.clone(),
8        description: loop_item.description.clone(),
9        status: loop_item.status.clone(),
10        depends_on: loop_item.depends_on.clone(),
11        notes: loop_item.notes.clone(),
12        active_form: loop_item.active_form.clone(),
13        parent_id: loop_item.parent_id.clone(),
14        phase: loop_item.phase.clone(),
15        priority: loop_item.priority.clone(),
16        completion_criteria: loop_item.completion_criteria.clone(),
17        evidence: loop_item.evidence.clone(),
18        blockers: loop_item.blockers.clone(),
19        transitions: loop_item.transitions.clone(),
20    }
21}
22
23fn into_task_item(loop_item: TaskLoopItem) -> TaskItem {
24    TaskItem {
25        id: loop_item.id,
26        description: loop_item.description,
27        status: loop_item.status,
28        depends_on: loop_item.depends_on,
29        notes: loop_item.notes,
30        active_form: loop_item.active_form,
31        parent_id: loop_item.parent_id,
32        phase: loop_item.phase,
33        priority: loop_item.priority,
34        completion_criteria: loop_item.completion_criteria,
35        evidence: loop_item.evidence,
36        blockers: loop_item.blockers,
37        transitions: loop_item.transitions,
38    }
39}
40
41impl TaskLoopContext {
42    /// Clone one loop item into the durable task projection used by SSE
43    /// progress events. Keeping the conversion here prevents the live view
44    /// from drifting from the snapshot representation.
45    pub(crate) fn task_item_snapshot(&self, item_id: &str) -> Option<TaskItem> {
46        self.items
47            .iter()
48            .find(|item| item.id == item_id)
49            .map(clone_loop_item_into_task_item)
50    }
51
52    /// Create `TaskLoopContext` from the session's task list.
53    pub fn from_session(session: &bamboo_agent_core::Session) -> Option<Self> {
54        session.task_list.as_ref().map(|task_list| {
55            // Preserve version from existing task_list metadata if available.
56            // This prevents version reset across multiple executions.
57            let existing_version = session
58                .task_list_version_meta()
59                .or_else(|| session.todo_list_version_meta())
60                .and_then(|v| v.parse::<u64>().ok())
61                .unwrap_or(0);
62
63            let items: Vec<TaskLoopItem> = task_list
64                .items
65                .iter()
66                .map(|item| TaskLoopItem {
67                    id: item.id.clone(),
68                    description: item.description.clone(),
69                    status: item.status.clone(),
70                    depends_on: item.depends_on.clone(),
71                    notes: item.notes.clone(),
72                    active_form: item.active_form.clone(),
73                    parent_id: item.parent_id.clone(),
74                    phase: item.phase.clone(),
75                    priority: item.priority.clone(),
76                    completion_criteria: item.completion_criteria.clone(),
77                    evidence: item.evidence.clone(),
78                    blockers: item.blockers.clone(),
79                    transitions: item.transitions.clone(),
80                    tool_calls: Vec::new(),
81                    started_at_round: item
82                        .transitions
83                        .iter()
84                        .find(|transition| {
85                            matches!(transition.to_status, TaskItemStatus::InProgress)
86                        })
87                        .and_then(|transition| transition.round),
88                    completed_at_round: item
89                        .transitions
90                        .iter()
91                        .rev()
92                        .find(|transition| {
93                            matches!(transition.to_status, TaskItemStatus::Completed)
94                        })
95                        .and_then(|transition| transition.round),
96                })
97                .collect();
98
99            Self {
100                session_id: task_list.session_id.clone(),
101                active_item_id: items
102                    .iter()
103                    .find(|item| matches!(item.status, TaskItemStatus::InProgress))
104                    .map(|item| item.id.clone()),
105                items,
106                current_round: 0,
107                max_rounds: 200,
108                created_at: task_list.created_at,
109                updated_at: task_list.updated_at,
110                version: existing_version,
111                task_list_dirty: false,
112            }
113        })
114    }
115
116    /// Clone into a `TaskList`, preserving the provided title.
117    pub fn to_task_list_with_title(&self, title: impl Into<String>) -> TaskList {
118        TaskList {
119            session_id: self.session_id.clone(),
120            title: title.into(),
121            items: self
122                .items
123                .iter()
124                .map(clone_loop_item_into_task_item)
125                .collect(),
126            created_at: self.created_at,
127            updated_at: self.updated_at,
128        }
129    }
130
131    /// Convert back to `TaskList` for persistence.
132    pub fn into_task_list(self) -> TaskList {
133        self.into_task_list_with_title("Agent Tasks")
134    }
135
136    /// Convert back to `TaskList` for persistence with an explicit title.
137    pub fn into_task_list_with_title(self, title: impl Into<String>) -> TaskList {
138        TaskList {
139            session_id: self.session_id,
140            title: title.into(),
141            items: self.items.into_iter().map(into_task_item).collect(),
142            created_at: self.created_at,
143            updated_at: self.updated_at,
144        }
145    }
146}