Files
dsh-purge2/lib/subagent-control.js
叫我小杨同学 335ddee90f fix: 全仓审计加固与死代码清理,版本 1.1.87
- 回滚/备份:空 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 脚本
2026-11-01 21:47:03 +08:00

68 lines
2.8 KiB
JavaScript
Raw Permalink 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.
/**
* 子代理审批接管。
*
* 宿主给子会话写死 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 { /* 已释放 */ }
}
};
}