Files
zclaw_openfang/crates/zclaw-runtime/src/tool/builtin/task.rs
iven 924ad5a6ec
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
fix(audit): Batch 0-1 文档校准 + let _ = 静默错误修复
Batch 0:
- TRUTH.md 中间件层 14→15 (补 EvolutionMiddleware@78)
- wiki/middleware.md 同步 15 层 + 优先级分类更新
- Store 数字确认 25 个

Batch 1:
- approvals.rs: 3 处 map_err+let _ = 简化为 if let Err
- director.rs: oneshot send 失败添加 debug 日志
- task.rs: 4 处子任务状态更新添加 debug 日志
- chat.rs: 流消息发送和事件 emit 添加 warn/debug 日志
- heartbeat.rs: 告警广播添加 debug 日志 + break 优化

全量测试通过: 719 passed, 0 failed
2026-04-19 08:30:33 +08:00

234 lines
8.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Task tool — delegates sub-tasks to a nested AgentLoop.
//!
//! Inspired by DeerFlow's `task_tool`: the lead agent can spawn sub-agent tasks
//! to parallelise complex work. Each sub-task runs its own AgentLoop with a
//! fresh session, isolated context, and a configurable maximum iteration count.
use async_trait::async_trait;
use serde_json::{json, Value};
use zclaw_types::{AgentId, Result, ZclawError};
use zclaw_memory::MemoryStore;
use crate::driver::LlmDriver;
use crate::loop_runner::{AgentLoop, LoopEvent};
use crate::tool::{Tool, ToolContext, ToolRegistry};
use crate::tool::builtin::register_builtin_tools;
use std::sync::Arc;
/// Default max iterations for a sub-agent task.
const DEFAULT_MAX_ITERATIONS: usize = 5;
/// Tool that delegates sub-tasks to a nested AgentLoop.
pub struct TaskTool {
driver: Arc<dyn LlmDriver>,
memory: Arc<MemoryStore>,
model: String,
max_tokens: u32,
temperature: f32,
}
impl TaskTool {
pub fn new(
driver: Arc<dyn LlmDriver>,
memory: Arc<MemoryStore>,
model: impl Into<String>,
) -> Self {
Self {
driver,
memory,
model: model.into(),
max_tokens: 4096,
temperature: 0.7,
}
}
pub fn with_max_tokens(mut self, max_tokens: u32) -> Self {
self.max_tokens = max_tokens;
self
}
pub fn with_temperature(mut self, temperature: f32) -> Self {
self.temperature = temperature;
self
}
}
#[async_trait]
impl Tool for TaskTool {
fn name(&self) -> &str {
"task"
}
fn description(&self) -> &str {
"Delegate a sub-task to a sub-agent. The sub-agent will work independently \
with its own context and tools. Use this to break complex tasks into \
parallel or sequential sub-tasks. Each sub-task runs in its own session \
with a focused system prompt."
}
fn input_schema(&self) -> Value {
json!({
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Short description of the sub-task (shown in progress UI)"
},
"prompt": {
"type": "string",
"description": "Detailed instructions for the sub-agent"
},
"max_iterations": {
"type": "integer",
"description": "Maximum tool-call iterations for the sub-agent (default: 5)",
"minimum": 1,
"maximum": 10
}
},
"required": ["description", "prompt"]
})
}
async fn execute(&self, input: Value, context: &ToolContext) -> Result<Value> {
let description = input["description"].as_str()
.ok_or_else(|| ZclawError::InvalidInput("Missing 'description' parameter".into()))?;
let prompt = input["prompt"].as_str()
.ok_or_else(|| ZclawError::InvalidInput("Missing 'prompt' parameter".into()))?;
let max_iterations = input["max_iterations"].as_u64()
.unwrap_or(DEFAULT_MAX_ITERATIONS as u64) as usize;
tracing::info!(
"[TaskTool] Starting sub-agent task: {:?} (max_iterations={})",
description, max_iterations
);
// Emit subtask_started event
// Create a sub-agent with its own ID
let sub_agent_id = AgentId::new();
let task_id = sub_agent_id.to_string();
if let Some(ref tx) = context.event_sender {
if tx.send(LoopEvent::SubtaskStatus {
task_id: task_id.clone(),
description: description.to_string(),
status: "started".to_string(),
detail: None,
}).await.is_err() {
tracing::debug!("[TaskTool] Subtask status dropped: parent loop ended");
}
}
// Create a fresh session for the sub-agent
let session_id = self.memory.create_session(&sub_agent_id).await?;
// Build system prompt focused on the sub-task
let system_prompt = format!(
"你是一个专注的子Agent负责完成以下任务{}\n\n\
要求:\n\
- 专注完成分配给你的任务\n\
- 使用可用的工具来完成任务\n\
- 完成后提供简洁的结果摘要\n\
- 如果遇到无法解决的问题,请说明原因",
description
);
// Create a tool registry with builtin tools
// (TaskTool itself is NOT included to prevent infinite nesting)
let mut tools = ToolRegistry::new();
register_builtin_tools(&mut tools);
// Build a lightweight AgentLoop for the sub-agent
let mut sub_loop = AgentLoop::new(
sub_agent_id,
self.driver.clone(),
tools,
self.memory.clone(),
)
.with_model(&self.model)
.with_system_prompt(&system_prompt)
.with_max_tokens(self.max_tokens)
.with_temperature(self.temperature);
// Optionally inject skill executor and path validator from parent context
if let Some(ref executor) = context.skill_executor {
sub_loop = sub_loop.with_skill_executor(executor.clone());
}
if let Some(ref validator) = context.path_validator {
sub_loop = sub_loop.with_path_validator(validator.clone());
}
// Emit subtask_running event
if let Some(ref tx) = context.event_sender {
if tx.send(LoopEvent::SubtaskStatus {
task_id: task_id.clone(),
description: description.to_string(),
status: "running".to_string(),
detail: Some("子Agent正在执行中...".to_string()),
}).await.is_err() {
tracing::debug!("[TaskTool] Subtask status dropped: parent loop ended");
}
}
// Execute the sub-agent loop (non-streaming — collect full result)
let result = match sub_loop.run(session_id.clone(), prompt.to_string()).await {
Ok(loop_result) => {
tracing::info!(
"[TaskTool] Sub-agent completed: {} iterations, {} input tokens, {} output tokens",
loop_result.iterations, loop_result.input_tokens, loop_result.output_tokens
);
// Emit subtask_completed event
if let Some(ref tx) = context.event_sender {
if tx.send(LoopEvent::SubtaskStatus {
task_id: task_id.clone(),
description: description.to_string(),
status: "completed".to_string(),
detail: Some(format!(
"完成 ({}次迭代, {}输入token)",
loop_result.iterations, loop_result.input_tokens
)),
}).await.is_err() {
tracing::debug!("[TaskTool] Subtask status dropped: parent loop ended");
}
}
json!({
"status": "completed",
"description": description,
"result": loop_result.response,
"iterations": loop_result.iterations,
"input_tokens": loop_result.input_tokens,
"output_tokens": loop_result.output_tokens,
})
}
Err(e) => {
tracing::warn!("[TaskTool] Sub-agent failed: {}", e);
// Emit subtask_failed event
if let Some(ref tx) = context.event_sender {
if tx.send(LoopEvent::SubtaskStatus {
task_id: task_id.clone(),
description: description.to_string(),
status: "failed".to_string(),
detail: Some(e.to_string()),
}).await.is_err() {
tracing::debug!("[TaskTool] Subtask status dropped: parent loop ended");
}
}
json!({
"status": "failed",
"description": description,
"error": e.to_string(),
})
}
};
Ok(result)
}
}