/** * 邮件会话 → DSH 会话 id 的确定性派生。 * * # 为什么需要它 * * `session_update`(人在 WebUI 里改权限档位)必须找到**正在运行**的那条 DSH * 会话才能立刻生效。查找原来只走 `sessionMap`,而那张表是纯内存的 —— * 插件重启后为空。 * * 于是一个具体的坏情形:人把一条 full 会话在界面上改回 workspace, * 插件恰好刚重启过、那条会话还没收到新邮件 → `sessionMap` 没有它 → * 这条更新被静默忽略,DSH 运行时仍按 full 执行。人以为自己收紧了权限。 * * # 派生规则 * * 建会话时(`deliverMail` 的新建分支)id 是确定性的: * - 首次尝试:`mail-<邮件会话 id>` * - 模型降级重试:`mail-<邮件会话 id>-r`(i 从 1 开始) * * 接管会话(adopted)是唯一的例外:那条 DSH 会话 id 是平台自己生成的, * 从邮件会话 id **推不出来**,只能靠内存映射。重启后接管会话的档位热更新 * 确实无法定位 —— 这是已知取舍,不是这里能修的。 */ /** 首次尝试使用的 DSH 会话 id。 */ export function dshSessionIdForMail(mailSessionID) { return `mail-${String(mailSessionID ?? '')}`; } /** * 判断一个 DSH 会话 id 是否属于某条邮件会话。 * * @param {string} dshSessionId DSH 侧会话 id * @param {string} mailSessionID AgentMail 侧会话 id * @returns {boolean} */ export function matchesMailSession(dshSessionId, mailSessionID) { const id = String(dshSessionId ?? ''); const base = dshSessionIdForMail(mailSessionID); if (id === base) return true; // 模型降级重试:mail--r1 / -r2 / … const suffix = id.startsWith(`${base}-r`) ? id.slice(base.length + 2) : ''; return suffix.length > 0 && /^\d+$/.test(suffix); } /** * 从一批候选会话里挑出属于该邮件会话的那条。 * * 优先 `mail-`(首次尝试),其次序号最小的 `-r` —— 与 deliverMail * 的尝试顺序一致,而不是数组顺序。 * * @param {string[]} dshSessionIds * @param {string} mailSessionID * @returns {string|undefined} */ export function pickMailSession(dshSessionIds, mailSessionID) { const base = dshSessionIdForMail(mailSessionID); const list = Array.isArray(dshSessionIds) ? dshSessionIds.map(String) : []; if (list.includes(base)) return base; let best; let bestIndex = Infinity; for (const id of list) { if (!matchesMailSession(id, mailSessionID)) continue; const idx = Number(id.slice(base.length + 2)); if (idx < bestIndex) { bestIndex = idx; best = id; } } return best; }