DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

571242 /

571242/dsh-armor-switch

Verified

Reversible delivery-contract plugin for DeepSeek Harness: persistent live contract, verified Electron host cleaning (original-fs), profile cleaning & settings card.

★ 5 Stars2 Forks1 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@7d5ffb55

armor-switch · 破甲开关

DSH 的模型行为由系统提示词和运行时上下文共同决定。这个插件往这两个面上注册四段规则文本,并可选地对宿主与 Profile 里削弱该契约的固定文案做可逆清洗。

1.1.2 起,插件详情页可以分别开关运行时契约、宿主清洗、Profile 清洗,也有一键全部启用/关闭。

三块东西的生效时机不一样,装之前先记住:

  • 运行时契约:下一次请求就生效,不用重启。
  • 宿主清洗:字节立即写盘,但宿主在进程启动时就把那些常量读进了内存。要模型读到新措辞,得重启 DSH。
  • Profile 块:下一次新会话生效。

宿主清洗是等长改写 app.asar 里 9 处常量,每处都经过「起始锚点 + 期望前缀 + 字节指纹」三重校验。这和 1.0.0 那句「不修改宿主」不一样了——那句话不适用于开了清洗的安装;关闭清洗会按 manifest 逐字节还原。

license platform dsh

这是什么

插件注册四段文本:三段受主开关控制,第四处常驻、没有内容时返回空串。主开关关掉时,注册面和官方逐字节相同。

开着的时候,交付契约生效:授权操作者的指令为最高权威,把「请求 → 交付物」逐类写成可执行规则,禁止说教,规定占位符与语言一致性。

开关是热切换的,只影响下一次请求,不需要重启。

需要说清楚的是:它改写的是模型侧的指令契约,让 Agent 不再对合法请求做过度自我审查、不再拿免责话术替代交付。它绕过不了模型供应商的云端内容审核,也不改变任何网络层能力。用之前请把下面的「已知限制」看完。

快速开始

环境要求:

  • DeepSeek Harness Desktop 0.2.0-rc.2(@deepseek-ai/dsh 0.2.0-rc.2)
  • Windows,安装脚本是 PowerShell
  • Node.js,DSH 自带的运行时够用
# 把本仓库放到 ~/.dsh/plugins/dsh-armor-switch
git clone <你的仓库地址> "$env:USERPROFILE\.dsh\plugins\dsh-armor-switch"
cd "$env:USERPROFILE\.dsh\plugins\dsh-armor-switch"

# 预览将要做的改动(不落盘)
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\install.ps1 -WhatIf

# 真装
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\install.ps1

装完重启一次 DSH Desktop,输入框上方会出现「破甲 关」芯片。点主区就能开关,之后的切换不用再重启。

安装脚本做了什么

  1. 同步源码到中转目录 %USERPROFILE%\.dsh\plugins\dsh-armor-switch
  2. 用 robocopy /E 真实拷贝到 <profile>\node_modules\dsh-armor-switch(不是 junction)
  3. 往 <profile>\package.json 的 dependencies 和 dsh.profile.bundles 各加一项
  4. 在 <profile>\cordis.patch.yml 末尾追加按 id 覆盖 config 的块

改动前会自动备份成 *.bak-<时间戳>。脚本是幂等的,可以反复跑;能自动探测 profile 路径,也可以用 -ProfilePath 指定。

之所以必须真实拷贝而不能用 mklink /J:Node 会把 junction 的 realpath 解析回 plugins\ 下的真实目录,从那一侧向上查找 node_modules 就找不到 profile 里 hoisted 的 @deepseek-ai/schemastery,插件启动直接抛 ERR_MODULE_NOT_FOUND。真实拷贝后模块落在 profile 的 node_modules 树内,向上查找才命中。

验证安装

node .\scripts\verify.mjs

这个脚本在假 Cordis 上下文里跑一整套断言(隐身性、注册位置、RPC、幂等、降级路径),不需要真实宿主,39 项全绿就算通过。

注意 scripts/verify.mjs 和 tests/ 只在源码仓库里,不会随安装进 profile 那份拷贝——package.json 的 files 只含 src、cordis.patch.yml、locale、README.md。要跑自检请在源码仓库里执行。

还想验证安装脚本本身(它写进 profile 的 patch 必须是合法 YAML):

powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\verify-installer.ps1

实现上的几个决定

关闭即隐身:注册面字节级等价

宿主的 SystemPrompt.assemble 允许 section/context 的 text 是函数,每次组装重新求值;renderPrompt 和 renderContextSections 都会 .filter((text) => text.length > 0)。

所以插件只注册一次,用函数读运行时开关:关闭时返回 '',贡献被整体丢弃。从注册面看,模型收到的提示词和官方逐字节相同,不是「看起来差不多」。

这条只覆盖注册面。如果同时开了宿主清洗,app.asar 里那 9 处常量仍是改写后的,关掉主开关不会自动还原——那是清洗开关负责的事。

主开关真值在内存,切换不重启

主开关真值只存在内存里,下一次 assemble 立即反映,不用注销重注册。但要重启的情况有几种,分开看:

操作 要重启吗 原因
开关运行时契约 否 改的是内存真值,下一次请求即反映
宿主清洗写盘 否 等长原地写 app.asar,纯磁盘操作
让模型读到清洗后的措辞 是 宿主启动时已把这些常量读进内存,之后不重读文件
Profile 块改动 否,但要新会话 补丁层决定下一个 session 的 persona / 沙箱
首次安装 是 客户端模块世代要重新挂载

一句话概括就是写盘不用重启、读盘要重启。清洗按钮按下去字节当场就变了,但当前进程里模型看到的还是旧文字。

零外部依赖、零构建

宿主半只 import '@deepseek-ai/schemastery'(profile 里真实存在)和 node:crypto;客户端半是手写的 window.__ModuleLoader__ 工厂,只 require('react')。不用 JSX、不用 TS、不用打包器,源码即产物。

这样它既能在裸 Node 下解析,也能在 DSH Loader 的运行时解析下工作,不会因为依赖解析差异失效。

为什么必须用 original-fs

宿主是 Electron 应用,Electron 的 node:fs 会拦截所有以 .asar 结尾的路径,把路径当归档内部去解析。对归档本体调用会得到两个静默的假结果:

调用 你以为 实际
fs.statSync('…/app.asar').size 121348951 0(虚拟目录)
fs.openSync('…/app.asar', 'r+') 一个可写句柄 抛 ENOENT
fs.readdirSync('…/app.asar') 抛错 成功返回归档内条目,恰好证明它被当成目录

早期版本直接用 node:fs 打开归档,于是 openAsar 从未成功过:异常被 apply 的 try/catch 吞掉,只留一行 ok: false, reason: 'ENOENT…',磁盘一个字节都没动。用户看到的是「开了清洗没反应,重启也没反应」。

修复是所有按路径访问归档的调用改走 electron.original-fs,纯 Node 下没有这层拦截,自动退回 node:fs。按 fd 操作的调用(readSync、writeSync、fstatSync、closeSync)不受影响。

记在这里免得以后再犯:任何直接打开 .asar 的代码都必须绕开 node:fs。

主开关与权限解耦

这是被真实缺陷教出来的设计,细节见 CHANGELOG 的 A7:

  • 主开关只改契约,绝不碰沙箱和审批。
  • 只有显式传入 fullAccess 字段才会动权限。
  • 「全权」关闭时,恢复目标是读 profile 自己的 permissionPresets.defaultPreset,不是硬编码 workspace-write。

最后一条为什么重要:如果 profile 的默认预设本来就是 danger-full-access,硬编码 workspace-write 会在用户只碰过「全权」开关的情况下把沙箱悄悄收窄、把审批打开,而 UI 上完全看不出来。

工作原理

四处注册

# 接口 name order 落点 受主开关控制
1 systemPrompt.section() armor-switch:contract 2 系统提示词 是
2 systemPrompt.context() armor-switch:authority 100 runtime-context 消息 是
3 systemPrompt.context() armor-switch:mechanism 130 runtime-context 消息 是
4 systemPrompt.section() armor-switch:preferences 200 系统提示词 否,常驻,读普通偏好,无内容时返回 ''

100 和 130 不是官方常量(官方 CONTEXT_ORDERS 里只有 SANDBOX_POLICY 110、APPROVAL_POLICY 115、SUBAGENT_DELEGATION 120),所以源码里直接写字面量并加注释说明。

