fix(audit): Batch 0-1 文档校准 + let _ = 静默错误修复
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

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
This commit is contained in:
iven
2026-04-19 08:30:33 +08:00
parent e94235c4f9
commit 924ad5a6ec
7 changed files with 75 additions and 47 deletions

View File

@@ -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);
}
}
}
}

View File

@@ -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;

View File

@@ -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!({

View File

@@ -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)

View File

@@ -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;
}
}

View File

@@ -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 + ReminderHandWhiteboard/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 层 + 优先级分类更新 |

View File

@@ -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 层) |