# 发生了什么 pi-lens 内置「安全格式化」:它会自动安装 biome 并对**编辑过的文件**跑 `biome format --write`。本机原先没有任何 biome 配置,于是 biome 用它自己的 默认值 —— tab 缩进 + 双引号 —— 把文件整体重写。 我在19a3161那次提交里用了 `git add -A`,把这批与功能无关的重排一起扫了进去: 约 7000 行改动散落在 20 个文件上,使那次提交无法审查,还掩盖了 server/internal/handler/permission.go 的一处删行(实为文件末尾空行,无代码丢失)。 # 为什么是「关掉」而不是「配置成我们的风格」 试过把缩进/引号/lineWidth 全部对齐本仓库习惯(biome.json + space/2/single/ lineWidth 120):`biome format --write` 仍然改动 17 个文件。原因是本仓库从未按 biome 的规则排版过 —— 注释按语义换行、数组与调用按可读性手工折行, 这些无法由格式化器还原。也就是说只要格式化器开着,每次编辑都会产生与内容无关的 大面积 diff,把真正的改动埋掉。 因此 biome.jsonc 里 formatter 与 linter 都关闭:本仓库的静态检查由 tsc / go vet / tree-sitter / ast-grep 与各自测试套件承担,不引入会改动无关行的 自动修复。 (pi-lens 这一版把 format 服务的 enabled 硬编码为 true,没有配置开关, 所以只能在仓库侧用 biome 配置让它不动文件;已验证 `biome format --write` 对这些文件零改动。) # 本提交内容 把19a3161里除「有意改动」外的 20 个文件还原到重排前的样子。19a3161中真正有意的改动是 deploy/install.sh 的扩展注册与 plugins/pi-mail-bridge/extension/index.ts 新文件,两者原样保留。 验证:Go 全量、三桥插件(320/362/409)、前端 196 全绿; `biome format --write` 对还原后的文件零改动。
73 lines
2.6 KiB
JavaScript
73 lines
2.6 KiB
JavaScript
/**
|
||
* 邮件会话 → 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;
|
||
}
|