
Claude-to-IM-skill 会话持久化完整揭秘JsonFileStore 的 write-through 缓存与原子写入如何实现【免费下载链接】Claude-to-IM-skillBridge Claude Code / Codex to IM platforms — chat with AI coding agents from Telegram, Discord, or Feishu/Lark.项目地址: https://gitcode.com/gh_mirrors/cl/Claude-to-IM-skillClaude-to-IM-skill 是一款把 Claude Code / Codex 桥接到 Telegram、Discord、飞书、QQ、微信的开源技能它的会话持久化能力让对话记录在守护进程重启后依然保留。本文用通俗的方式带你拆解它背后JsonFileStore的 write-through 缓存与原子写入机制看懂「为什么关掉再开、消息还在」。 一句话总结内存里用 Map 缓存读写每次改动立刻同步落盘成 JSON 文件写入时走「临时文件 重命名」保证不写坏。IM 桥接为什么会话持久化重启也不丢消息Claude-to-IM 在后台跑一个 Node.js 守护进程负责把你的 IM 消息转给 AI 编码助手再把回复、工具调用、权限请求送回聊天窗口。这个进程会因升级、重启、崩溃等原因反复启停。如果对话历史只存在内存里一重启就「失忆」体验会很差。因此项目把会话、绑定关系、消息历史、权限链接、去重键、审计日志全部持久化到磁盘实现「重启不丢数据」。这套逻辑集中在 src/store.ts 的JsonFileStore类里它是整个持久化层的核心。数据目录布局~/.claude-to-im 里存了什么所有数据都放在~/.claude-to-im/data/下由 src/config.ts 中的CTI_HOME决定根目录可用环境变量CTI_HOME覆盖。~/.claude-to-im/ ├── config.env ← 凭据与设置chmod 600 ├── data/ ← 持久化 JSON 存储 │ ├── sessions.json ← 会话 │ ├── bindings.json ← 渠道绑定 │ ├── permissions.json ← 权限链接 │ ├── offsets.json ← 渠道偏移 │ ├── dedup.json ← 去重键 │ ├── audit.json ← 审计日志 │ └── messages/ ← 每会话一个消息文件 ├── logs/ └── runtime/目录常量定义在 src/store.tsDATA_DIR指向data/MESSAGES_DIR指向data/messages/。write-through 写穿缓存读写路径如何协同所谓write-through写穿就是「写内存的同时立刻写磁盘」而不是攒一批再统一刷盘。好处是任何时刻磁盘上的文件都基本是最新的即使进程突然挂掉最多丢当前这一条正在写的记录。JsonFileStore内部用一组Map做内存缓存例如会话、绑定、消息、权限链接、去重键见 src/store.ts。启动阶段loadAll 一次性装载构造器里先ensureDir建好目录再调用loadAll()src/store.ts把六个 JSON 文件一次性读进内存 Map文件装入的 Mapsessions.jsonthis.sessionsbindings.jsonthis.bindingspermissions.jsonthis.permissionLinksoffsets.jsonthis.offsetsdedup.jsonthis.dedupKeysaudit.jsonthis.auditLog读取用readJsonsrc/store.ts文件不存在或解析失败时返回空对象兜底保证首次运行不会报错。运行阶段改内存即落盘每次改动内存 Map 后都会紧跟一个persist*方法把数据写回文件。以创建会话为例src/store.tscreateSession把新会话set进 Map立刻调用persistSessions()落盘。渠道绑定、权限链接、偏移、去重键都遵循同样的「改一写一」模式。这就是 write-through读走内存缓存写同时进内存和磁盘。原子写入原理临时文件 rename 两步法真正决定「数据会不会写坏」的是atomicWritesrc/store.ts只有两行核心逻辑function atomicWrite(filePath: string, data: string): void { const tmp filePath .tmp; fs.writeFileSync(tmp, data, utf-8); fs.renameSync(tmp, filePath); }writeJsonsrc/store.ts会把对象序列化成 JSON 后交给atomicWrite。为什么 rename 能保证不写坏文件先写临时文件所有内容先落到xxx.json.tmp目标文件此刻完全没被触碰。再原子重命名rename在同一文件系统上是原子操作要么整个替换成功要么不生效绝不会留下「写了一半」的半截文件。崩溃也安全即使进程在写临时文件时崩溃最多丢一个.tmp残留正式文件仍是上一版完整数据。同样的套路也用在别处——配置保存src/config.ts和状态文件写入src/main.ts都采用「写 tmp 再 rename」形成全项目一致的可靠性风格。会话与消息的两类持久化sessions.json 与 messages/会话元数据和消息历史采用了不同的存储策略这是理解该模块的关键维度会话sessions消息messages存储位置单文件data/sessions.json每会话一个data/messages/id.json加载时机启动时loadAll全量装载首次访问时才懒加载落盘方法persistSessionspersistMessages消息采用懒加载loadMessagessrc/store.ts先查内存缓存没有才从磁盘读入并缓存避免启动时把几百个会话的历史一次性全部载入。追加即落盘addMessagesrc/store.ts把消息 push 进数组后立即persistMessages写回该会话的独立文件。这种「按会话分文件」的设计让单个大对话不会拖累其它会话也便于单独备份或清理。这套机制带来的可靠性收益重启不丢数据守护进程重启后loadAll恢复全部状态消息历史完整延续。崩溃不损坏文件原子写入确保磁盘上永远是完整 JSON不会出现半截文件。读写快热数据都在内存 Map磁盘只做「每次写一份快照」的兜底。可观测audit.json以环形缓冲保留最近 1000 条审计记录src/store.ts方便排查消息流向。这套实现配合 src/main.ts 里的依赖装配——先loadConfig、再new JsonFileStore(settings)、最后initBridgeContext注入 store——就完成了整个持久化链路的接线。相关源码与文档导航内容位置存储核心类JsonFileStoresrc/store.ts原子写入atomicWritesrc/store.ts启动装载loadAllsrc/store.ts消息懒加载loadMessagessrc/store.ts数据目录根CTI_HOMEsrc/config.ts配置保存的原子写入src/config.ts守护进程装配src/main.ts持久化单元测试src/tests/store.test.ts数据目录架构说明README.md使用与数据位置references/usage.md常见故障排查references/troubleshooting.md一句话带走Claude-to-IM 的会话持久化靠的是一组内存 Map 做 write-through 缓存 临时文件 rename 的原子落盘再加上「会话全量、消息懒加载」的分层存储策略——简单却足够可靠。【免费下载链接】Claude-to-IM-skillBridge Claude Code / Codex to IM platforms — chat with AI coding agents from Telegram, Discord, or Feishu/Lark.项目地址: https://gitcode.com/gh_mirrors/cl/Claude-to-IM-skill创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考