//! 进化引擎中间件 //! 在管家对话中检测并呈现"技能进化确认"提示 //! 优先级 78(在 ButlerRouter@80 之前运行) use async_trait::async_trait; use std::sync::Arc; use tokio::sync::RwLock; use crate::middleware::{ AgentMiddleware, MiddlewareContext, MiddlewareDecision, }; use zclaw_types::Result; /// 待确认的进化事件 #[derive(Debug, Clone)] pub struct PendingEvolution { pub pattern_name: String, pub trigger_suggestion: String, pub description: String, } /// 进化引擎中间件 /// 检查是否有待确认的进化事件,根据模式: /// - suggest 模式(默认): 注入确认提示到 system prompt /// - auto 模式: 不注入,仅排队等待 kernel 自动处理 pub struct EvolutionMiddleware { pending: Arc>>, auto_mode: bool, } impl EvolutionMiddleware { pub fn new() -> Self { Self { pending: Arc::new(RwLock::new(Vec::new())), auto_mode: false, } } /// Create with auto mode enabled pub fn new_auto() -> Self { Self { pending: Arc::new(RwLock::new(Vec::new())), auto_mode: true, } } /// Check if auto mode is enabled pub fn is_auto_mode(&self) -> bool { self.auto_mode } /// 添加一个待确认的进化事件 pub async fn add_pending(&self, evolution: PendingEvolution) { let mut pending = self.pending.write().await; if pending.len() >= 100 { tracing::warn!( "[EvolutionMiddleware] Pending queue full (100), dropping oldest event" ); pending.remove(0); } pending.push(evolution); } /// 获取并清除所有待确认事件 pub async fn drain_pending(&self) -> Vec { let mut pending = self.pending.write().await; std::mem::take(&mut *pending) } /// 当前待确认事件数量 pub async fn pending_count(&self) -> usize { self.pending.read().await.len() } } impl Default for EvolutionMiddleware { fn default() -> Self { Self::new() } } #[async_trait] impl AgentMiddleware for EvolutionMiddleware { fn name(&self) -> &str { "evolution" } fn priority(&self) -> i32 { 78 // 在 ButlerRouter(80) 之前 } fn parallel_safe(&self) -> bool { true } async fn before_completion( &self, ctx: &mut MiddlewareContext, ) -> Result { // 先用 read lock 快速判空,避免每次对话都获取写锁 if self.pending.read().await.is_empty() { return Ok(MiddlewareDecision::Continue); } // Auto mode: don't inject into prompt, leave for kernel to process if self.auto_mode { return Ok(MiddlewareDecision::Continue); } // Suggest mode: 只移除第一个事件,保留后续事件留待下次注入 let to_inject = { let mut pending = self.pending.write().await; if pending.is_empty() { return Ok(MiddlewareDecision::Continue); } pending.remove(0) }; let injection = format!( "\n\n\n\ 我注意到你经常做「{pattern}」相关的事情。\n\ 我可以帮你整理成一个技能,以后直接说「{trigger}」就能用了。\n\ 技能描述:{desc}\n\ 如果你同意,请回复 '确认保存技能'。如果你想调整,可以告诉我怎么改。\n\ ", pattern = to_inject.pattern_name, trigger = to_inject.trigger_suggestion, desc = to_inject.description, ); ctx.system_prompt.push_str(&injection); tracing::info!( "[EvolutionMiddleware] Injected evolution suggestion for: {}", to_inject.pattern_name ); Ok(MiddlewareDecision::Continue) } } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_no_pending_continues() { let mw = EvolutionMiddleware::new(); assert_eq!(mw.pending_count().await, 0); } #[tokio::test] async fn test_add_and_drain() { let mw = EvolutionMiddleware::new(); mw.add_pending(PendingEvolution { pattern_name: "报表生成".to_string(), trigger_suggestion: "生成报表".to_string(), description: "自动生成每日报表".to_string(), }) .await; assert_eq!(mw.pending_count().await, 1); let drained = mw.drain_pending().await; assert_eq!(drained.len(), 1); assert_eq!(drained[0].pattern_name, "报表生成"); assert_eq!(mw.pending_count().await, 0); } #[tokio::test] async fn test_name_and_priority() { let mw = EvolutionMiddleware::new(); assert_eq!(mw.name(), "evolution"); assert_eq!(mw.priority(), 78); } #[tokio::test] async fn test_only_first_event_injected() { let mw = EvolutionMiddleware::new(); mw.add_pending(PendingEvolution { pattern_name: "事件A".to_string(), trigger_suggestion: "触发A".to_string(), description: "描述A".to_string(), }) .await; mw.add_pending(PendingEvolution { pattern_name: "事件B".to_string(), trigger_suggestion: "触发B".to_string(), description: "描述B".to_string(), }) .await; // 模拟注入:用 read 判空 + write 取第一个 let first = { let mut pending = mw.pending.write().await; pending.remove(0) }; assert_eq!(first.pattern_name, "事件A"); assert_eq!(mw.pending_count().await, 1); // 事件B 仍保留 } }