14 KiB
故障排查指南
记录开发过程中遇到的问题、根因分析和解决方案。
1. 连接问题
1.1 WebSocket 连接失败
症状: WebSocket connection failed 或 Unexpected server response: 400
排查步骤:
# 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
解决方案:
# Windows - 查找并终止进程
netstat -ano | findstr "1420"
taskkill /PID <PID> /F
# 或使用 PowerShell
Stop-Process -Id <PID> -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
解决方案:
- 检查 Agent 使用的提供商:
curl -s http://127.0.0.1:50051/api/status | jq '.agents[] | {name, model_provider}'
- 配置对应的 API Key:
# 编辑 ~/.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
- 重启 OpenFang:
./openfang.exe restart
快速解决: 使用已配置的 Agent
| Agent | 提供商 | 状态 |
|---|---|---|
| General Assistant | zhipu | 通常已配置 |
2.1.1 配置热重载限制(重要)
症状: 修改 config.toml 后,/api/config 和 /api/status 仍然返回旧配置
根本原因: OpenFang 将配置持久化在 SQLite 数据库中,config.toml 只在启动时读取
验证问题:
# 检查 config.toml 内容
cat ~/.openfang/config.toml
# 输出: provider = "zhipu"
# 检查 API 返回的配置
curl -s http://127.0.0.1:50051/api/config
# 输出: {"default_model":{"provider":"bailian",...}} # 不一致!
解决方案:
- 必须完全重启 OpenFang(热重载
/api/config/reload不会更新持久化配置)
# 方法 1: 通过 API 关闭(然后手动重启)
curl -X POST http://127.0.0.1:50051/api/shutdown
# 方法 2: 使用 CLI
./openfang.exe stop
./openfang.exe start
- 验证配置已生效:
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 端点
错误代码:
// ❌ 错误 - /api/status 不返回 agents 字段
const status = await this.restGet('/api/status');
if (status?.agents && status.agents.length > 0) { ... }
修复代码:
// ✅ 正确 - 使用 /api/agents 端点
const agents = await this.restGet<Array<{ id: string; name?: string; state?: string }>>('/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
验证修复:
# 确认 /api/agents 返回数据
curl http://127.0.0.1:50051/api/agents
# 应返回: [{ "id": "uuid", "name": "...", "state": "Running" }]
2.3 流式响应不显示
症状: 消息发送后无响应或响应不完整
排查步骤:
- 确认 WebSocket 连接状态:
console.log(client.getState()); // 应为 'connected'
- 检查事件处理:
// 确保处理了 text_delta 事件
ws.on('message', (data) => {
const event = JSON.parse(data.toString());
if (event.type === 'text_delta') {
console.log('Delta:', event.content);
}
});
- 验证消息格式:
// ✅ 正确
{ type: 'message', content: 'Hello', session_id: 'xxx' }
// ❌ 错误
{ type: 'chat', message: { role: 'user', content: 'Hello' } }
2.3 消息格式错误
症状: WebSocket 连接成功,但发送消息后收到错误
根本原因: 使用了文档中的格式,而非实际格式
正确的消息格式:
{
"type": "message",
"content": "你的消息内容",
"session_id": "唯一会话ID"
}
3. 前端问题
3.1 Zustand 状态不更新
症状: UI 不反映状态变化
检查:
- 确保使用 selector:
// ✅ 正确 - 使用 selector
const messages = useChatStore((state) => state.messages);
// ❌ 错误 - 可能导致不必要的重渲染
const store = useChatStore();
const messages = store.messages;
- 检查 immer/persist 配置
3.2 切换 Agent 后对话消失
症状: 点击其他 Agent 后,之前的对话内容丢失
根本原因: setCurrentAgent 切换 Agent 时清空了 messages,但没有恢复该 Agent 之前的对话
解决方案:
修改 chatStore.ts 中的 setCurrentAgent 函数:
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:
partialize: (state) => ({
conversations: state.conversations,
currentModel: state.currentModel,
currentAgentId: state.currentAgent?.id, // 添加此行
currentConversationId: state.currentConversationId,
}),
添加 onRehydrateStorage 钩子恢复消息:
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;
}
}
},
验证修复:
- 与 Agent A 对话
- 切换到 Agent B
- 切换回 Agent A → 对话应恢复
文件: desktop/src/store/chatStore.ts
3.3 流式消息累积错误
症状: 流式内容显示不正确或重复
解决方案:
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 配置错误
解决方案:
# 更新 Rust
rustup update
# 清理并重新构建
cd desktop/src-tauri
cargo clean
cargo build
4.2 Tauri 窗口白屏
原因: Vite 开发服务器未启动或连接失败
解决方案:
- 确保
pnpm dev在运行 - 检查
tauri.conf.json中的beforeDevCommand - 检查浏览器控制台错误
4.3 Tauri 热重载不工作
检查:
beforeDevCommand配置正确- 文件监听未超出限制(Linux:
fs.inotify.max_user_watches)
5. 调试技巧
5.1 启用详细日志
// gateway-client.ts
private log(level: string, message: string, data?: unknown) {
if (this.debug) {
console.log(`[GatewayClient:${level}]`, message, data || '');
}
}
5.2 WebSocket 抓包
# 使用 wscat 测试
npm install -g wscat
wscat -c ws://127.0.0.1:50051/api/agents/{agentId}/ws
5.3 检查 OpenFang 状态
# 完整状态
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 等)。
实现方案:
-
检测首次使用 - 使用
useOnboardinghook 检查 localStorage:// 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 } } -
引导向导 - 使用
AgentOnboardingWizard组件:// App.tsx 中的集成 if (showOnboarding) { return ( <AgentOnboardingWizard isOpen={true} onClose={() => { 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); }} /> ); } -
数据持久化 - 用户信息存储在 localStorage:
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 |
引导流程集成 |
引导步骤:
- 认识用户 - 收集用户名称和角色
- Agent 身份 - 设置助手名称、昵称、emoji
- 人格风格 - 选择沟通风格
- 使用场景 - 选择应用场景
- 工作环境 - 配置工作目录
7.2 Onboarding 创建 Agent 失败
症状: 首次使用引导完成后,点击"完成"按钮报错,Agent 创建失败
根本原因: Onboarding 应该更新现有的默认 Agent,而不是创建新的 Agent
错误代码:
// ❌ 错误 - 总是尝试创建新 Agent
const clone = await createClone(createOptions);
修复代码:
// ✅ 正确 - 如果存在现有 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
验证修复:
- 清除 localStorage 中的 onboarding 标记
- 重新启动应用
- 完成引导流程 → 应该成功更新默认 Agent
8. 相关文档
- OpenFang 配置指南 - 配置文件位置、格式和最佳实践
- Agent 和 LLM 提供商配置 - Agent 管理和 Provider 配置
- OpenFang WebSocket 协议 - WebSocket 通信协议
更新历史
| 日期 | 变更 |
|---|---|
| 2026-03-17 | 添加首次使用引导流程 |
| 2026-03-17 | 添加配置热重载限制问题 |
| 2026-03-14 | 初始版本 |