首页
社区
课程
招聘
[原创] 我们给 DeepSeek Harness 加了一个逆向模式:逆向 Agent 的未来,也许就是 Harness 插件
发表于: 59分钟前 53

[原创] 我们给 DeepSeek Harness 加了一个逆向模式:逆向 Agent 的未来,也许就是 Harness 插件

59分钟前
53

我们给 DeepSeek Harness 加了一个逆向模式:逆向 Agent 的未来,也许就是 Harness + 插件

图片描述
我们给 DeepSeek Harness 加了一个逆向模式。

dsh plugin add dsh-js-reverse-plugin

命令执行完,Agent 选择器里多出“逆向模式”。三个逆向 skill、js-reverse-mcp 和 reverse preset 一起安装完成。用户不需要先向通用 Agent 解释什么是 iv8、怎样保护原始文件、JSVMP 应该如何验收;选择这个模式,会话就按这套方法开始。

我们的插件原本已经支持 Codex 和 Claude Code。如果这里只是“又兼容了一个宿主”,补一段 README 就够了。

我们随后看了 DSH 的源码,又检查了安装后的真实 profile。结果很清楚:DSH 没有把第三方插件拦在 Agent 外面。npm 包可以进入产品的 bundle 层,preset 可以挂载一棵 Agent 级插件子树,模型、工具、会话、策略乃至 Agent Loop 也由插件提供。

本文讨论的是这套 Agent 架构。兼容只是表面结果,我们的逆向模式是一个现成的例子。

对于逆向 Agent,DeepSeek Harness 这种“Agent 由插件组成”的模式,不是更好的选择,而是唯一正确的模式。

图片描述

一、先看结果:一个 npm 包进入了 Agent 运行时

安装完成后,我们查看了本机的 $DSH_HOME/profiles/web/package.json。DSH 下次启动时读取的就是这份 profile:

{
  "dsh": {
    "profile": {
      "bundles": [
        "@deepseek-ai/dsh-base",
        "@deepseek-ai/dsh-web-app",
        "dsh-js-reverse-plugin"
      ]
    }
  },
  "dependencies": {
    "dsh-js-reverse-plugin": "^0.2.0"
  }
}

我们的包和 dsh-basedsh-web-app 并列出现在 bundle 列表中,已经成为整个 Web profile 的第三层配置。复制几份 Markdown 到此为止做不到这一点。

这个 cordis.patch.yml 插入两个运行项:

- insert:
    - id: js-reverse-plugin
      name: 'dsh-js-reverse-plugin'
      inject: [skills]
    - id: mcp-js-reverse
      name: '@deepseek-ai/dsh-mcp-client'
      config:
        transport: stdio
        serverName: js-reverse
        command: npx
        args: ['-y', 'js-reverse-mcp@4.0.1']

第一项加载插件自己的 index.js,把三个 SKILL.md 注册到 DSH 的 skill registry;第二项复用 DSH 的 MCP client,启动 js-reverse-mcp@4.0.1。安装完成后,skill 和 mcp__js-reverse__* 工具会同时出现,来源就在这两行配置里。

Agent 选择器里的“逆向模式”来自第三部分。包内的 preset.yml 被安装到 .agent-presets/reverse

name: 逆向模式
description: JS 逆向工程 Agent:证据驱动、逐步决策,配备 iv8 受控执行与 js-reverse 浏览器取证。
order: 2

安装动作也在 index.js 里。省去遍历和错误处理,只看两次注册:

for (const skill of collectSkills()) {
  ctx.skills.register(skill)
}
installPreset(join(presetUserRoot(), PRESET_ID))

到这里,我们只能证明这个包安装成功。它为什么能从“能力包”继续变成“Agent 模式”,要从 DSH 自己的插件树里找答案。

图片描述

二、沿着三段源码,看插件到底能走多深

DeepSeek Harness 官方 README 用一句话概括设计:everything is a plugin。发布后的作者反馈帖里,很快有人问:已经有这么多 Harness,为什么还需要一个新的?

如果 everything 只包括 skill、MCP 和 hook,这个问题没有得到回答。Codex 和 Claude Code 早就支持这些扩展。DSH 的差别出现在下面三段源码里。

第一段:profile 从空列表开始组装产品

profile.ts 中,profile 记录有序的 bundle 列表;每个 bundle 在 manifest 里指向自己的 patch。最终组装发生在 composeEntries()

export function composeEntries(
  layers: readonly PatchOptions[][], warn: (message: string) => void = () => {},
): EntryOptions[] {
  return applyEntryPatches([], structuredClone(layers.flat()), (message: string, ...args: unknown[]) => {
    let index = 0
    warn(message.replace(/%C/g, () => JSON.stringify(args[index++])))
  })
}

