DSH 续谈分支不刷回信上下文 → 第二封的回信挂在第一封上

## 症状

全流程回归时发现:同一条 dsh 会话的第二封邮件,回信主题写的是**第一封**的主题,
`parent_mail_id` 也指向第一封。实测(旧版负向对照):

  jianf  负向对照 第一封
  dsh    Re: 负向对照 第一封   parent=48c4fedc  ← 对
  jianf  负向对照 第二封
  dsh    Re: 负向对照 第一封   parent=48c4fedc  ← 错,应为「第二封」

模型答的内容是对的(收到甲 / 收到乙),坏的是回信的主题与线索归属 ——
在收件箱里看起来像「同一封信被回了两遍」,而第二封的回复无处可寻。

## 根因

`mailContexts` 只在两处写入:`bindAdopted`(接管时)与新开会话分支。
`deliverMail` 的**续谈分支**(`existing` 且 agent 还活着)不写 —— 于是自动转发
用的还是第一封的 subject / mailID。

pi 与 opencode 都没有这个问题:pi 的 worker 一封一进程,每次重建 mailContext;
opencode 在 `deliverMail` 开头统一刷,注释写的就是「一个会话里可能来过多封信,
只保留最近那封」。DSH 漏了这一处,语义与另两个平台不一致。

## 修法

续谈分支进入 `locked()` 后先刷 `mailContexts`(`kind === 'mail'` 才刷 ——
权限通知不是新来信,不该改回信目标)。

## 顺带:pi 主进程删掉不会被调用的 createMailTools

工具是给模型调的,而重构后主进程没有会话。`connect_to_server` 换坐标的闭环在
pool 的 `onReconfigure` 里(工具跑在 worker,worker 回报给主进程)。
schema 约束由 `test/tool-schema.test.mjs` 直接验 `createMailTools`,
不需要在主进程建一份没人用的副本。

## 验证

- 旧版负向对照:确认第二封的回信 parent 指向第一封(复现)
- 修复后:`Re: 修复确认 甲` parent=d879f429 / `Re: 修复确认 乙` parent=e8f216a2,
  各自归位
- 四平台同发一封(pi 接管会话 + dsh/opencode/homeagent 抄送):四封回信全部到位
- dsh 219 / pi 269 / tsc 0
This commit is contained in:
2026-09-04 21:00:11 +08:00
parent 8e501f041e
commit c941fa0f87
2 changed files with 22 additions and 13 deletions

View File

@ -667,6 +667,23 @@ export function apply(ctx: any, config: PluginConfig): void {
const live = ctx.agents.get(existing.dshSessionId);
if (live) {
return locked(existing.dshSessionId, async () => {
// 回信上下文必须刷成**这一封**。
//
// 续谈分支原来不刷mailContexts 只在 bindAdopted 与新开会话时写一次,
// 于是同一条会话的第二封邮件跑完后,自动转发用的还是**第一封**的
// subject / mailID —— 回信主题与 parent_mail_id 都指向上一封。
// 实测撞出过:回「全流程回归」那封的信,主题写的是上一封
// 「platformID 归属验证」parent 也挂在那封上。
//
// 与 pi 桥的语义对齐:那边每封邮件都重建 mailContextworker 一封一进程),
// 注释写的就是「一个会话里可能来过多封信,只留最近那封」。
if (kind === 'mail' && mailSessionID) {
mailContexts.set(mailSessionID, {
replyTo: data.from_name || '',
subject: data.subject || '',
mailID: data.mail_id || '',
});
}
const promptText = kind === 'permission'
? `你之前发起的权限请求已有结论:${data.decision}(决策人:${data.decided_by || '用户'})。请据此继续后续工作。`
: [