- 添加 SubagentCatalog::discover() 方法,支持从文件系统加载自定义子代理 - 支持 ~/.picobot/subagents/ 和 ./.picobot/subagents/ 两个目录 - 项目级定义可覆盖用户级定义 - 支持 YAML frontmatter + body 格式解析 - 修复 Windows 换行符兼容性问题 - 移除未使用的 read_only 字段 - 实现 TaskTool 动态 schema,子代理类型列表从运行时获取 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
155 lines
4.9 KiB
Rust
155 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. \
|
|
You can resume a previous task by providing its task_id."
|
|
}
|
|
|
|
fn parameters_schema(&self) -> serde_json::Value {
|
|
let types = self.runtime.available_subagent_names();
|
|
let types_array: Vec<serde_json::Value> = types.into_iter().map(|t| json!(t)).collect();
|
|
|
|
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": types_array,
|
|
"default": "general",
|
|
"description": "Type of subagent to use for the task"
|
|
},
|
|
"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()),
|
|
}),
|
|
}
|
|
}
|
|
} |