applyEntryPatches() 的第一个参数是 []。DSH 没有先造好一个产品再开放几个插槽;bundle、profile patch、home patch 和命令行 overlay 直接在空列表上叠出本次启动的配置树。

webheadless 也由这套流程产生:两者共同使用 dsh-base,再分别加入 Web UI 或一次性 runner。用户安装的 bundle 继续叠在上面。

运行 dsh --profile web --dump-config 就能看到结果:命令打印当前机器将要启动的完整配置树。模型适配器、工具、持久化、sandbox、审批、前端都在里面,后续 patch 可以按 id 替换任意一行。

架构文档对这件事的表述更加直接:There is no privileged core to patch. DSH 没有留下一块只能由官方修改的特权核心。

第二段:Agent Loop 也注册在这棵树上

如果 Agent Loop 仍然固定,everything is a plugin 只完成了一半。我们接着看 AgentLoop

export class AgentLoop extends Service implements AgentFactory {
  static inject = ['agents', 'sessions', 'llm', 'tools', 'systemPrompt']

  constructor(ctx: Context, config: Config) {
    super(ctx, 'agentLoop')
    ctx.effect(() => ctx.agents.setFactory(this), 'agentLoop.setFactory()')
  }
}

先看类声明:默认 Loop 是一个 Cordis Service。再看 inject:LLM、tools、session 和 system prompt 都由外部服务提供。最后一行 ctx.effect() 把它注册为 AgentFactory,插件卸载时这次注册也会撤销。

图片描述
于是换模型不用改 Loop,换工具管线不用改 Loop,换会话实现也不用改 Loop。默认 Loop 不合适时,替换提供 agentLoop 的插件即可。DSH 的扩展面穿过了请求组装、工具执行和下一步调度,不只停在函数调用列表。

第三段:preset 挂载的是插件子树

截图里的“逆向模式”是不是一段预设提示词?mountPreset() 可以回答。

省去错误文案,关键代码只有三步:

const scope = scopeOf(agentCtx)
if (scope === undefined) {
  throw new Error(/* registrations would apply to every agent in the process */)
}

const handle = agentCtx.plugin(PresetTree, config)

// 挂载完成并检查所有配置行之后
const leaked = leakedServices(agentCtx, fiber)
if (leaked.length > 0) {
  throw new Error(/* row(s) published process-global service(s) */)
}

先确认 agentCtx 有 scope,再挂载整棵 PresetTree,最后检查服务有没有泄漏到进程全局。三步中任何一步失败,preset 都不会发布。

身份也没有被写死在 prompt builder 里。dsh-persona 通过 ctx.systemPrompt.section() 注册 persona,只覆盖加入当前 preset 的 Agent。工具注册表同样支持 scoped registration;tools.presentAs() 还能让一个 preset 使用 Code Mode,同时让同一进程里的另一个 Agent 保持 native tools。

preset 在这里已经超出“角色模板”:它是一棵带作用域的 Agent 插件树。persona、tools、plan、compaction、subagent、workflow 可以一起挂载,也可以在整棵树卸载时一起撤销。

图片描述

还有一条约束:模型看见的内容必须能够重建

DSH 的 session log 不只是聊天记录。agent-loop invariant 会在 llm/stream 前重新派生 messages,并折叠 request header 中的 model、system prompt 和 tools,再逐项比较真实请求。任何模型可见内容如果无法从日志重建,运行时直接报错。

这条约束解释了为什么 session 也要进入插件体系。工具可以临时返回一个结果,但只要这个结果影响后续推理,它就必须进入可恢复的会话语义。Agent 恢复、fork、换 UI 时,事实不会因为提供它的插件已经离开调用栈而消失。

图片描述
三段源码和一条 invariant 拼在一起,DSH 的设计才完整:profile 组装产品,preset 组装 Agent,scope 隔离不同 Agent,service 提供可替换实现,effect 管理生命周期,session event 保存模型依赖的事实。

再回头看“为什么还需要一个新的 Harness”,答案就在这里:DSH 开放到了 Agent 本身,不只增加几处插件入口。

对于逆向 Agent,DeepSeek Harness 这种“Agent 由插件组成”的模式,不是更好的选择,而是唯一正确的模式。

三、为什么逆向会一路碰到 Agent 的核心

说“逆向任务变化很快”没有用。需要看这些变化最后落在什么地方。

假设目标是一个请求签名。最初看起来只需要浏览器工具:找到请求,设置 XHR 断点,查看调用栈,定位参数生成入口。js-reverse-mcp 可以完成这些动作。

定位之后,问题马上离开浏览器工具层。源码要不要修改?原始文件必须只读,格式化和 AST 变换只能进入派生副本。这是文件策略。

入口能不能直接在 Node.js 运行?不能。浏览器负责提供真实环境和对账样本,目标 JavaScript 进入 iv8 受控执行,Node.js 只做静态工程工作。这是执行环境。

