Skip to main content

matrixcode_core/tools/
task.rs

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