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:
@ -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 桥的语义对齐:那边每封邮件都重建 mailContext(worker 一封一进程),
|
||||
// 注释写的就是「一个会话里可能来过多封信,只留最近那封」。
|
||||
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 || '用户'})。请据此继续后续工作。`
|
||||
: [
|
||||
|
||||
@ -39,7 +39,6 @@ import { join } from 'node:path';
|
||||
import { ModelRuntime } from '@earendil-works/pi-coding-agent';
|
||||
|
||||
import { GatewayClient, readLocalKey, generateLocalKey, saveConfig } from './gateway.mjs';
|
||||
import { createMailTools } from './tools.mjs';
|
||||
import { createWorkerPool } from './pool.mjs';
|
||||
import { describeError } from './turn.mjs';
|
||||
import { snapshotPiModels } from '../lib/model-scope.js';
|
||||
@ -282,19 +281,12 @@ async function main() {
|
||||
workerMaxMs: WORKER_MAX_MS,
|
||||
});
|
||||
|
||||
// 主进程仍要一套邮件工具:它自己不跑模型,但 connect_to_server 的
|
||||
// onReconnect 语义要在这里闭环(worker 侧那套只负责回报给主进程)。
|
||||
// 主进程不跑模型,因此**不建**邮件工具:工具是给模型调的,而这里没有会话。
|
||||
// (工具 schema 的约束由 test/tool-schema.test.mjs 直接验证 createMailTools,
|
||||
// 不需要在这里建一份没人用的副本。)
|
||||
//
|
||||
// 这些工具不会被任何模型调用 —— 主进程没有会话。留着是因为
|
||||
// createMailTools 同时承担「校验工具 schema」的职责(test/tool-schema.test.mjs),
|
||||
// 而工具总数是契约里核对过的数字。
|
||||
createMailTools({
|
||||
client, log, agentName: AGENT_NAME,
|
||||
onReconnect: () => {
|
||||
client.stopSSE();
|
||||
client.startSSE(handleSSEEvent, log);
|
||||
},
|
||||
});
|
||||
// connect_to_server 换坐标的闭环在 pool 的 onReconfigure 里 —— 那个工具
|
||||
// 跑在 worker 里,worker 把新坐标回报给主进程,主进程据此重建 SSE。
|
||||
|
||||
try {
|
||||
await client.register(); // B-1.2
|
||||
|
||||
Reference in New Issue
Block a user