入口后面如果是 JSVMP,Agent 还要保存 Bytecode、Handler、Opcode、CFG 和动态 Trace,并维护参考实现与独立实现的对应关系。这是会话状态和 workflow。

最后怎样算完成?服务器接受请求不能证明算法等价。独立实现必须和 iv8 中的参考实现精确相等。这又会改变任务停止条件和循环。

同一个“恢复签名”任务,已经连续穿过六层:

逆向要求 只写进提示词会怎样 DSH 中应该落在哪里
保护原始文件 模型记得时才遵守 filesystem policy
从真实请求取证 只有原则,没有现场 MCP tools
目标代码只在 iv8 执行 仍可能误用 Node.js runtime / tool policy
保存断点、样本和 Trace 压缩或恢复后容易丢失 session event
按阶段推进任务 靠模型自行维持顺序 workflow
精确等价后才结束 容易以“服务器接受”提前收工 workflow / Agent Loop

这张表给出了固定核心的问题。skill 和 MCP 可以增加知识与工具,却不能单独改变文件系统、会话语义和 Loop。于是所有约束只能继续堆进提示词:不要改原文件、不要用 Node.js、记得保存 Trace、服务器接受不算完成。

提示词能告诉 Agent 应该做什么,不能保证运行时只能这样做。逆向任务越复杂,这个差距越大。

图片描述
DSH 允许这些要求各自进入正确的插件:浏览器能力留在 MCP,方法和验收标准留在 skill,文件限制进入 policy,证据进入 session event,阶段推进进入 workflow。普通 AST 任务可以少装几层,JSVMP 任务可以继续加入 Trace 与专用循环。不变的能力继续复用。

逆向 Agent 因此不能是一颗固定核心,外面挂一圈工具。目标变化会穿过工具、环境、策略、会话和循环,Agent 自己必须能够跟着重新组合。

四、我们的插件提供了一次实际验证

在 DSH 出现以前,我们已经在 ai-reverse-resources 中维护 js-reverse-plugin。三个 skill 使用同一份源码,js-reverse-mcp 独立发布到 npm,Codex 和 Claude Code 各自读取自己的插件清单。

这三个 skill 也不是同一套提示词换三个名字。js-ast-deobfuscation 管源码保护、混淆画像和 AST 路线;iv8-env-runtime 管入口定位、浏览器环境补齐和受控执行;iv8-jsvmp-recovery 管 Bytecode、Handler、Trace 和精确等价验收。前一个任务的结果会成为后一个任务的输入,但每个 skill 可以独立升级。

那套插件已经解决了分发问题:一个版本可以同时把方法和浏览器工具送到多个宿主。但安装以后,Agent 的模型、会话、策略和循环仍然由宿主定义。

接入 DSH 时,我们没有重写这些成熟能力。bundle 注册原有 skill 和 MCP,reverse preset 把它们与 DSH 已有的文件、shell、plan、goal、compaction、subagent 和 workflow 组合起来。

图片描述
agent.cordis.yml 开头写着:The capability set is unchanged. 这一版沿用 standard preset 已经工作的通用能力集合,只加入逆向 persona,并接入我们的 skill 和 MCP。重复开发 shell、文件工具和任务系统不会让它更像一个逆向 Agent,正确的组合才会。

persona 中已经写明三条工作规则:

保护原始输入:目标文件一律只读;分析前先做格式化派生副本。
受控执行:动态证据通过 iv8 或 js-reverse MCP 取得;Node.js 仅作静态工程工具。
等价验收:“服务器恰好接受”不是证明。

它们恰好对应上一章的文件、执行和停止条件。第一版先把成熟的方法、工具和 DSH 通用运行时组合起来;以后增加文件 policy、逆向证据事件或专用 workflow,仍然由同一个 reverse preset 选择。

图片描述
从 DSH 发布到 dsh-js-reverse-plugin@0.2.0 上线,我们用很短的时间做出了一个可安装、可升级、可直接选择的逆向模式。安装后的真实 profile、真实 preset 和页面截图都能验证这件事。

这个速度来自清楚的分工:成熟的逆向能力保留自己的版本,DSH 的通用能力继续由 DSH 提供,reverse preset 只声明选择哪些组件以及怎样组合。三部分可以各自演进。

同样的办法也能继续加入 MCP 访问范围,甚至替换 Agent Loop。已有 skill 和 MCP 不需要推倒重来。

过去,我们的插件负责给 Agent 增加能力。现在,这个插件开始交付 Agent 的组成。逆向模式只是第一版,但路径已经通了。

五、从一个 npm 包,到一种可以版本化的 Agent

最后再看一次 dsh-js-reverse-pluginpackage.json。这次不看版本号,只看它交付了什么:

