refactor(crates): kernel/generation module split + DeerFlow optimizations + middleware + dead code cleanup

- Split zclaw-kernel/kernel.rs (1486 lines) into 9 domain modules
- Split zclaw-kernel/generation.rs (1080 lines) into 3 modules
- Add DeerFlow-inspired middleware: DanglingTool, SubagentLimit, ToolError, ToolOutputGuard
- Add PromptBuilder for structured system prompt assembly
- Add FactStore (zclaw-memory) for persistent fact extraction
- Add task builtin tool for agent task management
- Driver improvements: Anthropic/OpenAI extended thinking, Gemini safety settings
- Replace let _ = with proper log::warn! across SaaS handlers
- Remove unused dependency (url) from zclaw-hands
This commit is contained in:
iven
2026-04-03 00:28:03 +08:00
parent 0a04b260a4
commit 52bdafa633
55 changed files with 4130 additions and 1959 deletions

View File

@@ -7,6 +7,7 @@ mod web_fetch;
mod execute_skill;
mod skill_load;
mod path_validator;
mod task;
pub use file_read::FileReadTool;
pub use file_write::FileWriteTool;
@@ -15,6 +16,7 @@ pub use web_fetch::WebFetchTool;
pub use execute_skill::ExecuteSkillTool;
pub use skill_load::SkillLoadTool;
pub use path_validator::{PathValidator, PathValidatorConfig};
pub use task::TaskTool;
use crate::tool::ToolRegistry;

View File

@@ -0,0 +1,179 @@
//! 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;
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
);
// Create a sub-agent with its own ID
let sub_agent_id = AgentId::new();
// 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());
}
// 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
);
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);
json!({
"status": "failed",
"description": description,
"error": e.to_string(),
})
}
};
Ok(result)
}
}