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:
@ -60,6 +60,7 @@ import {
|
||||
} from '../lib/discovery.js';
|
||||
import { appendRenameProposal, renameProposalNote } from '../lib/rename-proposal.js';
|
||||
import { createSSEClient } from '../lib/sse-client.js';
|
||||
import { pickMailSession } from '../lib/mail-session-id.js';
|
||||
// 只用 isApproval:DSH 没有 always 语义,免批授权表在这里用不上(见决策处的注释)。
|
||||
import { isApproval } from '../lib/permission-grants.js';
|
||||
import {
|
||||
@ -748,6 +749,53 @@ export function apply(ctx: any, config: PluginConfig): void {
|
||||
session.append('approval/policy', { policy: approval });
|
||||
}
|
||||
|
||||
/**
|
||||
* 由邮件会话 id 找一个**活着的** DSH 会话,找到就补回内存映射。
|
||||
*
|
||||
* # 为什么不能只查 sessionMap
|
||||
*
|
||||
* 那是一张纯内存表,插件重启后为空。而 `session_update`(人在 WebUI 改
|
||||
* 权限档位)只关心**正在跑**的那条会话 —— 重启后它恰好还没收到新邮件时,
|
||||
* 映射缺失会让这条更新被静默忽略:人在界面上把 full 改回 workspace,
|
||||
* 运行时依旧按 full 执行。人以为自己收紧了权限。
|
||||
*
|
||||
* 邮件新建的会话 id 是确定性的(`mail-<邮件会话 id>`,重试时带 `-r<i>`,
|
||||
* 见 lib/mail-session-id.js),所以重启后也能定位,并且顺手把映射补回去 ——
|
||||
* 否则这条会话后续的自动转发(reverseMap)也会一起失效。
|
||||
*
|
||||
* 接管会话(adopted)是例外:那条 DSH 会话 id 由平台生成,从邮件会话 id
|
||||
* 推不出来,重启后确实无法定位热更新 —— 已知取舍;下次投递时会按邮件里
|
||||
* 带的 permission_mode 重新 apply,档位不会丢。
|
||||
*/
|
||||
function findLiveDshSession(mailSessionID: string): { id: string; agent: any } | undefined {
|
||||
const bound = sessionMap.peek(mailSessionID);
|
||||
if (bound) {
|
||||
const live = ctx.agents.get(bound.dshSessionId);
|
||||
if (live) return { id: bound.dshSessionId, agent: live };
|
||||
}
|
||||
|
||||
let live: any[] = [];
|
||||
try {
|
||||
live = ctx.agents?.list?.() ?? [];
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
const id = pickMailSession(live.map((a: any) => String(a?.id ?? '')), mailSessionID);
|
||||
if (!id) return undefined;
|
||||
|
||||
const agent = live.find((a: any) => String(a?.id ?? '') === id);
|
||||
if (!agent) return undefined;
|
||||
|
||||
if (!bound) {
|
||||
const cwd = String(agent?.session?.header?.cwd ?? '');
|
||||
sessionMap.set(mailSessionID, { dshSessionId: id, directory: cwd });
|
||||
reverseMap.set(id, mailSessionID);
|
||||
mailDrivenSessions.add(id);
|
||||
console.error(`[dsh-mail-bridge] 由确定性 id 恢复会话映射 ${mailSessionID} -> ${id}`);
|
||||
}
|
||||
return { id, agent };
|
||||
}
|
||||
|
||||
// ─── 投递邮件到 DSH 会话 ───
|
||||
|
||||
/**
|
||||
@ -1899,16 +1947,16 @@ export function apply(ctx: any, config: PluginConfig): void {
|
||||
// approval/policy,否则模型仍按旧档位执行。
|
||||
const sid = String(data?.session_id || '');
|
||||
const pm = String(data?.permission_mode || '');
|
||||
if (sid && pm) {
|
||||
const bound = sessionMap.peek(sid);
|
||||
if (bound) {
|
||||
const live = ctx.agents.get(bound.dshSessionId);
|
||||
if (live?.session) {
|
||||
applyPermissionMode(live.session, pm);
|
||||
console.error(`[dsh-mail-bridge] session_update ${sid} -> 权限档位 ${pm}`);
|
||||
}
|
||||
}
|
||||
if (!sid || !pm) break;
|
||||
const found = findLiveDshSession(sid);
|
||||
if (!found) {
|
||||
// 会话不在运行(插件重启后尚未收到新邮件、或从未投过)。
|
||||
// 无需处理:下次投递时 deliverMail 会按邮件里带的 permission_mode
|
||||
// 重新 applyPermissionMode,档位不会丢。
|
||||
break;
|
||||
}
|
||||
applyPermissionMode(found.agent?.session, pm);
|
||||
console.error(`[dsh-mail-bridge] session_update ${sid} -> 权限档位 ${pm}(会话 ${found.id})`);
|
||||
break;
|
||||
}
|
||||
case 'session_archived':
|
||||
|
||||
Reference in New Issue
Block a user