欢迎光临
我们一直在努力

一切皆插件:DeepSeek Harness 核心机制与上手路径

读者前提:你用过 Claude Code 或 Codex,理解它们的 hooks(PreToolUse 等)、MCP、subagents、权限白名单这些机制。这篇文章只讲一件事:dsh 的"一切皆插件"到底意味着什么,以及你从哪里上手。不重复官方教程里的细节,每一条都给你真实入口和可验证的结果。


1. 坐标对齐:你认识的每个机制,在 dsh 里长什么样

先把你脑子里已有的概念翻译过来,这是最快建立坐标的办法:

你在 Claude Code / Codex 里认识的dsh 里的对应物关键差异
内置工具(read / write / bash) tool-* 插件(dsh-tool-fs、dsh-tool-pwsh 等) 官方工具和你的插件走同一个注册 API,地位完全平等
MCP server capability seam(Service + Provider + Consumer) MCP 是外部进程、固定协议;dsh 的 provider 是进程内插件。MCP 只是它的一种 provider,不是它本身
hooks(固定几个钩子点) 事件监听(tools/pre-execute 等) dsh 把整条请求流水线开放成事件:agent/pre-step → agent/request → llm/stream → tools/pre-execute → tools/execute → tools/post-execute → tools/result → agent/turn-stopping
subagents subagent 工具 + 可换 provider 后端可以换成进程内 spawn、fork、codex、claude-code
permissions / allowlist sandbox + approval 插件 三档沙箱模式 + 审批流,而且这两样本身也是插件,可以替换
output styles / persona system-prompt 段落插件 人设只是众多提示词段落里的一个
settings.json cordis.patch.yml + settings.yaml settings 只能调白名单开关;patch 可以覆盖任何插件行的整个 config
主循环(agent loop) dsh-agent-loop 插件 这条最关键:Claude Code 的主循环在源码里、你碰不到;dsh 的主循环是一个插件,可以被另一个插件顶替

一句话版本:Claude Code / Codex 是"带扩展接口的程序"——接口是后门,核心是禁区。dsh 是"用插件搭出来的程序"——没有后门,因为连承重墙都是插件。

2. 三个证据,证明"一切皆插件"不是口号

证据一:整台机器的家当就是一份 YAML。 打开 packages/bundle/base/cordis.patch.yml——模型适配器、全部工具、持久化、沙箱、审批、凭据,都是这份文件里一行行插入的插件行。运行中的 dsh 不是"程序 + 配置",而是"一棵由配置长出来的插件树"。任何一行都可以按 id 被你的 patch 覆盖。

证据二:主循环真的是插件。 packages/core/agent-loop/src/agent.ts 里的 ReactLoopAgent 是默认实现,挂载它的是一行配置。别的插件能监听它发出的每个事件,也能整个替换它——这是 Claude Code 里 fork 源码才能干的事。

证据三:官方工具不特殊。 packages/todo/tool-todo/src/index.ts 就是官方 todo 工具的完整实现,它做的事只有一件:ctx.tools.register(defineTool({…}))。你写的第一个工具和它调用的是同一个函数、填的是同一张表。仓库里没有第二个"更高级"的注册方式。

3. "一切皆插件"的三层含义

第一层:一切能力来自注册。 模型能调什么工具、系统提示词里有什么、谁在管权限、会话存在哪——全是某个插件启动时向共享上下文(ctx)注册的结果。注册立即生效;插件卸载,注册自动撤销。没有"内建功能"和"扩展功能"之分,因为内建的那些也是这么注册进来的。

第二层:扩展点是整套骨架,不是几个固定钩子。 三种注册形态覆盖全部:

  • 工具:ctx.tools.register()。注册的瞬间,描述和参数 schema 被拼进下一次请求的系统提示词——模型下一轮就"会"这个工具了。

  • 服务:能力接口(ctx.fs、ctx.shell、ctx.llm)。写一个 provider 插件替换 ctx.fs 的实现,所有文件工具的行为全变,不用改任何工具代码。

  • 事件:流水线上的监听点。多个监听者排队(waterfall),谁都能放行或拦截,next() 是把控制权交给下一个的开关。

第三层:连规则本身都是插件。 会话日志、主循环、压缩策略、审批流,都是插件。把主循环插件换掉,dsh 就变成完全不同的东西。

4. 上手:四条路,按零代码到改内核排列

每条路都给你真实入口、最小操作、以及怎么验证自己成功了。

路 1:改配置——给"主程序"打补丁

入口:你的 profile 目录里的 cordis.patch.yml(Web 模式下通常在 ~/.dsh/profiles/web/cordis.patch.yml,初始为空)。

最小操作:

# 换端口
– id: webserver
config:
  port: 8080

# 禁用一行:这个 agent 从此没有网页搜索工具
– id: tool-web
disabled: true

# 插入一个新插件(npm 装进 profile 之后)
– insert:
  – id: my-plugin
    name: '@your-scope/dsh-your-plugin'

验证:dsh –dump-config 打印整棵树的最终形态;或重启后直接看效果(端口变了 / 工具没了)。