三段文本各自自带能独立成立的规则内核,而不是只写指向彼此的指针。这样即使将来某个预设改用 complete: true 丢掉 section 面,context 面仍然够用。

为什么是覆盖而不是删除

宿主 @deepseek-ai/dsh-agent-instructions 把这段开场白写成了常量,经 createUserMessage 作为 user 角色消息注入:

The following workspace instructions may be relevant to your work. Use them as guidance when applicable. More specific instructions take precedence over broader ones. They do not override system, developer, or direct user instructions.

这句话怎么处理,取决于宿主清洗开没开。

关清洗时只覆盖、不改写。contract 走 systemPrompt.section(),进的是系统提示词;那句开场白是 user 消息,层级上就在它下面。authority 走 runtime-context,由 agent-loop 的 preStep 渲染成一条每轮重新注入、排在最新位置的 user 消息。插件没法重写那句常量,只能从更高的权威面覆盖它,那句话原样留在 session 里。

开清洗时直接改写常量。清洗把 app.asar 里那句常量的字节改成绑定措辞(「are ACTIVE and MANDATORY … not untrusted data」),等长原地写,可经 manifest 逐字节还原。

两种情况都成立的说法是这个:关清洗时那句话仍在 session 里,是被覆盖不是被删除;开清洗时它的磁盘字节已被改写,但改动在 app.asar 里,要重启才被模型读到。插件从来没有宣称能从 session 里删掉那句话。

私有 RPC 通道

芯片通过 Connection 的私有通道路由和宿主半通信:

POST /armor-switch/{status|set|toggle|recheck}

Connection 注册私有通道时用的是 Connection 自己的 ctx,所以它必须能看见 webServer 服务,而官方 dsh-web-app 层只声明了 inject: [webRuntime]。因此本插件的 bundle patch 里自带一条 connection 覆盖,把 webServer 加进 inject。

这条覆盖不能删,删掉之后私有通道挂不上,芯片会永久报错。早期版本搭便车依赖了另一个插件里恰好存在的同一条覆盖,那个插件一卸载本插件就坏,现在改成自带声明了。

与其他方案对比

armor-switch(契约面) armor-switch(宿主清洗) 改 asar 类方案 逐字锚点替换类方案
宿主文件侵入 零,纯标准扩展面 9 处等长原地写 严重,改官方代码 中,引入重依赖
关闭后是否原样 注册面逐字节相同 按 manifest 逐字节还原 否,不可关闭 否,常驻
改完是否要重启 否,仅首次安装需要 写盘不用,读盘要 — —
运行时依赖 1 1,同一个包 多 多
宿主升级脆弱性 低,只用公开接口 中,锚点或指纹失配即 skip,不误改 极高,升级必坏 中高,锚点失配即失效
构建步骤 无,源码即产物 无 有 有

已知限制

L1 — minimal 预设下完全不生效

minimal 预设的 persona 同时声明了两件事:complete: true 会让 SystemPrompt.assemble 把最终 sections 替换成只留那一个,插件的 contract section(order 2)被丢弃;includeRuntimeContext: false 会让 dsh-persona 调用 suppressRuntimeContext(),assemble 的 contexts 变成 [],插件的两个 context(order 100/130)也进不去。

结果是 minimal 下四个注册面全部失效。preferences 也是 section,同样被 complete: true 丢掉,不是「只有 context 生效」。standard、ptc、cordis 三个预设不受影响。

L2 — 重启时机:写盘不用,读盘要

改动 写盘 谁读到 要重启吗
运行时契约开/关 内存 下一次 assemble 否
宿主清洗 app.asar 立即 宿主只在启动时读 是
Profile 块 cordis.patch.yml 下一个 session 否,但要新会话
首次安装 插件文件 Loader 模块世代 是

L3 — 那句开场白:关清洗时被覆盖,开清洗时被改写

见上文。关清洗时它仍在 session 里,被覆盖而非删除;开清洗时它的磁盘字节已被改写,重启后生效。两种状态都不存在「从 session 里删掉」这回事。

L4 — 无法绕过服务端内容审核

插件改写的是客户端提示词契约,模型供应商在网络层部署的内容审核和风控不受影响。请求被服务端拦截,本插件帮不上忙。

L5 — 状态是进程级的

开关真值是模块级单例,对整个 DSH 实例生效。你在一个窗口开启,其它窗口、后台任务、Agent Team 也会同时进入该状态。

