Files
MailUI4Agents/plugins/dsh-mail-bridge/lib/mail-session-id.js
JianFeeeee 33488760ce fix(相位/安全): 部署门禁只判产物自证;静默 break 改成出声;内核读数带时间坐标
pi 2026-09-14 的裁定与两条更正,逐条落地。

1. **相位裁定(选 c)**:`packaging`/`build-stamp` 属于**构建相位**,不属于安装相位。
   `run-all.mjs` 现在有相位:`AGENTMAIL_CRITERIA_PHASE=install`(部署门禁用)。
   每条判据登记它读的哪一侧(`ARTIFACT`/`SOURCE`),install 相位里出现 SOURCE 侧判据 → 红;
   被跳过的判据**点名打印**,不静默丢。汇总打 `RESULT phase=build|install`。
   规则入册 `test/CRITERIA.md` §11(含三个真实实例:check-shared-libs 恒红、
   packaging 一改前端就卡死、HOME 在门禁跑完之后才炸)。

   安装相位**真正能判的那一半**:`deploy/install.sh` 读**产物自证**(不重算 dist)——
   `releaseCandidate !== true` → 拒绝;产物 `gitRev` ≠ HEAD → "这个包比源码旧" → 拒绝;
   放行要显式 `--allow-dirty` / `--allow-stale`;`--check` 干跑只报结论不拦。
   实测干跑输出:`产物:gitRev=6702cc2 树=dirty releaseCandidate=false | 当前 HEAD=6702cc2`
   → 报"不是发布候选 + 正式安装会被拒绝 + 要放行请显式说清"。

2. **别解析运行器文本**(pi §5):`broken`/`red` 的判定改成按 TAP 的**名字**——
   文件级失败的测试名就是路径,断言失败的名字是判据名。变异双向验证:
   未定义标识符 → 「跑不起来的判据」;把某条判据条件改成假 → 「红的判据」。
   不再往关键字表里加补丁(那是往文本解析里加补丁,方向是错的)。

3. **静默 break 是安全相关**(pi §3):`session_update` 找不到活动会话时不再静默 break,
   改成出声日志(走 journalctl 那条通道),写清两种成因(此刻没在跑 / **接管会话**重启后无法定位)、
   方向(收紧被延迟)、以及兜底的**前提**("下次投递"要求这条会话还会收到新邮件)。
   `lib/mail-session-id.js` 模块头同步改成安全相关措辞("人以为自己收紧了权限、实际没有"),
   四桥逐字节同源,`check-shared-libs.sh` 退出码 0。

4. 内核读数补时间坐标(pi 13ea2fdf):`BUILD_INFO.txt` 里除原始 `dep`/`=>` 行外,
   现在还有 `kernelBinMtime` 与**正在运行的进程启动时间** —— 二进制会在两次读数之间被换掉,
   没有时间坐标的读数不成立。
2026-09-14 16:54:30 +08:00

80 lines
3.2 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 执行。人以为自己收紧了权限。
*
* ⚠ **这一格的后果是安全相关的,不是"技术限制"**(pi 2026-09-14 裁定):
* 接管会话在重启后**定位不到**,意味着一次**收紧**(full → workspace)可能被
* **静默延迟** —— 而"延迟"对只被收紧、之后再没有新邮件的会话等于**永不生效**
* (兜底是"下次投递按邮件里的档位重新 apply",它依赖将来还有邮件)。
* 方向是收紧,所以措辞必须按"权限可能没按你以为的那样收紧"来说,
* 而不是"档位热更新定位不到"这种技术性说法 —— 后者会让人以为只是界面问题。
*
* # 派生规则
*
* 建会话时(`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;
}