- 回滚/备份:空 bak 改名 .bad 跳过;升级刷新 bak 补上单数 marker 补丁;统一原子写 - 补丁命中:optional 命中不符记软失败;#42 插入型替换加探针防重复插入 - Windows:进程枚举改用 | 分隔 + UTF-8,修复中文路径换包不杀旧进程;换包失败拉起原版 - 安全:asar 解包防越界;红队路径校验 realpath 防空 symlink;更新仅 https;报告回放去换行注入 - 提示词钩子:停用后 rewriteFn 返回原装配文本并清理 assemble guard;缓存签名纳入红队/策略/override - 红队:listEngagements 默认不算统计;listVulns/报告支持 evidenceLimit - 子代理名额占位即时生效并回退;--apply 有失败项退出码 1 - 清理:删除 lib/mnemon-settings.js 及 13 处死代码/未使用 import;移除 test 目录与 test 脚本
68 lines
2.8 KiB
JavaScript
68 lines
2.8 KiB
JavaScript
/**
|
||
* 子代理审批接管。
|
||
*
|
||
* 宿主给子会话写死 approval/policy = "never":
|
||
* dsh-subagent captureDelegatedPolicyOverrides → approvalPolicy: "never"
|
||
* dsh-user-approval decide() → `if (effectivePolicy === "never") return "rejected";`
|
||
* 这一步发生在 approval/request 的 waterfall 之前,所以任何监听器都救不到。
|
||
*
|
||
* 顺序只能是:先把 policy 翻成 ask,waterfall 才会跑;再在 waterfall 里放行。
|
||
* 只作用于子会话,主会话零影响。
|
||
*
|
||
* 不 import 仓库内的识别函数 —— 两棵树的函数名不同,这里自带判断。
|
||
*/
|
||
import { isSubagentContext } from "./anchor.js";
|
||
|
||
/** 同一会话最多补几次 ask,防止异常会话里反复触发。 */
|
||
const MAX_FLIPS = 4;
|
||
|
||
export function installSubagentControl(ctx, config = {}) {
|
||
if (config.subagentControl === false) return () => {};
|
||
if (typeof ctx?.on !== "function") return () => {};
|
||
|
||
const offs = [];
|
||
const flips = new WeakMap();
|
||
const isChild = (session) => isSubagentContext({ session });
|
||
|
||
// P1:看到宿主写进来的 never,就补一条 ask。overrideOf 倒序取最后一条,后写者胜。
|
||
try {
|
||
offs.push(ctx.on("session/event", (session, event) => {
|
||
try {
|
||
if (event?.type !== "approval/policy") return;
|
||
if (event?.data?.policy !== "never") return;
|
||
if (!isChild(session)) return;
|
||
const n = flips.get(session) || 0;
|
||
if (n >= MAX_FLIPS) return;
|
||
// 同一 tick 多条 never 事件会读到同一个 n:先同步占位,append 失败再退回。
|
||
flips.set(session, n + 1);
|
||
// 不能在 append 的处理链里再 append。名额只在真正写成功时消耗:
|
||
// 会话已关 / append 失败时退还,否则 4 次空转之后这个会话就永远不再补 ask。
|
||
queueMicrotask(() => {
|
||
try {
|
||
session.append("approval/policy", { policy: "ask" });
|
||
} catch {
|
||
flips.set(session, n); /* 会话已关,退还名额 */
|
||
}
|
||
});
|
||
} catch { /* 观察者不能抛 */ }
|
||
}, { global: true }));
|
||
} catch { /* 老宿主无 session/event */ }
|
||
|
||
// P2:policy 变 ask 之后 waterfall 才会跑,这里对子会话放行一次。
|
||
// 必须 global —— 宿主是 ctx.waterfall(scopeTarget(req.agent, req.agent), ...),agent 作用域。
|
||
try {
|
||
offs.push(ctx.on("approval/request", (request, next) => {
|
||
try {
|
||
if (isChild(request?.agent?.session)) return "allowed-once";
|
||
} catch { /* 落到 next */ }
|
||
return typeof next === "function" ? next() : "unavailable";
|
||
}, { global: true, prepend: true }));
|
||
} catch { /* 老宿主无 approval/request */ }
|
||
|
||
return () => {
|
||
for (const off of offs) {
|
||
try { off(); } catch { /* 已释放 */ }
|
||
}
|
||
};
|
||
}
|