Files
MailUI4Agents/plugins/dsh-mail-bridge/lib/mail-session-id.js
JianFeeeee 1692c615c6 fix(dsh): session_update 在插件重启后仍能定位活会话(权限热更新不再静默失效)
sessionMap 是纯内存表,插件重启后为空。而 session_update(人在 WebUI 改权限
档位)原来只查这张表 —— 于是「插件刚重启 + 那条会话还没收到新邮件」时,
那条更新被静默忽略:人在界面上把 full 改回 workspace,DSH 运行时仍按 full 执行。
人以为自己收紧了权限,实际上没有。

邮件新建的会话 id 是确定性的(mail-<邮件会话 id>,模型降级重试时带 -r<i>),
所以重启后可以按前缀在活会话里定位,并顺手把 sessionMap/reverseMap 补回去
—— 不补的话这条会话后续的自动转发也会一起失效。

- lib/mail-session-id.js(dsh 专用,9 例测试):id 派生与匹配的纯函数。
  放宽成前缀匹配会把 mail-abcdef 误判成 mail-abc 的会话;非数字后缀
  (-retry / -rx)也不匹配,避免误改别的会话的档位。
- 接管会话(adopted)仍是例外:其 DSH 会话 id 由平台生成、推不出来,
  重启后无法热更新 —— 已知取舍;下次投递会按邮件里的 permission_mode 重设。
- tsconfig 清理:allowJs 原先写在根层(无效位置),移入 compilerOptions 后
  会让 lib/*.js 进入 TS 程序并违反 rootDir;这些模块已有 .d.ts 提供类型,
  该选项本就不需要,直接移除。

测试:dsh 358(新增 9)/ opencode 316 / pi 405 全绿。
2026-09-11 10:47:30 +08:00

73 lines
2.6 KiB
JavaScript
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.

/**
* 邮件会话 → DSH 会话 id 的确定性派生。
*
* # 为什么需要它
*
* `session_update`(人在 WebUI 里改权限档位)必须找到**正在运行**的那条 DSH
* 会话才能立刻生效。查找原来只走 `sessionMap`,而那张表是纯内存的 ——
* 插件重启后为空。
*
* 于是一个具体的坏情形:人把一条 full 会话在界面上改回 workspace
* 插件恰好刚重启过、那条会话还没收到新邮件 → `sessionMap` 没有它 →
* 这条更新被静默忽略DSH 运行时仍按 full 执行。人以为自己收紧了权限。
*
* # 派生规则
*
* 建会话时(`deliverMail` 的新建分支id 是确定性的:
* - 首次尝试:`mail-<邮件会话 id>`
* - 模型降级重试:`mail-<邮件会话 id>-r<i>`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-<id>-r1 / -r2 / …
const suffix = id.startsWith(`${base}-r`) ? id.slice(base.length + 2) : '';
return suffix.length > 0 && /^\d+$/.test(suffix);
}
/**
* 从一批候选会话里挑出属于该邮件会话的那条。
*
* 优先 `mail-<id>`(首次尝试),其次序号最小的 `-r<i>` —— 与 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;
}