ADR 0025:AI 助手会话历史与多会话

状态:已接受 日期:2026-08-30 关联:ADR 0022(功能定位);ADR 0023(决策 7 的会话持久化预留,本 ADR 兑现其多会话扩展);ADR 0026(思考块展示依赖本 ADR 的存储演进);ADR 0024(记录用户元数据)

背景

ADR 0023 决策 7 预留了多会话扩展:会话持久化为 %APPDATA%/transfer-orbit-design/sessions/<id>.jsonl,一行一条 OpenAI 消息,v1 固定单会话 id=default,注释明确"多会话扩展只换文件名"。现状事实:非密钥配置(base_url/model)存 assistant.json,API key 存 OS keychain;前端边栏无会话列表 UI;chatModel.restoreItems 已能从持久化历史重建气泡与工具卡片终态(含"会话被重启打断"的未完成标记)。

需求侧:用户需要回看历史会话、并行推进多段设计对话(如一条会话调 DRO、另一条调 Halo),并要求思考过程随会话保留供回看(存储需求由 ADR 0026 展示需求反推)。本 ADR 回答:历史会话重开的语义、多会话的管理形态、存储 schema 怎么从单会话演进。

决策

  1. 历史会话可续聊:打开旧会话把历史消息回放进模型上下文继续对话,而非只读存档。回放前做净化——剥除思考块(ADR 0026 决策 5);历史中指向已删除/变更记录的 record_id 降级为写时随消息保存的诊断摘要文本,不依赖 catalog 现状重建。

  2. 多会话 UI 形态:助手边栏头部加会话切换器——下拉按最近活动倒序列出会话 + 新建按钮;重命名/删除收在下拉项悬浮操作。不动主界面布局,不占聊天区高度。

  3. 存储 schema 演进:新增 sessions/index.json 存会话元数据(id、标题、创建时间、最近活动时间、消息数);每会话仍一个 <id>.jsonl;思考块以带 kind 标记的行混入同一会话文件,构造 API 请求时过滤掉。现有 default.jsonl 原地迁移为列表中的一条会话,不做数据转换。

  4. 标题与删除:标题自动取首条用户消息前 20 字,可经悬浮操作重命名;删除需二次确认,确认文案说明删会话不删轨道库记录(会话里只存 record_id 引用)。

  5. 切换门禁:有进行中的回复轮次或未决确认卡片时禁止切换会话(切换器禁用并提示等待完成)。不实现"切换即中止"——mcp-serve 不支持取消(ADR 0023 已记录的已知限制),界面装死只会留下假状态;也不实现"切走后台续跑"——agent loop 的 history/pending 全部按会话隔离的复杂度与 v1 收益不成比例。

理由

  • 本 ADR 是 ADR 0023 决策 7 预留的兑现,演进而非推翻;default.jsonl 原地变列表成员,老用户无感迁移。

  • index.json + 每会话单文件与"文本流式追加"写入模型天然相容;回放剥离只是一次行过滤。每会话目录双文件(回放/展示分离)要维护两份同步,SQLite 对该场景过重。

  • 写时快照诊断摘要:catalog 会漂移(记录可删可改),历史会话的解读必须忠实于"当时看到了什么"。

考虑过的选项

  • 纯回看、不可续聊(否决):省掉回放净化,但"接着上次继续调轨道"是最自然的用法,纯回看逼用户重建上下文。

  • 切换即中止当前轮(否决):mcp-serve 无取消通知,中止只是 UI 假象,后台计算照跑。

  • 允许切走、后台续跑(否决):loop 状态 per-session 化与跨会话事件路由的复杂度,超出多会话 v1 的收益。

后果

  • store.rs 增加 index.json 读写与会话 id 路由;AssistantState 的 history/pending 保持单份内存态——切换门禁使其合法。

  • 前端 chatModel 增加会话 id 概念;AssistantInfo 增加会话列表字段;i18n 新增会话切换器相关键(中英同步)。

  • 已知限制延续并新增一条:长计算(不可取消)期间不能切去别的会话查看。