{
  "files": [
    "index.js",
    "cordis.patch.yml",
    "skills",
    "preset"
  ],
  "dsh": {
    "bundle": {
      "patch": "./cordis.patch.yml"
    }
  }
}

skills 装的是逆向方法,cordis.patch.yml 把 skill registrar 和 MCP client 插进运行时,preset 定义逆向 Agent 的组成,index.js 负责把前面几部分接到 DSH。MCP 也没有使用浮动的最新版,patch 明确写着 js-reverse-mcp@4.0.1

因此,0.2.0 交付的已经不是一个孤立工具。这个版本同时确定了方法从哪里来、浏览器工具用哪个版本、运行时增加哪些配置、界面里出现哪一种 Agent。安装后的 profile 又把 dsh-js-reverse-plugin: ^0.2.0 和当前产品组合记录在一起。包、配置和实际运行结果能够互相对上。

这件事改变了 Agent 的交付单位。

过去发布一个 MCP,解决的是“Agent 能调用什么”;发布一个 skill,解决的是“Agent 应该怎样做”。它们到了宿主以后,模型怎么选、会话怎样保存、任务何时继续、循环何时结束,仍由宿主决定。现在 reverse preset 把这些组件放进一个明确的 Agent scope,bundle 再把这份组合送进 profile。用户安装的是一个 npm 包,最后得到的是一种可选择的运行方式。

下一版怎样扩展,也能从现有源码直接推出来。

假设我们要让断点、样本、Bytecode 和 Trace 在恢复会话后继续存在。DSH 已经规定:新的模型可见事实进入 SessionEventMap,再由 session log 重建。我们只需要在 reverse bundle 中加入逆向证据事件及其插件,并让 reverse preset 选择它,不需要另写一套会话系统。

再假设 JSVMP 恢复需要专门的停止条件:参考实现与独立实现没有精确相等,任务就不能结束。默认 AgentLoop 本来就是注册到 AgentFactory 的 Service;新的 Loop 可以由 reverse bundle 插入专用 profile,在产品组装层替换默认 factory。三个现有 skill、js-reverse-mcp、文件工具和 shell 都可以继续使用,DeepSeek Harness 的核心代码也不用为逆向任务增加分支。

两个改造进入的位置并不相同:

新要求 DSH 已有扩展点 进入哪一层
断点、样本和 Trace 跨会话保存 SessionEventMap 与 session log reverse bundle 提供事件插件,preset 选择对应能力
JSVMP 精确等价前不得结束 AgentLoop / AgentFactory reverse bundle 在专用 profile 中替换 Loop provider

这不是对未来接口的猜测。前面已经从 DSH 源码确认了 session event、Agent Loop、preset scope 和 bundle patch 的位置;我们的 0.2.0 又确认了这些配置能够随 npm 包安装,并最终出现在 Agent 选择器里。从“增加一个逆向证据事件”到“替换一套任务循环”,使用的是同一套插件机制,只是前者扩展 session log 并由 preset 选择相应能力,后者进入产品 profile。

这也解释了为什么逆向尤其适合这种架构。普通 AST 解混淆只需要文件、skill 和基础工具;浏览器签名再加入 MCP 与 iv8;JSVMP 恢复继续加入长期证据、workflow,必要时更换 Loop。目标越复杂,选择的插件越多,但不必把所有能力永久塞进一个通用 Agent。

Harness 保存相对稳定的部分:插件生命周期、scope、session、工具管线和配置加载。逆向插件保存持续变化的部分:方法、工具、访问策略、证据类型、工作流和停止条件。两边各自升级,再由 preset 决定某个 Agent 此刻采用哪一种组合。

图片描述
到这一步,Agent 也有了比“功能多少”更具体的衡量方式:某个任务到底需要哪组依赖,这组依赖能不能从配置中看见。dsh --profile web --dump-config 可以打印当前产品的完整插件树,preset 文件可以说明一个 Agent 选择了哪些能力,session log 可以说明模型实际看见了什么。方法、工具、状态和循环之间的关系,不再只藏在一段不断膨胀的系统提示词里。

换句话说,Agent 开始变成一个可以检查的依赖图。领域开发者负责维护变化快的节点,Harness 负责保证这些节点能够加载、隔离、记录和卸载;npm 包把节点及其组合关系一起送到用户手里。这比再造一个“内置所有逆向能力”的客户端更接近逆向工作的真实情况。

所以,未来的逆向 Agent 未必是一套独立开发、越写越大的宿主。它更可能是一份可以安装、检查和版本化的运行时组合:底下是 Harness,上面是按任务选择的插件。我们的逆向模式已经把这条路径从源码里的可能,变成了页面上可以直接选择的结果。

对于逆向 Agent,DeepSeek Harness 这种“Agent 由插件组成”的模式,不是更好的选择,而是唯一正确的模式。


传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!

收藏
免费 1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回