dsh-reverse-mode
Verifieddsh-reverse-mode · v0.3.4 · MIT
Reverse-engineering agent preset for DeepSeek Harness: a REA (rea-agents) MCP bridge mounted only by the 逆向模式 preset
Install
dsh plugin add dsh-reverse-mode Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Tags
Readme
dsh-reverse-mode
DeepSeek Harness 的 「逆向模式」agent 预设:在标准工具箱之上挂载 REA(rea-agents)的逆向工程能力,并且只在选择该预设的会话里挂载。
本包是 dsh-rea(MIT,作者 JeremyWangCY)的二次开发分支:把它从"全局宿主插件"改造成 agent preset 子插件,并优先复用 harness 自带的官方 MCP 桥。
为什么不是给 dsh-rea 加开关
Agent preset 是每个会话独立的 Cordis 插件树:预设里挂载的插件,其 ctx.tools / ctx.systemPrompt 注册会继承预设的 scope,因此只对使用该预设的 Agent 可见。所以「只在逆向模式触发」不需要任何运行时布尔开关:
standard/ptc/minimal/cordis会话完全不加载本插件,REA 的工具 schema 一次都不会出现;- 选了「逆向模式」的新会话才挂载桥接行、拉起
npx rea-agents、注册mcp__rea__*(官方桥)或rea__*(内置桥)工具; - 现有会话保持它们创建时的组合,切换预设只影响新建会话。
用哪个桥
本包有两条桥,配置项 bridge 默认 auto,但预设行预置的是 builtin(原因见下):
官方桥(@deepseek-ai/dsh-mcp-client) |
内置桥(lib/mcp-stdio.js) |
|
|---|---|---|
| 工具筛选 | ❌ 没有 allow-list,注册全部 ~170 个 | ✅ entry(9) / navigate(30) / analyze(17) / 显式数组 |
REA 的 $defs/$ref schema |
❌ 原样注册,pi-ai 的 legacy 投影会丢 $defs → 部分模型 HTTP 400 |
✅ 注册前把 $defs 内联进 properties |
| 模型侧 schema 成本 | ~436 kB ≈ 10.9 万 token / 次请求(全量) | 全量内联后 ~547 kB ≈ 13.7 万 token;entry ~11 kB ≈ 2.8k token |
| 工具名前缀 | mcp__rea__… |
rea__… |
| 追加能力 | SDK 分页、工具表变化订阅、注册失败整代回滚、canonical structuredContent/图片保真、取消与超时细化 |
体积小、零依赖、只实现用到的 MCP 子集;同样清理环境变量(丢弃 KEY/PASSWORD/SECRET/TOKEN 与 DSH_*) |
| 来源 | harness 自带;运行时从 ~/.dsh/profiles/node_modules 解析到宿主那一份(不复制、不新增依赖) |
本仓库 |
auto 的规则:只有没有筛选需求时(tools: all 或 null)才选官方桥;请求了层级名或数组一律走内置桥——否则官方桥会把 9 个工具悄悄放大到 170 个(约 39×)。官方桥解析不到时(例如 ~/.dsh 布局不同)自动退回内置桥并告警,只写日志、不会让预设挂载失败。
为什么预设默认用内置桥
REA 的 170 个工具里有 158 个用 $defs + $ref 写 schema。pi-ai 对不声明 strict tool sampling 的模型,会把工具 schema 投影成 { type, properties, required },把 $defs 丢掉,于是 $ref: "#/$defs/…" 变成悬空引用,provider 直接返回:
HTTP 400 {"model":"deepseek-flash"}
实测(opencode-go deepseek-flash-1017):原始 schema 200、legacy(丢 $defs)400、legacy + $defs 200。标准工具箱没有 $ref,所以只有逆向模式会撞上。内置桥在注册前内联引用,因此对 strict 与非 strict 模型都能用;官方桥没有这个钩子。
presets/reverse.patch.yml 预置 bridge: builtin + tools: all。想要省上下文的档位,把那一行改成 tools: entry;如果你的模型支持 strict tool sampling 且你更想要官方桥的能力,可显式设 bridge: official:
config:
bridge: official
tools: all
与上游 dsh-rea 的差异
| 方面 | 上游 | 本分支 |
|---|---|---|
| 挂载位置 | profile 顶层的宿主插件行(全局:所有会话都付 schema 成本) | 「逆向模式」预设的子插件行(scope 内:只有该预设付费) |
| 模式提示 | 只有一条启动日志 | 额外的 scoped systemPrompt 段(lib/guidance.js):模式说明、目标路由、证据纪律、许可与执行边界,并跟随实际前缀(mcp__rea__ / rea__) |
| 桥 | 官方 @deepseek-ai/dsh-mcp-client(v0.2 起) |
默认也走官方桥;解析不到时退回零依赖内置实现(lib/mcp-stdio.js) |
| 工具面 | 全量 170(v0.2 起取消 tier) | 官方桥同样全量;内置桥可按层级筛选(lib/catalog.js 是上游 v0.1 目录的逐字快照,上游 v0.2 已删除该文件) |
| 环境自检 | 无 | lib/environment.js + scripts/doctor.mjs:连接后调用 REA 的 doctor,只读地报告引擎现状,缺失时在日志与模式提示里给出安装指引 |
为什么需要"运行时解析":profile 插件用 dsh plugin add link:<dir> 安装时是符号链接,Node 的 ESM 解析会跟随到真实路径,裸模块名于是从本仓库目录解析——那里没有 harness 的包(~/.dsh/profiles/node_modules 不是工作区的祖先)。lib/official-bridge.js 因此用 createRequire 锚定启动器提供的共享 node_modules(它是指向 DSH 安装目录的符号链接),拿到宿主自己的那一份官方桥,因此本仓库仍然零依赖、仍然可以就地编辑。代价是锚点依赖 ~/.dsh 布局,所以这个函数永不抛错:解析失败就回退内置桥。
安装
已发布到 npm:
dsh plugin --profile web add dsh-reverse-mode
开发时也可以从源码以 link: 方式安装(改完直接生效,方便就地编辑):
git clone https://github.com/xingxue-ux/dsh-reverse-mode.git
dsh plugin --profile web add 'link:/absolute/path/to/dsh-reverse-mode'
插件管理器会把声明了 dsh.bundle 的包自动选中为 profile bundle,其 presets/reverse.patch.yml 随即在启动时 insert 出 preset-reverse-engineering 声明。也可以在 Web 侧边栏的 Plugins 页里安装同一个包名或本地路径。
安装后:重启或等 HMR 重载 → 新建会话 → 在预设选择里选「逆向模式」。
安装后自检(环境与逆向工具)
DSH bundle 没有安装钩子,所以本包提供一条只读的自检命令,安装完(或换机器后)跑一次:
npm run doctor # 中文报告
node scripts/doctor.mjs --json # 机器可读
node scripts/doctor.mjs --local # 不启动子进程,离线最快
它先调用 REA 自带的 rea doctor(权威、与 version 绑定),失败时退回本地快速探测(只读环境变量与常见安装路径),并标明结论来自哪一侧。报告分两部分:
- 必需:Node 版本、REA 能否经
npx拉起;归档 / 安装包 / JavaScript / Electron / 托管程序集分析不需要分析引擎。 - 原生二进制深挖(可选引擎):Hopper / Ghidra / IDA 的现状、解锁的能力与安装指引。
缺失引擎不会让模式不可用,只影响原生二进制的伪代码、反编译与交叉引用;把引擎接进来只需在 rea-bridge 行加 env(改完重载即生效,无需重启):
- id: rea-bridge
name: dsh-reverse-mode
config:
env:
GHIDRA_INSTALL_DIR: /opt/ghidra_12.1.1_PUBLIC # Ghidra 12.1.x,需要 JDK 21
# HOPPER_LAUNCHER_PATH: /Applications/Hopper Disassembler.app/Contents/MacOS/hopper
# REA_IDA_MCP_CONFIG: /path/to/ida-mcp-registration.json
引擎由使用者自备并自行授权(Ghidra 免费;Hopper / IDA Pro 需商业授权)。本包不安装、不升级任何引擎,npm run doctor 也只读取环境。除手动运行外,插件会在 REA 连接成功后自动做一次同样的检查:缺失时在宿主日志写下 dsh-rea: environment check: no native analysis engine found …,并把「无原生引擎、如何启用」并入模式系统提示;已就绪时只记一条 info,提示段不加环境说明(避免无谓的 token)。
配置
编辑 presets/reverse.patch.yml 里 rea-bridge 行的 config(改完需重载):
| 字段 | 默认值 | 含义 |
|---|---|---|
enabled |
true |
设为 false 时保留挂载但不注册工具、不拉 REA 进程 |
bridge |
'auto' |
auto / official / builtin;见上节。auto 只在 tools: all/null 时用官方桥 |
tools |
'entry' |
层级名 entry(9) / navigate(30) / analyze(17)、REA 工具名数组,或 'all' / null 表示全量。只有在内置桥下才生效:官方桥没有 allow-list,会注册全部 ~170 个(~10.9 万 token/次);预设行现在预置 all + builtin |
version |
'6.4.0' |
固定的 rea-agents 版本,保证工具名与 schema 稳定 |
runner |
'npx' |
启动 REA 的包运行器(两条桥都用它) |
env |
{} |
追加给 REA 子进程的环境变量(如 REA_ANALYSIS_PROVIDER) |
toolCallTimeoutMs |
300000 |
单次工具调用超时;原生分析可能很慢 |
startupTimeoutMs |
120000 |
首次 npx 下载 + initialize 握手 + tools/list 的预算 |
guidance |
true |
是否注册「逆向模式」系统提示段 |
checkEnvironment |
true |
REA 连接成功后做一次环境自检(复用 REA 的 doctor),结果写日志并(无引擎时)并入模式提示;只读、非致命 |
environmentCheckTimeoutMs |
60000 |
单次环境自检的预算;超时只影响这条报告 |
failOnStartupError |
false |
true 时 REA 起不来会拒绝激活该预设挂载;默认降级为「没有 REA 工具」 |
验证
- 新建「逆向模式」会话,问一句「你现在能调用哪些 REA 工具」,或直接让它调用
open_binary; - 该会话的宿主日志应按顺序出现:
dsh-rea: bridge=official (…, serverName=rea, namespace=mcp__rea__) resolved from <路径>(或bridge=builtin …);dsh-rea: registered N/M REA tools (x%, ~yk model-visible bytes)(内置桥)/ 官方桥自己注册全部工具;dsh-rea: environment check: …(无引擎时 warn,就绪时 info);
- 新建一个
standard会话,确认工具表里没有任何mcp__rea__/rea__前缀的工具; - 想单独确认环境:
npm run doctor(安装后自检,见上;--strict可在无引擎时返回 1)。
事后核查(直接读会话日志)
node test/read-session-log.mjs ~/.dsh/sessions/<workspace>/<session-id>/session.v4.jsonl.zstd --tools
输出给出该会话真实绑定的预设、REA 工具定义的数量与名单。三个容易误判的坑,这个脚本都绕开了:
- 会话日志是多帧 zstd:
zstdDecompressSync()只解第一帧(即 session header),长会话会看起来只有 198 字节、一行、且"预设 = standard"; - header 里的预设不可信:那是创建时刻写入的,真正生效的绑定在投影文件
~/.dsh/storages/session_projcache/sessions/<id>.json的record.rows.agentPreset; - 字面统计
rea__会被对话正文污染:只要在会话里聊过 REA,字面命中就很高,但"工具是否注册给该会话"只能看 JSON 中的"name":"mcp__rea__…"(官方桥)或"name":"rea__…"(内置桥)+"description"形态。
注意事项
- 原生分析需要外部引擎:Hopper / Ghidra / IDA 需自行安装并持有合法授权;静态 JavaScript/Electron 分析不需要引擎。
- 官方桥必须来自"正在运行的这一份":解析锚点(
DSH_PROFILE_DIR/DSH_HOME/~/.dsh/profiles)逐个尝试后,还会把结果与realpath(process.argv[1])推导出的运行中 DSH 安装做一致性校验;不在同一安装里的包会被跳过且永不执行(防"陈旧的~/.dsh/profiles指向另一套 DSH")。检查在import()之前完成。 - 降级是显式的,且不会静默断供:官方桥解析不到、或解析到却启动失败(版本不匹配、Config 变化等),都会写一条 warn 并自动改用内置桥;失败原因会一并打印(
not usable (<原因>))。因此看到bridge=builtin就说明发生了降级——功能仍在,少的是官方桥的健壮性与 env 清理。注意官方桥路径会await首次连接(上游同款),所以预设激活会等 REA 起来或失败,而不是后台挂载后静默留空。 - 官方桥下
tools不生效:它会注册 REA 声明的全部 ~170 个工具(~436 kB ≈ 10.9 万 token/次)。要低成本的层级筛选,必须bridge: builtin。 - 官方桥 + 非 strict-tool 模型会 400:官方桥原样注册 REA 的
$defs/$refschema,而 pi-ai 的 legacy 投影会丢掉$defs,导致悬空引用(实测 opencode-godeepseek-flash-1017返回400 {"model":"deepseek-flash"})。因此预设默认bridge: builtin;内置桥会在注册前内联引用。 - 两条桥都会清理环境:名字匹配
KEY/PASSWORD/SECRET/TOKEN的继承变量与DSH_*会被丢弃,只有你在env里显式写下的变量会传给 REA;引擎变量(GHIDRA_INSTALL_DIR、HOPPER_LAUNCHER_PATH、REA_IDA_MCP_CONFIG)不属于被清理的模式,照常生效。 - 预设的子插件列表是完全替换式的:本文件的列表是标准预设的快照,DSH 升级后若标准预设新增插件,需要手动同步过来。
- REA 自身可能提示结果超出 stdio 传输预算,此时用
export_evidence_bundle或 REA CLI 取回:npx -y rea-agents@latest <command>(工具前缀依桥而定:官方桥mcp__rea__,内置桥rea__)。 - 工具名与
version绑定;内置桥下未知名字只告警并跳过,官方桥下工具表由 REA 的tools/list决定。
迭代这个包
改完 lib/*.js 后必须重启 dsh web(或重启 harness 进程)才生效:本 profile 的 hmr 行沿用 base bundle 的默认值 root: [],即只监听 profile manifest 与 patch 文件。patch 变化时 dsh-hmr 只比对内容后调 reconcileProfilePatches() 更新 Loader entries,不清 ESM loadCache,所以插件源码改动会在同一个 URL 上命中模块缓存、继续跑旧代码;配置改动(如预设行的 config)则能即时生效。
排查"预设加载失败"时,重启后终端会打印具体细节,格式为 <行 id> (<名字>): <detail>,例如 rea-bridge (dsh-reverse-mode): …。
想边跑边改源码,可给 hmr 行加 root: ["."],但会监听整个 profile 目录(含 node_modules),通常不值得。