PicoBot/src/tools/task/tool.rs
ooodc bee1a39a06 feat: add task management tool with subagent support
- Introduced `TaskConfig` struct to manage task-related configurations.
- Implemented `TaskTool` for creating and managing subagents for complex tasks.
- Added `TaskSession` and `TaskRepository` for handling task sessions and persistence.
- Created `DefaultSubAgentRuntime` to execute tasks with timeout and history support.
- Enhanced `ToolContext` to include `subagent_description` for better context tracking.
- Implemented error handling for task execution and session management.
- Updated `ToolRegistryFactory` to register task tools conditionally based on configuration.
- Added prompt builders for subagent tasks to improve interaction clarity.
2026-05-16 16:12:28 +08:00

153 lines
4.9 KiB
Rust

use async_trait::async_trait;
use serde_json::json;
use std::sync::Arc;
use crate::tools::{Tool, ToolContext, ToolResult};
use super::runtime::SubAgentRuntime;
use super::types::{TaskDefinition, TaskToolArgs};
/// Task 工具 - 创建和管理子代理
pub struct TaskTool {
runtime: Arc<dyn SubAgentRuntime>,
}
impl TaskTool {
pub fn new(runtime: Arc<dyn SubAgentRuntime>) -> Self {
Self { runtime }
}
}
#[async_trait]
impl Tool for TaskTool {
fn name(&self) -> &str {
"task"
}
fn description(&self) -> &str {
"Launch a specialized subagent to handle complex, multi-step tasks. \
Subagents run in isolated contexts and can work in parallel. \
Use 'general' type for complex tasks, 'explore' type for read-only exploration. \
You can resume a previous task by providing its task_id."
}
fn parameters_schema(&self) -> serde_json::Value {
json!({
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Short description (3-5 words) of what this task does",
"maxLength": 50
},
"prompt": {
"type": "string",
"description": "Detailed instructions for the subagent to execute"
},
"subagent_type": {
"type": "string",
"enum": ["general", "explore"],
"default": "general",
"description": "Type of subagent: 'general' for complex multi-step tasks, 'explore' for read-only search/exploration"
},
"task_id": {
"type": "string",
"description": "Optional: Resume an existing task session by providing its task_id"
}
},
"required": ["description", "prompt"]
})
}
fn read_only(&self) -> bool {
false
}
fn exclusive(&self) -> bool {
// Task 工具创建子代理,不应与其他工具并发执行
true
}
async fn execute(&self, _args: serde_json::Value) -> anyhow::Result<ToolResult> {
// Task 工具必须通过 execute_with_context 获取父会话信息
Ok(ToolResult {
success: false,
output: String::new(),
error: Some(
"task tool requires tool context with session_id, chat_id, and channel_name"
.to_string(),
),
})
}
async fn execute_with_context(
&self,
context: &ToolContext,
args: serde_json::Value,
) -> anyhow::Result<ToolResult> {
// 1. 解析参数
let task_args: TaskToolArgs = serde_json::from_value(args.clone())
.map_err(|e| anyhow::anyhow!("invalid task arguments: {}", e))?;
// 2. 验证描述长度
let word_count = task_args.description.split_whitespace().count();
if task_args.description.len() > 50 || word_count > 7 || word_count < 1 {
return Ok(ToolResult {
success: false,
output: String::new(),
error: Some(
"description should be 1-5 words, max 50 characters".to_string(),
),
});
}
// 3. 验证上下文
if context.session_id.is_none() {
return Ok(ToolResult {
success: false,
output: String::new(),
error: Some("task tool requires session_id in context".to_string()),
});
}
if context.chat_id.is_none() {
return Ok(ToolResult {
success: false,
output: String::new(),
error: Some("task tool requires chat_id in context".to_string()),
});
}
if context.channel_name.is_none() {
return Ok(ToolResult {
success: false,
output: String::new(),
error: Some("task tool requires channel_name in context".to_string()),
});
}
// 4. 执行任务
let result = if let Some(task_id) = task_args.task_id {
// 恢复现有任务
self.runtime
.resume(&task_id, context, task_args.prompt)
.await
} else {
// 创建新任务
let task_def = TaskDefinition::from(task_args);
self.runtime.spawn(context, task_def).await
};
// 5. 构建返回结果
match result {
Ok(task_result) => Ok(ToolResult {
success: true,
output: serde_json::to_string(&task_result)?,
error: None,
}),
Err(e) => Ok(ToolResult {
success: false,
output: String::new(),
error: Some(e.to_string()),
}),
}
}
}