# 故障排查指南 > 记录开发过程中遇到的问题、根因分析和解决方案。 --- ## 1. 连接问题 ### 1.1 WebSocket 连接失败 **症状**: `WebSocket connection failed` 或 `Unexpected server response: 400` **排查步骤**: ```bash # 1. 检查 OpenFang 是否运行 curl http://127.0.0.1:50051/api/health # 2. 检查端口是否正确 netstat -ano | findstr "50051" # 3. 验证 Agent ID curl http://127.0.0.1:50051/api/agents ``` **常见原因**: | 原因 | 解决方案 | |------|----------| | OpenFang 未启动 | `./openfang.exe start` | | 端口错误 | OpenFang 使用 50051,不是 4200 | | Agent ID 无效 | 使用 `/api/agents` 获取真实 UUID | ### 1.2 端口被占用 **症状**: `Port 1420 is already in use` **解决方案**: ```bash # Windows - 查找并终止进程 netstat -ano | findstr "1420" taskkill /PID /F # 或使用 PowerShell Stop-Process -Id -Force ``` ### 1.3 Vite 代理不工作 **症状**: 前端请求返回 404 或 CORS 错误 **检查清单**: - [ ] `vite.config.ts` 中配置了 `/api` 代理 - [ ] `ws: true` 已启用(WebSocket 需要) - [ ] `changeOrigin: true` 已设置 - [ ] 重启 Vite 开发服务器 --- ## 2. 聊天问题 ### 2.1 LLM API Key 未配置 **症状**: ``` Missing API key: No LLM provider configured. Set an API key (e.g. GROQ_API_KEY) and restart ``` **根本原因**: Agent 使用的 LLM 提供商没有配置 API Key **解决方案**: 1. 检查 Agent 使用的提供商: ```bash curl -s http://127.0.0.1:50051/api/status | jq '.agents[] | {name, model_provider}' ``` 2. 配置对应的 API Key: ```bash # 编辑 ~/.openfang/.env echo "ZHIPU_API_KEY=your_key" >> ~/.openfang/.env echo "BAILIAN_API_KEY=your_key" >> ~/.openfang/.env echo "GEMINI_API_KEY=your_key" >> ~/.openfang/.env ``` 3. 重启 OpenFang: ```bash ./openfang.exe restart ``` **快速解决**: 使用已配置的 Agent | Agent | 提供商 | 状态 | |-------|--------|------| | General Assistant | zhipu | 通常已配置 | ### 2.1.1 配置热重载限制(重要) **症状**: 修改 `config.toml` 后,`/api/config` 和 `/api/status` 仍然返回旧配置 **根本原因**: OpenFang 将配置持久化在 SQLite 数据库中,`config.toml` 只在启动时读取 **验证问题**: ```bash # 检查 config.toml 内容 cat ~/.openfang/config.toml # 输出: provider = "zhipu" # 检查 API 返回的配置 curl -s http://127.0.0.1:50051/api/config # 输出: {"default_model":{"provider":"bailian",...}} # 不一致! ``` **解决方案**: 1. **必须完全重启 OpenFang**(热重载 `/api/config/reload` 不会更新持久化配置) ```bash # 方法 1: 通过 API 关闭(然后手动重启) curl -X POST http://127.0.0.1:50051/api/shutdown # 方法 2: 使用 CLI ./openfang.exe stop ./openfang.exe start ``` 2. **验证配置已生效**: ```bash curl -s http://127.0.0.1:50051/api/status | grep -E "default_provider|default_model" # 应输出: "default_provider":"zhipu" ``` **配置文件位置**: | 文件 | 用途 | |------|------| | `~/.openfang/config.toml` | 主配置(启动时读取) | | `~/.openfang/.env` | API Key 环境变量 | | `~/.openfang/secrets.env` | 敏感信息 | | `~/.openfang/data/openfang.db` | SQLite 数据库(持久化配置) | **支持的 Provider**: | Provider | 环境变量 | 模型示例 | |----------|----------|----------| | zhipu | `ZHIPU_API_KEY` | glm-4-flash, GLM-4-Plus | | bailian | `BAILIAN_API_KEY` | qwen3.5-plus, qwen3-coder-next | | gemini | `GEMINI_API_KEY` | gemini-2.5-flash | | deepseek | `DEEPSEEK_API_KEY` | deepseek-chat | | openai | `OPENAI_API_KEY` | gpt-4, gpt-3.5-turbo | ### 2.2 Agent ID 获取失败导致无法对话 **症状**: Gateway 显示已连接,但发送消息无响应或报错 "No agent available" **根本原因**: `fetchDefaultAgentId()` 使用错误的 API 端点 **错误代码**: ```typescript // ❌ 错误 - /api/status 不返回 agents 字段 const status = await this.restGet('/api/status'); if (status?.agents && status.agents.length > 0) { ... } ``` **修复代码**: ```typescript // ✅ 正确 - 使用 /api/agents 端点 const agents = await this.restGet>('/api/agents'); if (agents && agents.length > 0) { const runningAgent = agents.find(a => a.state === 'Running'); this.defaultAgentId = (runningAgent || agents[0]).id; } ``` **历史背景**: 这个问题与 OpenClaw 的握手认证问题类似,但根因不同: - OpenClaw: 需要 `cli/cli/operator` 身份 + Ed25519 配对 - OpenFang: 不需要认证握手,但需要正确的 Agent UUID **验证修复**: ```bash # 确认 /api/agents 返回数据 curl http://127.0.0.1:50051/api/agents # 应返回: [{ "id": "uuid", "name": "...", "state": "Running" }] ``` ### 2.3 流式响应不显示 **症状**: 消息发送后无响应或响应不完整 **排查步骤**: 1. 确认 WebSocket 连接状态: ```typescript console.log(client.getState()); // 应为 'connected' ``` 2. 检查事件处理: ```typescript // 确保处理了 text_delta 事件 ws.on('message', (data) => { const event = JSON.parse(data.toString()); if (event.type === 'text_delta') { console.log('Delta:', event.content); } }); ``` 3. 验证消息格式: ```javascript // ✅ 正确 { type: 'message', content: 'Hello', session_id: 'xxx' } // ❌ 错误 { type: 'chat', message: { role: 'user', content: 'Hello' } } ``` ### 2.3 消息格式错误 **症状**: WebSocket 连接成功,但发送消息后收到错误 **根本原因**: 使用了文档中的格式,而非实际格式 **正确的消息格式**: ```json { "type": "message", "content": "你的消息内容", "session_id": "唯一会话ID" } ``` --- ## 3. 前端问题 ### 3.1 Zustand 状态不更新 **症状**: UI 不反映状态变化 **检查**: 1. 确保使用 selector: ```typescript // ✅ 正确 - 使用 selector const messages = useChatStore((state) => state.messages); // ❌ 错误 - 可能导致不必要的重渲染 const store = useChatStore(); const messages = store.messages; ``` 2. 检查 immer/persist 配置 ### 3.2 切换 Agent 后对话消失 **症状**: 点击其他 Agent 后,之前的对话内容丢失 **根本原因**: `setCurrentAgent` 切换 Agent 时清空了 `messages`,但没有恢复该 Agent 之前的对话 **解决方案**: 修改 `chatStore.ts` 中的 `setCurrentAgent` 函数: ```typescript setCurrentAgent: (agent) => set((state) => { if (state.currentAgent?.id === agent.id) { return { currentAgent: agent }; } // Save current conversation before switching const conversations = upsertActiveConversation([...state.conversations], state); // Try to find existing conversation for this agent const agentConversation = conversations.find(c => c.agentId === agent.id); if (agentConversation) { // Restore the agent's previous conversation return { conversations, currentAgent: agent, messages: [...agentConversation.messages], sessionKey: agentConversation.sessionKey, currentConversationId: agentConversation.id, }; } // No existing conversation, start fresh return { conversations, currentAgent: agent, messages: [], sessionKey: null, currentConversationId: null, }; }), ``` 修改 `partialize` 配置以保存 `currentAgentId`: ```typescript partialize: (state) => ({ conversations: state.conversations, currentModel: state.currentModel, currentAgentId: state.currentAgent?.id, // 添加此行 currentConversationId: state.currentConversationId, }), ``` 添加 `onRehydrateStorage` 钩子恢复消息: ```typescript onRehydrateStorage: () => (state) => { // Rehydrate Date objects from JSON strings if (state?.conversations) { for (const conv of state.conversations) { conv.createdAt = new Date(conv.createdAt); conv.updatedAt = new Date(conv.updatedAt); for (const msg of conv.messages) { msg.timestamp = new Date(msg.timestamp); msg.streaming = false; } } } // Restore messages from current conversation if exists if (state?.currentConversationId && state.conversations) { const currentConv = state.conversations.find(c => c.id === state.currentConversationId); if (currentConv) { state.messages = [...currentConv.messages]; state.sessionKey = currentConv.sessionKey; } } }, ``` **验证修复**: 1. 与 Agent A 对话 2. 切换到 Agent B 3. 切换回 Agent A → 对话应恢复 **文件**: `desktop/src/store/chatStore.ts` ### 3.3 流式消息累积错误 **症状**: 流式内容显示不正确或重复 **解决方案**: ```typescript onDelta: (delta: string) => { set((state) => ({ messages: state.messages.map((m) => m.id === assistantId ? { ...m, content: m.content + delta } // 累积内容 : m ), })); } ``` --- ## 4. Tauri 桌面端问题 ### 4.1 Tauri 编译失败 **常见错误**: - Rust 版本不兼容 - 依赖缺失 - Cargo.toml 配置错误 **解决方案**: ```bash # 更新 Rust rustup update # 清理并重新构建 cd desktop/src-tauri cargo clean cargo build ``` ### 4.2 Tauri 窗口白屏 **原因**: Vite 开发服务器未启动或连接失败 **解决方案**: 1. 确保 `pnpm dev` 在运行 2. 检查 `tauri.conf.json` 中的 `beforeDevCommand` 3. 检查浏览器控制台错误 ### 4.3 Tauri 热重载不工作 **检查**: - `beforeDevCommand` 配置正确 - 文件监听未超出限制(Linux: `fs.inotify.max_user_watches`) --- ## 5. 调试技巧 ### 5.1 启用详细日志 ```typescript // gateway-client.ts private log(level: string, message: string, data?: unknown) { if (this.debug) { console.log(`[GatewayClient:${level}]`, message, data || ''); } } ``` ### 5.2 WebSocket 抓包 ```bash # 使用 wscat 测试 npm install -g wscat wscat -c ws://127.0.0.1:50051/api/agents/{agentId}/ws ``` ### 5.3 检查 OpenFang 状态 ```bash # 完整状态 curl -s http://127.0.0.1:50051/api/status | jq # Agent 状态 curl -s http://127.0.0.1:50051/api/agents | jq '.[] | {id, name, state}' # Hands 状态 curl -s http://127.0.0.1:50051/api/hands | jq '.[] | {id, name, requirements_met}' ``` --- ## 6. 错误代码参考 | 错误信息 | 原因 | 解决方案 | |---------|------|----------| | `Port 1420 is already in use` | Vite 服务器已运行 | 终止现有进程 | | `Unexpected server response: 400` | Agent ID 无效 | 使用真实 UUID | | `Missing API key` | LLM 提供商未配置 | 配置 API Key | | `Connection refused` | OpenFang 未运行 | 启动服务 | | `CORS error` | 代理未配置 | 检查 vite.config.ts | --- ## 7. 新用户引导 (Onboarding) ### 7.1 首次使用引导流程 **需求**: 当用户第一次使用系统时,引导用户设置默认助手的人格信息(about me、you in my eye 等)。 **实现方案**: 1. **检测首次使用** - 使用 `useOnboarding` hook 检查 localStorage: ```typescript // desktop/src/lib/use-onboarding.ts const ONBOARDING_COMPLETED_KEY = 'zclaw-onboarding-completed'; const USER_PROFILE_KEY = 'zclaw-user-profile'; export function useOnboarding(): OnboardingState { // 检查 localStorage 是否有完成记录 // 返回 { isNeeded, isLoading, markCompleted, resetOnboarding } } ``` 2. **引导向导** - 使用 `AgentOnboardingWizard` 组件: ```typescript // App.tsx 中的集成 if (showOnboarding) { return ( { markCompleted({ userName: 'User', userRole: 'user' }); setShowOnboarding(false); }} onSuccess={(clone) => { markCompleted({ userName: clone.userName || 'User', userRole: clone.userRole, }); setCurrentAgent({ id: clone.id, name: clone.name, icon: clone.emoji || '🦞', // ... }); setShowOnboarding(false); }} /> ); } ``` 3. **数据持久化** - 用户信息存储在 localStorage: ```typescript interface UserProfile { userName: string; userRole?: string; completedAt: string; } ``` **文件位置**: | 文件 | 用途 | |------|------| | `desktop/src/lib/use-onboarding.ts` | 引导状态管理 hook | | `desktop/src/components/AgentOnboardingWizard.tsx` | 5 步引导向导组件 | | `desktop/src/App.tsx` | 引导流程集成 | **引导步骤**: 1. 认识用户 - 收集用户名称和角色 2. Agent 身份 - 设置助手名称、昵称、emoji 3. 人格风格 - 选择沟通风格 4. 使用场景 - 选择应用场景 5. 工作环境 - 配置工作目录 ### 7.2 Onboarding 创建 Agent 失败 **症状**: 首次使用引导完成后,点击"完成"按钮报错,Agent 创建失败 **根本原因**: Onboarding 应该**更新现有的默认 Agent**,而不是创建新的 Agent **错误代码**: ```typescript // ❌ 错误 - 总是尝试创建新 Agent const clone = await createClone(createOptions); ``` **修复代码**: ```typescript // ✅ 正确 - 如果存在现有 Agent 则更新 let clone: Clone | undefined; if (clones && clones.length > 0) { // 更新现有的默认 Agent clone = await updateClone(clones[0].id, personalityUpdates); } else { // 没有现有 Agent 才创建新的 clone = await createClone(createOptions); } ``` **文件**: `desktop/src/components/AgentOnboardingWizard.tsx` **验证修复**: 1. 清除 localStorage 中的 onboarding 标记 2. 重新启动应用 3. 完成引导流程 → 应该成功更新默认 Agent ### 3.4 刷新页面后对话内容丢失 **症状**: 切换 Tab 时对话正常保留,但按 F5 刷新页面后对话内容消失 **根本原因**: `onComplete` 回调中没有将当前对话保存到 `conversations` 数组,导致 `persist` 中间件无法持久化 **问题分析**: Zustand 的 `persist` 中间件只保存 `partialize` 中指定的字段: ```typescript partialize: (state) => ({ conversations: state.conversations, // ← 从这里恢复 currentModel: state.currentModel, currentAgentId: state.currentAgent?.id, currentConversationId: state.currentConversationId, }), ``` 但 `messages` 数组只在内存中,刷新后丢失。恢复逻辑依赖 `conversations` 数组: ```typescript onRehydrateStorage: () => (state) => { if (state?.currentConversationId && state.conversations) { const currentConv = state.conversations.find(c => c.id === state.currentConversationId); if (currentConv) { state.messages = [...currentConv.messages]; // ← 从 conversations 恢复 } } }, ``` **问题**: `onComplete` 回调中只更新了 `messages`,没有调用 `upsertActiveConversation` 保存到 `conversations` **修复代码**: ```typescript onComplete: () => { const state = get(); // Save conversation to persist across refresh const conversations = upsertActiveConversation([...state.conversations], state); const currentConvId = state.currentConversationId || conversations[0]?.id; set({ isStreaming: false, conversations, // ← 保存到 conversations 数组 currentConversationId: currentConvId, // ← 确保 ID 被设置 messages: state.messages.map((m) => m.id === assistantId ? { ...m, streaming: false, runId } : m ), }); // ... rest of the callback }, ``` **文件**: `desktop/src/store/chatStore.ts` **验证修复**: 1. 发送消息并获得 AI 回复 2. 按 F5 刷新页面 3. 对话内容应该保留 ### 3.5 ChatArea 输入框布局错乱 **症状**: 对话过程中输入框被移动到页面顶部,而不是固定在底部 **根本原因**: `ChatArea` 组件返回的是 React Fragment (`<>...`),没有包裹在 flex 容器中 **问题代码**: ```typescript // ❌ 错误 - Fragment 没有 flex 布局 return ( <>
Header
Messages
Input
); ``` **修复代码**: ```typescript // ✅ 正确 - 使用 flex 容器 return (
Header
Messages
Input
); ``` **文件**: `desktop/src/components/ChatArea.tsx` **布局原理**: ``` ┌─────────────────────────────────────┐ │ Header (h-14, flex-shrink-0) │ ← 固定高度 ├─────────────────────────────────────┤ │ │ │ Messages (flex-1, overflow-y-auto) │ ← 占据剩余空间,可滚动 │ │ ├─────────────────────────────────────┤ │ Input (flex-shrink-0) │ ← 固定在底部 └─────────────────────────────────────┘ ``` --- ## 7. 记忆系统问题 ### 7.1 Memory Tab 为空,多轮对话后无记忆 **症状**: 经过多次对话后,右侧面板的"记忆"Tab 内容为空 **根本原因**: 多个配置问题导致记忆未被提取 **问题分析**: 1. **LLM 提取默认禁用**: `useLLM: false` 导致只使用规则提取 2. **提取阈值过高**: `minMessagesForExtraction: 4` 短对话不会触发 3. **agentId 不一致**: `MemoryPanel` 用 `'zclaw-main'`,`MemoryGraph` 用 `'default'` 4. **Gateway 端点不存在**: `GatewayLLMAdapter` 调用 `/api/llm/complete`,OpenFang 无此端点 **修复方案**: ```typescript // 1. 启用 LLM 提取 (memory-extractor.ts) export const DEFAULT_EXTRACTION_CONFIG: ExtractionConfig = { useLLM: true, // 从 false 改为 true minMessagesForExtraction: 2, // 从 4 降低到 2 // ... }; // 2. 统一 agentId fallback (MemoryGraph.tsx) const agentId = currentAgent?.id || 'zclaw-main'; // 从 'default' 改为 'zclaw-main' // 3. 修复 Gateway 适配器端点 (llm-service.ts) // 使用 OpenFang 的 /api/agents/{id}/message 端点 const response = await fetch(`/api/agents/${agentId}/message`, { method: 'POST', body: JSON.stringify({ message: fullPrompt, ... }), }); ``` **文件**: - `desktop/src/lib/memory-extractor.ts` - `desktop/src/lib/llm-service.ts` - `desktop/src/components/MemoryGraph.tsx` **验证修复**: 1. 打开浏览器控制台 2. 进行至少 2 轮对话 3. 查看日志: `[MemoryExtractor] Using LLM-powered semantic extraction` 4. 检查 Memory Tab 是否显示提取的记忆 ### 7.2 Memory Graph UI 与系统风格不一致 **症状**: 记忆图谱使用深色主题,与系统浅色主题不协调 **根本原因**: `MemoryGraph.tsx` 硬编码深色背景 `#1a1a2e` **修复方案**: ```typescript // Canvas 背景 - 支持亮/暗双主题 ctx.fillStyle = '#f9fafb'; // gray-50 (浅色) // 工具栏 - 添加 dark: 变体
// 图谱画布 - 添加 dark: 变体
``` **文件**: `desktop/src/components/MemoryGraph.tsx` **设计规范**: - 使用 Tailwind 的 `dark:` 变体支持双主题 - 强调色使用 `orange-500` 而非 `blue-600` - 文字颜色使用 `gray-700 dark:text-gray-300` --- ## 8. 相关文档 - [OpenFang 配置指南](./openfang-configuration.md) - 配置文件位置、格式和最佳实践 - [Agent 和 LLM 提供商配置](./agent-provider-config.md) - Agent 管理和 Provider 配置 - [OpenFang WebSocket 协议](./openfang-websocket-protocol.md) - WebSocket 通信协议 --- ## 更新历史 | 日期 | 变更 | |------|------| | 2026-03-18 | 添加记忆提取和图谱 UI 问题 | | 2026-03-18 | 添加刷新后对话丢失问题和 ChatArea 布局问题 | | 2026-03-17 | 添加首次使用引导流程 | | 2026-03-17 | 添加配置热重载限制问题 | | 2026-03-14 | 初始版本 |