- Split server/chat.py, server/status.py, server/tasks.py into sub-packages
- Split utils/context_manager.py, utils/conversation_manager.py into mixin packages
- Split modules/file_manager.py, persistent_terminal.py, terminal_ops.py, mcp_client_manager.py into packages
- Split core/main_terminal_parts/context.py into base + mixins
- Split static/src/app/methods/{ui,message,upload,taskPolling,conversation} into sub-modules
- Fix flattened method exports, dynamic import paths, missing cross-module imports
- Add missing permission/network/execution mode constants and _PERMISSION_MODE_LABEL
- Update AGENTS.md, CLAUDE.md and project memories to reflect new structure
67 lines
3.2 KiB
Markdown
67 lines
3.2 KiB
Markdown
---
|
||
name: conversation_model_persistence
|
||
description: 当加载/恢复对话时发现模型未恢复,或新建对话后旧对话模型被覆盖时,应该索引本记忆
|
||
---
|
||
|
||
# 对话模型持久化约定
|
||
|
||
## 核心规则
|
||
|
||
- 每个对话的 `model_key` 保存在对话文件的 `metadata.model_key` 中。
|
||
- 切换模型时(`/api/model`)会立即调用 `utils/conversation_manager/ConversationManager.save_conversation(..., model_key=terminal.model_key)` 写入对话文件。
|
||
- **加载对话时必须恢复该对话保存的模型**,不能沿用终端当前的默认模型或用户个性化默认模型。
|
||
- **创建新对话时,必须先保存旧对话、再应用默认模型到新对话**,否则旧对话的 `model_key` 会被默认模型覆盖。
|
||
|
||
## 关键实现点
|
||
|
||
### 1. 手动加载对话
|
||
|
||
路径:`/api/conversations/<conversation_id>/load` → `core/web_terminal.py::WebTerminal.load_conversation()`
|
||
|
||
- 该方法会从对话元数据中恢复 `thinking_mode`、`permission_mode`、`execution_mode`、`network_permission` 等状态。
|
||
- **必须同时恢复 `model_key`**:
|
||
```python
|
||
saved_model_key = meta.get("model_key")
|
||
if saved_model_key:
|
||
try:
|
||
self.set_model(saved_model_key)
|
||
except Exception as exc:
|
||
logger.warning("加载对话模型 %s 失败: %s", saved_model_key, exc)
|
||
```
|
||
- 恢复后,`terminal.model_key` 即对话保存的模型,`get_status()` 和 API 返回的 `model_key` 都会正确。
|
||
|
||
### 2. 程序重启后自动恢复最近对话
|
||
|
||
路径:`core/web_terminal.py::WebTerminal._ensure_conversation()`
|
||
|
||
- Web 终端初始化时会自动加载最近的对话。
|
||
- 原来的实现只调用 `utils/context_manager/ContextManager.load_conversation_by_id()`,不会恢复 `model_key` 等运行时状态。
|
||
- 应改为调用 `self.load_conversation(conv_id)`,以复用完整的状态恢复逻辑(含模型恢复)。
|
||
|
||
### 3. 创建新对话时保护旧对话的模型
|
||
|
||
路径:`core/web_terminal.py::WebTerminal.create_new_conversation()`
|
||
|
||
- **错误顺序**:先 `set_model(默认模型)` → 再 `start_new_conversation()` → `start_new_conversation()` 内部先 `save_current_conversation()` → 旧对话被按默认模型保存,覆盖了之前的模型。
|
||
- **正确顺序**:
|
||
1. 计算默认 `run_mode`、`permission_mode` 并应用(不改 `model_key`)。
|
||
2. 调用 `start_new_conversation()`,此时 `terminal.model_key` 仍是旧对话的模型,旧对话被正确保存。
|
||
3. 新对话创建完成后,再 `set_model(默认模型)`,并 `save_conversation` 更新新对话的 `model_key`。
|
||
|
||
### 4. 前端
|
||
|
||
路径:`static/src/app/methods/conversation/load.ts::loadConversation()`
|
||
|
||
- 前端在调用 `/api/conversations/<id>/load` 后,会根据返回的 `result.model_key` 更新当前模型:
|
||
```typescript
|
||
if (typeof result.model_key === 'string' && result.model_key) {
|
||
this.modelSet(result.model_key);
|
||
}
|
||
```
|
||
- 后端修复后,返回的 `model_key` 即为对话保存的模型,前端会自动应用。
|
||
|
||
## 注意事项
|
||
|
||
- `set_model()` 可能因图片/视频不兼容或模型被禁用而抛异常,加载场景下应捕获并记录警告,不要让加载失败。
|
||
- 如果对话元数据中没有 `model_key`(旧数据或新建对话),保持当前模型即可。
|