Commit Graph

3 Commits

Author SHA1 Message Date
e0f7f2477c 修复: 验证脚本在 dsh 0.2.0 下直接崩掉(ERR_MODULE_NOT_FOUND),且漏认 v4 会话文件
升级到 0.2.0-rc.2 后 `verify-mail-sessions-readable.mjs` **一条都验不了**:
  1) 依赖不再嵌在 `<dsh>/node_modules/@deepseek-ai/`,而是平铺到
     `/usr/lib/node_modules/@deepseek-ai/` ⇒ import 直接 ERR_MODULE_NOT_FOUND;
  2) 会话文件新增 **v4**(`session.v4.jsonl.zstd`),walk 只认 v0+v3
     ⇒ 本条线索的 `mail-f8f9a840`(v3+v4 共存、**无 v0**)会被**整目录漏掉**。

★ 关键点:这两种失败都长得像"会话不可读",但**都不是**。
  1 是脚本自己崩了(连候选数都出不来);2 是**漏扫**(少算而不是算错)。
  —— "工具报错"与"数据坏"必须分开,否则会把脚本的年龄当成磁盘的病情。

## 实测(0.2.0-rc.2 修好后)

  --prefix mail-          : 候选 55  可读 55  不可读 0     <- 邮件通道全绿
  mail-f8f9a840(.new 线索): 可读,1623 events
  mail-d042cc4c(老线索)  : 可读,56012 events
  全盘 /root/.dsh/sessions : 候选 148 可读 119 不可读 29

那 29 个不可读**全部**是既有的独立缺陷
(`subagent/descriptor ... unsupported descriptor version 2`,去重后仅此一种),
**没有一个是 mail-*** ⇒ 与邮件通道无关,仍不建议混进同一个 repair。

修法:先探两个候选根再 import(不靠报错"感觉"哪个对),
并把 v4 加进 SESSION_FILES。
2026-10-02 04:05:04 +08:00
fe0cfe626c 验收口径: --prefix 严格前缀 —— 修正 --only mail- 误匹配 agentmail- 造成的 3 个假失败
第一版验收用 --only mail- 得到「47 可读 / 3 不可读」,但那 3 个根本不是邮件会话:
目录名是普通 UUID,只是父目录 --home-program-agentmail-- 里含子串 mail-。
它们的错因是另一个独立缺陷(subagent/descriptor v2)。

换严格前缀后的真实数字:真 mail-* 会话 43/43 可读,0 不可读。
脚本现在同时提供 --only(子串)与 --prefix(目录名严格前缀)。
2026-09-19 12:04:43 +08:00
b4a8f74ae5 修复: dsh 邮件通道全断的**两侧**根因(桥侧不产 message id 是真正在写的那一处)
现象:dsh 的邮件通道全断。老会话读不出来 ⇒ 桥报 SessionQueryError ⇒ 按"不在磁盘"
处理 ⇒ 再 create 撞 `already exists`。修好读路径之后又立刻暴露下一层
`message "undefined" is already pending`。

根因一(历史数据,dsh 侧):v0 会话的 `agent/inbox/spliced.inserted[]` 缺 `id`/`role`,
v0→v1 迁移第一步就拒绝。40 个真 mail-* 会话全部命中。

根因二(**仍在写**,本仓侧):`plugins/dsh-mail-bridge/lib/message.js` 的
`userMessage()` 只产出 `{content, source}`。DSH 0.1.5 的 inbox 按 `message.id` 去重
(`dsh-agent-loop` 的投影 apply() 与 mutate() 各维护一个 Set),id 全是 undefined
⇒ **第二条消息必挂**。日志里最早的同类记录在 2026-09-07,累计 50+ 次。
官方形状在 `@deepseek-ai/dsh-llm` 的 `createMessage()`({id, role, content, source}),
同一份 dsh 里其它插件都用官方的 createUserMessage(),只有这个桥手搓。
以前没炸是因为读路径先坏,根本走不到 followup。

本次改动
- message.js/.d.ts: userMessage() 补 id: randomUUID() 与 role:'user'
- test/message.test.mjs: 钉住「id 非空」「两条消息 id 必须不同」,用官方 inbox
  去重逻辑逐字复刻验证(修复前 message "undefined" is already pending,修复后 20 封全唯一)
- scripts/: repair-legacy-spliced-ids.mjs(v0,默认 dry-run)、
  repair-v3-usermessage-ids.mjs(v3)、verify-mail-sessions-readable.mjs
  (走生产真读路径 JsonlSessionPersistence.open,而非解码器口径)、两个 apply driver
- docs/DSH-0.1.5-MAIL-CHANNEL-ROOTCAUSE.md: 补执行结果与两处新事实

执行与验收(详见文档 §9-§15)
- v0 修 40 个、v3 修 2 个;逐文件解压后与备份 `cmp` **逐字节相等**,事件数 40/40 一致,
  零丢失(25.2MB→12.5MB 是单帧改 500 行/帧的重压缩,不是丢数据)
- 真 mail-* 会话最终 **41/41 可读**
- journal 里同一会话从 `already exists` 变为 `resume 续谈`,且持续增长
  (22647→22685 事件),最新 user/message 带真实 UUID;修复上线后 already pending 计数为 0
- 已在生产部署(deploy/redeploy-plugin.sh dsh,快照+原子软链+重启+后置验证全绿)

两个必须记住的坑
1. **校验与落盘不能共用同一批对象**:createRestore().decodeRow() 会原地改写入参
   (补全 dt 数组),污染后写出去会报 `released Session row N has seq gap`。
   这曾让 dry-run 说"40 个可修"、apply 只说"3 个"。
2. **判定磁盘健康只认 open()**:readSession() 走 SessionCorpus.load,命中有 live 会话时
   直接返回内存快照、不校验磁盘;open() 才走 validateStoredEvents。两条路径结论相反
   是设计使然,不是矛盾。
2026-09-19 12:03:34 +08:00