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            ..Default::default()
53        }
54    }
55
56    fn risk_level(&self) -> RiskLevel {
57        RiskLevel::Mutating // Tasks can modify files
58    }
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        // Generate task ID
74        let task_id = Uuid::new_v4().to_string();
75
76        // Create task info
77        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        // Get or create task manager
88        let manager = get_task_manager();
89
90        // Add task
91        {
92            let mut tasks = manager.tasks.lock().await;
93            tasks.insert(task_id.clone(), task_info);
94        }
95
96        if run_in_background {
97            // Spawn background task
98            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                // Update task status
110                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                // Send notification (if channel available)
125                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            // Execute synchronously
139            let result = execute_subagent_task(prompt, subagent_type, isolation).await?;
140
141            // Update task status
142            {
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/// Task status
156#[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/// Task info
178#[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/// Task notification
190#[derive(Debug, Clone)]
191pub struct TaskNotification {
192    pub task_id: String,
193    pub status: String,
194}
195
196/// Task manager singleton
197pub 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
215/// Execute subagent task
216async fn execute_subagent_task(
217    prompt: &str,
218    subagent_type: &str,
219    isolation: &str,
220) -> Result<String> {
221    // For now, return a placeholder indicating the task type
222    // In full implementation, this would spawn a real agent
223
224    // Handle isolation mode
225    let work_path = if isolation == "worktree" {
226        // Create temporary worktree
227        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    // Simulate task execution based on type
235    match subagent_type {
236        "Explore" => {
237            // Fast read-only search task
238            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            // Architecture planning task
245            Ok(format!(
246                "[Plan Agent] Architecture analysis completed\nPrompt: {}\nResult: Implementation plan generated (check main context)",
247                prompt
248            ))
249        }
250        _ => {
251            // General purpose task - in real implementation would run actual agent
252            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
260/// TaskCreate tool for creating background tasks
261pub 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
321/// TaskGet tool for getting task status
322pub 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
375/// TaskList tool for listing all tasks
376pub 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
409/// TaskStop tool for stopping a task
410pub 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}