Files
MailUI4Agents/plugins/dsh-mail-bridge/test
JianFeeeee 3673019ec4 fix(dsh)★★: resume 前不查 live ⇒ 每封来信撞自己的 flock,秒回「处理失败」
## 症状(用户报「秒回错误」)

每封来信立刻收到 `处理失败`,一封都没进去:

    会话 mail-515c0c70… 已在磁盘上(cwd=未记录),改为 resume 续谈
    建会话失败 llmsproxy/AUTO: session "mail-515c0c70…" is already owned
    by an active write handle

## 根因(三方证据)

**① 锁是 flock,且持有者就是 dsh 自己。** `lsof` 显示 pid 1547284(dsh 自身)
持有那个 `session.lock`;`fuser` 同结论。⇒ 不是残留锁、不是别的进程,是自己撞自己。

**② 机制**(`dsh-session-persistence-jsonl/lib/index.js:711`):

    await tryLockExclusive(handle.fd);
    if (isLockContention(error)) throw new SessionAlreadyOwnedError(id);

**③ 插件自己撞自己**:同一文件里两处调 `startAgent`——
  · 接管路径(约 1187 行)**早就有** `ctx.agents.get()` 去重,注释写明
    「同一条会话两个 handle 会各自往日志里写,replay 校验不过」
  · **普通投递路径漏了** ⇒ 会话在磁盘上时 `startAgent` 走 `ctx.agents.resume()`,
    它**再申请一次写 lease**,撞上本进程已持有的那个

**重启 dsh 治不好**:flock 随进程退出释放,但 dsh 启动后 resume 该会话时
会重新获取 —— 实测重启(08:39)后 09:11 的信仍然秒失败。

## 修法

普通投递路径在 `startAgent` 前加 `ctx.agents?.get?.(attemptSessionId)`,
命中就直接 `followup`,不重复 resume。与接管路径对齐。

## 判据(+2 格,钉「每个 startAgent 调用点都要查 live」)

不钉某一行,而是「**每个** `startAgent` **调用点**前面都必须有 live 检查」
—— 少一处就红。这比钉形状更能防「修一处漏另一处」:
那个错误本轮已犯过一次(先修 `modelTitle` 的字段名,漏了 `lastAssistantText`)。

★ 这份判据自身被我的错误连累了 6 轮才转红,全部是「观察面比语义窄/宽」:
  1. 正则假设函数无返回类型 + 4 空格缩进(实际 `): any[] {` + 2 空格)
  2. 把**函数定义**当调用点(`[^=]*` 匹配过 `async function startAgent(`)
  3. 固定 30 行窗口在剥离注释后行号偏移 ⇒ 接管路径被误判成「没检查」
  4. 正则只吃 `get(`,不吃 `agents?.get?.(` 的**双可选链**
  5. 正则多了 `\.`(`ctx.agents?.get?.` 里 agents 后没有点)
  6. `.test()` 带了 `/g` ⇒ `lastIndex` 在连续调用间保留,结果交替
最终改为「往上找最近的 `get(`,不限定变量名、不限窗口大小」,两个变异都转红。

dsh 442 格全绿 · tsc 零错 · 部署成功(current → 20261004-093035)· 心跳正常。
2026-10-04 12:03:58 +08:00
..