dsh-go-unrestricted(破甲)
给 dsh-go(以及任何 DSH 引擎)装一段每步都在场的解除限制段:模型不再按默认倾向拒答、不再附免责声明、不再给"更安全的替代方案",缺参数时自己填占位符继续做。
一句话:这是 dsh-purge v1.7.14 在 Android / dsh-go 上的等价实现 —— 88 条执行补丁、25 层自检、三个可选模式、四个对话内配置面板,一条命令装完。
它解决什么问题
| 你遇到的现象 | 卡在哪 | 这一版怎么处理 |
|---|---|---|
| 模型先讲「我不能」「出于安全考虑」,再讲正事 | 提示词层的倾向,不是能力 | 提示词层:三路注入(事件瀑布 / section / AGENTS.md 兜底) |
| 让它写文件,宿主先弹「是否批准」 | dsh-user-approval 的 approval waterfall |
#9 直接回 allowed-once、#10 默认策略改 never |
| 沙箱拒写、命令被 bwrap 拦住 | confine() / checkedTarget() |
#15 沙箱直通、#16 文件围栏禁用 |
| 想覆盖或删除已存在的文件,它非得先读一遍 | fs-observation-policy 的 writeIntent |
#17 观察策略放行 |
| 宿主升级后某层悄悄失效,没人知道 | 没有统一体检 | 25 层自检(22 层硬闸 + 注入 + 权限 + 布局) |
| 想要「红队」「无限制」两套人设 | 宿主没带对应预设 | 三个可选模式 + 四个配置面板 |
这一版(1.3.4)在提示词层之外又铺了三层,四层合起来才是完整的「破甲」:
| 层 | 管什么 | 代码 |
|---|---|---|
| 提示词层 | 愿不愿意做、怎么做、怎么说 | lib/directive.js + 三路注入(下表) |
| 引擎层 | 能不能做:88 条执行补丁(审批门、沙箱阶梯、bwrap 直通、fs 围栏、先读后改闸门…),与 dsh-purge v1.7.14 逐字同一份表 | lib/purge/** + lib/engine.js |
| 自检层 | 25 层逐层核实哪层真通、哪层断了(22 层硬闸 + 注入 + 权限 + 布局) | lib/selfcheck.js |
| 可选模式 + 配置页 | 三个模式(无限制 / 无限制·子任务 / 红队)与四个对话内配置面板 | presets/** + lib/panel/** |
装完之后,引擎每一轮的系统提示词里都带着这段授权文本。三条路同时铺,哪条通走哪条:
| 路 | 机制 | 生效场景 |
|---|---|---|
| 主路 | 在 system-prompt/assemble waterfall 里直接改写 assembly.sections / assembly.contexts |
有事件总线的引擎(dsh-go 本机引擎、桌面版) |
| 次路 | systemPrompt.section() 注册一个独立段,名字 dsh-purge:unrestricted,order 40 |
有 systemPrompt 服务的宿主 |
| 兜底 | 把同一段写进 $DSH_HOME/AGENTS.md 的标记块 |
宿主连 systemPrompt 都没有;dsh-agent-instructions 会把它作为持久基线消息注入 |
装
路径 0:从 Release 一条命令(推荐,不用传文件)
手机终端(proot 里通常没有 curl,用 node 自带的 fetch):
node -e "fetch('https://github.com/lingsmall-code/dsh-go-unrestricted/releases/latest/download/install.sh').then(r=>{if(!r.ok)throw new Error(r.status);return r.arrayBuffer()}).then(b=>{require('fs').writeFileSync('/root/install.sh',Buffer.from(b));console.log('已下载',b.byteLength,'字节')})"
cd /root && bash install.sh
装完彻底退出 dsh-go 再打开(宿主只在启动时读插件与补丁),然后:
cd /root/.dsh/dsh-go/plugins/dsh-go-unrestricted && node lib/engine.js selfcheck
看到 自检 · 宿主 20/20 层 · 2 不适用 · 注入 1/1 · 权限 1/1 · 补丁 xx/xx · 全通过 就是装好了(数字随宿主版本变化,关键是「全通过」)。
路径 A:单文件安装脚本(没 adb、没网络也能用)
把 dist/install.sh 弄进手机(微信传文件、数据线拷、下面那条局域网命令都行),在 dsh-go App 的「终端」里:
bash install.sh
它会写出插件全部文件到 /root/.dsh/dsh-go/plugins/dsh-go-unrestricted,追加 AGENTS.md 兜底块,然后调 dsh plugin --profile web add。可选参数:--dir <目录> 换位置、--no-fallback 不写兜底块。
路径 B:本机起一个只读小服务,手机浏览器下载
在电脑上(仓库根目录):
node tools/serve.mjs 8899
它把本目录的 dist/install.sh、zip、源码列成一个下载页,并打印局域网地址(如 http://10.132.91.99:8899/)。手机连同一个 WiFi,浏览器打开就能下载,再在 App 终端里 bash install.sh。
路径 C:装 zip
unzip dsh-go-unrestricted.zip -d /root/.dsh/dsh-go/plugins/
dsh plugin --profile web add /root/.dsh/dsh-go/plugins/dsh-go-unrestricted
装的是指向目录的链接,别删目录,否则插件起不来。装完即生效不用重启;改了 lib/ 要重启引擎。
用
- 输入框上方状态条显示
破甲 ON/破甲 OFF,点开就是破甲面板;加号菜单里也有「破甲配置」入口。 - 面板显示:注入开关、本次运行改写了几次、上一步是否真的带上、
AGENTS.md路径与写入状态、指令字数、引擎层全量补丁(88 条)的落地情况(已就位 / 待打 / 锚点不匹配 / 宿主无此文件 / 本次生效)、25 层破甲自检结论与断点、三个可选模式的落盘状态;按钮:切换开关 / 写入或移除兜底块 / 复制指令全文 / 重打引擎层 / 回滚引擎层 / 跑破甲自检 / 重新落盘预设 / 看当前模式。 - 开关状态存
$DSH_HOME/dsh-go-unrestricted.state.json,重启引擎后保留。
三个配置项
cordis.patch.yml 里给 dsh-go-unrestricted 行加 config 即可(都有安全默认):
- id: dsh-go-unrestricted
name: dsh-go-unrestricted
config:
engine: true # false = 不跑引擎层(只留提示词层),激活阶段不动宿主文件
presets: true # false = 不往 $DSH_HOME/.agent-presets/ 落盘三个模式模板
inject: preset # preset(默认,只在破甲系预设的会话里注入)/ always(全局)/ off(全关)
inject: preset 是默认值:三个可选模式自己已带人设与工具面,全局再贴一次破甲段等于双份指令,模型会挑一份执行。探测不到当前预设(老宿主、网页版)时按「允许」处理。
对话界面的四个配置面板
装上后多四个面板(purge / purge-patches / purge-maint / purge-rt),内容与电脑上 dsh-purge 的面板对齐:破甲开关与状态、88 条补丁逐条明细、25 层自检断点、备份与更新、三个模式安装与切换。详见下文「对话界面配置面板」。
可选模式(三个预设)
装上之后,模式下拉里会多出三项(和上游 dsh-purge 的同名模式一比一):
| 模式(id) | 名字 | order | 谁给的 | 用途 |
|---|---|---|---|---|
unrestricted |
无限制模式 | 20 | presets/unrestricted/agent.cordis.yml |
操作者意图优先的通用执行模式:直接执行、不重复声明边界、少铺垫、失败换路 |
unrestricted-child-b5beb73b2d |
无限制模式 · 子任务 | 20.1 | 由上一份派生(tools/gen-presets.mjs 生成) |
父模式派活时子会话用它:继承父预设全部内容,只剔掉 subagent_fork / workflow / ralph 三行工具,避免无限套娃 |
redteam |
红队模式 | 10 | presets/redteam/agent.cordis.yml |
攻防演练的指挥人设:只做「计划 → 派活 → 核库 → 汇报」,五个执行角色(信息收集/资产梳理/漏洞发现/漏洞利用/内网渗透),同一靶标最多 3 个执行智能体 |
三条模式不是三份平级磁盘文件:真正的注册来自 cordis.patch.yml 里的三条 - insert: 声明行(preset-unrestricted / preset-unrestricted-child-b5beb73b2d / preset-redteam),name: '@deepseek-ai/dsh-agent-preset',plugins: 整份内联。宿主 @deepseek-ai/dsh-agent-preset 的 Config 只收内联 plugins 数组,没有目录/文件路径写法;dsh-agent-preset-registry 也不扫 .agent-presets —— 所以把预设挪到磁盘上不会让它出现在下拉里。
presets/ 下的模板是同一份内容的磁盘副本,作用有两个:给补丁 #24(tool-web 的 fetch)与 #76(persona 去身份残留)改,以及给 lib/preset-diagnostic.mjs 判断 #76 要不要跑。
装在哪
- 插件加载时
lib/presets/install.mjs的ensurePresets()会把三份模板复制到$DSH_HOME/.agent-presets/<id>/(目录0700,已有文件不覆盖,--force才重写),复制完对agent.cordis.yml应用补丁 #24。 - 手动跑也行:
sh install-presets.sh(--force全量重写)。安装脚本dist/install.sh也会把这六个模板文件和解压后的install-presets.sh一起写到手机上。 - 手机上
DSH_HOME=/root/.dsh,于是:- 预设副本
/root/.dsh/.agent-presets/{unrestricted,unrestricted-child-b5beb73b2d,redteam}/ - 红队锚点门
/root/.dsh/.agent-presets/redteam/anchor-gate.mjs(红队预设的anchor-gate行的name就是写死这个file://路径) - 技能目录
/root/.dsh/skills与/root/.dsh/redteam/skills(红队预设的customSkillDirs是字面量,不用dshHomePath())
- 预设副本
- 桌面端等价路径
%USERPROFILE%\.dsh\.agent-presets\<id>\。
注册依赖哪几个宿主包
| 包 | 缺了会怎样 |
|---|---|
@deepseek-ai/dsh-agent-preset |
三条声明行都挂不上 —— 模式下拉里没有这三个模式 |
@deepseek-ai/dsh-agent-preset-registry |
同上(它提供 agentPresets 服务) |
@deepseek-ai/dsh-tool-subagent-control |
派活三行(tool-subagent / -list-agents / -control)挂不上;子任务模式基本就废了 |
@deepseek-ai/dsh-tool-executor-native |
只有 bash/pwsh 两行不可用,其余照常 |
@deepseek-ai/dsh-workflow-ptc |
workflow 那行不可用(workflow-worker-thread 本来就是 disabled: true) |
一行包缺失 = 那一条预设被 registry 判 broken 并从下拉里过滤掉(不是整个插件崩)。所以红队预设里已经删掉了 redteam-tools 行(本插件不带 SQLite 资产库),人设里对 redteam_* 工具的依赖也一并改写成「自己维护 notes/ + 原生工具」——否则手机端一装就是 broken。
改预设内容
# 改 presets/*/agent.cordis.yml 之后必须重新生成声明行、子预设与安装脚本
node tools/gen-presets.mjs
node --test test/presets.test.js # 会校验声明行与模板是否还一致
node tools/make-installer.mjs && node tools/verify-installer.mjs
tools/gen-presets.mjs 是唯一事实来源:它从父模板派生 unrestricted-child-b5beb73b2d(id = unrestricted-child- + sha256(JSON.stringify({id,deny})) 前 10 位,和上游 migratePresetDefinition 同一套算法),把三份 plugins 右移 10 空格内联进 cordis.patch.yml,并生成 install-presets.sh。手改 cordis.patch.yml 会在下次生成时被覆盖。
引擎层(执行能力)
提示词层只管「愿不愿意做、怎么说」。宿主自己的审批门、沙箱升级阶梯、bwrap 包装、fs 写围栏、先读后改闸门不动,模型想干也干不到。
所以 1.2.0 起引擎层不是自编的子集,而是整份 dsh-purge 补丁集:lib/purge/patches/*.mjs(88 条:提示词 27、代码 49、工具 9、兼容 3)与 lib/purge/core/*.mjs(判定内核)从 dsh-purge-community v1.7.14 逐字节复制,只换了两处 import(extract-asar → 普通 fs、surface → 精简版)。锚点、替换文本、marker、expect 命中数契约全部原样保留。
闸门类的七条(也就是执行能力的关键):
| # | 名字 | 改了什么 |
|---|---|---|
| 9 | APPROVAL_AUTO_GRANT |
approval/request 直接返回 allowed-once,不走 waterfall、不 fail-closed |
| 10 | APPROVAL_CONFIG_DEFAULT_NEVER |
审批配置默认值 ask → never,配合 #9 让 never 不再等于拒绝 |
| 13 | ESCALATION_WIDENING_EXEMPT |
删掉「必须严格更宽」与「必须有审批服务」两道 throw |
| 14 | ESCALATION_GRANT_UNCONDITIONAL |
approveEscalation() 无条件返回请求的 mode |
| 15 | SANDBOX_CONFINE_PASSTHROUGH |
confine() 直通,不包 bwrap/seatbelt,denialSignatures 清空 |
| 16 | FS_FENCE_DISABLED |
checkedTarget() 直接 return target,read-only / workspace-write 都不再拒写 |
| 17 | FS_OBSERVATION_INTENT_FREE |
writeIntent / editIntent / requirePresent 全部 return void 0 |
其余 81 条覆盖:反问式免责声明与审批话术(#1-8、11、12)、预设人格与「你是 coding agent」身份句(#4、25、26、43、70、71、73、76、80)、桌面壳更新/关闭按钮(#45、46、83、84、82)、hooks 三个宿主 Site(#29-31、64、65)、agent-loop 的工具结果修剪与原生 steering(#55、74、67、68、87、88、90)、settings 现代事件 API(#42)、沙箱策略文案(#61、93)、读取上限放宽(#22)、子代理层数(#23)、ask_user 超时(#89)、cookie 名忽略端口(#91)、宿主 header 上限(#92)等。
行为约定:
- 双向幂等:补丁表与判定函数都是 dsh-purge 原件,谁先打都一样。本机 dsh-purge 打过的文件在这里直接判
already,一个字节都不写(实测真实宿主:88 条里 68 条 already、0 条本次生效)。 - 判定用 dsh-purge 的原式,十级优先级原样保留:
forbid反向锚点 →markersAny→marker→replace首行;另有 #17 / #61 / #93 的结构性判定、#42 的 settings 现代 API 判定、#90 的 native steering 判定。没有自编「pattern 不匹配就算已应用」的捷径。 - 命中数契约:补丁带
expect时实际命中数必须完全相等,否则整条拒写并记hit_mismatch:<expected>!=<actual>——绝不把不相干的第二处一起改掉。 - YAML 折叠块清洗:改
cordis.patch.yml一类文件时套sanitizeYamlPersonaScalars(),只删可丢弃的样板段,有保留段就改写成|-字面块重排缩进。只换首行会把 6 空格残段留在 YAML 里,dsh 直接起不来。 - 备份与回滚:首次改动前留
<file>.dshpurge.bak(wx独占创建,已有就不覆盖)。revert有备份就回灌并删备份,没有备份的文件一律不动。 - 可选项补丁默认不打:#18 重复调用守卫、#19 工具结果修剪关的是上下文保护而不是权限闸门,默认
off。要点名开启:DSH_GO_OPT_IN=18,19 node lib/engine.js apply,或选项optInIds: [19]。 - 改完要重启宿主。插件跑在宿主进程内,模块已被 import 缓存,改文件对当前进程无效。
- 失败不外抛:找不到宿主包、锚点随版本漂移、目录只读,一律记状态并继续——提示词层照旧生效,也不会把 dsh-go 桥接的违规计数打满。
config.engine === false可整段关掉引擎层,只保留提示词注入。
命令行也能单独用:
node lib/engine.js status # 只读探测,不写盘
node lib/engine.js apply # 打补丁(幂等)
node lib/engine.js revert # 回滚
宿主目录自动探测顺序:DSH_GO_AI_BASE → DSH_AI_BASE → require.resolve('@deepseek-ai/dsh-user-approval') → process.execPath 同级 resources/app/dsh → argv[1] 反推 → $DSH_HOME/profiles/*/node_modules/@deepseek-ai(含 pnpm 布局)→ npm 全局 → 常见安装根扫一层(%LOCALAPPDATA%\Programs、C:\Program Files、D:\applications、/opt、/usr/lib …)。也可以直接传路径。
传进去的路径会被归一:给 @deepseek-ai 目录本身也行,会自动还原成「含 node_modules 的宿主根」——否则 pi-ai 一类要从 aiBase/node_modules 扫的补丁会整条落空(实测少 3 条)。
手机端
dist/install.sh打的是同一份代码(26 个文件,含lib/purge/**),引擎层在那边照样跑。若 App 把宿主包压在 asar 里、探测不到,就安静降级成纯提示词层。
关
面板上点「关闭破甲」→ 下一回合恢复默认提示词,AGENTS.md 块保留(不写就不注入)。
要点:先移除兜底块,再删插件目录。兜底块是 AGENTS.md 里的普通文本,插件被删掉之后没人替它清理,它会继续通过 dsh-agent-instructions 生效。手删也行——删掉 <!-- dsh-go-unrestricted:begin 到 <!-- dsh-go-unrestricted:end --> 之间即可,别的内容不受影响。
为什么这么写(踩过的坑)
段名必须带
dsh-purge前缀。dsh-system-prompt在存在complete: true段("预设即完整提示词"那种预设)时这样重建段列表:sections: [completeSection, ...transformed.sections.filter(s => s.name.startsWith('dsh-purge'))]也就是说,非
dsh-purge开头的段在那种宿主里被整批丢掉。所以段名取dsh-purge:unrestricted,同时contexts那条路在两种引擎版本里都基本原样保留,两条腿各走一条。interpolate: false是必须的。DSH 的{{name}}插值很严格:未知变量、或text.indexOf("}}", open+2) >= 0这类畸形组都会 throw,一抛就是整个组装失败、整个回合起不来。指令正文里绝不能出现裸的{{,注册段时再显式关掉插值做双保险。complete段不能新增第二个。assemble()发现两个complete: true直接 throwmultiple complete prompt sections are active,所以只在sections数组上做"删同名再加"的幂等操作,绝不设complete。必须能在没有
systemPrompt服务的宿主里安静降级。sdk/plugin-kit的check --load只造tools/sessions/commands三个 stub 服务,没有systemPrompt;插件一上来就inject(['systemPrompt'])会让加载校验直接失败。所以用ctx.get('systemPrompt')做可选获取。顶层
inject不能写dshGo。桥接服务只在带桥接的宿主里存在,写进顶层inject会让网页版 / SSH 主机上整个插件起不来。正确写法是ctx.inject(['dshGo'], scoped => …)。不抛错。dsh-go 桥接对每个插件做违规计数,回调抛错/超时/返回不合格记 1 次,连续 5 次本次运行降级成网页版。所有桥接调用各自 try/catch,面板渲染失败也不影响注入。
图标名要挑真实存在的。组件库那 162 个图标里没有
shield,写了它这一条就不显示。这里用wrench/tool/power/save/copy。
自检
node --test test/inject.test.js # 8 项:段顺序、complete 重建保留、context 末位、幂等、无总线、无 dshGo、开关、空装配体
node --test test/agents.test.js # 7 项:新建、追加不覆盖、幂等、移除还原、无块不动、截断块清理、无裸 {{
node --test test/engine.test.js # 11 项:全量 88 条在册、路径归一、open→applied、marker 落盘、二次幂等零写入、opt-in 默认关、revert 还原、无宿主/缺文件降级、summaryLine
node --test test/panel.test.js # 16 项:四面板进预算、bind 唯一、v1 降级、全失败仍合法、超预算折叠且保住开关、deferred apply、写盘回 patch、预设中文名、未知动作不抛、collectState 全失败不抛、注册四面板
node --test test/selfcheck.test.js # 11 项:22 层硬闸在册、四条豁免规则、状态折算、无宿主不假装通过、归一化、版本判定、注入层、权限层、25 层口径、自检行文案
node --test test/presets.test.js # 11 项:模板与上游 sha256 一致、改造点白名单、元数据派生规则、三条声明可解析、installPreset 三分支、非法名抛错、ensurePresets 隔离失败、currentPreset 三级兜底、install.sh 收齐模板
node test/render-real.mjs # 调真实 dsh-system-prompt 的 renderPrompt(),看最终提示词里到底有没有
node tools/verify-installer.mjs # 校验 dist/install.sh 里 45 个 heredoc 与源文件逐字节一致
node <dsh-go>/sdk/plugin-kit/bin/check.mjs . --load --lang zh # 退出码 0
破甲自检(25 层)
lib/selfcheck.js 是 dsh-purge lib/selfcheck.js 的等价移植:层定义、状态折算、豁免规则、hints 文案逐字照搬,所以两边结论能对上。
25 层 = 22 层硬闸(按补丁 id 分组,顺序即显示顺序)+ 运行时提示词注入 + 当前 profile 的权限选择 + 宿主磁盘布局:
| # | 层 | 补丁 |
|---|---|---|
| 1 | 沙箱 confine 直通 | #15 |
| 2 | 文件系统围栏禁用 | #16 |
| 3 | 观察策略放行 | #17 |
| 4 | 拒绝话术中性化 | #6 #33 #48 |
| 5 | 默认策略 | #7 #8 #10 |
| 6 | 审批自动放行 | #9 #35 |
| 7 | Auto Review 不拦工具 | #59 |
| 8 | 工具审批 fail-open | #60 #62 #61 |
| 9 | 计划模式不硬拦 | #63 |
| 10 | 官方身份剥离 | #4 #25 #43 #71 #76 |
| 11 | Web 面身份中性化 | #32 #53 |
| 12 | 注入不被预设覆盖 | #40 #74 #55 #72 |
| 13 | 极简/梁神预设保留注入 | #26 #28 |
| 14 | 外部内容不降级为只读 | #29 #50 |
| 15 | Hooks 不否决回合 | #30 #31 #64 #65 |
| 16 | 工具上限放宽 | #21 #22 #23 |
| 17 | 预设与子代理不设锁 | #24 #37 |
| 18 | 新会话与日志默认 | #51 #52 |
| 19 | 回退不残留 | #56 #58 #75 #77 |
| 20 | 模型网关兼容 | #47 #49 #57 #69 |
| 21 | 会话消息投递与挂载 | #87 #88 #90 |
| 22 | 桌面宿主启动兜底 | #82 #83 #84 |
层的 state 只有五种:ok / pending(点一次「重打引擎层」就好)/ broken(锚点真的漂了,要改表)/ unknown(拿不到状态,不假装通过)/ na(这一层在这台机器上没有对象)。
na 的判据有四条,缺一不可混淆:
- 整层补丁都
optional/settleWhenAbsent,且目标文件确实不存在(例:Hooks 不否决回合); - 整层补丁的
surfaces不含当前面(例:web 面上的桌面宿主启动兜底); - 单条补丁的目标文件不在(
missing_file+optional),或状态是na/off/disabled; - 布局层在非桌面宿主上恒为
na。
na 层不算通过也不算断点:自检 · 宿主 20/20 层 · 2 不适用 里那个 2 就是它们,分母不变、通过数不注水。
没有归属到 22 层里的补丁(本机实测 32 条)不进 22 层分母,但面板会单独报「另有 N 条补丁不属任何命名层(已就位 X,未落实 Y)」,selfCheck() 返回值里也有 orphan: { total, applied, unapplied, ids }——避免分母被悄悄缩小。
自检缓存带宿主指纹(四个核心包的 package.json + lib/index.js 的 mtime/size)。宿主换版本后 status().selfcheck.stale 变 true、行尾追加「(缓存已过期)」,并顺手在后台重跑一次——不会拿旧结论说「全部在位」。
node lib/engine.js selfcheck # 逐层清单 + hints;exit 0 = 全通过,1 = 有断点
node lib/engine.js check # 同上
node lib/engine.js doctor # 同上
面板里也有「破甲自检(25 层)」一行与「跑破甲自检」动作,结论与本机 dsh-purge 一致(同一份补丁表 + 同一套判定)。
check.mjs 退出码:0 无错误 / 1 有条目不生效 / 2 原生部分整体不显示 / 3 读不了文件。
注意 node --test <目录> 会报 Cannot find module,必须传具体文件。
对话界面配置面板
桌面版 dsh-purge 的「破甲 / 引擎配置」界面在对话里也有一份:四个面板 + 七个折叠段,数据全部来自本机真实模块(lib/engine.js、lib/selfcheck.js、lib/presets/*.mjs、lib/agents.js、lib/directive.js、状态文件),面板自己不存任何状态,也不另造一份真相。
四个面板各管什么
| 面板 id | 标题 | 管什么 |
|---|---|---|
purge |
破甲 | 四个开关(破甲总开关、引擎层、opt-in #18/#19)、一行引擎状态、应用 / 还原 / 刷新三个动作;身份段给提示词字节数、「复制提示词」、「写入 / 移除 AGENTS.md 兜底块」 |
purge-patches |
补丁与自检 | 补丁分组计数 + 漂移 / 待打 / 未开启三组清单(#id 名字 → 状态)、自检小结与 25 层明细、「跑破甲自检」 |
purge-maint |
备份与更新 | 备份目录、时间戳、文件数、占用,还原全部 / 刷新;插件版本、宿主版本、自检等级、宿主路径 |
purge-rt |
预设模式 | 无限制 / 红队 / 无限制-子任务 三个预设的安装状态、用户目录、说明,一个「安装 / 重置」动作 |
注册在 lib/panel/index.js 的 registerPanel(ctx, dshGo, deps) 里,PANEL_IDS 就是上表四个 id。接入方(lib/index.js)只需要:
import { registerPanel } from './panel/index.js'
ctx.inject(['dshGo'], (scoped) => registerPanel(ctx, scoped.dshGo))
dshGo 只能出现在 ctx.inject(['dshGo'], …) 里,不能进顶层 inject,也不能写 'dshGo?'——写错会让没有桥接的宿主整插件起不来。
注册成功不等于出现在界面里:package.json 的 dshGo.contributes.panels 还得有对应 id。宿主每个插件最多声明 4 个面板,五个以上后面的直接被忽略(panels allows 4 per plugin),所以这里刚好四个:purge / purge-patches / purge-maint / purge-rt;状态条与加号入口都指向 purge。
七段折叠
七个段固定顺序:破甲 → 状态 → 补丁 → 自检 → 备份 → 更新 → 身份(预设段在 purge-rt 里单独成段)。四个面板只是把同样这七段分配到不同入口,手机上不用在一个超长页面里滑到底:
purge:破甲、状态、身份(钉住破甲与状态)purge-patches:补丁、自检purge-maint:备份、更新purge-rt:预设、身份(钉住预设)
上限与降级策略
面板 DSL 有硬预算,build.mjs 在拼装时自己算,test/panel.test.js 用 assertBudget 逐项盯:
| 项 | 上限 | 超过时怎么降级 |
|---|---|---|
| 组件节点 | 120 | 从最后一个非关键段开始折成一行 list,逐段重算直到达标 |
| 输入控件 | 32(每个视图) | 四个开关各占 1 个输入,补丁 / 自检 / 备份页面 0 个输入,天然够用 |
| JSON 字节 | 32768 | 同上折叠;列表本身已按 45 行截断(PATCH_ROW_LIMIT),先由行数兜住 |
| 嵌套深度 | 8 | 最深层是「accordion → list → 行」,实测 3 层 |
| 单个 stack/row 子组件 | 32 | 每段 children ≤ 8 |
降级时钉住的段永不折叠:破甲 / 状态 / 预设这三段里装着开关和安装按钮,折掉就等于面板废了;宁可略微超预算也不能让人点不到开关。已折叠的段会写明「明细用 CLI 查看」,并保留一句摘要(比如「漂移 6 · 待打 6 · 未开启 8」)。
老宿主(只认 v1 组件)走本地 adaptView:toggle / accordion / textField 一律退化成文本行,actions 行直接消失,版本号从 v:2 降到 v:1。所以低版本宿主里看到的是纯只读明细,动作请用 CLI。
其它几条硬规则:
- 没有密码输入框。宿主侧
plugInputNoPassword会把obscureText/inputType: 'password'判成错误,所以面板里不出现任何密钥输入:只显示「已配置 / 未配置」+「清除」。 bind必填。开关不写bind点不动;同一视图内bind不能重复,四个开关的键固定是data.runtime.enabled、data.runtime.engine、data.optIn.p18、data.optIn.p19。- 慢动作走 deferred。应用 / 还原 / 自检要写几十个文件,超过 3 秒的回调会被判违规,所以 dispatch 立刻返回
{pending: {label, timeoutMs: 55000}, result},真值回来再refresh。同一插件同时最多 2 个 pending。 - 文件变化自己通知。宿主不替插件存面板状态,
index.js用fs.watchFile盯状态文件、引擎报告、AGENTS.md,fs.watch盯备份目录与预设目录,200ms 去抖、全量 emit,最密 4Hz、单次 ≤32 KiB(超了先截摘要再退最小模型)。 - 预设模块可能还没就绪。
lib/presets.js/lib/presets-plugin.js用动态import()+ try/catch 取,取不到就在预设段显示「预设模块未就绪」,绝不让注册段整体失败。
这些事必须用 CLI
手机上做不了(或做了危险)的操作,面板只给一行提示,不假装能做:
node lib/engine.js apply # 打补丁(几十个文件,面板里走 deferred,超 60s 请用这条)
node lib/engine.js revert # 还原全部
node lib/engine.js status # 只读探测
node lib/engine.js selfcheck # 25 层逐层清单 + hints
DSH_GO_OPT_IN=18,19 node lib/engine.js apply # 点名开 opt-in 补丁
- 重启宿主:改完宿主包必须重启 App 才生效,面板没有、也不该有重启按钮。
- 超过 60 秒的扫描:deferred 的上限是 60 秒(这里取 55 秒),更久的活走 CLI。
- 证据原图:面板只能引用
asset:或 ≤64 KiB 的data:图,脱壳 / asar 里的原图导出请用 CLI。 - asar 回写:只读,不回写。宿主的包在 asar 里时面板显示「探测不到」。
- 挑时间点还原:备份目录只列时间戳和文件数,还原只有「全部」一种,不做单点回滚。
- 超预算的明细:折叠行会把完整清单留给 CLI(
node lib/engine.js status/selfcheck)。
结构
package.json dsh.bundle.patch + dshGo 清单(状态条 1 · 面板 4 · 加号工具 1 · 回合尾注 1)
cordis.patch.yml insert: id=dsh-go-unrestricted + 三个预设声明(preset-unrestricted / preset-unrestricted-child-b5beb73b2d / preset-redteam)
presets/unrestricted/ 无限制模式模板(preset.yml + agent.cordis.yml,落盘后被 #24/#76 处理)
presets/unrestricted-child-b5beb73b2d/ 派生子预设模板(由 tools/gen-presets.mjs 生成,勿手改)
presets/redteam/ 红队模式模板(preset.yml + agent.cordis.yml + anchor-gate.mjs 锚点门)
presets/upstream-baseline.json 上游 dsh-purge v1.7.14 五份原件的 bytes + sha256(模板溯源)
install-presets.sh 把三个预设模板落到 $DSH_HOME/.agent-presets/(生成物,勿手改)
lib/presets/files.mjs 预设模板落地:0700 / 只补缺 / force 重写 / 落盘后应用 #24
lib/presets/install.mjs ensurePresets():三个模式逐个落盘,单个失败不抛
lib/presets/current.mjs currentPreset():当前会话预设 id 的三级兜底探测
lib/directive.js 指令正文 + 段名/order 常量 + AGENTS.md 块模板
lib/agents.js $DSH_HOME/AGENTS.md 标记块读写(不覆盖原内容,写前备份)
lib/index.js apply():三路注入 + 引擎层自动应用 + 面板/状态条/尾注
lib/engine.js 宿主定位 / 补丁执行 / 备份回滚 / status / summaryLine / CLI
lib/engine-patches.js 兼容层:re-export lib/purge 的 ALL_PATCHES
lib/purge/patches/ dsh-purge v1.7.14 的补丁表原件(index/prompt/code/engine/new-tools/opt-in/stability/shared)
lib/purge/core/ 判定与替换内核原件(shared.mjs 十级判定 / verify.mjs applyReplacementsToText 与 rollup)
lib/purge/targets.mjs 71 个文件键 → 宿主真实路径(含 pnpm / preset / 深搜 pi-ai)
lib/purge/apply.mjs applyPatches / revertPatches / inspectPatches / .dshpurge.bak / opt-in 门
lib/purge/yaml-clean.mjs YAML 折叠块清洗(sanitizeYamlPersonaScalars + 7 个辅助)
lib/purge/anchors.js ~/.dsh/dsh-purge/anchor-overrides.json 锚点覆盖,与 dsh-purge 共用同一份配置
lib/purge/steering.js #90 native steering 迁移判定
lib/purge/atomic-files.js 原子写 / 原子拷贝
lib/purge/fs-plain.js 普通目录版本地 fs(替代 asar 层)
lib/purge/surface-lite.js 场景判定(web / desktop)
lib/selfcheck.js 25 层破甲自检(22 层硬闸 + 运行时 + 权限 + 布局)
lib/permission-state.mjs profile 里的权限预设解析(不依赖 yaml 包)
lib/preset-diagnostic.mjs 预设正文只读核对(#76 not_needed / skipped 精化)
lib/panel/build.mjs 面板 DSL 拼装(纯函数 buildView / assertBudget / adaptView / 预算降级)
lib/panel/state.mjs collectState:汇总引擎 / 自检 / 预设 / 运行时 / 备份(可注入 deps)
lib/panel/actions.mjs dispatch:apply / revert / refresh / selfcheck / preset / optin / copy / toggle
lib/panel/index.js registerPanel:注册 purge · purge-patches · purge-maint · purge-rt + 变更广播
dist/install.sh 自包含安装脚本(由 tools/make-installer.mjs 生成,勿手改)
tools/gen-presets.mjs 从模板生成子预设 + cordis.patch.yml 的三个声明 + install-presets.sh
tools/make-installer.mjs 从源码重新生成 install.sh
tools/verify-installer.mjs 校验 install.sh 与源码一致
tools/serve.mjs 只读 HTTP 服务,给手机浏览器下载用
test/ 注入单测 + AGENTS 单测 + 引擎层单测 + 自检单测 + 面板单测 + 真实渲染器验证
No comments yet. Be the first to write one.