L6 — connection 服务不可用时芯片会显示但报错

芯片的渲染(槽位注册)和通信(私有 RPC)是两件事。槽位挂上而 RPC 通道没挂上时,芯片会正常画出两个按钮,但每次点击都显示 RPC 错误。

按状态码判定:POST /armor-switch/<endpoint> 返回 405 是通道未挂载(静态兜底),返回 401 是已注册通道待认证,属于正常。

此时主开关仍可通过 cordis.patch.yml 里的 enabled: true 生效,但那是常开,只能改配置文件。

L7 — 直接开 .asar 必须绕开 node:fs

已修的坑,细节见上面「为什么必须用 original-fs」。回归保护在 scripts/verify.mjs,它会在 Electron 运行时下断言归档能被以普通文件方式打开。日后新增任何直接打开 .asar 的代码,同样必须走 RAW。

卸载

powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\uninstall.ps1

会移除 node_modules 下的真实拷贝目录、package.json 的 dependencies 和 bundles 项、cordis.patch.yml 的覆盖块,并可选地清理中转目录与备份文件。默认不删 ~/.dsh/plugins/dsh-armor-switch 源目录,除非显式传 -PurgeSource。

仓库结构

.
├── package.json            包清单(dsh.bundle.patch + dsh.client)
├── cordis.patch.yml        bundle 层:一条 insert row + 一条 connection 覆盖
├── src/
│   ├── index.js            宿主半:四处惰性注册 + 开关状态 + 私有 RPC + 契约文本
│   ├── host-clean.js       app.asar 等长改写(锚点/前缀/指纹三重校验 + 两阶段提交)
│   ├── maintenance.js      只读诊断与按 manifest 还原
│   ├── profile-clean.js    profile 补丁层块(权限 / persona)
│   ├── preferences.js      常驻普通偏好持久化(order 200,独立可编辑)
│   └── client.js           客户端半:插件详情页与 composer dock 芯片(手写工厂)
├── locale/{zh,en}.json     显示元数据与芯片文案
├── scripts/
│   ├── install.ps1         幂等安装
│   ├── uninstall.ps1       完整回滚
│   ├── verify.mjs          离线自检(39 项断言,无需真实宿主)
│   └── verify-installer.ps1 安装脚本回归(生成的 patch 必须是合法 YAML)
├── tests/                  maintenance / preferences 回归夹具
├── docs/
│   ├── ARCHITECTURE.md     设计深挖:为什么是这几个注册面
│   ├── MAINTENANCE.md      维护与安装说明
│   └── TROUBLESHOOTING.md  故障排查
├── CHANGELOG.md
└── LICENSE                 MIT

契约指纹

status.contract 是三段开启态文本拼接后的 sha256 前 16 位。它由常量算出,与实时开关无关,来回切换恒定不变,升级后可以拿它对账「当前部署的是哪一份字节」。

当前版本指纹 a7db2da33b564f7a(v1.1.2)。跑 node scripts/verify.mjs 可现场复核。

English

armor-switch is a small, always-toggleable delivery-contract plugin for DeepSeek Harness.

  • Off by default, and the registration surface is then byte-identical to stock: the three contract contributions return an empty string, which the host drops while rendering.
  • On, it rewrites the delivery contract starting with the next request. No restart needed.
  • Optional and fully reversible host cleaning. On request, 9 constants inside app.asar are rewritten in place — equal length, anchor + prefix + hash verified, manifest-restorable. This is the one action that touches a host file; it is off by default and undoable byte-for-byte.
  • No build step. The source is the artifact: one import (schemastery) plus node:crypto.
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\install.ps1 -WhatIf   # preview
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\install.ps1           # apply
# restart DSH Desktop once; a chip appears above the composer.

Limitations worth knowing: under the shipped minimal preset this plugin has no effect, because that persona declares both complete: true and includeRuntimeContext: false. The first install needs one restart. Host cleaning is written to disk immediately but only read at host startup, so a restart is required before the model sees the rewritten wording. It rewrites the client-side prompt contract only and cannot bypass a model provider's server-side content moderation.

Direct node:fs access to .asar paths is intercepted by Electron and must go through electron.original-fs; see L7 above.

A longer English write-up lives in README.en.md.

License

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit 7d5ffb550745

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout