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 全绿。
This commit is contained in:
72
plugins/dsh-mail-bridge/lib/mail-session-id.js
Normal file
72
plugins/dsh-mail-bridge/lib/mail-session-id.js
Normal file
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 邮件会话 → 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;
|
||||
}
|
||||
Reference in New Issue
Block a user