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    /// Create `TaskLoopContext` from the session's task list.
43    pub fn from_session(session: &bamboo_agent_core::Session) -> Option<Self> {
44        session.task_list.as_ref().map(|task_list| {
45            // Preserve version from existing task_list metadata if available.
46            // This prevents version reset across multiple executions.
47            let existing_version = session
48                .metadata
49                .get("task_list_version")
50                .or_else(|| session.metadata.get("todo_list_version"))
51                .and_then(|v| v.parse::<u64>().ok())
52                .unwrap_or(0);
53
54            let items: Vec<TaskLoopItem> = task_list
55                .items
56                .iter()
57                .map(|item| TaskLoopItem {
58                    id: item.id.clone(),
59                    description: item.description.clone(),
60                    status: item.status.clone(),
61                    depends_on: item.depends_on.clone(),
62                    notes: item.notes.clone(),
63                    active_form: item.active_form.clone(),
64                    parent_id: item.parent_id.clone(),
65                    phase: item.phase.clone(),
66                    priority: item.priority.clone(),
67                    completion_criteria: item.completion_criteria.clone(),
68                    evidence: item.evidence.clone(),
69                    blockers: item.blockers.clone(),
70                    transitions: item.transitions.clone(),
71                    tool_calls: Vec::new(),
72                    started_at_round: item
73                        .transitions
74                        .iter()
75                        .find(|transition| {
76                            matches!(transition.to_status, TaskItemStatus::InProgress)
77                        })
78                        .and_then(|transition| transition.round),
79                    completed_at_round: item
80                        .transitions
81                        .iter()
82                        .rev()
83                        .find(|transition| {
84                            matches!(transition.to_status, TaskItemStatus::Completed)
85                        })
86                        .and_then(|transition| transition.round),
87                })
88                .collect();
89
90            Self {
91                session_id: task_list.session_id.clone(),
92                active_item_id: items
93                    .iter()
94                    .find(|item| matches!(item.status, TaskItemStatus::InProgress))
95                    .map(|item| item.id.clone()),
96                items,
97                current_round: 0,
98                max_rounds: 200,
99                created_at: task_list.created_at,
100                updated_at: task_list.updated_at,
101                version: existing_version,
102            }
103        })
104    }
105
106    /// Clone into a `TaskList`, preserving the provided title.
107    pub fn to_task_list_with_title(&self, title: impl Into<String>) -> TaskList {
108        TaskList {
109            session_id: self.session_id.clone(),
110            title: title.into(),
111            items: self
112                .items
113                .iter()
114                .map(clone_loop_item_into_task_item)
115                .collect(),
116            created_at: self.created_at,
117            updated_at: self.updated_at,
118        }
119    }
120
121    /// Convert back to `TaskList` for persistence.
122    pub fn into_task_list(self) -> TaskList {
123        self.into_task_list_with_title("Agent Tasks")
124    }
125
126    /// Convert back to `TaskList` for persistence with an explicit title.
127    pub fn into_task_list_with_title(self, title: impl Into<String>) -> TaskList {
128        TaskList {
129            session_id: self.session_id,
130            title: title.into(),
131            items: self.items.into_iter().map(into_task_item).collect(),
132            created_at: self.created_at,
133            updated_at: self.updated_at,
134        }
135    }
136}