ai_agent/utils/
inspector.rs1#[derive(Debug, Clone)]
32pub struct TodoItemInfo {
33 pub content: String,
34 pub status: String,
35 pub active_form: Option<String>,
36}
37
38#[derive(Debug, Clone)]
40pub struct TaskInfo {
41 pub id: String,
42 pub subject: String,
43 pub status: String,
44 pub owner: Option<String>,
45}
46
47pub fn check() -> Option<String> {
52 let incomplete_todos = collect_unfinished_todos();
53 let incomplete_tasks = collect_unfinished_tasks();
54
55 if incomplete_todos.is_empty() && incomplete_tasks.is_empty() {
56 return None;
57 }
58
59 Some(format_nudge(&incomplete_todos, &incomplete_tasks))
60}
61
62fn collect_unfinished_todos() -> Vec<TodoItemInfo> {
63 let session_key = "default_session";
64 crate::tools::todo::get_unfinished_todos(session_key)
65 .into_iter()
66 .map(|t| TodoItemInfo {
67 content: t.content,
68 status: t.status,
69 active_form: t.active_form,
70 })
71 .collect()
72}
73
74fn collect_unfinished_tasks() -> Vec<TaskInfo> {
75 let mut tasks = crate::tools::tasks::get_unfinished_tasks()
76 .into_iter()
77 .map(|t| TaskInfo {
78 id: t.id,
79 subject: t.subject,
80 status: t.status,
81 owner: t.owner,
82 })
83 .collect::<Vec<_>>();
84
85 for t in crate::utils::task_list::get_unfinished_tasks() {
87 if !tasks.iter().any(|ti| ti.id == t.id) {
89 tasks.push(TaskInfo {
90 id: t.id,
91 subject: t.subject,
92 status: t.status.to_string(),
93 owner: t.owner,
94 });
95 }
96 }
97
98 tasks
99}
100
101pub fn format_nudge(incomplete_todos: &[TodoItemInfo], incomplete_tasks: &[TaskInfo]) -> String {
103 let mut lines = Vec::new();
104
105 lines.push(
106 "You have unfinished items that may not be complete. Review and address them:".to_string(),
107 );
108
109 if !incomplete_todos.is_empty() {
110 lines.push(String::new());
111 lines.push("**TODOs:**".to_string());
112 for todo in incomplete_todos {
113 let status_tag = format!("[{}]", todo.status);
114 let active = todo
115 .active_form
116 .as_deref()
117 .map(|a| format!(" ({})", a))
118 .unwrap_or_default();
119 lines.push(format!(" - {} {}{}", status_tag, todo.content, active));
120 }
121 }
122
123 if !incomplete_tasks.is_empty() {
124 lines.push(String::new());
125 lines.push("**Tasks:**".to_string());
126 for task in incomplete_tasks {
127 let owner = task
128 .owner
129 .as_deref()
130 .map(|o| format!(" ({})", o))
131 .unwrap_or_default();
132 lines.push(format!(
133 " - {} [{}]{}{}",
134 task.id, task.status, task.subject, owner
135 ));
136 }
137 }
138
139 lines.push(String::new());
140 lines.push(
141 "Please continue working on these unfinished items before considering the task complete."
142 .to_string(),
143 );
144
145 lines.join("\n")
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn test_format_empty_nudge() {
154 let msg = format_nudge(&[], &[]);
155 assert!(msg.contains("unfinished items"));
156 assert!(msg.contains("continue working"));
157 }
158
159 #[test]
160 fn test_format_nudge_with_todos() {
161 let todos = vec![
162 TodoItemInfo {
163 content: "Implement feature X".to_string(),
164 status: "in_progress".to_string(),
165 active_form: Some("Implementing feature X".to_string()),
166 },
167 TodoItemInfo {
168 content: "Write tests".to_string(),
169 status: "pending".to_string(),
170 active_form: None,
171 },
172 ];
173 let msg = format_nudge(&todos, &[]);
174 assert!(msg.contains("in_progress"));
175 assert!(msg.contains("Implement feature X"));
176 assert!(msg.contains("pending"));
177 assert!(msg.contains("Write tests"));
178 }
179
180 #[test]
181 fn test_format_nudge_with_tasks() {
182 let tasks = vec![TaskInfo {
183 id: "task-1".to_string(),
184 subject: "Add error handling".to_string(),
185 status: "pending".to_string(),
186 owner: Some("agent-1".to_string()),
187 }];
188 let msg = format_nudge(&[], &tasks);
189 assert!(msg.contains("task-1"));
190 assert!(msg.contains("[pending]"));
191 assert!(msg.contains("Add error handling"));
192 assert!(msg.contains("(agent-1)"));
193 }
194
195 #[test]
196 fn test_format_nudge_combined() {
197 let todos = vec![TodoItemInfo {
198 content: "Fix bug".to_string(),
199 status: "in_progress".to_string(),
200 active_form: Some("Fixing bug".to_string()),
201 }];
202 let tasks = vec![TaskInfo {
203 id: "task-2".to_string(),
204 subject: "Update docs".to_string(),
205 status: "pending".to_string(),
206 owner: None,
207 }];
208 let msg = format_nudge(&todos, &tasks);
209 assert!(msg.contains("**TODOs:**"));
210 assert!(msg.contains("**Tasks:**"));
211 assert!(msg.contains("Fix bug"));
212 assert!(msg.contains("task-2"));
213 }
214
215 #[test]
216 fn test_check_no_stores() {
217 let todos = collect_unfinished_todos();
220 let tasks = collect_unfinished_tasks();
221 let _ = check();
223 }
224}