Skip to main content

j_agent/tools/task/
task_tool.rs

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/// TaskTool 参数
10#[derive(Deserialize, JsonSchema)]
11#[serde(rename_all = "camelCase")]
12struct TaskParams {
13    /// Operation to perform: create, get, list, or update
14    action: String,
15    /// A brief, actionable title for the task (required for create)
16    #[serde(default)]
17    title: Option<String>,
18    /// Detailed description of what needs to be done
19    #[serde(default)]
20    description: Option<String>,
21    /// Paths to task documents containing full details about the task
22    #[serde(default)]
23    task_doc_paths: Option<Vec<String>>,
24    /// List of task IDs that must complete before this task can start (for create)
25    #[serde(default)]
26    blocked_by: Option<Vec<u64>>,
27    /// The ID of the task to retrieve or update (required for get/update)
28    #[serde(default)]
29    task_id: Option<u64>,
30    /// When true (list only), return only tasks that are pending and have no unresolved blockers
31    #[serde(default)]
32    ready: bool,
33    /// New status for the task (for update)
34    #[serde(default)]
35    status: Option<String>,
36    /// Person or agent responsible for the task (for update)
37    #[serde(default)]
38    #[allow(dead_code)]
39    owner: Option<String>,
40    /// Task IDs to add as blockers of the current task (for update)
41    #[serde(default)]
42    #[allow(dead_code)]
43    add_blocked_by: Option<Vec<u64>>,
44}
45
46/// 任务管理工具,支持任务的创建、查询、列表和更新
47#[derive(Debug)]
48pub struct TaskTool {
49    /// 任务管理器实例
50    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        // We also parse as Value for update_task which takes a Value
109        let parsed: Value = serde_json::from_str(arguments).unwrap_or_default();
110
111        match params.action.as_str() {
112            "create" => self.execute_create(&params),
113            "get" => self.execute_get(&params),
114            "list" => self.execute_list(&params),
115            "update" => self.execute_update(&params, &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}