1use serde_json::{Value, json};
6use tracing::instrument;
7
8use super::SharedTaskManager;
9use crate::tasks::{
10 Task, TaskIcon, TaskId, TaskStatus, format_task_indicators, format_task_status,
11};
12
13pub fn is_task_tool(name: &str) -> bool {
15 matches!(
16 name,
17 "task_list"
18 | "task_status"
19 | "task_foreground"
20 | "task_background"
21 | "task_cancel"
22 | "task_pause"
23 | "task_resume"
24 | "task_input"
25 | "task_describe"
26 )
27}
28
29#[instrument(skip(task_mgr, args), fields(tool = %name))]
31pub async fn execute_task_tool(
32 name: &str,
33 args: &Value,
34 task_mgr: &SharedTaskManager,
35 session_key: Option<&str>,
36) -> Result<String, String> {
37 match name {
38 "task_list" => exec_task_list(args, task_mgr, session_key).await,
39 "task_status" => exec_task_status(args, task_mgr).await,
40 "task_foreground" => exec_task_foreground(args, task_mgr).await,
41 "task_background" => exec_task_background(args, task_mgr).await,
42 "task_cancel" => exec_task_cancel(args, task_mgr).await,
43 "task_pause" => exec_task_pause(args, task_mgr).await,
44 "task_resume" => exec_task_resume(args, task_mgr).await,
45 "task_input" => exec_task_input(args, task_mgr).await,
46 "task_describe" => exec_task_describe(args, task_mgr, session_key).await,
47 _ => Err(format!("Unknown task tool: {}", name)),
48 }
49}
50
51async fn exec_task_list(
53 args: &Value,
54 task_mgr: &SharedTaskManager,
55 current_session: Option<&str>,
56) -> Result<String, String> {
57 let session_filter = args.get("session").and_then(|v| v.as_str());
58 let include_completed = args
59 .get("includeCompleted")
60 .and_then(|v| v.as_bool())
61 .unwrap_or(false);
62
63 let tasks: Vec<Task> = if let Some(session) = session_filter {
64 task_mgr.for_session(session).await
65 } else if let Some(session) = current_session {
66 task_mgr.for_session(session).await
68 } else {
69 task_mgr.all().await
70 };
71
72 let filtered: Vec<&Task> = tasks
73 .iter()
74 .filter(|t| include_completed || !t.status.is_terminal())
75 .collect();
76
77 if filtered.is_empty() {
78 return Ok(json!({
79 "tasks": [],
80 "message": "No active tasks"
81 })
82 .to_string());
83 }
84
85 let task_list: Vec<Value> = filtered
86 .iter()
87 .map(|t| {
88 json!({
89 "id": t.id.0,
90 "kind": t.kind.display_name(),
91 "label": t.display_label(),
92 "status": format_status_short(&t.status),
93 "foreground": t.status.is_foreground(),
94 "elapsed": t.elapsed().map(|d| d.as_secs()),
95 "progress": t.status.progress(),
96 })
97 })
98 .collect();
99
100 let indicators =
101 format_task_indicators(&filtered.iter().cloned().cloned().collect::<Vec<_>>(), 5);
102
103 Ok(json!({
104 "tasks": task_list,
105 "count": filtered.len(),
106 "indicators": indicators,
107 })
108 .to_string())
109}
110
111async fn exec_task_status(args: &Value, task_mgr: &SharedTaskManager) -> Result<String, String> {
113 let task_id = parse_task_id(args)?;
114
115 let task = task_mgr
116 .get(task_id)
117 .await
118 .ok_or_else(|| format!("Task {} not found", task_id))?;
119
120 Ok(json!({
121 "id": task.id.0,
122 "kind": task.kind.display_name(),
123 "kindDetails": task.kind.description(),
124 "label": task.display_label(),
125 "status": format_task_status(&task),
126 "statusCode": format_status_short(&task.status),
127 "foreground": task.status.is_foreground(),
128 "progress": task.status.progress(),
129 "message": task.status.message(),
130 "elapsed": task.elapsed().map(|d| d.as_secs()),
131 "session": task.session_key,
132 "output": if task.status.is_terminal() {
133 task.output_buffer.clone()
134 } else {
135 String::new()
136 },
137 })
138 .to_string())
139}
140
141async fn exec_task_foreground(
143 args: &Value,
144 task_mgr: &SharedTaskManager,
145) -> Result<String, String> {
146 let task_id = parse_task_id(args)?;
147
148 task_mgr.set_foreground(task_id).await?;
149
150 let task = task_mgr
151 .get(task_id)
152 .await
153 .ok_or_else(|| format!("Task {} not found", task_id))?;
154
155 Ok(json!({
156 "success": true,
157 "id": task_id.0,
158 "label": task.display_label(),
159 "message": format!("Task {} is now in foreground", task_id),
160 })
161 .to_string())
162}
163
164async fn exec_task_background(
166 args: &Value,
167 task_mgr: &SharedTaskManager,
168) -> Result<String, String> {
169 let task_id = parse_task_id(args)?;
170
171 task_mgr.set_background(task_id).await?;
172
173 let task = task_mgr
174 .get(task_id)
175 .await
176 .ok_or_else(|| format!("Task {} not found", task_id))?;
177
178 Ok(json!({
179 "success": true,
180 "id": task_id.0,
181 "label": task.display_label(),
182 "message": format!("Task {} moved to background", task_id),
183 })
184 .to_string())
185}
186
187async fn exec_task_cancel(args: &Value, task_mgr: &SharedTaskManager) -> Result<String, String> {
189 let task_id = parse_task_id(args)?;
190
191 task_mgr.cancel(task_id).await?;
192
193 Ok(json!({
194 "success": true,
195 "id": task_id.0,
196 "message": format!("Task {} cancelled", task_id),
197 })
198 .to_string())
199}
200
201async fn exec_task_pause(args: &Value, task_mgr: &SharedTaskManager) -> Result<String, String> {
203 let task_id = parse_task_id(args)?;
204
205 task_mgr
207 .update_status(task_id, TaskStatus::Paused { reason: None })
208 .await;
209
210 Ok(json!({
211 "success": true,
212 "id": task_id.0,
213 "message": format!("Task {} paused", task_id),
214 "note": "Not all task types support pause/resume",
215 })
216 .to_string())
217}
218
219async fn exec_task_resume(args: &Value, task_mgr: &SharedTaskManager) -> Result<String, String> {
221 let task_id = parse_task_id(args)?;
222
223 let task = task_mgr
225 .get(task_id)
226 .await
227 .ok_or_else(|| format!("Task {} not found", task_id))?;
228
229 if !matches!(task.status, TaskStatus::Paused { .. }) {
230 return Err(format!(
231 "Task {} is not paused (status: {})",
232 task_id,
233 format_status_short(&task.status)
234 ));
235 }
236
237 task_mgr
239 .update_status(
240 task_id,
241 TaskStatus::Running {
242 progress: None,
243 message: Some("Resumed".to_string()),
244 },
245 )
246 .await;
247
248 Ok(json!({
249 "success": true,
250 "id": task_id.0,
251 "message": format!("Task {} resumed", task_id),
252 })
253 .to_string())
254}
255
256async fn exec_task_input(args: &Value, task_mgr: &SharedTaskManager) -> Result<String, String> {
258 let task_id = parse_task_id(args)?;
259 let input = args
260 .get("input")
261 .and_then(|v| v.as_str())
262 .ok_or("Missing required parameter: input")?;
263
264 let task = task_mgr
265 .get(task_id)
266 .await
267 .ok_or_else(|| format!("Task {} not found", task_id))?;
268
269 if !matches!(task.status, TaskStatus::WaitingForInput { .. }) {
270 return Err(format!(
271 "Task {} is not waiting for input (status: {})",
272 task_id,
273 format_status_short(&task.status)
274 ));
275 }
276
277 Ok(json!({
281 "success": true,
282 "id": task_id.0,
283 "input": input,
284 "message": format!("Input sent to task {}", task_id),
285 "note": "Task input delivery not yet fully implemented",
286 })
287 .to_string())
288}
289
290async fn exec_task_describe(
292 args: &Value,
293 task_mgr: &SharedTaskManager,
294 session_key: Option<&str>,
295) -> Result<String, String> {
296 let description = args
297 .get("description")
298 .and_then(|v| v.as_str())
299 .ok_or("Missing required parameter: description")?;
300
301 let task_id = if let Ok(id) = parse_task_id(args) {
303 id
304 } else if let Some(session) = session_key {
305 let tasks = task_mgr.for_session(session).await;
307 tasks
308 .iter()
309 .find(|t| matches!(t.status, TaskStatus::Running { .. }))
310 .map(|t| t.id)
311 .ok_or("No active task found for current session")?
312 } else {
313 return Err("No task ID provided and no session context".to_string());
314 };
315
316 task_mgr.set_description(task_id, description).await?;
317
318 Ok(json!({
319 "success": true,
320 "id": task_id.0,
321 "description": description,
322 "message": format!("Task {} description updated", task_id),
323 })
324 .to_string())
325}
326
327fn parse_task_id(args: &Value) -> Result<TaskId, String> {
330 let id = args
331 .get("id")
332 .or_else(|| args.get("taskId"))
333 .and_then(|v| v.as_u64())
334 .ok_or("Missing required parameter: id (task ID)")?;
335
336 Ok(TaskId(id))
337}
338
339fn format_status_short(status: &TaskStatus) -> &'static str {
340 match status {
341 TaskStatus::Pending => "pending",
342 TaskStatus::Running { .. } => "running",
343 TaskStatus::Background { .. } => "background",
344 TaskStatus::Paused { .. } => "paused",
345 TaskStatus::Completed { .. } => "completed",
346 TaskStatus::Failed { .. } => "failed",
347 TaskStatus::Cancelled => "cancelled",
348 TaskStatus::WaitingForInput { .. } => "waiting_input",
349 }
350}
351
352pub async fn generate_task_prompt_section(
354 task_mgr: &SharedTaskManager,
355 session_key: &str,
356) -> Option<String> {
357 let tasks = task_mgr.for_session(session_key).await;
358 let active: Vec<_> = tasks.iter().filter(|t| !t.status.is_terminal()).collect();
359
360 if active.is_empty() {
361 return None;
362 }
363
364 let mut section = String::from("## Active Tasks\n");
365
366 for task in &active {
367 let icon = TaskIcon::from_status(&task.status);
368 let fg = if task.status.is_foreground() {
369 " [foreground]"
370 } else {
371 ""
372 };
373 let desc = task.display_description();
375 section.push_str(&format!(
376 "- {} #{}: {}{}\n",
377 icon.emoji(),
378 task.id.0,
379 desc,
380 fg
381 ));
382 }
383
384 section.push_str(
385 "\nUse task_foreground/task_background to switch focus, task_cancel to stop.\n\
386 Use task_describe to update what your task is doing.\n",
387 );
388
389 Some(section)
390}