dsh-session-fix · 会话修复
修 DSH 里坏掉的会话记录。装好之后在 设置 → 会话修复 里一键扫描、选中、修复,不用退 DSH、不用手工搬文件。
作者:汐(记忆修复师)· 许可:MIT
一、什么时候用它
会话报这种错、从此每轮都失败的时候:
messages.3.1: `tool_use` ids were found without `tool_result` blocks
immediately after: call_xxxxxxxx (INVALID_REQUEST)
或者会话打不开、历史加载失败,但文件还在。
如果你的报告是「凭据失效 / 网络超时 / 版本不兼容 / 配置读不进去」,那不是它,别在这上面花时间。
二、真因
模型的工具调用有一条死规矩:
一条助手消息里声明了几个工具调用,紧随其后就必须有同样数量的工具结果,一一对应,中间不许夹别的东西。
而 DSH 会把助手消息切成若干段。如果一条助手消息里的块顺序是:
tool-call + text + tool-call ← 坏
切开之后,文本块夹在了两个调用之间,那条调用的结果就不再是「紧接的下一条」。上游直接拒收。
正确顺序是把非调用块放前面:
text + reasoning + tool-call + tool-call ← 好
这个坏结构一旦进了历史,每一轮都会重放它,所以这条会话此后每轮都失败。
三、它到底改什么
只做一件事:
把
assistant/message里的text/reasoning块,挪到所有tool-call块前面。
三条不碰:
| 不碰 | 为什么 |
|---|---|
| 不删任何一行 | DSH 校验 seq 连续,删一条整份判损坏 |
| 不改记录类型、不从旧备份「恢复」记录 | 记录类型是契约的一部分,乱改会把结构弄成另一种坏 |
| 不动聊天正文 | user / assistant 的文字一个字节都不改 |
技术细节:
- 会话日志是一串 zstd 帧,DSH 逐帧校验。只重压改过的帧(带 checksum,与 DSH 自身写法一致),没改的帧原样复制 —— 所以帧数和边界不变,这是它还能被读回来的前提。
- 改完立刻从磁盘重读一遍自查:记录数是否没变、
seq是否连续、残留病灶是否 0、调用与结果是否配对完整。任何一项不过就当场回滚。
四、回退
修之前,原文件被改名保留在同目录:
session.v4.jsonl.zstd.before-fix-2026-10-31_20-15-33
想退回去,把它的 .before-fix-<时间戳> 去掉、覆盖回 session.v4.jsonl.zstd 就行。
五、能修什么 / 修不了什么
这一节是故意写清楚的 —— 免得你装了之后发现「我那个报错它居然不管」。
✅ 能修
报 tool_use ids found without tool_result blocks immediately after 的会话,也就是上面第二节说的那一种坏法。
❌ 修不了(别指望它救命)
| 情况 | 为什么 | 怎么办 |
|---|---|---|
| 会话文件被整个删掉(目录/文件没了) | 没有数据可修 | 先用文件恢复工具(Recuva / Disk Drill / testdisk 之类)把文件捞回来,再交给本插件 |
seq 已经断档 |
结构性损坏,修了也是坏的 | 插件只报不修,把文件发给作者 |
| 调用与结果配对不上(悬空调用 / 孤立结果) | 同上,缺的那条补不回来 | 同上 |
| 帧解不开(文件被截断 / 还在被写) | 读都读不动 | 确认 DSH 已完全退出后重试;不行就是文件本身坏了 |
| 报错根本不是这一类 | 病因不在会话结构 | 看具体报错排查,别归因到这里 |
⏸ 唯一不能碰的会话
正被打开着的那条。
本插件跑在 DSH 里面,所以不用退出 DSH —— 退出了它自己也死了,那就什么都修不了。
但「正在说话的那条会话」文件还在被宿主追加写入,动它就是拿历史换事故。所以列表里会把它标成 「被占用」,并且不给修复按钮。
要修它,先切到别的会话,再回来。
六、适用版本
- 会话格式 v4(
session.v4.jsonl.zstd) - 需要 Node 内置 zstd 支持:Node ≥ 22.15(桌面版自带的运行时满足)
- 在 DSH 0.2.1-rc 系列上实测
七、安装
本插件是标准的 DSH 插件包(dsh.bundle.patch + dsh.client)。
A. 从 GitHub 装(推荐)
# 在 profile 目录里(默认 %USERPROFILE%\.dsh\profiles\<profile>)
pnpm add github:CalamitySpiritKite/dsh-session-fix
B. 本地链接(改代码 / 自己用)
git clone https://github.com/CalamitySpiritKite/dsh-session-fix.git
cd <profile 目录>
pnpm add link:<刚 clone 下来的目录绝对路径>
两种方式装完,确认 cordis.patch.yml 里有这一行(多数情况 dsh.bundle.patch 会自动带上;没有就手加):
- id: dsh-session-fix
name: dsh-session-fix
然后重启 DSH,设置页左侧会出现「会话修复」。
卸载:
pnpm remove dsh-session-fix,并把cordis.patch.yml里那两行删掉,重启。
八、自己验一遍(可选)
仓库里带了两个自测脚本,用的是你自己的真实会话文件,全程只读,所有写入都在系统临时目录:
# 内存里造坏 → 修 → 逐项对照(不写盘)
node tools/selftest.mjs "%USERPROFILE%\.dsh\sessions\<某个>\session.v4.jsonl.zstd"
# 完整落盘流程:临时目录里复制一份,造坏 → 原子替换 → 验备份 → 验回退
node tools/e2e.mjs "%USERPROFILE%\.dsh\sessions\<某个>\session.v4.jsonl.zstd"
两个都跑出「全部通过」再装,心里就有底了。
九、它会动你的哪些文件
| 文件 | 动作 |
|---|---|
session.v4.jsonl.zstd |
修复时原子替换(先改名原件,再放新文件) |
session.v4.jsonl.zstd.before-fix-<时间戳> |
新建,原件备份,不自动删 |
同目录 *.tmp-<时间戳> |
只在替换过程中短暂存在,失败会清掉 |
不碰:别的会话、记忆、附件、配置、当前打开的会话。
十、给踩到的人一句话
会话记录是唯一真源:没有索引、没有副本、没有回收站。它在,你的历史就在;它没了,那段历史与你再无关系。
这个插件能救的是「写坏了的结构」,救不了「已经不存在的文件」。
所以:别在运行中删会话目录,删之前先复制一份。
No comments yet. Be the first to write one.