feat(runtime): DeerFlow 模式中间件链 Phase 1-4 全部完成

借鉴 DeerFlow 架构,实现完整中间件链系统:

Phase 1 - Agent 中间件链基础设施
- MiddlewareChain Clone 支持
- LoopRunner 双路径集成 (middleware/legacy)
- Kernel create_middleware_chain() 工厂方法

Phase 2 - 技能按需注入
- SkillIndexMiddleware (priority 200)
- SkillLoadTool 工具
- SkillDetail/SkillIndexEntry 结构体
- KernelSkillExecutor trait 扩展

Phase 3 - Guardrail 安全护栏
- GuardrailMiddleware (priority 400, fail_open)
- ShellExecRule / FileWriteRule / WebFetchRule

Phase 4 - 记忆闭环统一
- MemoryMiddleware (priority 150, 30s 防抖)
- after_completion 双路径调用

中间件注册顺序:
100 Compaction | 150 Memory | 200 SkillIndex
400 Guardrail  | 500 LoopGuard | 700 TokenCalibration

向后兼容:Option<MiddlewareChain> 默认 None 走旧路径
This commit is contained in:
iven
2026-03-29 23:19:41 +08:00
parent 7de294375b
commit 04c366fe8b
15 changed files with 1302 additions and 43 deletions

View File

@@ -5,6 +5,7 @@ mod file_write;
mod shell_exec;
mod web_fetch;
mod execute_skill;
mod skill_load;
mod path_validator;
pub use file_read::FileReadTool;
@@ -12,6 +13,7 @@ pub use file_write::FileWriteTool;
pub use shell_exec::ShellExecTool;
pub use web_fetch::WebFetchTool;
pub use execute_skill::ExecuteSkillTool;
pub use skill_load::SkillLoadTool;
pub use path_validator::{PathValidator, PathValidatorConfig};
use crate::tool::ToolRegistry;
@@ -23,4 +25,5 @@ pub fn register_builtin_tools(registry: &mut ToolRegistry) {
registry.register(Box::new(ShellExecTool::new()));
registry.register(Box::new(WebFetchTool::new()));
registry.register(Box::new(ExecuteSkillTool::new()));
registry.register(Box::new(SkillLoadTool::new()));
}

View File

@@ -0,0 +1,81 @@
//! Skill load tool — on-demand retrieval of full skill details.
//!
//! When the `SkillIndexMiddleware` is active, the system prompt contains only a lightweight
//! skill index. This tool allows the LLM to load full skill details (description, input schema,
//! capabilities) on demand, exactly when the LLM decides a particular skill is relevant.
use async_trait::async_trait;
use serde_json::{json, Value};
use zclaw_types::{Result, ZclawError};
use crate::tool::{Tool, ToolContext};
pub struct SkillLoadTool;
impl SkillLoadTool {
pub fn new() -> Self {
Self
}
}
#[async_trait]
impl Tool for SkillLoadTool {
fn name(&self) -> &str {
"skill_load"
}
fn description(&self) -> &str {
"Load full details for a skill by its ID. Use this when you need to understand a skill's \
input parameters, capabilities, or usage instructions before calling execute_skill. \
Returns the skill description, input schema, and trigger conditions."
}
fn input_schema(&self) -> Value {
json!({
"type": "object",
"properties": {
"skill_id": {
"type": "string",
"description": "The ID of the skill to load details for"
}
},
"required": ["skill_id"]
})
}
async fn execute(&self, input: Value, context: &ToolContext) -> Result<Value> {
let skill_id = input["skill_id"].as_str()
.ok_or_else(|| ZclawError::InvalidInput("Missing 'skill_id' parameter".into()))?;
let executor = context.skill_executor.as_ref()
.ok_or_else(|| ZclawError::ToolError("Skill executor not available".into()))?;
match executor.get_skill_detail(skill_id) {
Some(detail) => {
let mut result = json!({
"id": detail.id,
"name": detail.name,
"description": detail.description,
"triggers": detail.triggers,
});
if let Some(schema) = &detail.input_schema {
result["input_schema"] = schema.clone();
}
if let Some(cat) = &detail.category {
result["category"] = json!(cat);
}
if !detail.capabilities.is_empty() {
result["capabilities"] = json!(detail.capabilities);
}
Ok(result)
}
None => Err(ZclawError::ToolError(format!("Skill not found: {}", skill_id))),
}
}
}
impl Default for SkillLoadTool {
fn default() -> Self {
Self::new()
}
}