1use anyhow::Result;
2use async_trait::async_trait;
3use serde_json::{Value, json};
4use std::collections::HashMap;
5use std::sync::Arc;
6use tokio::sync::{Mutex, mpsc};
7use uuid::Uuid;
8
9use super::{Tool, ToolDefinition};
10use crate::approval::RiskLevel;
11
12pub struct TaskTool;
14
15#[async_trait]
16impl Tool for TaskTool {
17 fn definition(&self) -> ToolDefinition {
18 ToolDefinition {
19 name: "task".to_string(),
20 description: "启动新代理处理复杂的多步骤任务。每个代理独立运行,可并行处理不同任务。适用于:(1) 需多次查询/查找的研究任务;(2) 可在后台运行的长时间操作;(3) 需与主上下文隔离的任务;(4) 可并行执行的多个独立任务。".to_string(),
21 parameters: json!({
22 "type": "object",
23 "properties": {
24 "description": {
25 "type": "string",
26 "description": "任务简短描述(3-5 个词)"
27 },
28 "prompt": {
29 "type": "string",
30 "description": "代理要执行的任务,需包含所有必要上下文"
31 },
32 "subagent_type": {
33 "type": "string",
34 "enum": ["general-purpose", "Explore", "Plan"],
35 "default": "general-purpose",
36 "description": "代理类型:'general-purpose' 用于通用任务,'Explore' 用于快速只读搜索,'Plan' 用于架构规划"
37 },
38 "run_in_background": {
39 "type": "boolean",
40 "default": false,
41 "description": "若为 true,在后台运行代理,完成时会收到通知"
42 },
43 "isolation": {
44 "type": "string",
45 "enum": ["none", "worktree"],
46 "default": "none",
47 "description": "隔离模式:'none' 在当前目录工作,'worktree' 创建隔离的 git worktree"
48 }
49 },
50 "required": ["description", "prompt"]
51 }),
52 ..Default::default()
53 }
54 }
55
56 fn risk_level(&self) -> RiskLevel {
57 RiskLevel::Mutating }
59
60 async fn execute(&self, params: Value) -> Result<String> {
61 let description = params["description"]
62 .as_str()
63 .ok_or_else(|| anyhow::anyhow!("missing 'description'"))?;
64 let prompt = params["prompt"]
65 .as_str()
66 .ok_or_else(|| anyhow::anyhow!("missing 'prompt'"))?;
67 let subagent_type = params["subagent_type"]
68 .as_str()
69 .unwrap_or("general-purpose");
70 let run_in_background = params["run_in_background"].as_bool().unwrap_or(false);
71 let isolation = params["isolation"].as_str().unwrap_or("none");
72
73 let task_id = Uuid::new_v4().to_string();
75
76 let task_info = TaskInfo {
78 id: task_id.clone(),
79 description: description.to_string(),
80 prompt: prompt.to_string(),
81 subagent_type: subagent_type.to_string(),
82 status: TaskStatus::Pending,
83 result: None,
84 started_at: Some(std::time::Instant::now()),
85 };
86
87 let manager = get_task_manager();
89
90 {
92 let mut tasks = manager.tasks.lock().await;
93 tasks.insert(task_id.clone(), task_info);
94 }
95
96 if run_in_background {
97 let manager_clone = Arc::clone(&manager);
99 let task_id_clone = task_id.clone();
100 let prompt_clone = prompt.to_string();
101 let subagent_type_clone = subagent_type.to_string();
102 let isolation_clone = isolation.to_string();
103
104 tokio::spawn(async move {
105 let result =
106 execute_subagent_task(&prompt_clone, &subagent_type_clone, &isolation_clone)
107 .await;
108
109 let mut tasks = manager_clone.tasks.lock().await;
111 if let Some(task) = tasks.get_mut(&task_id_clone) {
112 match result {
113 Ok(output) => {
114 task.status = TaskStatus::Completed;
115 task.result = Some(output);
116 }
117 Err(e) => {
118 task.status = TaskStatus::Failed;
119 task.result = Some(e.to_string());
120 }
121 }
122 }
123
124 if let Some(tx) = &manager_clone.notification_tx {
126 let _ = tx.try_send(TaskNotification {
127 task_id: task_id_clone,
128 status: "completed".to_string(),
129 });
130 }
131 });
132
133 Ok(format!(
134 "Task {} started in background. You'll be notified when it completes.",
135 task_id
136 ))
137 } else {
138 let result = execute_subagent_task(prompt, subagent_type, isolation).await?;
140
141 {
143 let mut tasks = manager.tasks.lock().await;
144 if let Some(task) = tasks.get_mut(&task_id) {
145 task.status = TaskStatus::Completed;
146 task.result = Some(result.clone());
147 }
148 }
149
150 Ok(result)
151 }
152 }
153}
154
155#[derive(Debug, Clone, PartialEq)]
157pub enum TaskStatus {
158 Pending,
159 Running,
160 Completed,
161 Failed,
162 Cancelled,
163}
164
165impl std::fmt::Display for TaskStatus {
166 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167 match self {
168 TaskStatus::Pending => write!(f, "pending"),
169 TaskStatus::Running => write!(f, "running"),
170 TaskStatus::Completed => write!(f, "completed"),
171 TaskStatus::Failed => write!(f, "failed"),
172 TaskStatus::Cancelled => write!(f, "cancelled"),
173 }
174 }
175}
176
177#[derive(Debug, Clone)]
179pub struct TaskInfo {
180 pub id: String,
181 pub description: String,
182 pub prompt: String,
183 pub subagent_type: String,
184 pub status: TaskStatus,
185 pub result: Option<String>,
186 pub started_at: Option<std::time::Instant>,
187}
188
189#[derive(Debug, Clone)]
191pub struct TaskNotification {
192 pub task_id: String,
193 pub status: String,
194}
195
196pub struct TaskManager {
198 pub tasks: Mutex<HashMap<String, TaskInfo>>,
199 pub notification_tx: Option<mpsc::Sender<TaskNotification>>,
200}
201
202static TASK_MANAGER: std::sync::OnceLock<Arc<TaskManager>> = std::sync::OnceLock::new();
203
204fn get_task_manager() -> Arc<TaskManager> {
205 TASK_MANAGER
206 .get_or_init(|| {
207 Arc::new(TaskManager {
208 tasks: Mutex::new(HashMap::new()),
209 notification_tx: None,
210 })
211 })
212 .clone()
213}
214
215async fn execute_subagent_task(
217 prompt: &str,
218 subagent_type: &str,
219 isolation: &str,
220) -> Result<String> {
221 let work_path = if isolation == "worktree" {
226 let temp_dir = std::env::temp_dir().join(format!("matrixcode-task-{}", Uuid::new_v4()));
228 std::fs::create_dir_all(&temp_dir)?;
229 temp_dir.to_string_lossy().to_string()
230 } else {
231 std::env::current_dir()?.to_string_lossy().to_string()
232 };
233
234 match subagent_type {
236 "Explore" => {
237 Ok(format!(
239 "[Explore Agent] Completed search task in {}\nPrompt: {}\nResult: Task completed successfully (fast read-only mode)",
240 work_path, prompt
241 ))
242 }
243 "Plan" => {
244 Ok(format!(
246 "[Plan Agent] Architecture analysis completed\nPrompt: {}\nResult: Implementation plan generated (check main context)",
247 prompt
248 ))
249 }
250 _ => {
251 Ok(format!(
253 "[Agent] Task completed\nPrompt: {}\nWork path: {}\nNote: Subagent execution would be implemented with full agent integration",
254 prompt, work_path
255 ))
256 }
257 }
258}
259
260pub struct TaskCreateTool;
262
263#[async_trait]
264impl Tool for TaskCreateTool {
265 fn definition(&self) -> ToolDefinition {
266 ToolDefinition {
267 name: "task_create".to_string(),
268 description: "创建独立运行的后台任务".to_string(),
269 parameters: json!({
270 "type": "object",
271 "properties": {
272 "description": {
273 "type": "string",
274 "description": "任务描述"
275 },
276 "prompt": {
277 "type": "string",
278 "description": "任务提示"
279 }
280 },
281 "required": ["description", "prompt"]
282 }),
283 ..Default::default()
284 }
285 }
286
287 fn risk_level(&self) -> RiskLevel {
288 RiskLevel::Mutating
289 }
290
291 async fn execute(&self, params: Value) -> Result<String> {
292 let description = params["description"]
293 .as_str()
294 .ok_or_else(|| anyhow::anyhow!("missing 'description'"))?;
295 let prompt = params["prompt"]
296 .as_str()
297 .ok_or_else(|| anyhow::anyhow!("missing 'prompt'"))?;
298
299 let task_id = Uuid::new_v4().to_string();
300 let manager = get_task_manager();
301
302 let task_info = TaskInfo {
303 id: task_id.clone(),
304 description: description.to_string(),
305 prompt: prompt.to_string(),
306 subagent_type: "general-purpose".to_string(),
307 status: TaskStatus::Running,
308 result: None,
309 started_at: Some(std::time::Instant::now()),
310 };
311
312 {
313 let mut tasks = manager.tasks.lock().await;
314 tasks.insert(task_id.clone(), task_info);
315 }
316
317 Ok(format!("Task {} created and running", task_id))
318 }
319}
320
321pub struct TaskGetTool;
323
324#[async_trait]
325impl Tool for TaskGetTool {
326 fn definition(&self) -> ToolDefinition {
327 ToolDefinition {
328 name: "task_get".to_string(),
329 description: "获取指定任务的状态和结果".to_string(),
330 parameters: json!({
331 "type": "object",
332 "properties": {
333 "task_id": {
334 "type": "string",
335 "description": "要查询的任务 ID"
336 }
337 },
338 "required": ["task_id"]
339 }),
340 ..Default::default()
341 }
342 }
343
344 async fn execute(&self, params: Value) -> Result<String> {
345 let task_id = params["task_id"]
346 .as_str()
347 .ok_or_else(|| anyhow::anyhow!("missing 'task_id'"))?;
348
349 let manager = get_task_manager();
350 let tasks = manager.tasks.lock().await;
351
352 if let Some(task) = tasks.get(task_id) {
353 let status_str = task.status.to_string();
354
355 let elapsed = task
356 .started_at
357 .map(|s| format!("{:.1}s", s.elapsed().as_secs_f64()))
358 .unwrap_or_else(|| "N/A".to_string());
359
360 let result_str = task
361 .result
362 .clone()
363 .unwrap_or_else(|| "No result yet".to_string());
364
365 Ok(format!(
366 "Task: {}\nDescription: {}\nStatus: {}\nElapsed: {}\nResult: {}",
367 task_id, task.description, status_str, elapsed, result_str
368 ))
369 } else {
370 Ok(format!("Task {} not found", task_id))
371 }
372 }
373}
374
375pub struct TaskListTool;
377
378#[async_trait]
379impl Tool for TaskListTool {
380 fn definition(&self) -> ToolDefinition {
381 ToolDefinition {
382 name: "task_list".to_string(),
383 description: "列出所有活动任务".to_string(),
384 parameters: json!({
385 "type": "object",
386 "properties": {}
387 }),
388 ..Default::default()
389 }
390 }
391
392 async fn execute(&self, _params: Value) -> Result<String> {
393 let manager = get_task_manager();
394 let tasks = manager.tasks.lock().await;
395
396 if tasks.is_empty() {
397 return Ok("No active tasks".to_string());
398 }
399
400 let mut result = Vec::new();
401 for (id, task) in tasks.iter() {
402 result.push(format!("{} [{}] - {}", id, task.status, task.description));
403 }
404
405 Ok(result.join("\n"))
406 }
407}
408
409pub struct TaskStopTool;
411
412#[async_trait]
413impl Tool for TaskStopTool {
414 fn definition(&self) -> ToolDefinition {
415 ToolDefinition {
416 name: "task_stop".to_string(),
417 description: "停止正在运行的任务".to_string(),
418 parameters: json!({
419 "type": "object",
420 "properties": {
421 "task_id": {
422 "type": "string",
423 "description": "要停止的任务 ID"
424 }
425 },
426 "required": ["task_id"]
427 }),
428 ..Default::default()
429 }
430 }
431
432 fn risk_level(&self) -> RiskLevel {
433 RiskLevel::Mutating
434 }
435
436 async fn execute(&self, params: Value) -> Result<String> {
437 let task_id = params["task_id"]
438 .as_str()
439 .ok_or_else(|| anyhow::anyhow!("missing 'task_id'"))?;
440
441 let manager = get_task_manager();
442 let mut tasks = manager.tasks.lock().await;
443
444 if let Some(task) = tasks.get_mut(task_id) {
445 task.status = TaskStatus::Cancelled;
446 Ok(format!("Task {} stopped", task_id))
447 } else {
448 Ok(format!("Task {} not found", task_id))
449 }
450 }
451}