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