open-codesign 会话历史持久化恢复:从 v0.2 TODO stub 到 JSONL 聊天存储的 IPC 链路重建

发布时间:2026/9/28 2:23:58
open-codesign 会话历史持久化恢复:从 v0.2 TODO stub 到 JSONL 聊天存储的 IPC 链路重建
人工智能AI 应用桌面应用【免费下载链接】open-codesignOpen-source Claude Design alternative. One-click import your Claude Code / Codex API key. Prompt → prototype / slides / PDF. Multi-model (Claude, GPT, Gemini, Kimi, GLM, Ollama). BYOK, local-first, MIT.项目地址https://gitcode.com/gh_mirrors/op/open-codesign点击查看免费下载导读本文基于仓库内的 session_history_restore_plan.md 计划文档完整还原 open-codesign 桌面端Electron一次会话历史持久化链路修复的根因分析与四步实施方案。该计划针对window.codesign.chat.*在 v0.2 阶段遗留的 TODO stub——聊天记录既不落盘也无法重载的问题通过主进程 IPC 通道恢复、preload 桥接、快照种子与工具状态更新回归覆盖四个步骤完成重建。读完本文你将掌握 open-codesign 主进程 / preload / 渲染进程三层之间聊天数据的完整调用链、JSONL 会话文件的存储与回放机制以及如何验证这套恢复方案的正确性。一、问题根因v0.2 遗留的 TODO stub计划文档开门见山地给出了问题的根因window.codesign.chat.*在apps/desktop/src/preload/index.ts中是 v0.2 的 TODO stub。它返回空列表、在内存中完成 append因此渲染器renderer的聊天记录从未被持久化也无法在重启后重新加载。这段话描述了三个具体症状可以对照源码逐一理解chat.list返回空列表界面每次启动读取聊天历史时得到的都是[]历史对话凭空消失chat.append只在内存中追加消息仅在当前进程存活期间可见进程退出即丢失聊天记录既不持久化也不重载没有落盘通道自然也没有恢复路径用户无法接续上一次会话。修复前preload 层chat对象是一段占位实现修复后它变成了一组真正指向主进程 IPC 通道的桥接方法详见下文第四节。二、恢复方案总览四步重建链路计划文档给出了完整的实施清单当前状态均为已完成步骤内容状态1在主进程中使用现有的聊天消息辅助函数chat message helpers恢复持久化的聊天 IPC 通道✅ 已完成2将 preload 中的聊天方法指向这些 IPC 通道✅ 已完成3为 append/list、快照种子snapshot seeding与工具状态更新tool status updates补充 IPC 回归测试覆盖✅ 已完成4运行聚焦的桌面端主进程测试✅ 已完成这套方案的核心思想是不新造轮子主进程侧本就存在一套围绕SessionManager的会话聊天辅助函数位于 apps/desktop/src/main/session-chat.ts只是缺少暴露给渲染层的 IPC 通道。恢复工作因此变成接线而非重写——把已有能力通过 IPC 暴露出去再让 preload 指向它。三、第一步主进程恢复持久化聊天 IPC 通道恢复后的 IPC 通道注册在 apps/desktop/src/main/snapshots-ipc.ts第 1601–1625 行共四条ipcMain.handle(chat:v1:list, (_e, raw): ChatMessageRow[] { const designId parseDesignIdPayload(raw, chat:v1:list); return runDb(chat:list, () listSessionChatMessages(chatStoreOptions(db), designId)); }); ipcMain.handle(chat:v1:append, (_e, raw): ChatMessageRow { const input parseChatAppendInput(raw); return runDb(chat:append, () appendSessionChatMessage(chatStoreOptions(db), input)); }); ipcMain.handle(chat:v1:seed-from-snapshots, (_e, raw): { inserted: number } { const designId parseDesignIdPayload(raw, chat:v1:seed-from-snapshots); return runDb(chat:seed-from-snapshots, () ({ inserted: seedSessionChatFromSnapshots(chatStoreOptions(db), designId), })); }); ipcMain.handle(chat:v1:update-tool-status, (_e, raw): { ok: true } { const input parseToolStatusInput(raw); runDb(chat:update-tool-status, () appendSessionToolStatus(chatStoreOptions(db), input)); return { ok: true }; });四条通道的职责划分非常清晰chat:v1:list读取某个 design 的全部聊天记录返回ChatMessageRow[]chat:v1:append追加一条聊天消息返回持久化后的完整行含id、seq、createdAtchat:v1:seed-from-snapshots首次打开既有 design 时用快照中的 prompt 反向填充聊天历史返回插入条数chat:v1:update-tool-status更新某条工具调用tool_call消息的执行状态返回{ ok: true }。底层真正干活的是 session-chat.ts 中导出的辅助函数——listSessionChatMessages、appendSessionChatMessage、seedSessionChatFromSnapshots、appendSessionToolStatus。每个 handler 都经过runDb包裹配合CodesignError与IPC_BAD_INPUT/IPC_NOT_FOUND等错误码见 packages/shared/src/error-codes.ts保证异常路径可控。3.1 存储层JSONL 会话文件session-chat.ts的存储模型是每个 design 一个 JSONL 文件function sessionFileForDesign(sessionDir: string, designId: string): string { const safeId designId.replace(/[^A-Za-z0-9_-]/g, _); return path.join(sessionDir, ${safeId}.jsonl); }designId 会被清洗为只含A-Za-z0-9_-的安全文件名避免路径注入会话文件放在db.sessionDir下主进程通过SessionChatStoreOptions{ db, sessionDir }统一传入写入前mkdirSync(..., { recursive: true })确保目录存在文件为JSON.stringify的逐行追加writeFileSync全量重写。3.2 消息条目类型与 schema 版本每条写入会话文件的自定义条目custom entry都带type: custom与customType由常量标识常量customType 值用途CHAT_MESSAGE_CUSTOM_TYPEopen-codesign.chat.message聊天消息本体CHAT_TOOL_STATUS_CUSTOM_TYPEopen-codesign.chat.tool_status工具调用状态更新COMMENT_CUSTOM_TYPEopen-codesign.comment.v1评论事件add/update/remove/mark-appliedCONTEXT_BRIEF_CUSTOM_TYPEopen-codesign.context.brief.v1设计简报RUN_PREFERENCES_CUSTOM_TYPEopen-codesign.context.run_preferences.v1运行偏好ACTIVE_MESSAGE_CUSTOM_TYPEopen-codesign.active-message.v1运行中的活跃消息所有持久化结构均携带schemaVersion: 1例如存储的聊天消息interface StoredChatMessage { schemaVersion: 1; id: number; seq: number; kind: ChatMessageKind; payload: unknown; snapshotId: string | null; }回放时通过parseStoredMessage、parseStatusUpdate、parseCommentEvent等解析函数做严格校验schemaVersion必须为 1、字段类型逐一检查遇到畸形条目会抛出IPC_DB_ERROR而不是静默吞掉数据。3.3 追加消息seq 与活动时间戳appendSessionChatMessage的实现要点const seq listSessionChatMessages(opts, input.designId).length; const stored: StoredChatMessage { schemaVersion: 1, id: seq, seq, kind: input.kind, payload: input.payload ?? {}, snapshotId: input.snapshotId ?? null, };seq取自当前消息总数保证同一会话内消息序号单调递增且稳定这是后面工具状态按seq回写的前提默认会调用touchDesignActivity(db, designId, createdAt)更新设计活跃时间快照种子等批量回填场景通过{ touchActivity: false }关闭避免污染排序。四、第二步preload 聊天方法指向 IPC 通道恢复后的 preload 桥接位于 apps/desktop/src/preload/index.ts第 865–898 行window.codesign.chat从 TODO stub 变成了四个真实调用ipcRenderer.invoke的方法chat: { list: (designId: string) ipcRenderer.invoke(chat:v1:list, { schemaVersion: 1, designId }) as PromiseChatMessageRow[], append: (input: ChatAppendInput) ipcRenderer.invoke(chat:v1:append, { schemaVersion: 1, ...input }) as PromiseChatMessageRow, seedFromSnapshots: (designId: string) ipcRenderer.invoke(chat:v1:seed-from-snapshots, { schemaVersion: 1, designId }) as Promise{ inserted: number }, updateToolStatus: (input: { designId; seq; status: done | error; result?; durationMs?; errorMessage? }) ipcRenderer.invoke(chat:v1:update-tool-status, { schemaVersion: 1, ...input }) as Promise{ ok: true }, onAgentEvent: (cb: (event: AgentStreamEvent) void) { ... }, }注意所有 IPC 载荷都携带schemaVersion: 1与主进程 handler 中的parseDesignIdPayload/requireSchemaV1校验对应同时onAgentEvent订阅了agent:event:v1通道用于接收主进程推送的实时 Agent 事件见第五节。渲染进程侧的消费端同样印证了这条链路在 apps/desktop/src/renderer/src/store/slices/chat.ts 中打开设计时会先window.codesign.chat.seedFromSnapshots(designId)再window.codesign.chat.list(designId)加载历史追加消息则走window.codesign.chat.append(input)——这正是计划中渲染器聊天行从未被持久化或重载问题被修复后的实际调用形态。五、快照种子让既有设计的历史复活对于 v0.2 之前创建、从未写入过会话文件的既有设计直接chat:list只能得到空数组。seedSessionChatFromSnapshotssession-chat.ts 第 690–724 行解决这个问题export function seedSessionChatFromSnapshots(opts, designId): number { if (listSessionChatMessages(opts, designId).length 0) return 0; // 已有历史则跳过 const snapshots listSnapshots(opts.db, designId).slice().reverse(); for (const snapshot of snapshots) { if (snapshot.prompt 非空) { appendSessionChatMessage(opts, { designId, kind: user, payload: { text: snapshot.prompt } }, { touchActivity: false }); } appendSessionChatMessage(opts, { designId, kind: artifact_delivered, payload: { createdAt: snapshot.createdAt }, snapshotId: snapshot.id, }, { touchActivity: false }); } return inserted; }关键行为幂等会话文件已存在聊天记录时直接返回 0绝不重复播种从快照反推历史每个快照的prompt回填为一条user消息并追加一条带snapshotId的artifact_delivered消息标记交付了哪个快照产物时间顺序快照按时间倒序取、再reverse()成正序写入保证聊天记录的自然顺序不触碰活动时间批量回填使用touchActivity: false避免把所有设计顶到活跃列表最前。六、实时事件投影run 事件到聊天行的持续写入持久化不只发生在用户手动 append 时。Agent 运行过程中的流式事件同样需要落到会话文件中这一职责由 apps/desktop/src/main/run-event-chat.ts 的projectRunEventToChat承担其注释点明设计原则日志journal是权威来源回放用于修复日志与聊天写入之间的崩溃间隙。事件 → 聊天行的映射规则Agent 事件生成的聊天行说明turn_endassistant_text写入finalText携带runId与runEventKeygenerationId:seqtool_call_starttool_call记录toolName、args、toolCallId、command、statustool_call_resulttool_call状态更新按runId toolCallId定位原行appendSessionToolStatus回写done/error、result、durationMserrorerror消息记录message与coderun_settled结算所有running工具 补写assistant_text/artifact_deliveredcompleted→done否则 →error去重与幂等是这套投影机制的关键每条追加都带唯一runEventKeyappend前先检查rows.some((row) payload(row)[runEventKey] eventKey)重复事件不会产生重复行工具结果按seq应用且通过runResultEventKey保证已提交的结果不会被运行结算推断的错误覆盖见applyStatusUpdate中删除error/errorMessage的逻辑。主进程在 apps/desktop/src/main/ipc/generate.ts第 333–381 行的publishEvent中完成日志先写、投影随后、事件再推送给渲染器的编排事件先journal.append持久化再projectRunEventToChat投影到聊天存储最后target.send(agent:event:v1, durable)推送给窗口。此外codesign:v1:recover-runshandler同文件第 463–511 行会读取 journal 中全部历史事件并重放投影实现启动时的崩溃恢复。七、工具状态更新的落地细节chat:v1:update-tool-status对应的appendSessionToolStatussession-chat.ts 第 599–629 行有几个值得注意的实现细节先定位再更新按seq找到原始消息若其kind ! tool_call直接返回绝不写入孤儿状态结果压缩工具结果先经compactToolResultForHistory(toolName, result)压缩见 apps/desktop/src/main/ipc/tool-log.ts避免把超大结果原样塞进 JSONL回放时合并replayEntries在重建聊天行时按seq找到对应tool_call行并applyStatusUpdate合并状态字段因此读取出来的行始终是已应用最新状态的最终形态错误字段规范化errorMessage会被展开为{ error: { message } }与平铺errorMessage两种形态供渲染层不同组件消费。八、回归测试覆盖与验证计划第三步要求为 append/list、快照种子、工具状态更新补充 IPC 回归测试仓库中的测试文件可以逐一对应apps/desktop/src/main/session-chat.test.ts直接覆盖appendSessionChatMessage、listSessionChatMessages、seedSessionChatFromSnapshots等存储函数——例如种子测试断言插入数为 21 条 user prompt 1 条 artifact_delivered且getDesign(...).updatedAt在touchActivity: false下保持不变apps/desktop/src/main/run-event-chat.test.ts覆盖projectRunEventToChat的事件投影规则包括忽略瞬态与未入日志的事件等边界apps/desktop/src/renderer/src/store.chat-continuity.test.ts以api.chat.seedFromSnapshots/api.chat.list的 mock 验证渲染器在重载/聚焦时的续聊行为apps/desktop/src/renderer/src/hooks/useAgentStream.completion.test.ts验证chat.append/chat.list在完成事件下的调用次数与窗口聚焦后的重新拉取。验证方式第四步即运行桌面端主进程的聚焦测试。仓库使用 pnpm vitest配置见 apps/desktop/vitest.config.ts典型命令为# 仓库根目录pnpm workspace pnpm --filter open-codesign/desktop test -- session-chat run-event-chat或单独跑某个文件pnpm --filter open-codesign/desktop vitest run apps/desktop/src/main/session-chat.test.ts若要在本地复现整个链路可先pnpm install再运行pnpm --filter open-codesign/desktop devdev 入口见 apps/desktop/scripts/dev.cjs启动 Electron 应用创建/打开一个 design 后重启应用即可验证聊天历史被从sessionDir/safeId.jsonl中恢复。九、小结从这份计划文档可以提炼出 open-codesign 桌面端会话历史持久化的完整链路渲染进程 store/slices/chat.ts │ window.codesign.chat.{list,append,seedFromSnapshots,updateToolStatus} ▼ preload 层 index.tsIPC 桥接携带 schemaVersion: 1 │ ipcRenderer.invoke(chat:v1:*) ▼ 主进程 snapshots-ipc.tsIPC handler 输入校验 runDb │ ▼ session-chat.tsSessionManager JSONL 文件 回放/校验 ▲ │ projectRunEventToChatrun-event-chat.ts │ RunJournal 事件流ipc/generate.ts publishEvent / recover-runs这条链路的修复价值在于聊天记录从进程内存中的易失数据升级为以 JSONL 落盘、可由快照种子恢复、可被运行日志回放修复的持久数据同时通过schemaVersion版本化、seq序号、runEventKey去重等手段保证读取结果的确定性与幂等性。对于需要在 Electron 桌面应用中实现会话级聊天持久化 崩溃恢复 历史回填的开发者这份计划与实现提供了一个结构清晰、可逐层验证的参考范式。赞分享人工智能AI 应用桌面应用【免费下载链接】open-codesignOpen-source Claude Design alternative. One-click import your Claude Code / Codex API key. Prompt → prototype / slides / PDF. Multi-model (Claude, GPT, Gemini, Kimi, GLM, Ollama). BYOK, local-first, MIT.项目地址https://gitcode.com/gh_mirrors/op/open-codesign点击查看免费下载相关推荐open-codesign v0.2 Agentic Design Loop 架构解析从 JSONL 会话存储到 15 工具 Harness 的完整实施指南open codesign v0.2 Agentic Design Loop 架构解析从 JSONL 会话存储到 15 工具 Harness 的完整实施指南人工智能AI 应用桌面应用Open-LLM-VTuber聊天记录管理完全指南持久化存储与历史对话切换Open LLM VTuber聊天记录管理完全指南持久化存储与历史对话切换 Open LLM VTuber是一个开源的AI虚拟主播项目支持通过语音与大型语言AI 应用大模型语音数字人交互助手本地部署kotaemon会话存储历史记录持久化方案kotaemon会话存储历史记录持久化方案 概述 在RAGRetrieval Augmented Generation应用中会话历史记录的持久化存储是确人工智能大模型RAG向量数据库后端上一篇如何快速掌握REFramework游戏模组开发的终极指南下一篇Proxyee 项目常见问题解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考