1use super::task_manager::TaskManager;
2use crate::tools::{PlanDecision, Tool, ToolResult, parse_tool_args, schema_to_tool_params};
3use schemars::JsonSchema;
4use serde::Deserialize;
5use serde_json::{Value, json};
6use std::borrow::Cow;
7use std::sync::{Arc, atomic::AtomicBool};
8
9#[derive(Deserialize, JsonSchema)]
11#[serde(rename_all = "camelCase")]
12struct TaskParams {
13 action: String,
15 #[serde(default)]
17 title: Option<String>,
18 #[serde(default)]
20 description: Option<String>,
21 #[serde(default)]
23 task_doc_paths: Option<Vec<String>>,
24 #[serde(default)]
26 blocked_by: Option<Vec<u64>>,
27 #[serde(default)]
29 task_id: Option<u64>,
30 #[serde(default)]
32 ready: bool,
33 #[serde(default)]
35 status: Option<String>,
36 #[serde(default)]
38 #[allow(dead_code)]
39 owner: Option<String>,
40 #[serde(default)]
42 #[allow(dead_code)]
43 add_blocked_by: Option<Vec<u64>>,
44}
45
46#[derive(Debug)]
48pub struct TaskTool {
49 pub manager: Arc<TaskManager>,
51}
52
53impl TaskTool {
54 pub const NAME: &'static str = "Task";
55}
56
57impl Tool for TaskTool {
58 fn name(&self) -> &str {
59 Self::NAME
60 }
61
62 fn description(&self) -> Cow<'_, str> {
63 r#"
64 Manage tasks (create / get / list / update). Use the `action` field to choose the operation.
65
66 **action: "create"**
67 Create a self-contained task. Tasks should be actionable based on the provided title,
68 description, and task documents, as they will be assigned to an agent for execution.
69
70 Do NOT use the Task tool for very small, single-step operations (use TodoWrite instead), such as:
71 - Reading one known file path
72 - Searching for a single class/function definition in a known file
73 - Finding a simple, localized match in one or two files
74 - Tasks that can be completed with a single read_file or search_file call
75
76 Use "create" for tasks that require multiple steps, such as when you break down a complex
77 task into multiple sub-tasks. Use blockedBy to specify dependencies between them.
78 Required fields: title
79
80 **action: "get"**
81 Retrieve full details of a single task by its ID, including title, description, status,
82 owner, and dependency information (blockedBy).
83 Required fields: taskId
84
85 **action: "list"**
86 List all tasks with summary information (ID, title, status, dependencies).
87 Use the optional `ready: true` filter to show only actionable tasks
88 (pending with no unresolved blockers).
89
90 **action: "update"**
91 Update an existing task's status, title, description, owner, or dependencies.
92 Status flow: pending → in_progress → completed. Use "deleted" to remove a task entirely.
93 When a task is completed or deleted, it is automatically removed from other tasks' blockedBy lists.
94 Required fields: taskId
95 "#.into()
96 }
97
98 fn parameters_schema(&self) -> Value {
99 schema_to_tool_params::<TaskParams>()
100 }
101
102 fn execute(&self, arguments: &str, _cancelled: &Arc<AtomicBool>) -> ToolResult {
103 let params: TaskParams = match parse_tool_args(arguments) {
104 Ok(p) => p,
105 Err(e) => return e,
106 };
107
108 let parsed: Value = serde_json::from_str(arguments).unwrap_or_default();
110
111 match params.action.as_str() {
112 "create" => self.execute_create(¶ms),
113 "get" => self.execute_get(¶ms),
114 "list" => self.execute_list(¶ms),
115 "update" => self.execute_update(¶ms, &parsed),
116 other => ToolResult {
117 output: format!(
118 "Unknown action: '{}'. Must be one of: create, get, list, update",
119 other
120 ),
121 is_error: true,
122 images: vec![],
123 plan_decision: PlanDecision::None,
124 },
125 }
126 }
127}
128
129impl TaskTool {
130 fn execute_create(&self, params: &TaskParams) -> ToolResult {
131 let title = match params.title.as_deref() {
132 Some(s) => s,
133 None => {
134 return ToolResult {
135 output: "title is required for action=create".to_string(),
136 is_error: true,
137 images: vec![],
138 plan_decision: PlanDecision::None,
139 };
140 }
141 };
142
143 let description = params.description.as_deref().unwrap_or("");
144 let blocked_by = params.blocked_by.clone().unwrap_or_default();
145 let task_doc_paths = params.task_doc_paths.clone().unwrap_or_default();
146
147 match self
148 .manager
149 .create_task(title, description, blocked_by, task_doc_paths)
150 {
151 Ok(task) => ToolResult {
152 output: serde_json::to_string_pretty(&task).unwrap_or_default(),
153 is_error: false,
154 images: vec![],
155 plan_decision: PlanDecision::None,
156 },
157 Err(e) => ToolResult {
158 output: e.to_string(),
159 is_error: true,
160 images: vec![],
161 plan_decision: PlanDecision::None,
162 },
163 }
164 }
165
166 fn execute_get(&self, params: &TaskParams) -> ToolResult {
167 let task_id = match params.task_id {
168 Some(id) => id,
169 None => {
170 return ToolResult {
171 output: "taskId is required for action=get".to_string(),
172 is_error: true,
173 images: vec![],
174 plan_decision: PlanDecision::None,
175 };
176 }
177 };
178
179 match self.manager.get_task(task_id) {
180 Ok(task) => ToolResult {
181 output: serde_json::to_string_pretty(&task).unwrap_or_default(),
182 is_error: false,
183 images: vec![],
184 plan_decision: PlanDecision::None,
185 },
186 Err(e) => ToolResult {
187 output: e,
188 is_error: true,
189 images: vec![],
190 plan_decision: PlanDecision::None,
191 },
192 }
193 }
194
195 fn execute_list(&self, params: &TaskParams) -> ToolResult {
196 let tasks = if params.ready {
197 self.manager.list_ready_tasks()
198 } else {
199 self.manager.list_tasks()
200 };
201
202 if tasks.is_empty() {
203 return ToolResult {
204 output: if params.ready {
205 "No ready tasks found (all tasks are either blocked, in progress, or completed)"
206 .to_string()
207 } else {
208 "No tasks exist".to_string()
209 },
210 is_error: false,
211 images: vec![],
212 plan_decision: PlanDecision::None,
213 };
214 }
215
216 let summary: Vec<Value> = tasks
217 .iter()
218 .map(|t| {
219 json!({
220 "taskId": t.task_id,
221 "title": t.title,
222 "status": t.status,
223 "blockedBy": t.blocked_by,
224 })
225 })
226 .collect();
227
228 ToolResult {
229 output: serde_json::to_string_pretty(&summary).unwrap_or_default(),
230 is_error: false,
231 images: vec![],
232 plan_decision: PlanDecision::None,
233 }
234 }
235
236 fn execute_update(&self, params: &TaskParams, parsed: &Value) -> ToolResult {
237 let task_id = match params.task_id {
238 Some(id) => id,
239 None => {
240 return ToolResult {
241 output: "taskId is required for action=update".to_string(),
242 is_error: true,
243 images: vec![],
244 plan_decision: PlanDecision::None,
245 };
246 }
247 };
248
249 let status = params.status.as_deref().unwrap_or_default();
250
251 match self.manager.update_task(task_id, parsed) {
252 Ok(task) => {
253 if status == "completed" {
254 ToolResult {
255 output: format!(
256 "Update task successfully. Following tasks are ready: \n\n{}",
257 serde_json::to_string_pretty(&self.manager.list_ready_tasks())
258 .unwrap_or_default()
259 ),
260 is_error: false,
261 images: vec![],
262 plan_decision: PlanDecision::None,
263 }
264 } else {
265 ToolResult {
266 output: format!(
267 "Update task successfully. updated task detail: {}",
268 serde_json::to_string_pretty(&task).unwrap_or_default()
269 ),
270 is_error: false,
271 images: vec![],
272 plan_decision: PlanDecision::None,
273 }
274 }
275 }
276 Err(e) => ToolResult {
277 output: e,
278 is_error: true,
279 images: vec![],
280 plan_decision: PlanDecision::None,
281 },
282 }
283 }
284}