diff --git a/crates/zclaw-kernel/src/director.rs b/crates/zclaw-kernel/src/director.rs index ae5264c..c15e601 100644 --- a/crates/zclaw-kernel/src/director.rs +++ b/crates/zclaw-kernel/src/director.rs @@ -573,10 +573,13 @@ Respond with ONLY the number (1-{}) of the agent who should speak next. No expla // Find and dispatch to the correct oneshot sender if msg.message_type == A2aMessageType::Response { if let Some(ref reply_to) = msg.reply_to { + let reply_to_clone = reply_to.clone(); let mut pending_guard = pending.lock().await; if let Some(sender) = pending_guard.remove(reply_to) { - // Send the response; if receiver already dropped, that's fine - let _ = sender.send(msg); + // Send the response; if receiver already dropped, request was cancelled + if sender.send(msg).is_err() { + tracing::debug!("[Director] Response dropped: receiver cancelled for reply_to={}", reply_to_clone); + } } } } diff --git a/crates/zclaw-kernel/src/kernel/approvals.rs b/crates/zclaw-kernel/src/kernel/approvals.rs index a74fa62..f3cff53 100644 --- a/crates/zclaw-kernel/src/kernel/approvals.rs +++ b/crates/zclaw-kernel/src/kernel/approvals.rs @@ -85,14 +85,14 @@ impl Kernel { started_at: None, completed_at: None, }; - let _ = memory.save_hand_run(&run).await.map_err(|e| { + if let Err(e) = memory.save_hand_run(&run).await { tracing::error!("[Approval] Failed to save hand run: {}", e); - }); + } run.status = HandRunStatus::Running; run.started_at = Some(chrono::Utc::now().to_rfc3339()); - let _ = memory.update_hand_run(&run).await.map_err(|e| { + if let Err(e) = memory.update_hand_run(&run).await { tracing::error!("[Approval] Failed to update hand run (running): {}", e); - }); + } // Register cancellation flag let cancel_flag = Arc::new(std::sync::atomic::AtomicBool::new(false)); @@ -121,9 +121,9 @@ impl Kernel { } run.duration_ms = Some(duration.as_millis() as u64); run.completed_at = Some(completed_at); - let _ = memory.update_hand_run(&run).await.map_err(|e| { + if let Err(e) = memory.update_hand_run(&run).await { tracing::error!("[Approval] Failed to update hand run (completed): {}", e); - }); + } // Update approval status based on execution result let mut approvals = approvals.lock().await; diff --git a/crates/zclaw-runtime/src/tool/builtin/task.rs b/crates/zclaw-runtime/src/tool/builtin/task.rs index e4eb274..6a906b6 100644 --- a/crates/zclaw-runtime/src/tool/builtin/task.rs +++ b/crates/zclaw-runtime/src/tool/builtin/task.rs @@ -112,12 +112,14 @@ impl Tool for TaskTool { let task_id = sub_agent_id.to_string(); if let Some(ref tx) = context.event_sender { - let _ = tx.send(LoopEvent::SubtaskStatus { + if tx.send(LoopEvent::SubtaskStatus { task_id: task_id.clone(), description: description.to_string(), status: "started".to_string(), detail: None, - }).await; + }).await.is_err() { + tracing::debug!("[TaskTool] Subtask status dropped: parent loop ended"); + } } // Create a fresh session for the sub-agent @@ -161,12 +163,14 @@ impl Tool for TaskTool { // Emit subtask_running event if let Some(ref tx) = context.event_sender { - let _ = tx.send(LoopEvent::SubtaskStatus { + if tx.send(LoopEvent::SubtaskStatus { task_id: task_id.clone(), description: description.to_string(), status: "running".to_string(), detail: Some("子Agent正在执行中...".to_string()), - }).await; + }).await.is_err() { + tracing::debug!("[TaskTool] Subtask status dropped: parent loop ended"); + } } // Execute the sub-agent loop (non-streaming — collect full result) @@ -179,7 +183,7 @@ impl Tool for TaskTool { // Emit subtask_completed event if let Some(ref tx) = context.event_sender { - let _ = tx.send(LoopEvent::SubtaskStatus { + if tx.send(LoopEvent::SubtaskStatus { task_id: task_id.clone(), description: description.to_string(), status: "completed".to_string(), @@ -187,7 +191,9 @@ impl Tool for TaskTool { "完成 ({}次迭代, {}输入token)", loop_result.iterations, loop_result.input_tokens )), - }).await; + }).await.is_err() { + tracing::debug!("[TaskTool] Subtask status dropped: parent loop ended"); + } } json!({ @@ -204,12 +210,14 @@ impl Tool for TaskTool { // Emit subtask_failed event if let Some(ref tx) = context.event_sender { - let _ = tx.send(LoopEvent::SubtaskStatus { + if tx.send(LoopEvent::SubtaskStatus { task_id: task_id.clone(), description: description.to_string(), status: "failed".to_string(), detail: Some(e.to_string()), - }).await; + }).await.is_err() { + tracing::debug!("[TaskTool] Subtask status dropped: parent loop ended"); + } } json!({ diff --git a/desktop/src-tauri/src/intelligence/heartbeat.rs b/desktop/src-tauri/src/intelligence/heartbeat.rs index bf97e9c..e8ffeab 100644 --- a/desktop/src-tauri/src/intelligence/heartbeat.rs +++ b/desktop/src-tauri/src/intelligence/heartbeat.rs @@ -383,7 +383,10 @@ async fn execute_tick( // Send alerts via broadcast channel (internal) for alert in &filtered_alerts { - let _ = alert_sender.send(alert.clone()); + if alert_sender.send(alert.clone()).is_err() { + tracing::debug!("[heartbeat] No alert receivers, alert dropped"); + break; + } } // Emit alerts to frontend via Tauri event (real-time toast) diff --git a/desktop/src-tauri/src/kernel_commands/chat.rs b/desktop/src-tauri/src/kernel_commands/chat.rs index 9f0e749..523a4fb 100644 --- a/desktop/src-tauri/src/kernel_commands/chat.rs +++ b/desktop/src-tauri/src/kernel_commands/chat.rs @@ -291,15 +291,19 @@ pub async fn agent_chat_stream( ); let (tx, rx) = tokio::sync::mpsc::channel(32); - let _ = tx.send(zclaw_runtime::LoopEvent::Delta(confirm_msg)).await; - let _ = tx.send(zclaw_runtime::LoopEvent::Complete( + if tx.send(zclaw_runtime::LoopEvent::Delta(confirm_msg)).await.is_err() { + tracing::warn!("[agent_chat_stream] Failed to send confirm msg to new channel"); + } + if tx.send(zclaw_runtime::LoopEvent::Complete( zclaw_runtime::AgentLoopResult { response: String::new(), input_tokens: 0, output_tokens: 0, iterations: 1, } - )).await; + )).await.is_err() { + tracing::warn!("[agent_chat_stream] Failed to send complete to new channel"); + } drop(tx); (rx, None) } else { @@ -400,10 +404,12 @@ pub async fn agent_chat_stream( // Check cancellation flag before each recv if cancel_clone.load(std::sync::atomic::Ordering::SeqCst) { tracing::info!("[agent_chat_stream] Stream cancelled for session: {}", session_id); - let _ = app.emit("stream:chunk", serde_json::json!({ + if let Err(e) = app.emit("stream:chunk", serde_json::json!({ "sessionId": session_id, "event": StreamChatEvent::Error { message: "已取消".to_string() } - })); + })) { + tracing::debug!("[agent_chat_stream] Failed to emit cancel event: {}", e); + } break; } @@ -491,12 +497,14 @@ pub async fn agent_chat_stream( } Err(_) => { tracing::warn!("[agent_chat_stream] Stream idle timeout for session: {}", session_id); - let _ = app.emit("stream:chunk", serde_json::json!({ + if let Err(e) = app.emit("stream:chunk", serde_json::json!({ "sessionId": session_id, "event": StreamChatEvent::Error { message: "流式响应超时,请重试".to_string() } - })); + })) { + tracing::debug!("[agent_chat_stream] Failed to emit timeout event: {}", e); + } break; } } diff --git a/docs/TRUTH.md b/docs/TRUTH.md index 708f3cc..738e335 100644 --- a/docs/TRUTH.md +++ b/docs/TRUTH.md @@ -38,7 +38,7 @@ | Admin V2 页面 | 17 个 | admin-v2/src/pages/ 全量统计 (2026-04-18 验证) | | 桌面端设置页面 | 19 个 | SettingsLayout.tsx tabs: 通用/用量统计/积分详情/模型与API/MCP服务/技能/IM频道/工作区/数据与隐私/安全存储/SaaS平台/订阅与计费/语义记忆/安全状态/审计日志/定时任务/心跳配置/提交反馈/关于 | | Admin V2 测试 | 17 个文件 (61 tests) | vitest 统计 | -| 中间件层 | 14 层 | `grep chain.register kernel/mod.rs` (2026-04-16 验证: ButlerRouter@80, DataMasking@90, Compaction@100, Memory@150, Title@180, SkillIndex@200, DanglingTool@300, ToolError@350, ToolOutputGuard@360, Guardrail@400, LoopGuard@500, SubagentLimit@550, TrajectoryRecorder@650, TokenCalibration@700) | +| 中间件层 | 15 层 | `grep chain.register kernel/mod.rs` (2026-04-19 校准: EvolutionMiddleware@78, ButlerRouter@80, DataMasking@90, Compaction@100, Memory@150, Title@180, SkillIndex@200, DanglingTool@300, ToolError@350, ToolOutputGuard@360, Guardrail@400, LoopGuard@500, SubagentLimit@550, TrajectoryRecorder@650, TokenCalibration@700) | --- @@ -207,3 +207,4 @@ Viking 5 个孤立 invoke 调用已于 2026-04-03 清理移除: | 2026-04-15 | Heartbeat 统一健康系统:(1) Tauri 命令 182→183 (+health_snapshot) (2) intelligence 模块 15→16 文件 (+health_snapshot.rs +heartbeat.rs 重构) (3) React 组件 104→105 (+HealthPanel.tsx) (4) 前端 lib 85→76 (删除 intelligence-client/ 9 文件) | | 2026-04-16 | 发布前深度测试 8 路并行验证 + 3 项 P0 修复:(1) Tauri 命令 183→190 (2) 前端 invoke 95→104 (3) SaaS .route() 136→137 (4) 中间件 15→14 (实际 chain.register 计数) (5) P0-01 Admin ApiKeys 创建功能修复 (/keys→/tokens 路由对齐) (6) P0-02 账户锁定 unwrap_or(false)→正确错误传播 (7) P0-03 Logout 增加 access token cookie fallback 撤销 refresh token | | 2026-04-18 | 发布前审计数字校准 + Batch 1 修复:(1) Rust 测试 801→734 (#[test] 433→425 + #[tokio::test] 368→309) (2) Zustand Store 21→26 (3) Admin V2 页面 15→17 (4) Pipeline YAML 17→18 (5) Hands 启用 9→7 (6 HAND.toml + ReminderHand,Whiteboard/Slideshow/Speech 标注开发中) (6) Pipeline executor 内存泄漏 cleanup + 步骤超时 + Delay 上限 (7) Director send_to_agent oneshot channel 重构防死锁 (8) cleanup_rate_limit Worker 实现 (DELETE >1h) | +| 2026-04-19 | 全系统穷尽审计 Batch 0 校准:(1) 中间件层 14→15 (补 EvolutionMiddleware@78,实际 chain.register 计数) (2) Zustand Store 确认 25 个 .ts 文件 (04-18 日志写 26 为误记) (3) wiki/middleware.md 同步 15 层 + 优先级分类更新 | diff --git a/wiki/middleware.md b/wiki/middleware.md index 6059d7a..1f616bd 100644 --- a/wiki/middleware.md +++ b/wiki/middleware.md @@ -20,24 +20,27 @@ tags: [module, middleware, runtime] ## 代码逻辑 -### 14 层 Runtime 中间件(注册顺序见 `kernel/mod.rs:248-361`) +### 15 层 Runtime 中间件(注册顺序见 `kernel/mod.rs:248-361`,执行按 priority 升序) -| # | 中间件 | 文件 | 职责 | 注册条件 | -|---|--------|------|------|----------| -| 1 | ButlerRouter | `middleware/butler_router.rs` | 语义技能路由 + system prompt 增强 | 始终 | -| 2 | DataMasking | `middleware/data_masking.rs` | 手机号/身份证等敏感数据脱敏 | 始终 | -| 3 | Compaction | `middleware/compaction.rs` | 超阈值时压缩对话历史 | `compaction_threshold > 0` | -| 4 | Memory | `middleware/memory.rs` | 对话后自动提取记忆 | 始终 | -| 5 | LoopGuard | `middleware/loop_guard.rs` | 防止工具调用无限循环 | 始终 | -| 6 | TokenCalibration | `middleware/token_calibration.rs` | Token 用量校准 | 始终 | -| 7 | SkillIndex | `middleware/skill_index.rs` | 注入技能索引到 system prompt | `!skill_index.is_empty()` | -| 8 | Title | `middleware/title.rs` | 自动生成会话标题 | 始终 | -| 9 | DanglingTool | `middleware/dangling_tool.rs` | 修复缺失的工具调用结果 | 始终 | -| 10 | ToolError | `middleware/tool_error.rs` | 格式化工具错误供 LLM 恢复 | 始终 | -| 11 | ToolOutputGuard | `middleware/tool_output_guard.rs` | 工具输出安全检查 | 始终 | -| 12 | Guardrail | `middleware/guardrail.rs` | shell_exec/file_write/web_fetch 安全规则 | 始终 | -| 13 | SubagentLimit | `middleware/subagent_limit.rs` | 限制并发子 agent | 始终 | -| 14 | TrajectoryRecorder | `middleware/trajectory_recorder.rs` | 轨迹记录 + 压缩 | 始终 (V13-FIX-01 已注册) | +| # | 中间件 | 优先级 | 文件 | 职责 | 注册条件 | +|---|--------|--------|------|------|----------| +| 1 | EvolutionMiddleware | 78 | `middleware/evolution.rs` | 推送进化候选项到 system prompt | 始终 | +| 2 | ButlerRouter | 80 | `middleware/butler_router.rs` | 语义技能路由 + system prompt 增强 | 始终 | +| 3 | DataMasking | 90 | `middleware/data_masking.rs` | 手机号/身份证等敏感数据脱敏 | 始终 | +| 4 | Compaction | 100 | `middleware/compaction.rs` | 超阈值时压缩对话历史 | `compaction_threshold > 0` | +| 5 | Memory | 150 | `middleware/memory.rs` | 对话后自动提取记忆 + 进化检查 | 始终 | +| 6 | Title | 180 | `middleware/title.rs` | 自动生成会话标题 | 始终 | +| 7 | SkillIndex | 200 | `middleware/skill_index.rs` | 注入技能索引到 system prompt | `!skill_index.is_empty()` | +| 8 | DanglingTool | 300 | `middleware/dangling_tool.rs` | 修复缺失的工具调用结果 | 始终 | +| 9 | ToolError | 350 | `middleware/tool_error.rs` | 格式化工具错误供 LLM 恢复 | 始终 | +| 10 | ToolOutputGuard | 360 | `middleware/tool_output_guard.rs` | 工具输出安全检查 | 始终 | +| 11 | Guardrail | 400 | `middleware/guardrail.rs` | shell_exec/file_write/web_fetch 安全规则 | 始终 | +| 12 | LoopGuard | 500 | `middleware/loop_guard.rs` | 防止工具调用无限循环 | 始终 | +| 13 | SubagentLimit | 550 | `middleware/subagent_limit.rs` | 限制并发子 agent | 始终 | +| 14 | TrajectoryRecorder | 650 | `middleware/trajectory_recorder.rs` | 轨迹记录 + 压缩 | 始终 | +| 15 | TokenCalibration | 700 | `middleware/token_calibration.rs` | Token 用量校准 | 始终 | + +> **注意**: 注册顺序(代码中的 chain.register 调用顺序)与执行顺序不同。Chain 按 priority 升序排列后执行。 ### 10 层 SaaS HTTP 中间件(`zclaw-saas/src/main.rs`) @@ -58,10 +61,12 @@ tags: [module, middleware, runtime] | 范围 | 类别 | 包含的中间件 | |------|------|-------------| +| 70-79 | 进化 | EvolutionMiddleware | +| 80-99 | 路由+安全 | ButlerRouter, DataMasking | | 100-199 | 上下文塑造 | Compaction, Memory | -| 200-399 | 能力 | SkillIndex, Guardrail | -| 400-599 | 安全 | LoopGuard, DataMasking | -| 600-799 | 遥测 | TokenCalibration, Title, TrajectoryRecorder | +| 200-399 | 能力 | SkillIndex, DanglingTool, ToolError, ToolOutputGuard | +| 400-599 | 安全 | Guardrail, LoopGuard, SubagentLimit | +| 600-799 | 遥测 | TrajectoryRecorder, TokenCalibration, Title | ### 中间件执行流 @@ -97,7 +102,7 @@ trait AgentMiddleware: Send + Sync { ### 注册位置 -`crates/zclaw-kernel/src/kernel/mod.rs:248-361` — `create_middleware_chain()` 方法,14 次 `chain.register()` + 1 个条件注册 (SkillIndex)。 +`crates/zclaw-kernel/src/kernel/mod.rs:248-361` — `create_middleware_chain()` 方法,15 次 `chain.register()`(含 2 个条件注册: SkillIndex, Compaction)。注册顺序与执行顺序不同,chain 按 priority 升序排列后执行。 ## 关联模块 @@ -111,6 +116,6 @@ trait AgentMiddleware: Send + Sync { | 文件 | 职责 | |------|------| | `crates/zclaw-runtime/src/middleware.rs` | AgentMiddleware trait + MiddlewareChain | -| `crates/zclaw-runtime/src/middleware/` | 14 个中间件实现 (14个 .rs 文件) | +| `crates/zclaw-runtime/src/middleware/` | 15 个中间件实现 (15个 .rs 文件) | | `crates/zclaw-kernel/src/kernel/mod.rs:248-361` | 注册入口 | | `crates/zclaw-saas/src/main.rs` | SaaS HTTP 中间件注册 (10 层) |