docs(wiki): Phase D完成 — 6模块页重构(routing/chat/butler/hands-skills/pipeline/data-model)

- routing.md: 移除Store/lib列表+5节模板 (330→131行)
- chat.md: 添加集成契约+不变量 (180→134行)
- butler.md: 移除重复→引用memory/hands-skills (215→150行)
- hands-skills.md: 5节模板+契约+不变量 (281→170行)
- pipeline.md: 添加契约+重组 (157→154行)
- data-model.md: 添加契约+双库架构图 (181→153行)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
iven
2026-04-22 21:53:17 +08:00
parent 5d88d129d1
commit c10e50d58e
6 changed files with 646 additions and 1067 deletions

View File

@@ -1,6 +1,6 @@
---
title: 聊天系统
updated: 2026-04-21
updated: 2026-04-22
status: active
tags: [module, chat, stream]
---
@@ -9,149 +9,126 @@ tags: [module, chat, stream]
> 从 [[index]] 导航。关联模块: [[routing]] [[saas]] [[butler]]
## 设计思想
## 1. 设计决策
**核心问题: 3 种运行环境需要 3 种 ChatStream 实现。**
| 决策 | 原因 |
|------|------|
| 3 种 ChatStream 实现 | 覆盖 3 种运行环境: KernelClient(Tauri) / SaaSRelay(浏览器) / GatewayClient(外部进程) |
| 5 Store 拆分 | 原 908 行 ChatStore → stream/conversation/message/chat/artifact单一职责 |
| 5 分钟超时守护 | 防止流挂起: kernel-chat.ts:76超时自动 cancelStream |
| 统一回调接口 | 3 种实现共享 `{ onDelta, onThinkingDelta, onTool, onHand, onComplete, onError }` |
| 环境 | 实现 | 传输 | 为什么 |
|------|------|------|--------|
| 桌面端 (Tauri) | KernelClient | Tauri Event | 内置 Kernel但 baseUrl 可指向 SaaS relay |
| 桌面端 (Tauri + SaaS) | KernelClient + relay | Tauri Event → SaaS | 主路径: Token Pool 中转 |
| 浏览器端 | SaaSRelayGatewayClient | HTTP SSE | 无 Tauri 运行时 |
| 外部 Gateway | GatewayClient | WebSocket | 独立进程部署 |
### ChatStream 实现
**统一接口**: 3 种实现共享同一套回调:
| 环境 | 实现 | 传输 |
|------|------|------|
| Tauri + SaaS (主路径) | KernelClient + relay | Tauri Event → SaaS Token Pool → LLM |
| Tauri 本地 | KernelClient | Tauri Event → Kernel → LLM 直连 |
| 浏览器端 | SaaSRelayGatewayClient | HTTP SSE → SaaS → LLM |
| 外部 Gateway | GatewayClient | WebSocket |
```ts
{ onDelta, onThinkingDelta, onTool, onHand, onComplete, onError }
```
## 2. 关键文件 + 数据流
## 功能清单
### 核心文件
| 功能 | 描述 | 入口文件 | 状态 |
|------|------|----------|------|
| 发送消息 | 流式/非流式,支持 thinking | streamStore.ts | |
| 流式响应 | SSE/Tauri Event 实时推送 | streamStore.ts | |
| 模型切换 | 运行时切换 LLM 模型 | conversationStore.ts | |
| 上下文管理 | 会话持久化 + 跨会话恢复 | conversationStore.ts | |
| 取消流式 | 原子标志位中断 | kernel-chat.ts | ✅ |
| Agent 聊天 | 指定 agent_id 独立对话 | streamStore.ts | ✅ |
| 课堂聊天 | 教育场景专用 | classroomStore.ts | ✅ |
| 消息持久化 | IndexedDB 存储 | messageStore.ts | ✅ |
| 聊天产物 | 附件/代码块管理 | artifactStore.ts | ✅ |
## 代码逻辑
| 文件 | 职责 |
|------|------|
| `desktop/src/store/chat/streamStore.ts` | 流式消息编排、发送、取消 |
| `desktop/src/store/chat/conversationStore.ts` | 会话管理、当前模型、sessionKey |
| `desktop/src/store/chat/messageStore.ts` | 消息持久化 (IndexedDB) |
| `desktop/src/lib/kernel-chat.ts` | KernelClient ChatStream (Tauri) |
| `desktop/src/components/ChatArea.tsx` | 聊天区域 UI |
| `crates/zclaw-runtime/src/loop_runner.rs` | Rust 主聊天循环 + 中间件链 |
### 发送消息流
入口: `streamStore.sendMessage(content)``store/chat/streamStore.ts`
```
用户输入 → ChatPanel.tsx
→ streamStore.sendMessage(content)
→ effectiveSessionKey = conversationStore.sessionKey || uuid()
→ effectiveAgentId = resolveGatewayAgentId(currentAgent)
→ getClient().chatStream(content, callbacks, { sessionKey, agentId, chatMode })
→ [KernelClient] Tauri invoke('agent_chat_stream')
→ Kernel → loop_runner → 14层中间件 → LLM Driver
→ Tauri Event emit('chat-response-delta')
→ onDelta → streamStore 追加 delta → UI 渲染
→ onComplete → conversationStore 持久化 → messageStore 写 IndexedDB
→ [SaaSRelay] POST /api/v1/relay/chat/completions → SSE
→ [GatewayClient] WebSocket send → onmessage
→ 5 分钟超时守护 (kernel-chat.ts:76)
```
```
sendMessage(content)
→ effectiveSessionKey = conversationStore.sessionKey || uuid()
→ effectiveAgentId = resolveGatewayAgentId(currentAgent)
→ client.chatStream(content, callbacks, { sessionKey, agentId, chatMode })
→ KernelClient: Tauri invoke('kernel_chat', ...)
→ Kernel → loop_runner → LLM Driver
→ 如果 baseUrl 指向 SaaS relay → 请求发往 Token Pool → LLM
→ 如果 baseUrl 指向 LLM 直连 → 请求直接发往 LLM
→ Tauri Event emit('chat-response-delta', ...)
→ onDelta(text) → streamStore 追加 delta
→ onTool(tool) → toolStore 更新
→ onHand(hand) → handStore 更新
→ onComplete() → conversationStore 持久化
→ SaaSRelay: HTTP POST /api/v1/relay/chat/completions → SSE
→ GatewayClient: WebSocket send → onmessage
→ 5 分钟超时守护 (kernel-chat.ts:76) 防止流挂起
```
### 集成契约
| 方向 | 模块 | 接口 | 说明 |
|------|------|------|------|
| Calls -> | routing | `getClient()` | 确定走哪条 ChatStream |
| Calls -> | middleware | Through loop_runner | 14 层 runtime 中间件链 |
| Called by <- | UI | ChatPanel.tsx | 用户发送消息取消流式 |
| Emits -> | streamStore | Tauri Event `chat-response-delta` | 流式增量更新 |
## 3. 代码逻辑
### Store 拆分 (5 Store)
原来 908 行的 ChatStore 已拆分为:
| Store | 文件 | 职责 |
|-------|------|------|
| streamStore | `store/chat/streamStore.ts` | 流式消息编排、发送、取消 |
| streamStore | `store/chat/streamStore.ts` | 流式编排、发送、取消 |
| conversationStore | `store/chat/conversationStore.ts` | 会话管理、当前模型 |
| messageStore | `store/chat/messageStore.ts` | 消息持久化 |
| messageStore | `store/chat/messageStore.ts` | 消息持久化 (IndexedDB) |
| chatStore | `store/chat/chatStore.ts` | 聊天通用状态 |
| artifactStore | `store/chat/artifactStore.ts` | 聊天产物/附件 |
### 前端 Tauri 命令映射
### 流式事件类型
```
kernel_chat / agent_chat / agent_chat_stream → 发送消息
cancel_stream → 取消流式响应
```
`agent_chat_stream` Tauri Event emit 的 tagged union:
`Delta` / `ThinkingDelta` / `ToolStart` / `ToolEnd` / `HandStart` / `HandEnd` / `SubtaskStatus` / `IterationStart` / `Complete` / `Error`
### 模型切换
```
UI 选择模型 → conversationStore.currentModel = newModel
→ 下次 sendMessage connectionStore 读取 currentModel
→ 下次 sendMessage → getClient() 读取 currentModel
→ SaaS 模式: relay 白名单验证 → 可用则切换
→ 本地模式: 直接使用用户配置的模型
```
## API 接口
### 不变量
- sessionKey 在会话内必须一致 (UUID 生成一次,持久化 IndexedDB)
- cancelStream 设置原子标志位,与 onDelta 回调无竞态
- 3 种 ChatStream 共享同一套回调接口,上层代码无需感知实现差异
- 消息持久化走 messageStore → IndexedDB与流式渲染解耦
### Tauri 命令
**聊天核心** (`desktop/src-tauri/src/kernel_commands/chat.rs`):
| 命令 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| `agent_chat` | ChatRequest { agent_id, message, thinking_enabled?, model? } | `ChatResponse` | 非流式聊天 |
| `agent_chat_stream` | StreamChatRequest { +session_id } | Tauri Event 流 | 流式聊天(主路径) |
| `cancel_stream` | session_id | `()` | 取消当前流式 |
**课堂聊天** (`desktop/src-tauri/src/classroom_commands/chat.rs`):
| 命令 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| `classroom_chat` | { classroom_id, user_message, scene_context? } | `Vec<ClassroomChatMessage>` | 课堂对话 |
| `classroom_chat_history` | classroom_id | `Vec<ClassroomChatMessage>` | 历史消息 |
**流式事件类型** (agent_chat_stream emit):
`Delta` / `ThinkingDelta` / `ToolStart` / `ToolEnd` / `HandStart` / `HandEnd` / `SubtaskStatus` / `IterationStart` / `Complete` / `Error`
### SaaS Relay 路由
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/v1/relay/chat/completions` | OpenAI 兼容格式,支持 session_key/agent_id 透传 |
## 测试链路
| 功能 | 测试文件 | 测试数 | 覆盖状态 |
|------|---------|--------|---------|
| ChatStore 完整流程 | `tests/desktop/chatStore.test.ts` | 11 | ✅ sendMessage/sessionKey/agent隔离/stream相关 |
| 类型契约测试 | `tests/seam/chat-seam.test.ts` | 8 | ✅ StreamChatRequest/ChatResponse camelCase/Event tagged union |
## 关联模块
- [[routing]] — 路由决定使用哪种 client
- [[saas]] — Token Pool 提供模型和 API Key
- [[butler]] — ButlerRouter 中间件增强 system prompt
- [[middleware]] — 消息经过 15 层 runtime 中间件处理
- [[memory]] — 对话内容可能触发记忆提取
## 关键文件
| 文件 | 职责 |
| 命令 | 说明 |
|------|------|
| `desktop/src/store/chat/streamStore.ts` | 流式消息编排 |
| `desktop/src/store/chat/conversationStore.ts` | 会话管理 |
| `desktop/src/store/chat/artifactStore.ts` | 聊天产物管理 |
| `desktop/src/lib/kernel-chat.ts` | Kernel ChatStream (Tauri) |
| `desktop/src/lib/saas-relay-client.ts` | SaaS Relay ChatStream |
| `desktop/src/lib/gateway-client.ts` | Gateway ChatStream (WS) |
| `desktop/src/components/ChatArea.tsx` | 聊天区域 UI |
| `crates/zclaw-runtime/src/loop_runner.rs` | Rust 主聊天循环 |
| `agent_chat_stream` | 流式聊天 (主路径) |
| `agent_chat` | 非流式聊天 |
| `cancel_stream` | 取消当前流式响应 |
| `classroom_chat` | 课堂场景对话 |
## 已知问题
## 4. 活跃问题 + 注意事项
- ⚠️ **B-CHAT-07: 混合域截断** — P2 Open。跨域消息时可能截断上下文
-**SSE Token 统计为 0** — P2 已修复 (SseUsageCapture stream_done flag)
-**Tauri invoke 参数名 snake_case** — P1 已修复 (commit f6c5dd2)
-**Provider Key 解密致 relay 500** — P1 已修复 (commit b69dc61)
| 问题 | 状态 | 说明 |
|------|------|------|
| B-CHAT-07 混合域截断 | P2 Open | 跨域消息时可能截断上下文 |
| SSE Token 统计为 0 | ✅ 已修复 | SseUsageCapture stream_done flag |
| Tauri invoke 参数名 | ✅ 已修复 (f6c5dd2) | camelCase 格式 |
| Provider Key 解密 | ✅ 已修复 (b69dc61) | warn+skip + heal |
**注意事项:**
- 辅助 LLM 调用 (记忆摘要/提取、管家路由) 复用 `kernel_init` 的 model+base_url与聊天同链路
- 课堂聊天是独立 Tauri 命令 (`classroom_chat`),不走 `agent_chat_stream`
## 5. 变更日志
| 日期 | 变更 |
|------|------|
| 04-22 | Wiki 重写: 5 节模板,增加集成契约和不变量 |
| 04-21 | 上一轮更新 |
| 04-17 | ChatStore 拆分为 5 Store (stream/conversation/message/chat/artifact) |
| 04-16 | Provider Key 解密修复 (b69dc61) |
| 04-16 | Tauri invoke 参数名修复 (f6c5dd2) |