dsh-rampart
给 DeepSeek Harness 加一层内存/CPU 压力护栏:机器快被吃穿时先踩刹车,而不是等它卡死。
rampart = 城墙的外围防御墙。采样压力、到线就拦重活、把宿主降到 below-normal —— 都是在机器被压垮之前筑一道墙。
两层,各管一件事:
| 层 | 干什么 | 在哪 |
|---|---|---|
| 插件(软) | 采样可用内存 → 分 normal/warn/critical → 降优先级、钉核、拦重活、给模型读数据 | lib/ |
| 启动壳(硬) | 给整棵 Electron 进程树套 Job Object 内存硬顶,撞上就被系统拒绝 | scripts/launch.mjs |
外加输入框下方的一条读数(CPU 12% · DSH 1.2 GB),排在 DSH 自己的上下文占用环
右边;点开是面板,用一根分段占比条回答「整机被谁吃了」,带一个「释放内存」按钮。
数据不走模型,是浏览器直接打宿主路由取的本机读数。
dsh plugin --profile web add dsh-rampart
⚠️ 与 npm 上已存在的
dsh-resource-guard(另一个作者的本地模型内存信号量插件) 不是同一个东西。本插件原名就是那个,为避让而改名。
为什么要自己写
先把「有没有现成的」查清楚了,结论是没有:
内置:本机 @deepseek-ai/dsh@0.2.1-rc.2 下 304 个包,逐个读 description 与 README,没有一声明对 harness 或子进程施加内存/CPU 上限。最接近的三处都不是 cap:
dsh-win32-process是唯一用 Job Object 的包,但 144 字节缓冲里只写JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE(偏移 16),从不设ProcessMemoryLimit/JobMemoryLimit/ CPU rate;dsh-output-retention/dsh-spill/dsh-compactioncap 的是回给模型的 token,不是进程内存;dsh-timeout只做时间算术,README 明写 "no termination";dsh-tool-call-timeout-policy是协作式超时,管不了不配合取消的进程。
社区:awesome-dsh-plugin.com/plugins.json 7723 条(2026-10-29 快照)逐条筛完,只有三类相关,都不是 cap:
dsh-code-runtime-container:cgroup 硬限,但只约束 Code Mode 里跑的程序,不管 harness 本体;dsh-memory-watchdog:临界时重启整个桌面应用(默认关闭),不是限制;dsh-system-monitor-plugin等 8 个:只监控展示,不动作。
所以本仓库补这两层。注意它防的是「自己把自己卡死」,不是恶意进程,也不是安全边界。
装法
插件
插件是普通 cordis 插件,两种装法。
A. 按包名装(推荐,能被 Plugin Manager 管到)
# 1) 把本目录链进 profile 的 node_modules
$profile = "D:\Users\zigang.wang\.dsh\profiles\desktop"
cmd /c mklink /J "$profile\node_modules\dsh-rampart" "D:/Projects/MyProjects/dsh-resource-guard"
# 2) 在 profile patch 里插一行(desktop profile 的这一行已存在,追加到末尾即可)
$profile\cordis.patch.yml 追加:
- insert:
- id: rampart
name: 'dsh-rampart'
桌面的 HMR 会监听 cordis.patch.yml,保存后约 2 秒自动重组,不用重启应用。
B. 用绝对路径直插(不想动 node_modules 时)
- insert:
- id: rampart
name: 'file:///D:/Projects/MyProjects/dsh-resource-guard/lib/index.js'
这条路要求进程环境里有 DSH_HOME / DSH_PROFILE_DIR(桌面应用会设),插件靠它找到
DSH 自带的 dsh-win32-process 与 koffi;找不到就退化为纯观测模式并在日志里说明。
启动壳(想要硬顶才需要)
cd D:/Projects/MyProjects/dsh-resource-guard
node scripts\launch.mjs --dry-run # 先看会施加多少
node scripts\launch.mjs # 默认按总内存 55% 给硬顶(本机 32 GB → 17810 MB)
node scripts\launch.mjs --cap 12000 --low-priority
或双击 dsh-guarded.cmd。
配置
只能写在 patch 的 config 里(Config 自己实现 StandardSchemaV1,没有 schemastery 的表单渲染)。
全部字段与默认值见 lib/config.js,最常改的是四个阈值:
| 字段 | 默认 | 含义 |
|---|---|---|
warnFreeMemoryMb |
2048 | 可用物理内存低于此值进入 warn(只降速 + 提醒) |
criticalFreeMemoryMb |
1024 | 低于此值进入 critical(拦重活) |
warnCommitMb |
3072 | 可用提交内存低于此值进入 warn;0 关闭该项 |
criticalCommitMb |
1536 | 低于此值进入 critical;0 关闭该项 |
priority |
below-normal |
降优先级;子进程会继承(实测) |
limitCores |
0 | 钉住核数;0 = 不动亲和性 |
heavyTools |
pwsh/bash/shell/workflow/subagent/subagent_fork/send_message/web_fetch/web_search | 精确名单 |
heavyPatterns |
['job_*'] |
通配名单 |
showStatusPill |
true |
是否挂输入框下方的读数条 + /rampart/api/* 路由 |
statusPillIntervalMs |
2000 | 读数条轮询间隔;低于 500 会被夹到 500 |
为什么默认值这么保守:本机实测物理 32 GB、可用长期 2.7~3.3 GB(约 9%),提交内存同量级。 误拦一条重活只是慢一点;漏拦一次重活是整机卡死。所以宁可早拦。
但装的时候一定要按本机实测重定阈值。 实测教训(详见 deploy-desktop.md):
第一版用 warnCommitMb: 4096 装上去,delayedCalls 一路涨——因为这台机器每一个
采样点的可用提交都在 4 GB 以下,那不是「压力大」,那是正常水位。
改成低于实测最小值(2200)后,才只在真异常时才响。
调参口诀:阈值要压在「常态低谷」之下,留 1 GB 左右的余量给真正的突发。
为什么「轻工具永不拦」:压力大时更不该阻止模型 read/grep/write/todo_write —— 收尾、汇报、保存状态都是轻活。
模型看到什么
被拦时返回一条结构化错误(RESOURCE_GUARD_DENIED),带实测读数与下一步建议,
而不是一句「禁止」——后者会让模型反复重试同一条命令:
资源护栏:pwsh 属于重活,当前内存压力为 critical(可用物理 3832 MB / 总 32381 MB,
占用 88%,可用提交 5330 MB)。机器已接近卡死临界点,继续跑会拖垮整台机器。
请改用轻量动作(read/grep/write/todo_write 等),或调用 resource_guard 查看实时读数后
向用户汇报并等待。
另有一个 resource_guard 工具:
{
"grade": "critical", "rawGrade": "critical",
"summary": "等级 critical(瞬时 critical);可用物理 3014 MB / 总量 32381 MB(占用 90%);可用提交 3725 MB;已拦 1 次,降速 0 次,采样 3 次",
"totalMb": 32381, "availPhysMb": 3014, "availCommitMb": 3725, "loadPercent": 90,
"deniedCalls": 1, "delayedCalls": 0, "samples": 3, "frozenPids": [], "win32": true
}
输入框下方那条读数
排在 DSH 自己的 154 tok/s、缓存命中 49%、上下文占用环之后:
154 tok/s · 缓存命中 49% · 38% · CPU 12% · DSH 1.2 GB
- normal → 灰色(与内置统计条同一个 tertiary 前景色)
- warn → 变警示色
- critical → 前置一个实心点 + 警示色
为什么「排在上下文环右边」要靠 CSS 而不是
order选项:上下文环比 slot 更硬 —— 它是InputBar在 dock 末尾硬编码渲染的,不在conversation.composer.dock里, 所以 slot 的order管不到它。dock 是 flex 容器、slot 外壳是display:contents, 我们的根<span>因此是 dock 的直接 flex item —— 给它order:1就落到默认 0 的环之后。 这个结论来自读dsh-client-ui-conversation的 bundle,不是猜的。
点开是面板。面板底与上下文面板完全同源:--dsw-specific-menu(半透明)+
backdrop-filter: var(--dsw-menu-backdrop-filter) + --dsw-elevation-stroke-color 让阴影带描边 ——
和 DSH 自己的上下文 / 统计 / todo 三块面板是同一组声明。只写 background 不写
backdrop-filter 会直接透出下面的对话文字,读数根本看不清(这就是上一版的问题)。
面板里的条:一根条回答「整机被谁吃了」
不是「DSH 一条、整机一条」两条并列 —— 那会让人以为是两件独立的事,而它们其实是 同一根尺子上的三段:
整机物理内存 18.4 GB / 31.6 GB · 58%
████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░
● DSH 5.1 GB ● 其他已用 13.3 GB ● 空闲 13.2 GB
- 蓝 = DSH 在用(
--dsw-static-blue-450,与 DSH 上下文面板同一个蓝) - 灰 = 已经用掉、不可用(
--dsw-static-neutral-bluish-400) - 留白 = 还能再吃
整机 CPU 同理:DSH 忙 / 其他忙 / 空闲,分母是「所有逻辑核跑满」。
本进程 JS 堆单独一条(它有自己的上限,不是整机的分母)。
两个防错细节,都是踩出来的:
- 百分比不夹成整数(
shareOf保留一位小数)。0.4% 的 DSH 占用夹成 0 会被filter(share > 0)整段丢掉 —— 「DSH 在用」这件事在图里直接消失,而它恰恰最该被看见。 - DSH 段先夹进整机已用再算「其他」。DSH 的提交量可能大于整机已用物理 (有换页、有共享页),不夹的话蓝色会溢出轨道,「其他已用」还会变成负数。
两个数分别是什么
| 显示 | 来源 | 含义 |
|---|---|---|
CPU 12% |
宿主 GetSystemTimes 差值 ÷ (经过时间 × 核数) |
整机 CPU 占用,不是单核 |
DSH 1.2 GB |
沿 ppid 找到同名的树根,把每个成员的 PagefileUsage 加起来 |
用提交量而不是工作集:与任务管理器"内存"列一致,被换页时不会塌成 0 |
树根是走出来的:从 process.pid 往上爬,只在父进程映像名相同且 pid 未访问时继续,
否则就停在当前进程。所以 Electron 的 main/GPU/renderer/tool 会自然形成一棵树。
「释放内存」到底做了什么
只对本进程的 V8 堆做一次强制 GC(v8.setFlagsFromString('--expose-gc') +
vm.runInNewContext('gc'),不需要启动参数)。实测:一次 ~3 ms,heapUsed 33.2 → 4.4 MB,
而 rss 只降 96.6 → 95.0 MB。
所以文案刻意分开写:「只回收本进程 JS 堆;renderer 堆、Electron native 内存与系统缓存不动,
所以任务管理器里不一定看到变化」,freedMb 也只报 heapUsed 的差,不报 RSS —— 报 RSS 会骗人。
数据怎么到浏览器
浏览器 ──GET /rampart/api/stats──▶ 宿主 webServer 路由 ──▶ GetSystemTimes / Toolhelp32 / GetProcessMemoryInfo
◀── { grade, guard, heap, tree } ──
- 不经过模型,不花 token,不刷对话。
- 路由用
ctx.connection.requestRejection(req)做信任判定(和dsh-better-sidebar同一道围栏), 不是自己发明鉴权。没有connection服务时退化为「只允许本机回环」并写一条 warn。 - 请求 URL 必须是页面同源的相对路径:桌面 shell 的页面是
dsh-app://app/,protocol.handle会把非静态路径同源转发到宿主;用宿主真实地址(__DSH_TRANSPORT__.streamBaseUrl) 会跨源,而插件路由不发 CORS 头 →Failed to fetch。 - 路由取不到数时组件渲染
null,不是占位符 —— 宿主半边挂掉不该把编辑器底栏弄坏。
能力边界(必须说清)
- 插件不能给宿主加内存硬顶。 Job Object 的限额一旦附加就无法解除(Windows 没有把进程移出 Job 的 API)。给 Electron 自己套上限 = 把它永久锁在那个上限里。所以硬顶只能在创建时由启动壳施加,这也是它必须是两层的原因。
- 插件不能抢占已经跑飞的工具。
tools/pre-execute只能在调用前拒绝。已经在跑的 pwsh 命令只能靠freeze()(NtSuspendProcess,可逆冻结)或用户取消。 - 硬顶撞上是「杀进程」而不是「返回错误」。 实测:Job 整树上限撞上时 Node 表现是
FATAL ERROR: Committing semi space failed ... JavaScript heap out of memory+ 退出码 134,拿不到可捕获的异常。所以软层阈值必须低于硬顶——撞上硬顶的一方没有纠正机会。 exec.arguments不能改写。 只能拦,不能「帮你把命令改小」。- 不是安全边界。 不防恶意进程,也不防 DSH 之外的软件吃内存。
- 「释放内存」不是「释放整机内存」。 它只回收本进程的 V8 堆。想真正把整机内存腾出来, 得关掉占内存的程序;想限制 DSH 的内存天花板,得用启动壳的 Job 硬顶。
- 读数有 2 秒延迟。 采样间隔默认
statusPillIntervalMs: 2000,且宿主侧满树采样本身要 20~30 ms (458 个进程的 Toolhelp 快照 23 ms)。
实测证据
单元 / 集成测试(72 条全绿)
npm test
# ℹ tests 72 ℹ pass 72 ℹ fail 0
覆盖:阈值分级、迟滞、重活判定、决策、配置校验、优先级/亲和性的可逆性、 以及在真 cordis + 真 ToolRuntime 里的门禁行为(critical 时工具体确实没执行——用副作用文件断言,不读返回值)。
性能:热路径开销
护栏挂在每一次工具调用上,所以它的开销必须小到看不见。npm run bench:
npm run bench
# Win32 能力层:可用 · 逻辑核 24
# gate() 轻工具 read 中位 7.8 µs p95 12.1 µs 最大 38.0 µs (n=300)
# gate() 重活 pwsh 中位 7.6 µs p95 21.3 µs 最大 157.8 µs (n=300)
# tick() 单次采样 中位 6.8 µs p95 7.7 µs 最大 387.6 µs (n=300)
# TreeSampler.sample() (421进程) 中位 15.4 ms p95 17.5 ms 最大 20.6 ms (n=60)
# decideCall() 纯函数 中位 0.4 µs p95 1.1 µs 最大 111.9 µs (n=2000)
三句话读懂:
| 路径 | 频率 | 成本 | 结论 |
|---|---|---|---|
gate() |
每次工具调用 | ~8 µs | 相对一次 pwsh 冷启动(~200 ms)是 1/25000,可忽略 |
tick() |
每 5 秒后台 + 每次 gate 内 | ~7 µs | GlobalMemoryStatusEx 就是一次很短的 FFI 调用 |
TreeSampler.sample() |
每 2 秒(面板轮询) | ~17 ms | 一次 Toolhelp 全表快照遍历 421 个进程,占单核 0.87% |
TreeSampler 那一项是唯一「贵」的,且只在桌面有面板时才跑(CLI-only profile 不挂路由)。
17 ms 里几乎全是 createToolhelp32Snapshot 本身(一次全表快照 ~12 ms),
不是我们的解析逻辑——拆开量过:readProcessUsage 每个成员只要 0.02 ms。
为什么不用更省的方案:可以把采样间隔从 2 秒放长,但面板读数的意义就在「实时」。
0.87% 的单核成本换一个能实时看到「整机被谁吃了」的面板,是划算的;
嫌贵就把 statusPillIntervalMs 调到 5000。
可靠性:泄漏、并发与降级
npm run soak
# 7000 次热路径调用后:heapUsed 5.3 → 5.4 MB(Δ 0.1),RSS Δ 3 MB
# PASS: 热路径无明显泄漏(heap Δ 0.1 MB < 8 MB)
# PASS: 采样计数准确(7000)
# PASS: 树采样无明显泄漏(heap Δ 0 MB < 8 MB)
# PASS: 200 个并发门禁全部放行(normal 等级)
# PASS: 并发下采样计数无丢失(200)
# PASS: 迟滞生效,交错抖动时等级不跳变
# PASS: dispose() 可重复调用(无异常)
npm run verify:degrade
# PASS: 无能力层时仍能定级 / gate 不抛 / start-dispose 不抛
# PASS: 能力位如实报告 win32=false
为什么这两条必须单独存在:单元测试每次都是新实例,测不出「连续用几小时后护栏突然失灵」;
而市场用户很可能在 koffi 加载失败的环境里跑,那时宁可退化为纯观测,也不能崩。
代码里所有副作用都走「失败只降级」:gate() 自身抛错时放行(宁可漏拦一次,
也不要把模型卡死在一条假错误上)。
Win32 真机探针
npm run verify
# RESULT: PASS — 优先级与亲和性装上后能完整恢复
# RESULT: PASS — Job 整树内存硬顶真的会拒绝分配,且不污染父进程
# RESULT: PASS — 启动壳的整树硬顶在真实 spawn 路径上生效,且不污染调用者
真 DSH profile 端到端
npm run verify:profile
# PASS: 插件已以包名挂进 profile node_modules(dsh-rampart)
# PASS: 探针在真实 composition 里执行了
# PASS: pwsh 被拒绝(isError)
# PASS: 错误码为 RESOURCE_GUARD_DENIED
# PASS: pwsh 的副作用没有发生(标记文件不存在)
# PASS: 返回了压力等级(critical,钉死阈值后必须是 critical)
# PASS: 计数被拦次数(1)
即:headless profile 真加载插件 → 真调 pwsh → 被拦 → 命令的副作用文件确实没被创建。
这条验收以前是随机通过的:它用「按本机实测调的」阈值,机器空闲时等级就是
normal, 门禁根本不触发。现在阈值钉成不可能达到的值,critical是确定状态。 教训:验收里的输入必须钉死,不能跟着环境走。
底栏读数条(真 web 服务,隔离 profile)
npm run verify:pill
# PASS: boot 图里有 dsh-rampart/client.js
# PASS: combo 脚本可下载(实际 200)
# PASS: 下载到的字节里有我们的 CSS 类名
# PASS: GET /rampart/api/stats → 200(应当 200)
# PASS: 带压力等级(normal)/ Win32 能力层可用 / 整树提交量有值(408 MB)
# PASS: POST /rampart/api/gc 回收成功(freed 66.7 MB)
# PASS: 不带 cookie → 403(围栏生效而不是路由缺失)
即:干净进程真加载客户端半边 → boot 图里真有这条 combo → 真下到字节 → 宿主路由真返回本机读数 → 真回收了一次堆。
真实桌面宿主(已装在本机,证据见 deploy-desktop.md)
- 拦下时我这个 agent 直接收到
RESOURCE_GUARD_DENIED,而不是「配置写对了」的推断 warn档delayedCalls涨了 → 门禁确实在真实调用链上disabled: true后优先级从BelowNormal回到Normal,改回来又变BelowNormal→ 可逆- 优先级沿进程树继承:host
BelowNormal→ runnerBelowNormal→ powershellBelowNormal
踩过的坑(留给下一个人)
| 坑 | 表现 | 真相 |
|---|---|---|
| Job 内存限额偏移 | SetInformationJobObject 返回 0 |
x64 上 ProcessMemoryLimit 在 112、JobMemoryLimit 在 120(BasicLimitInfo 是 64 字节,不是 48)。写在 64/72 会被内核拒绝 |
AssignProcessToJobObject |
ERROR_ACCESS_DENIED(5) |
少 PROCESS_TERMINATE(0x1)。这个错误码完全指错方向,看起来像「没权限」 |
| 本进程已在 Job 里 | CREATE_BREAKAWAY_FROM_JOB 也返回 5 |
DSH 的沙箱已把进程放进 Job。不 breakaway 直接 assign 反而成功(Windows 8+ 嵌套 Job) |
| 短命进程采样 | 峰值报 2 MB | 0.3 秒就结束的命令一次都采不到;要读 Job 的 PeakProcessMemoryUsed |
| schemastery 依赖 | file:// 加载时报 Cannot find module |
Node 从插件目录向上找 node_modules,那里没有 DSH 的包 |
加了 dsh.client 却看不到读数条 |
插件在跑,底栏什么都没有 | dsh-client-modules 把「这个包不是客户端包」这个否定结论按 Loader specifier 缓存到进程结束(pkgMeta 从不清理)。第一次加 dsh.client 必须重启 Electron,HMR 救不了 |
手拼 /plugins/??<id>/client.js 拿到 404 |
连内置包也 404 | 该路由只认识已组合好的批次 URL:bundleResource 用完整的 pathname+search 去 responses / batchResponses 查表。要从首页 HTML 里取真实 combo URL |
客户端 fetch 报 Failed to fetch |
宿主路由明明在,浏览器就是打不通 | 桌面 shell 的页面是 dsh-app://app/。用宿主真实地址(streamBaseUrl)就是跨源,而插件路由不发 CORS 头。必须用页面同源的相对路径 |
| 路由返回 400 而不是 404/403 | 看起来像路径写错 | 在 trustOf 里直接写 ctx.connection —— cordis 里访问未声明服务的属性本身会抛,被 webserver 统一转成 400。要用 ctx.get('connection') 并包在 try 里 |
改了插件的 lib/*.js 却像没生效 |
HMR 没反应 | HMR 只监视 profile 的 cordis.patch.yml / package.json。改插件代码后要 touch 一下 patch 才会重新 import |
| 面板半透明、读数看不清 | 数字后面透出对话正文 | --dsw-specific-menu 本身是半透明的(浅色 #f8f9fa94 / 深色 #43454a73)。只写 background 不够,必须同时给 backdrop-filter: var(--dsw-menu-backdrop-filter) —— DSH 自己那三块面板都这么写 |
读数值想着「排在上下文环右边」,改 order 没用 |
还是在最左边 | 上下文环不经过 slot,是 InputBar 在 dock 末尾硬编码渲染的。slot 的 order 只在 slot 内部排序;要越过它得给自己加 CSS order(slot 外壳是 display:contents,我们的根节点就是 dock 的 flex item) |
| DSH 的占比在图上「消失」 | 明明在用,条里看不见蓝 | 百分比被夹成整数:0.4% → 0 → 被 filter(share > 0) 丢掉。占比要保留一位小数 |
| 「其他已用」算出负数 | 蓝段溢出轨道 | DSH 的提交量可能大于整机已用物理(换页 / 共享页)。必须先把 DSH 夹进「整机已用」,再算「其他 = 已用 − DSH」 |
| 验收脚本「全 PASS 却退出码 1」 | PowerShell 把 stderr 当失败 | spawnSync(cmd, args, { shell: true }) 会发 DEP0190 警告到 stderr。改成把整条命令行交给 ComSpec、参数数组留空 |
| 门禁验收随机通过或失败 | 机器空闲时等级是 normal | 阈值跟着本机实测内存走,护栏就不触发。验收的输入要钉死:设成不可能达到的值,critical 才是确定状态 |
目录
lib/pressure.js 纯逻辑:分级、迟滞、重活判定、决策(无副作用,可单测)
lib/guardian.js 运行时:采样、装/撤可逆限制、门禁
lib/win32.js Win32 能力层:内存读数、优先级、亲和性、冻结、Job
lib/config.js StandardSchemaV1 配置校验
lib/index.js 插件入口:pre-execute 门禁 + resource_guard 工具 + 读数路由
lib/tree-stats.js 整树读数:Toolhelp 枚举、按提交量求和、CPU 按 GetSystemTimes 差值算
lib/host-api.js /rampart/api 的 HTTP 处理器(stats / gc / 404 / 405)
lib/gc.js 本进程 V8 堆的强制回收(不需要 --expose-gc 启动参数)
client/source.js 浏览器半边:读数条 + 分段占比面板(ESM 源码,人读的那份)
lib/client.js 上面那份经 scripts/build-client.mjs 机械转换后的产物(serve 层加载这个)
scripts/build-client.mjs 只改 import/export 的构建脚本,转完会跑一次真语法校验
scripts/make-icon.py 生成 assets/icon-{1024,180}.png(深蓝底 + 白色城垛)
scripts/launch.mjs 启动壳 CLI
scripts/launcher.mjs 启动壳实现(Job 整树硬顶)
scripts/verify-*.mjs 真机验收(可逆性 / Job 内核语义 / 启动壳 / 真 profile / 读数条 / 降级)
scripts/bench.mjs 热路径性能基准(gate / tick / 整树采样 / 纯函数决策)
scripts/soak.mjs 可靠性浸泡(泄漏、并发门禁、迟滞抖动、dispose 幂等)
scripts/preview-panel.mjs 用无头 Chrome 把面板截成图(验观感,不跑 DSH)
scripts/*-probe.js 只在 .verify profile 里挂载的探针
scripts/probe-*.cjs 一次性诊断探针(错误码、偏移量),留着是因为它们是那些教训的证据
test/ 72 条(pressure 19 / tree-stats 16 / guardian 14 / config 10 / integration 5 / client 契约 8)
发布相关的信息(命名取舍、npm/GitHub/市场三处各要什么、格式 1 索引示例)见
PUBLISHING.md。
一个必须改的地方:重活名单
默认名单是 pwsh / bash / shell / workflow / subagent / subagent_fork / send_message / web_fetch / web_search / job_*。
判断标准是「会不会拉子进程或扇出并发」。如果你的常用流程里有某个被误伤,在 patch 里覆盖:
- id: rampart
name: 'dsh-rampart'
config:
heavyTools: ['pwsh', 'bash']
heavyPatterns: []
注意 interrupt_agent 故意不在名单里 —— 压力大时取消子代理是降压动作,不该被拦。
No comments yet. Be the first to write one.