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
混合矩阵式审计:10 个功能模块 × 五维检查清单 - 项目整体健康度: 76/100 - 2 个 P0 (M4 双数据库 + 反思引擎 LLM 未接入) - 15 个 P1 (跨 M2/M3/M4/M5/M6/M7/M11) - 三类断链模式: 写了没接/接了不对/双实现未统一 - 三阶段修复路线图: P0(2-3天) → P1(5-7天) → P2(5-7天)
12 KiB
12 KiB
模块 M1 智能对话 审计报告
审计版本: V12 审计日期: 2026-04-04 审计范围: 用户输入 → ChatArea → chatStore → Kernel → LLM Driver → 流式回传
1. 模块概况
功能描述
ZCLAW 智能对话模块是用户与 AI Agent 交互的核心入口,支持流式响应、8 个 LLM Provider、推理模式、工具调用、多轮对话。
涉及文件清单
前端 (Desktop)
- UI 组件:
desktop/src/components/ai/(ChatMode, StreamingText, ReasoningBlock, ToolCallChain, TokenMeter, SuggestionChips, ArtifactPanel, ModelSelector) - 入口组件:
desktop/src/components/ChatArea.tsx - Store 层:
desktop/src/store/chat/streamStore.ts,conversationStore.ts,messageStore.ts,artifactStore.ts - Client 层:
desktop/src/lib/kernel-chat.ts,gateway-client.ts,saas-relay-client.ts - 类型:
desktop/src/types/chat.ts,kernel-types.ts
后端 (Rust)
- Kernel:
crates/zclaw-kernel/src/kernel/messaging.rs - Agent Loop:
crates/zclaw-runtime/src/loop_runner.rs - Driver:
crates/zclaw-runtime/src/driver/(anthropic.rs, openai.rs, gemini.rs, local.rs) - 中间件:
crates/zclaw-runtime/src/middleware/(11 个中间件) - SaaS Relay:
crates/zclaw-saas/src/relay/(handlers.rs, service.rs, key_pool.rs) - 存储:
crates/zclaw-memory/src/store.rs - Tauri 命令:
desktop/src-tauri/src/kernel_commands/chat.rs
调用链路图
用户输入
→ ChatArea.handleSend()
→ streamStore.sendMessage()
→ getClient().chatStream(content, callbacks, opts)
├─ [Kernel 模式] kernel-chat.ts → invoke('agent_chat_stream', { request })
│ → Rust: agent_chat_stream() → Kernel.send_message_stream_with_prompt()
│ → AgentLoop.run_streaming()
│ → create_middleware_chain() (11层 before_completion)
│ → driver.stream() (LLM调用)
│ → 工具执行循环 (max 10 iterations)
│ → app.emit("stream:chunk", payload)
│ ← listen("stream:chunk") → streamCallbacks.onDelta/onTool/onComplete
│
├─ [Gateway 模式] gateway-client.ts → WebSocket
│ → ZCLAW WebSocket 协议
│ ← WS onmessage → streamCallbacks
│
└─ [SaaS 模式] saas-relay-client.ts → HTTP SSE
→ POST /api/v1/relay/chat/completions
← SSE stream → streamCallbacks
← streamStore 更新 isStreaming/activeRunId
← UI 重渲染
2. 五维检查结果
2.1 链路完整性
| 链路 | 起点 | 终点 | 状态 | 备注 |
|---|---|---|---|---|
| Kernel 流式聊天 | ChatArea.tsx | agent_chat_stream (Rust) | ✅ 连通 | invoke 参数一致 |
| Gateway 聊天 | ChatArea.tsx | WebSocket server | ✅ 连通 | 独立路径 |
| SaaS Relay 聊天 | ChatArea.tsx | /api/v1/relay/chat/completions | ✅ 连通 | SSE 流 |
| 流式事件 stream:chunk | Rust emit | kernel-chat.ts listen | ✅ 连通 | listen 在 line 91 |
| 取消流 cancel_stream | streamStore | Rust cancel_stream | ✅ 连通 | AtomicBool 标志 |
| hand-execution-complete | Rust emit | ChatArea.tsx + kernel-hands.ts listen | ✅ 连通 | 双重监听 |
| 消息持久化 | AgentLoop | SqliteStorage.append_message | ✅ 连通 | |
| Token 用量记录 | AgentLoop after_completion | usage 数据 | ✅ 连通 | |
| SaaS 计费 | relay service.rs | quota 递增 | ✅ 连通 | Semaphore(16) 限流 |
| 中间件链 | Kernel | 11 层中间件 | ✅ 连通 | 按 priority 排序 |
2.2 参数/类型一致性
| 接口 | 前端类型 | 后端类型 | 一致性 | 备注 |
|---|---|---|---|---|
| agent_chat_stream.request | { agentId, sessionId, message, thinkingEnabled?, reasoningEffort?, planMode? } |
StreamChatRequest { agent_id, session_id, message, thinking_enabled?, reasoning_effort?, plan_mode? } |
✅ 一致 | #[serde(rename_all = "camelCase")] 自动转换 |
| stream:chunk payload | StreamChunkPayload |
serde_json::json!({ sessionId, type, ... }) |
✅ 一致 | |
| cancel_stream | { sessionId } |
session_id: String |
✅ 一致 | camelCase 转换 |
| ChatStreamOptions | thinking_enabled?, reasoning_effort?, plan_mode? |
thinking_enabled, reasoning_effort, plan_mode |
✅ 一致 | 全部 Optional |
参数一致性结论: 无断链。Tauri 的 rename_all = "camelCase" 正确处理了命名风格差异。
2.3 边界与错误处理
| 场景 | 输入 | 预期行为 | 实际行为 | 级别 |
|---|---|---|---|---|
| 空消息发送 | "" |
阻止发送 | ChatArea line 211 检查空输入并 return | ✅ 正确 |
| 流式中重复发送 | 连续点击发送 | 阻止重复 | streamStore line 193 if (isStreaming) return |
✅ 正确 |
| 同 session 并发流 | 相同 sessionId 两次 invoke | 拒绝第二个 | Rust AtomicBool CAS 保证 (chat.rs:131-133) | ✅ 正确 |
| 后端断开 | 流式中后端 crash | 超时或错误回调 | StreamBridge 90s 超时 (6个心跳) + channel close 检测 | ✅ 正确 |
| 流式中断(悬空工具) | 中途断网留下 ToolUse 无 ToolResult | 修补悬空 | DanglingToolMiddleware 自动插入占位 ToolResult | ✅ 正确 |
| 超长消息 | 100K 字符 | 拒绝或截断 | Rust 端 validate_string_length(100000) 限制 | ✅ 正确 |
| Payload 过大 | 大量工具结果 | 截断 | OpenAiDriver 1.8MB payload limit + 紧急截断 (保留 sys+最近4条) | ✅ 正确 |
| SaaS Key 全部冷却 | 无可用 API Key | 等待提示 | key_pool.rs 返回预计等待时间 | ✅ 正确 |
| LLM 请求超时 | Provider 不响应 | 超时终止 | 流式 chunk 超时 60s (loop_runner.rs:683) | ✅ 正确 |
| 模型只输出 reasoning 无 text | thinking 模式下 | 回退为 reasoning 内容 | loop_runner.rs:769-774 自动回退 | ✅ 正确 |
| 循环调用检测 | 重复工具调用 | warn/block/abort | LoopGuard SHA256 指纹 + 三级阈值 (3/5/30) | ✅ 正确 |
| SaaS 请求体过大 | > 1MB | 拒绝 | handlers.rs:41 大小限制 | ✅ 正确 |
| temperature 越界 | > 2.0 或 < 0 | 拒绝 | handlers.rs:91-103 范围验证 | ✅ 正确 |
| max_tokens 越界 | > 128000 | 拒绝 | handlers.rs:106-118 范围验证 | ✅ 正确 |
| 前端 cancel 时 Rust 端清理 | 取消后继续收到 chunk | 丢弃并清理 | cancel_flag 检测 + channel close + stream_guard 释放 | ✅ 正确 |
2.4 状态管理
| Store/组件 | 状态机完整性 | 持久化 | 竞态风险 |
|---|---|---|---|
| streamStore | ✅ isStreaming/activeRunId 完整 | ❌ 无持久化(纯运行时状态) | ⚠️ 低风险 — cancelStream 中使用 stale activeRunId |
| conversationStore | ✅ 会话列表/当前会话 | ✅ IndexedDB 持久化 | ✅ 低风险 |
| messageStore | ✅ 消息追加/更新 | ❌ 消息持久化在 SQLite 后端 | ✅ 低风险 |
| artifactStore | ✅ 文件列表/选中/开关 | ❌ 无持久化 | ✅ 无风险 |
| ChatArea 组件 | ✅ 输入/流式状态 | ❌ 无持久化 | ✅ 低风险 |
cancelStream 竞态风险详情:
streamStore.ts:476:const sessionId = useConversationStore.getState().sessionKey || activeRunId || ''- 如果用户在 cancel 和新 stream 之间快速操作,
sessionKey可能已指向新 session,导致 cancel 错误的 stream - 风险等级: P3(极端操作顺序,且 cancel 错误 stream 只会导致空操作)
2.5 安全与资源
| 检查项 | 状态 | 说明 |
|---|---|---|
| API Key 安全存储 | ✅ | SecretString 包装,expose_secret() 仅在 HTTP header 中使用 |
| API Key 加密存储 (SaaS) | ✅ | AES-256-GCM + 随机 nonce |
| 对话内容不进入日志 | ✅ | 仅记录前 50 字符 (memory.rs:65) |
| SSRF 防护 | ✅ | 精确的 URL 验证 (service.rs:810-922) |
| SQL 注入防护 | ✅ | 全部参数化查询 |
| 请求体大小限制 | ✅ | SaaS 1MB, OpenAI driver 1.8MB |
| 流式 channel 泄漏 | ✅ | stream_guard 在所有路径(成功/错误/cancel)都清理 |
| 工具输出敏感信息检测 | ⚠️ 只 warn 不 block | 检测到 API Key 等敏感信息时仅 warn,仍然传递给 LLM |
| Gemini API Key URL 泄漏 | ⚠️ P2 风险 | Key 通过 ?key= query param 传递,出现在日志/代理/网络面板 |
3. 问题清单
| ID | 描述 | 文件:行号 | 级别 | 修复建议 | 验证方法 |
|---|---|---|---|---|---|
| M1-01 | GeminiDriver API Key 通过 URL query param 传递,出现在日志和代理 | driver/gemini.rs:71-74 |
P2 | 改用 HTTP header 认证(Gemini 支持 x-goog-api-key header) |
检查 HTTP 日志是否包含 key |
| M1-02 | ToolOutputGuardMiddleware 检测到敏感信息只 warn 不 block | middleware/tool_output_guard.rs:99-128 |
P2 | 对确认的 API Key/密码模式应 Block 或至少截断 | 构造含 sk-xxx 的工具输出测试 |
| M1-03 | MemoryMiddleware 中 std::sync::Mutex::unwrap() 在 async 上下文 |
middleware/memory.rs:46 |
P2 | 改用 lock().unwrap_or_else(|e| e.into_inner()) 或 tokio::sync::Mutex |
stress test 并发记忆提取 |
| M1-04 | LoopGuardMiddleware 中 std::sync::Mutex::unwrap() 在 async 上下文 |
middleware/loop_guard.rs:40 |
P2 | 同 M1-03 | stress test 循环检测 |
| M1-05 | Agent Loop 迭代上限硬编码为 10 | loop_runner.rs:298 |
P3 | 提取为 KernelConfig 配置项 | 修改配置验证生效 |
| M1-06 | TitleMiddleware 是空占位符 | middleware/title.rs |
P3 | 完成实现或移除以减少 chain 开销 | 确认无功能依赖后移除 |
| M1-07 | OpenAiDriver trace 级别记录完整请求体 |
driver/openai.rs:127 |
P3 | 生产环境确保 trace 级别不启用,或移除内容日志 | 检查日志配置 |
| M1-08 | cancelStream 竞态:sessionKey 可能指向新 session | streamStore.ts:476 |
P3 | cancel 时使用 activeRunId 而非 sessionKey |
快速 cancel + 新 stream 测试 |
| M1-09 | LoopGuard 不在 agent turn 间 reset | middleware/loop_guard.rs |
P3 | 考虑在 after_completion 中 reset |
多轮对话验证计数器不累积 |
| M1-10 | OpenAiDriver 将 SecretString 转为普通 String | driver/openai.rs:130 |
P3 | String 在 async 闭包生命周期内可被内存转储获取;考虑使用零化 | 内存安全审计 |
| M1-11 | loop_runner.rs:513/804 使用 unwrap_or_default() 静默吞掉数据库错误 |
loop_runner.rs:513,804 |
P3 | 改为 log::warn! + 默认值 |
断开 DB 验证有 warn 日志 |
4. 改进建议
短期修复项(按优先级排序)
- [P2] Gemini API Key URL → Header: 将
?key=改为x-goog-api-keyheader 传递 - [P2] ToolOutputGuard Block: 对检测到
sk-、AKIA等确认敏感信息的工具输出应 Block - [P2] Mutex unwrap → 安全处理: MemoryMiddleware 和 LoopGuardMiddleware 的
std::sync::Mutex改用安全解包方式
长期架构建议
- 将 Agent Loop 迭代上限和 LoopGuard 阈值提取为 KernelConfig 配置项
- 为 TitleMiddleware 完成实现或清理移除
- 考虑 Driver 层的 SecretString 生命周期管理改进(避免
to_string())
5. 健康度评分
| 维度 | 评分 | 说明 |
|---|---|---|
| 链路完整性 | 95/100 | 三种连接模式全部连通,无断链 |
| 参数一致性 | 100/100 | 全部接口参数匹配,Tauri camelCase 自动转换正确 |
| 边界处理 | 90/100 | 空值/超长/并发/超时全覆盖 |
| 状态管理 | 85/100 | 基本完整,cancelStream 有低概率竞态 |
| 安全资源 | 85/100 | Gemini Key 泄漏和 ToolOutputGuard warn-only 需修复 |
综合健康度: 91/100
模块整体质量很高,11 层中间件设计参考了 DeerFlow 2.0 最佳实践,三层连接模式(Kernel/Gateway/SaaS)全部连通。发现的问题均为 P2/P3 级别,无 P0/P1 断链。