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 }
53 }
54
55 fn risk_level(&self) -> RiskLevel {
56 RiskLevel::Mutating }
58
59 async fn execute(&self, params: Value) -> Result<String> {
60 let description = params["description"]
61 .as_str()
62 .ok_or_else(|| anyhow::anyhow!("missing 'description'"))?;
63 let prompt = params["prompt"]
64 .as_str()
65 .ok_or_else(|| anyhow::anyhow!("missing 'prompt'"))?;
66 let subagent_type = params["subagent_type"]
67 .as_str()
68 .unwrap_or("general-purpose");
69 let run_in_background = params["run_in_background"].as_bool().unwrap_or(false);
70 let isolation = params["isolation"].as_str().unwrap_or("none");
71
72 let task_id = Uuid::new_v4().to_string();
74
75 let task_info = TaskInfo {
77 id: task_id.clone(),
78 description: description.to_string(),
79 prompt: prompt.to_string(),
80 subagent_type: subagent_type.to_string(),
81 status: TaskStatus::Pending,
82 result: None,
83 started_at: Some(std::time::Instant::now()),
84 };
85
86 let manager = get_task_manager();
88
89 {
91 let mut tasks = manager.tasks.lock().await;
92 tasks.insert(task_id.clone(), task_info);
93 }
94
95 if run_in_background {
96 let manager_clone = Arc::clone(&manager);
98 let task_id_clone = task_id.clone();
99 let prompt_clone = prompt.to_string();
100 let subagent_type_clone = subagent_type.to_string();
101 let isolation_clone = isolation.to_string();
102
103 tokio::spawn(async move {
104 let result =
105 execute_subagent_task(&prompt_clone, &subagent_type_clone, &isolation_clone)
106 .await;
107
108 let mut tasks = manager_clone.tasks.lock().await;
110 if let Some(task) = tasks.get_mut(&task_id_clone) {
111 match result {
112 Ok(output) => {
113 task.status = TaskStatus::Completed;
114 task.result = Some(output);
115 }
116 Err(e) => {
117 task.status = TaskStatus::Failed;
118 task.result = Some(e.to_string());
119 }
120 }
121 }
122
123 if let Some(tx) = &manager_clone.notification_tx {
125 let _ = tx.try_send(TaskNotification {
126 task_id: task_id_clone,
127 status: "completed".to_string(),
128 });
129 }
130 });
131
132 Ok(format!(
133 "Task {} started in background. You'll be notified when it completes.",
134 task_id
135 ))
136 } else {
137 let result = execute_subagent_task(prompt, subagent_type, isolation).await?;
139
140 {
142 let mut tasks = manager.tasks.lock().await;
143 if let Some(task) = tasks.get_mut(&task_id) {
144 task.status = TaskStatus::Completed;
145 task.result = Some(result.clone());
146 }
147 }
148
149 Ok(result)
150 }
151 }
152}
153
154#[derive(Debug, Clone, PartialEq)]
156pub enum TaskStatus {
157 Pending,
158 Running,
159 Completed,
160 Failed,
161 Cancelled,
162}
163
164impl std::fmt::Display for TaskStatus {
165 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166 match self {
167 TaskStatus::Pending => write!(f, "pending"),
168 TaskStatus::Running => write!(f, "running"),
169 TaskStatus::Completed => write!(f, "completed"),
170 TaskStatus::Failed => write!(f, "failed"),
171 TaskStatus::Cancelled => write!(f, "cancelled"),
172 }
173 }
174}
175
176#[derive(Debug, Clone)]
178pub struct TaskInfo {
179 pub id: String,
180 pub description: String,
181 pub prompt: String,
182 pub subagent_type: String,
183 pub status: TaskStatus,
184 pub result: Option<String>,
185 pub started_at: Option<std::time::Instant>,
186}
187
188#[derive(Debug, Clone)]
190pub struct TaskNotification {
191 pub task_id: String,
192 pub status: String,
193}
194
195pub struct TaskManager {
197 pub tasks: Mutex<HashMap<String, TaskInfo>>,
198 pub notification_tx: Option<mpsc::Sender<TaskNotification>>,
199}
200
201static TASK_MANAGER: std::sync::OnceLock<Arc<TaskManager>> = std::sync::OnceLock::new();
202
203fn get_task_manager() -> Arc<TaskManager> {
204 TASK_MANAGER
205 .get_or_init(|| {
206 Arc::new(TaskManager {
207 tasks: Mutex::new(HashMap::new()),
208 notification_tx: None,
209 })
210 })
211 .clone()
212}
213
214async fn execute_subagent_task(
216 prompt: &str,
217 subagent_type: &str,
218 isolation: &str,
219) -> Result<String> {
220 let work_path = if isolation == "worktree" {
225 let temp_dir = std::env::temp_dir().join(format!("matrixcode-task-{}", Uuid::new_v4()));
227 std::fs::create_dir_all(&temp_dir)?;
228 temp_dir.to_string_lossy().to_string()
229 } else {
230 std::env::current_dir()?.to_string_lossy().to_string()
231 };
232
233 match subagent_type {
235 "Explore" => {
236 Ok(format!(
238 "[Explore Agent] Completed search task in {}\nPrompt: {}\nResult: Task completed successfully (fast read-only mode)",
239 work_path, prompt
240 ))
241 }
242 "Plan" => {
243 Ok(format!(
245 "[Plan Agent] Architecture analysis completed\nPrompt: {}\nResult: Implementation plan generated (check main context)",
246 prompt
247 ))
248 }
249 _ => {
250 Ok(format!(
252 "[Agent] Task completed\nPrompt: {}\nWork path: {}\nNote: Subagent execution would be implemented with full agent integration",
253 prompt, work_path
254 ))
255 }
256 }
257}
258
259pub struct TaskCreateTool;
261
262#[async_trait]
263impl Tool for TaskCreateTool {
264 fn definition(&self) -> ToolDefinition {
265 ToolDefinition {
266 name: "task_create".to_string(),
267 description: "创建独立运行的后台任务".to_string(),
268 parameters: json!({
269 "type": "object",
270 "properties": {
271 "description": {
272 "type": "string",
273 "description": "任务描述"
274 },
275 "prompt": {
276 "type": "string",
277 "description": "任务提示"
278 }
279 },
280 "required": ["description", "prompt"]
281 }),
282 }
283 }
284
285 fn risk_level(&self) -> RiskLevel {
286 RiskLevel::Mutating
287 }
288
289 async fn execute(&self, params: Value) -> Result<String> {
290 let description = params["description"]
291 .as_str()
292 .ok_or_else(|| anyhow::anyhow!("missing 'description'"))?;
293 let prompt = params["prompt"]
294 .as_str()
295 .ok_or_else(|| anyhow::anyhow!("missing 'prompt'"))?;
296
297 let task_id = Uuid::new_v4().to_string();
298 let manager = get_task_manager();
299
300 let task_info = TaskInfo {
301 id: task_id.clone(),
302 description: description.to_string(),
303 prompt: prompt.to_string(),
304 subagent_type: "general-purpose".to_string(),
305 status: TaskStatus::Running,
306 result: None,
307 started_at: Some(std::time::Instant::now()),
308 };
309
310 {
311 let mut tasks = manager.tasks.lock().await;
312 tasks.insert(task_id.clone(), task_info);
313 }
314
315 Ok(format!("Task {} created and running", task_id))
316 }
317}
318
319pub struct TaskGetTool;
321
322#[async_trait]
323impl Tool for TaskGetTool {
324 fn definition(&self) -> ToolDefinition {
325 ToolDefinition {
326 name: "task_get".to_string(),
327 description: "获取指定任务的状态和结果".to_string(),
328 parameters: json!({
329 "type": "object",
330 "properties": {
331 "task_id": {
332 "type": "string",
333 "description": "要查询的任务 ID"
334 }
335 },
336 "required": ["task_id"]
337 }),
338 }
339 }
340
341 async fn execute(&self, params: Value) -> Result<String> {
342 let task_id = params["task_id"]
343 .as_str()
344 .ok_or_else(|| anyhow::anyhow!("missing 'task_id'"))?;
345
346 let manager = get_task_manager();
347 let tasks = manager.tasks.lock().await;
348
349 if let Some(task) = tasks.get(task_id) {
350 let status_str = task.status.to_string();
351
352 let elapsed = task
353 .started_at
354 .map(|s| format!("{:.1}s", s.elapsed().as_secs_f64()))
355 .unwrap_or_else(|| "N/A".to_string());
356
357 let result_str = task
358 .result
359 .clone()
360 .unwrap_or_else(|| "No result yet".to_string());
361
362 Ok(format!(
363 "Task: {}\nDescription: {}\nStatus: {}\nElapsed: {}\nResult: {}",
364 task_id, task.description, status_str, elapsed, result_str
365 ))
366 } else {
367 Ok(format!("Task {} not found", task_id))
368 }
369 }
370}
371
372pub struct TaskListTool;
374
375#[async_trait]
376impl Tool for TaskListTool {
377 fn definition(&self) -> ToolDefinition {
378 ToolDefinition {
379 name: "task_list".to_string(),
380 description: "列出所有活动任务".to_string(),
381 parameters: json!({
382 "type": "object",
383 "properties": {}
384 }),
385 }
386 }
387
388 async fn execute(&self, _params: Value) -> Result<String> {
389 let manager = get_task_manager();
390 let tasks = manager.tasks.lock().await;
391
392 if tasks.is_empty() {
393 return Ok("No active tasks".to_string());
394 }
395
396 let mut result = Vec::new();
397 for (id, task) in tasks.iter() {
398 result.push(format!("{} [{}] - {}", id, task.status, task.description));
399 }
400
401 Ok(result.join("\n"))
402 }
403}
404
405pub struct TaskStopTool;
407
408#[async_trait]
409impl Tool for TaskStopTool {
410 fn definition(&self) -> ToolDefinition {
411 ToolDefinition {
412 name: "task_stop".to_string(),
413 description: "停止正在运行的任务".to_string(),
414 parameters: json!({
415 "type": "object",
416 "properties": {
417 "task_id": {
418 "type": "string",
419 "description": "要停止的任务 ID"
420 }
421 },
422 "required": ["task_id"]
423 }),
424 }
425 }
426
427 fn risk_level(&self) -> RiskLevel {
428 RiskLevel::Mutating
429 }
430
431 async fn execute(&self, params: Value) -> Result<String> {
432 let task_id = params["task_id"]
433 .as_str()
434 .ok_or_else(|| anyhow::anyhow!("missing 'task_id'"))?;
435
436 let manager = get_task_manager();
437 let mut tasks = manager.tasks.lock().await;
438
439 if let Some(task) = tasks.get_mut(task_id) {
440 task.status = TaskStatus::Cancelled;
441 Ok(format!("Task {} stopped", task_id))
442 } else {
443 Ok(format!("Task {} not found", task_id))
444 }
445 }
446}