Files
zclaw_openfang/docs/features/audit-v12/M1-intelligent-chat.md
iven 442ec0eeef
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
docs(audit): V12 模块化端到端审计报告 — 11 模块 + 总报告
混合矩阵式审计: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天)
2026-04-04 17:55:03 +08:00

12 KiB
Raw Blame History

模块 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. 改进建议

短期修复项(按优先级排序)

  1. [P2] Gemini API Key URL → Header: 将 ?key= 改为 x-goog-api-key header 传递
  2. [P2] ToolOutputGuard Block: 对检测到 sk-AKIA 等确认敏感信息的工具输出应 Block
  3. [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 断链。