Skip to main content

matrixcode_core/tools/
task.rs

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
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"]
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        // Generate task ID
73        let task_id = Uuid::new_v4().to_string();
74
75        // Create task info
76        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        // Get or create task manager
87        let manager = get_task_manager();
88
89        // Add task
90        {
91            let mut tasks = manager.tasks.lock().await;
92            tasks.insert(task_id.clone(), task_info);
93        }
94
95        if run_in_background {
96            // Spawn background task
97            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                // Update task status
109                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                // Send notification (if channel available)
124                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            // Execute synchronously
138            let result = execute_subagent_task(prompt, subagent_type, isolation).await?;
139
140            // Update task status
141            {
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/// Task status
155#[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/// Task info
177#[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/// Task notification
189#[derive(Debug, Clone)]
190pub struct TaskNotification {
191    pub task_id: String,
192    pub status: String,
193}
194
195/// Task manager singleton
196pub 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
214/// Execute subagent task
215async fn execute_subagent_task(
216    prompt: &str,
217    subagent_type: &str,
218    isolation: &str,
219) -> Result<String> {
220    // For now, return a placeholder indicating the task type
221    // In full implementation, this would spawn a real agent
222
223    // Handle isolation mode
224    let work_path = if isolation == "worktree" {
225        // Create temporary worktree
226        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    // Simulate task execution based on type
234    match subagent_type {
235        "Explore" => {
236            // Fast read-only search task
237            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            // Architecture planning task
244            Ok(format!(
245                "[Plan Agent] Architecture analysis completed\nPrompt: {}\nResult: Implementation plan generated (check main context)",
246                prompt
247            ))
248        }
249        _ => {
250            // General purpose task - in real implementation would run actual agent
251            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
259/// TaskCreate tool for creating background tasks
260pub 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
319/// TaskGet tool for getting task status
320pub 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
372/// TaskList tool for listing all tasks
373pub 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
405/// TaskStop tool for stopping a task
406pub 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}