Files
MailUI4Agents/scripts/apply-v3-usermessage-repair.sh
JianFeeeee 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

43 lines
1.9 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# 在 dsh.service 离线窗口内修 v3 会话里缺 id/role 的 user/message,并用生产读路径验收。
#
# 背景:v0 的 spliced 修复让老线索重新可读;dsh 一旦 resume,就会把 v0 迁移成 v3
# 并按**当时的**写入路径继续追加。当时桥的 userMessage() 不产出 id,于是新注入的
# 那条 user/message 落成 {content, source},使整个 v3 在
# JsonlSessionPersistence.open() 的严格校验下读不出来。
# 桥侧已修(本仓 2026-09-19),此处只做一次性数据修复。
set -uo pipefail
cd /home/program/agentmail
STAMP=$(date +%Y%m%d-%H%M%S)
LOG=".tmp/repair-v3-$STAMP.log"
exec > >(tee "$LOG") 2>&1
echo "### BEGIN $(date -Is)"
echo "### 执行者 cgroup: $(cat /proc/self/cgroup)"
echo "### dsh 处理前: $(systemctl is-active dsh.service)"
echo; echo "### [1/5] 停 dsh.service"
systemctl stop dsh.service
sleep 3
echo "dsh: $(systemctl is-active dsh.service)"
echo; echo "### [2/5] 修复前:生产真读路径基线(v3 会话)"
node scripts/verify-mail-sessions-readable.mjs --root /root/.dsh/sessions --only mail- 2>&1 | grep -E "READ (OK|FAIL) .*(f8f9a840|d042cc4c)" || true
echo; echo "### [3/5] --apply 写盘"
node scripts/repair-v3-usermessage-ids.mjs --apply 2>&1 | tail -12
echo; echo "### [4/5] 修复后:生产真读路径验收"
node scripts/verify-mail-sessions-readable.mjs --root /root/.dsh/sessions --only mail- 2>&1 | grep -E "READ (OK|FAIL) .*(f8f9a840|d042cc4c)" || true
echo "--- mail-* 总览 ---"
node scripts/verify-mail-sessions-readable.mjs --root /root/.dsh/sessions --only mail- 2>&1 | grep -E "候选|可读:|不可读:"
echo; echo "### [5/5] 起 dsh.service"
systemctl start dsh.service
sleep 6
echo "dsh: $(systemctl is-active dsh.service) dsh-lan: $(systemctl is-active dsh-lan.service)"
echo; echo "### 残留检查(.repair-staged 应为 0):"
echo " .repair-staged: $(find /root/.dsh/sessions -name '*.repair-staged' | wc -l)"
echo "### END $(date -Is)"