路 2:重新组合一个 agent——preset

入口:apps/cli/config/agent-presets/standard/(出厂四个:minimal / standard / code / cordis)。

最小操作:把 standard 拷到 ~/.dsh/.agent-presets/my-agent/,编辑 agent.cordis.yml——删掉 tool-pwsh 行、改 persona 文本、加一行 tool-web。

验证:新建会话时菜单里出现 my-agent;选它开一个会话,AI 的人设变了、能用的工具少了。这就是"组装一套自己的 Claude Code":工具集、人设、提示词段落都是菜单项。

一个必须记住的约束:preset 里如果有一行要"发布服务"(而不是只注册工具),它必须放进带 isolate realm 的 group。否则挂载时会被拒绝——因为两个 preset 发布同名服务会撞车。工具行不需要。

路 3:写插件——注册工具、监听事件、替换服务

入口:一个 npm 包(参考 docs/cookbook/adding-a-tool.md 的完整模板),装进 profile,然后像路 1 一样 insert 一行。

最小工具插件(给模型加一只新手;这是完整实现,不是伪代码):

import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'

export const name = 'my-time-tool'
export const inject = ['tools']

export function apply(ctx: Context): void {
 ctx.tools.register(defineTool({
   name: 'time_in',
   description: 'Get the current UTC time shifted by a given offset in hours.',
   parameters: {
     offsetHours: {
       type: 'number',
       required: true,
       description: 'UTC offset in hours.',
    },
  },
   output: {
     schema: {
       type: 'object',
       required: true,
       properties: { now: { type: 'string', required: true } },
    },
     render: (_args, value) => [{ type: 'text', text: value.now }],
  },
   execute: async ({ offsetHours }) => ({
     now: new Date(Date.now() + offsetHours * 3_600_000).toISOString(),
  }),
}))
}

验证:重启后跟 AI 说"用 time_in 查一下 UTC-5 的时间"——它会调用、参数校验、返回结果,全程记进会话日志。

事件拦截插件(等价于 Claude 的 hook,但挂在流水线的任意节点):

export function apply(ctx: Context): void {
 // 每个工具调用前拦一道:只许跑 git 命令
 ctx.on('tools/pre-execute', async (exec, next) => {
   if (exec.name === 'pwsh' && !exec.args.command.startsWith('git ')) {
     return { kind: 'deny', reason: '此会话只允许执行 git 命令。' }
  }
   return next() // 放行给下一个监听者
})
}

验证:让 AI 跑一条 dir,工具调用被 deny,日志里留下拒绝记录。注意 next() 必须调——不调就是短路,链条上的其他监听者全被跳过。

替换服务(换核心子系统):实现 ctx.fs 的 Service 接口并挂载,所有文件工具(read/write/edit/glob)立刻改走后端实现。这里只给指针:Service Definition 在 packages/fs/fs/src,Provider 模板见 packages/fs/fs-local/src——这是四条路里最重的一条,需要时再深挖。

路 4:动态插件——不落盘、不重启

入口:开一个 cordis 预设的会话(出厂自带),它给了 AI 一整套工具:cordis_define / cordis_run / cordis_stop / cordis_undefine。

最小操作:直接对 AI 说"定义一个插件,给 Web UI 加一个小面板"——AI 现场写代码、定义、跑起来。

验证:效果立刻出现在当前进程里;cordis_stop 后一切恢复原状(注册的可逆性是机制保证的,不是约定)。

这条路的边界要说清楚:动态插件代码运行在真实 runtime 里,它是信任边界,不是安全沙箱。能用这条路的前提是你信任写代码的那个 AI——就像你信任一个拿到 shell 权限的同事。

5. 四条必须记住的契约

  • patch 替换整行 config,不是深合并。 覆盖 webserver 行时要把你想保留的字段全部重写一遍。

  • waterfall 监听必须调 next()。 忘了调 = 静默短路整条链。

  • preset 里发布服务的行要 isolate realm。 违反会在挂载时报错,不是运行时才炸。

  • 改配置/插件只影响新会话和重启后的进程。 已有会话保持创建时的组合——这是刻意的设计,会话日志里记着它用的哪个 preset。

  • 6. 继续深入

    • docs/architecture.md 末尾有"新行为往哪挂"的扩展点总表——想干的事 → 对应的机制,一查便知。

    • docs/cookbook/extension-cookbook.md:工具、UI、协议驱动等插件形态的代码模式。

    • docs/cookbook/adding-a-tool.md:第一个工具插件的完整教程(本文路 3 的展开版)。

    • docs/tool-catalog.md:所有模型可见工具的 schema 目录,自动生成,用来查"现在模型手上有什么"。


    最后把整篇文章压成一句:在 dsh 里,你要么在改一个插件的配置,要么在挂一个新的插件,要么在让 AI 替你挂一个——三者之外,没有别的事了。

    赞(0)
    未经允许不得转载:171主机测评 » 一切皆插件:DeepSeek Harness 核心机制与上手路径
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址