fix(desktop): session persistence — refresh/login/context/empty-content 4-bug fix
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

1. App.tsx: add restoreSession() call on startup to prevent redirect
   to login page after refresh (isRestoring guard + BootstrapScreen)
2. CloneManager: call syncAgents() after loadClones() to restore
   currentAgent and conversation history on app load
3. zclaw-memory: add get_or_create_session() so frontend session UUID
   is persisted directly — kernel no longer creates mismatched IDs
4. openai.rs: assistant message content must be non-empty for
   Kimi/Qwen APIs — replace empty content with meaningful placeholders

Also includes admin-v2 ModelServices unified page (merge providers +
models + API keys into expandable row layout)
This commit is contained in:
iven
2026-03-31 13:38:59 +08:00
parent 3e5d64484e
commit 6cae768401
29 changed files with 1982 additions and 933 deletions

View File

@@ -2084,7 +2084,58 @@ const adminRouting = storedAccount?.llm_routing;
---
## 16. 相关文档
## 16. 桌面端会话持久化三连 Bug (2026-04-01)
### 16.1 页面刷新跳回登录页
**症状**: F5 刷新页面后被踢回登录界面,必须重新输入密码。
**根本原因**: `saasStore.restoreSession()` 方法已实现(从 OS keyring 读取 token + 验证但从未被调用。Zustand store 初始化 `isLoggedIn: false``App.tsx` 同步检查后立即渲染 `<LoginPage />`。
**修复**: `desktop/src/App.tsx` — 添加 `isRestoring` 状态,启动时调用 `restoreSession()`,恢复完成前显示加载屏。
### 16.2 登录后空白对话页
**症状**: 登录后看到空白对话页面,必须手动点击 agent 才能看到之前的对话。
**根本原因**: `CloneManager` 在 `loadClones()` 后没有调用 `chatStore.syncAgents()`。`syncAgents` 包含恢复 `currentAgent` 和从 conversations 数组恢复 messages 的安全网逻辑。
**修复**: `desktop/src/components/CloneManager.tsx` — `loadClones().then()` 中调用 `syncAgents(clones)`。
### 16.3 Agent 无上下文记忆 — session ID 映射断裂
**症状**: 每条消息 Agent 都说"这是第一条消息",多轮对话上下文完全丢失。
**根本原因**: 前端生成的 `sessionKey` UUID 传到 kernel 后,`get_messages()` 查不到(因为该 ID 从未存入 sessions 表),于是 `create_session()` 生成全新的 UUID。下一轮前端再传原 UUID又创建新 session。前端 session ID 和数据库 session ID 永远无法匹配。
```
Turn 1: 前端 "abc-123" → DB 查不到 → 创建 "xyz-789" → 消息存到 "xyz-789"
Turn 2: 前端 "abc-123" → DB 查不到 → 创建 "def-456" → 消息存到 "def-456"
结果: 永远找不到历史消息
```
**修复**:
1. `crates/zclaw-memory/src/store.rs` — 添加 `get_or_create_session()` 方法,直接用前端提供的 ID 创建 session
2. `crates/zclaw-kernel/src/kernel.rs` — 使用 `get_or_create_session` 替代 lookup-then-create
### 16.4 `messages[N] must not be empty` — assistant 消息空 content
**症状**: Bug 16.3 修复后暴露:`API error 400: messages[4] with role 'assistant' must not be empty`
**根本原因**: Bug 16.3 修复后对话历史终于被正确发送给 LLM。但历史中的 `ToolUse` 消息对应的 assistant 消息 content 为空(只有 tool_calls 没有 text。Kimi/Qwen API 要求 assistant 消息的 `content` 不能为空字符串。
**修复**: `crates/zclaw-runtime/src/driver/openai.rs` — `flush_pending` 中 assistant 消息的 `content` 字段:空字符串 → 有意义的占位符(`"正在思考..."` / `"正在调用工具..."`)。
**涉及文件**:
- `desktop/src/App.tsx`
- `desktop/src/components/CloneManager.tsx`
- `crates/zclaw-memory/src/store.rs`
- `crates/zclaw-kernel/src/kernel.rs`
- `crates/zclaw-runtime/src/driver/openai.rs`
---
## 17. 相关文档
- [ZCLAW 配置指南](./zclaw-configuration.md) - 配置文件位置、格式和最佳实践
- [Agent 和 LLM 提供商配置](./agent-provider-config.md) - Agent 管理和 Provider 配置
@@ -2096,6 +2147,7 @@ const adminRouting = storedAccount?.llm_routing;
| 日期 | 变更 |
|------|------|
| 2026-04-01 | 添加第 16 节:桌面端会话持久化四连 Bug — 页面刷新跳登录 + 空白对话页 + Agent 无记忆(session ID 映射断裂) + assistant 空 content 400 |
| 2026-03-31 | 添加第 14/15 节llm_routing 读取路径 Bug + SaaS Relay 403 User-Agent 缺失 — relay 模式从未生效的根因分析 |
| 2026-03-30 | 添加第 13 节Admin 前端 ERR_ABORTED / 后端卡死 — Next.js SSR/hydration + SWR 根本冲突导致连接池耗尽admin-v2 (Ant Design Pro 纯 SPA) 替代方案 |
| 2026-03-28 | 添加 12.1-12.4 节SaaS 后端问题 — Admin 登录无请求、SQLite→PostgreSQL 遗留语法、角色权限不足、usage 路由不匹配 |