/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}
DeepSeek Harness 实战:二次开发实战——把"有手的公司 AI"改造成你自己的框架
系列导航:概念 / 教程 / 架构 / 插件 / 编码实战 / 框架对比 / 会话日志 / Headless·CI / 自定义工具 / 模型适配 / Web 协同 / 安全沙箱 / 多 Agent / 本文(二次开发实战)
十篇写到这里,我们从"DSH 是什么"一路走到了"多 Agent 编排"。最后这篇,我们要亲手掀开机器的外壳——二次开发。
前面所有能力(模型、工具、UI、安全、子代理),本质上都是"别人写好的插件"。而 DSH 的灵魂是"一切皆插件"(Everything is a Plugin)——这意味着你也能写插件。一个工具、一个 Hook 监听、一个 MCP 桥接、甚至一个自定义预设,都能被你塞进插件树,让框架长出你需要的那只手。
这篇是实战系列收官,我们不讲虚的,直接上手:Cordis 内核的几个核心 API(ctx.plugin/ctx.effect/ctx.on/ctx.waterfall)怎么用、defineTool 怎么写工具、cordis.yml 怎么叠加配置、自定义预设怎么做、编译运行怎么做、怎么给官方贡献。看完你会发现:所谓"改框架",不过是"按它的契约,写一段插件"。
一、先建立心智模型:插件树,不是配置项
要动手,先得有正确的心智模型。DSH 基于 Cordis 微内核(源自 Koishi 生态),核心理念是"时空可组合性"(spatiotemporal composability)。落到 DSH 上就是:整个运行时是一棵插件树,模型、工具、UI、沙箱、会话、子代理……全都是挂在这棵树上的插件,没有"特权核心"。
这意味着二次开发的正确姿势不是"改源码里的某个函数",而是"写一个新插件,挂到树上"。好处是:你不碰核心代码,升级框架时你的插件大概率还能用;坏处是:你得按 Cordis 的契约来写。下面几节就是这套契约的入门。
二、ctx:你插件拿到的一把"万能钥匙"
每个插件被加载时,框架会传给它一个 ctx(context)对象。这把钥匙能开树上几乎所有能力。开发者最常用的是这几个:
- ctx.plugin:注册一个子插件(或子组件)。你的大插件可以再挂小插件,形成子树。
- ctx.effect:声明一个"副作用",并提供一个清理函数。插件卸载/重载时,框架调用清理函数回收资源(关连接、清定时器)。这是"可热重载"的基础——dsh 改一行配置能热断开重连,靠的就是 effect 的回收机制。
- ctx.on:监听事件(比如 tools/pre-execute、agent/session-start)。你写 Hook 监听,就是用它。
- ctx.waterfall:注册一个"瀑布流"钩子。多个插件可以对同一份数据依次加工(比如 post-execute 阶段多个 guard 依次处理工具结果)。它保证了"钩子可组合、可排序",是"一切皆插件"能不打架的关键。
把这四个记住,你就握住了 Cordis 开发的把手。后面写工具、写 Hook、写桥接,全是它们的组合。
三、写一个最小插件骨架
概念落地成代码。一个 DSH 插件(TypeScript)的最小骨架大概是这样(结构与官方 extension-cookbook 一致):
import { definePlugin } from '@cordisjs/core' // 或 DSH 对应的插件基类
export const myPlugin = definePlugin({
name: 'my-plugin',
setup(ctx) {
// 在这里用 ctx 挂能力
ctx.on('agent/session-start', (session) => {
ctx.logger.info('新会话开始了:', session.id)
})
ctx.effect(() => {
// 插件激活时做的事
return () => {
// 插件卸载时清理
ctx.logger.info('插件卸载,资源已回收')
}
})
},
})
要点:
- setup(ctx) 是插件入口,框架把 ctx 传进来。
- 你在里面用 ctx.on 监听、ctx.effect 管资源、ctx.plugin 挂子插件。
- 返回清理函数是好习惯——这让插件能被热重载而不留垃圾。
注意:上面是"结构示意",具体基类/装饰器名以你安装的 @deepseek-ai 相关包为准(开发者预览阶段 API 可能微调)。但 ctx 这套心智模型是稳定的,理解了它,对着官方 extension-cookbook(docs/cookbook/extension-cookbook.md)的样例改,就能跑起来。
四、写工具:defineTool + ctx.tools.register
我们"自定义工具"那篇讲过工具的用法,这里从"怎么造一个"讲。DSH 提供了 @deepseek-ai/dsh-tools 里的 defineTool,配合 ctx.tools.register 注册:
import { defineTool } from '@deepseek-ai/dsh-tools'
const myTool = defineTool({
name: 'my-tool',
description: '做什么用的',
// 参数 schema(JSON Schema 风格)
parameters: {
type: 'object',
properties: {
query: { type: 'string', description: '查询内容' },
},
required: ['query'],
},
// 实际执行体
async execute({ query }, ctx) {
const result = `处理了: ${query}`
return { content: result }
},
})
// 在插件里注册
export const myPlugin = definePlugin({
name: 'my-tool-plugin',
setup(ctx) {
ctx.tools.register(myTool, { owner: 'my-plugin' })
},
})
几个关键点(呼应我们自定义工具篇):
- defineTool 描述工具的"名字、说明、参数 schema、执行体"。
- ctx.tools.register 把它挂到工具注册表,第三个参数是 owner——工具的"主人",用于权限和作用域隔离(谁注册的、谁能调用)。
- 参数用 JSON Schema 描述,模型据此知道怎么填。
- 执行体返回 { content: … },就是模型看到的工具结果。
这就是"教 Agent 干你特有的活"的最小闭环:定义 → 注册 → 模型自动会用。
五、cordis.yml:用配置叠加,不碰源码
写插件是"加能力",而 cordis.yml 是"组合能力"。它是 DSH 的插件组合配置文件——声明"挂哪些插件、按什么顺序、传什么参数"。
更重要的是它支持叠加(overlay):你可以用 –patch 传一段补丁配置,覆盖/追加到基础 cordis.yml 上,而不用改原文件。配合 –dump-config,你能看清"最终生效的插件组合到底是什么"。
# 看最终生效配置(排错神器)
dsh –dump-config
# 用补丁叠加配置,不碰原文件
dsh –patch ./my-overlay.yml web
常用操作还有(社区实测的 dsh plugin 子命令形态,具体以你版本为准):
# 给某个 profile 加装一个 bundle(插件包)
dsh plugin –profile <p> add <bundle>
这条命令的意义是:profile(预设包)本身是插件的集合,你能在它基础上再 add 一个 bundle,灵活拼装。记住那句老话:永远用 –dump-config 看真实生效值,别凭单个文件猜——这在二次开发时尤其重要,因为你的插件、patch、profile 会叠加,猜错组合是常态。
六、自定义预设(Preset):能力组合包
预设(Preset)我们在前面多次提到(标准/代码/极简/创造)。它本质就是"一组插件 + 参数的组合包"。二次开发时,你可以写自己的预设:把你需要的一组插件(含你自己写的)打包,命名成一个岗位(比如"安全审计岗"“前端生成岗”)。
新建会话时选这个预设,Agent 就自动带上你拼好的能力面。这比"每次手动开一堆插件"高效太多。团队落地时,自定义预设尤其有用:把"我们团队标配的工具集 + 权限 + 模型"固化成一个预设,新人一键即用,行为一致。
预设和 profile bundle 是同一套机制的两面:官方给的是 profile bundle,你写的就是自定义预设。理解了"预设 = 插件组合",你就掌握了"给 Agent 分配岗位"的底层逻辑。
七、Hook 监听:写一次,当多种扩展点
"一切皆插件"最妙的地方之一是:同一个插件机制,能让你同时成为工具、Hook 监听、MCP 桥接。你不用学三套 API,只要用 ctx.on 监听不同事件,就是不同的扩展点。
比如 ctx.on('tools/pre-execute', …) 就是在做"工具执行前拦截"(我们安全篇讲过这是审批/guard 的挂钩点);ctx.on('agent/session-start', …) 就是"会话开始时的动作"。官方甚至提供了桥接包:
- dsh-hooks-claude-code:把你已有的 Claude Code hooks.json 翻译成 DSH 的扩展点监听(agent/session-start、tools/pre-execute 等)。你之前在 Claude Code 上写的 hook 脚本,不用重写就能在 DSH 里用。
- dsh-hooks-codex:同理,桥接 Codex CLI 的 hooks。
这对"从别家迁移过来"的用户是巨大善意——你不是"从头再来",而是"把旧资产接进来"。这也印证了 DSH 的取向:编排层,不是替代层。
八、MCP 桥接:让插件变成协议网关
插件还能做什么?当一个 MCP(Model Context Protocol)桥接器。你写一个插件,把某个外部 MCP server 的能力映射成 DSH 的工具/Hook,模型就能通过 DSH 调到那个 MCP server 的资源。
因为插件机制统一,MCP 桥接和写普通工具用的是同一套 ctx API——区别只是你的执行体内部去调外部 MCP。这让 DSH 能无缝接入庞大的 MCP 生态,而不用为 MCP 单独搞一套扩展系统。再次体现"一切皆插件"的简洁:所有扩展形态,最终都归约到 ctx 上的几个原语。
九、Skills:可被发现的技能注册表
除了工具,DSH 还有 ctx.skills——一个技能注册表,合并"文件系统来源"和"内置来源"。简单说:你把技能(一段可复用的提示词/流程)放在约定目录,或通过内置注册,DSH 就能在运行时发现并调用它们。
需要澄清的是:DSH 的 Skill 系统(基于 ctx.skills 的 provider registry)和 Claude Code 的 SKILL.md 目录约定不是同一套格式。社区有个 dsh-skillport 插件,专门去发现你机器上 Claude Code / Codex / Cursor / Gemini 各处的 SKILL.md,再加载进 DSH——这是社区 workaround,不是官方兼容保证。
所以如果你想跨框架复用技能库,dsh-skillport 是个现成桥梁;但别假设"DSH 原生认所有 SKILL.md"。二次开发时,优先用 DSH 自家的 ctx.skills 体系,跨框架靠社区桥接。
十、ADR:框架为什么这么设计,看决策记录
二次开发遇到"为什么它要这样"时,去看 ADR(架构决策记录)。官方仓库里至少有两个和我们讲过的主题强相关:
- ADR 0009:capability seams(能力缝隙)——讲框架怎么把"可扩展点"设计成清晰的接缝,让你能在不碰核心的前提下插进去。这解释了为什么插件能这么干净地挂上。
- ADR 0010:twin LLM adapters(双 LLM 适配器)——讲模型适配层为什么做成"双适配器",呼应我们模型适配那篇的 llm-pi-ai 设计。
读 ADR 的好处:它不是文档里的"怎么用",而是"为什么这么用"。当你要做一个偏离常规的扩展时,先看 ADR,能避免和框架设计哲学打架,少走弯路。开发者预览阶段的框架,ADR 比稳定文档更能告诉你"边界在哪"。
十一、extension-cookbook:你的动手教科书
如果你要正经二次开发,第一站应该是官方 docs/cookbook/extension-cookbook.md(扩展食谱)。它是"怎么写插件"的实操样例集——从最小插件到复杂桥接,一步步可抄。
我们前面讲的 ctx.plugin/ctx.effect/ctx.on/ctx.waterfall、defineTool、cordis.yml 叠加,在这个 cookbook 里都有对应样例。我的建议是:别从零背 API,直接打开 cookbook,找一个和你需求最接近的样例,改它的名字和逻辑,跑起来,再慢慢扩展。这是 DSH 二次开发最快的上手路径。
十二、编译与运行:pnpm workspace 工作流
DSH 本身是 pnpm 多包工作区(仓库里有 pnpm-workspace.yaml、pnpm-lock.yaml、各 packages/*)。你要本地改/跑,工作流大概是:
# 克隆
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
# 装依赖(用 pnpm)
pnpm install
# 构建(用 tsdown,仓库配置里有 tsdown.config.ts)
pnpm build
# 跑测试(vitest)
pnpm test
几个点:
- 用 pnpm,别用 npm/yarn——它是 monorepo,lockfile 和 workspace 配置都按 pnpm 来。
- 构建走 tsdown(TypeScript 打包器),测试走 vitest。
- 改动后重新 build,再用 dsh 跑你的插件。
- 开发者预览,没有 SemVer 承诺,pin 确切版本(比如 @deepseek-ai/dsh@0.1.0-rc.8),别用 latest 裸奔。
这套工作流和大多现代 TS monorepo 一致,前端/Node 背景的人很快能上手。
十三、版本纪律:开发者预览,pin 确切版本
这条值得单列,因为它关乎"你的二次开发成果会不会一夜失效"。
DSH 明确是开发者预览(developer preview),README 自己写"会有兼容性破坏的变更"(THERE WILL BE COMPATIBILITY-BREAKING CHANGES)。贡献指南也说:因为还没有外部消费者,项目优先保证正确的地基,会自由地重命名或重打包。
实操纪律:
- 你的项目 package.json 里,锁死确切版本,不写 ^ 或 latest。
- 把会话日志当"一次性"看待,直到格式被版本化(官方建议)。
- 升级前看 changelog/ADR,评估破坏性。
- 你的插件依赖的 ctx API,若在某版被改,要有心理准备去适配。
不是要你因噎废食,而是"预览期开发"的正确姿态:享受前沿,但也接受一个事实——地基还在动,你的上层建筑要留好适配的余地。这十篇,愿成为你动手前那块稳得住的踏板。
十四、贡献流程:怎么把你的插件回馈上游
如果你写了一个不错的插件,想回馈官方,走标准开源流程:
- 读 CONTRIBUTING.md(仓库里有 CONTRIBUTING.md / CONTRIBUTING.zh.md)。
- 读 AGENTS.md / CLAUDE.md(仓库里给 AI agent 看的协作说明,也值得人读,能看清项目约定)。
- 看 architecture.md(架构文档,二次开发前必读,知道各部分怎么咬合)。
- Fork → 分支 → 改 → 过 lefthook 钩子和 oxlint lint → 提 PR。
官方还用 lefthook.yml(git 钩子)、.oxlintrc.json(lint)、knip.json(死代码检测)等做工程治理。提交前本地跑一遍这些检查,能少被 CI 打回。社区讨论走 Discussions(Issues 对外部禁用),有问题先去那问。
十五、实战:写一个"日报生成"插件
给个具体动手例子,串起前面知识点。假设你想让 Agent 多一个能力:“根据今天改的文件,生成一段 commit 风格的日报”。
整个过程你没改一行核心代码,只是写了个插件挂上去。这就是"一切皆插件"的威力:扩展框架 = 写插件,而不是 fork 改源码。团队积累的每个小能力,都是可复用、可组合、可升级的插件。
十六、实战:用 Hook 做"敏感操作拦截"
再一个实战,呼应我们安全篇。假设你想加一条规则:“任何写 /etc 的工具调用都拒”。
export const guardEtc = definePlugin({
name: 'guard-etc',
setup(ctx) {
ctx.on('tools/pre-execute', (call) => {
if (call.target?.includes('/etc')) {
return 'deny' // 单调守卫,只有 deny/abstain
}
})
},
})
注意我们用的是 ctx.on('tools/pre-execute', …)——这正是安全篇讲过的"审批/guard 挂钩点"。你写的这个 guard,会和框架内置的 guard 一起,按 waterfall 顺序对每次调用做裁决。因为守卫是单调的(只能 deny/abstain),你这条规则不会被后面的监听器悄悄放开。这把"业务安全规则"做成了几行插件,不用碰核心。
十七、实战:桥接一个外部 MCP server
第三个实战,把外部 MCP 接进来。你写一个插件,内部用 MCP client 连某个 server,把它的能力包装成 DSH 工具:
// 伪结构,展示思路
export const mcpBridge = definePlugin({
name: 'mcp-bridge',
setup(ctx) {
const client = connectMcp('https://some-mcp-server')
ctx.effect(() => {
// 连接,卸载时断开
return () => client.close()
})
ctx.tools.register(defineTool({
name: 'mcp-something',
description: '来自外部 MCP 的能力',
parameters: {…},
async execute(args) {
return client.call('something', args)
},
}), { owner: 'mcp-bridge' })
},
})
这就是我们第八节讲的"MCP 桥接"。用同一套 ctx API,你把庞大的 MCP 生态变成了 DSH 的工具。框架不用为 MCP 单独搞扩展系统,因为插件机制本身就够用。
十八、常见二次开发坑
列几个新手容易踩的坑,帮你省时间:
- 直接用 npm 装依赖:DSH 是 pnpm monorepo,用 pnpm,否则 workspace 链接会乱。
- 改了不 rebuild:TS 改完要 pnpm build,直接跑旧 dsh 看不到改动。
- 忘了 owner/作用域:工具注册不写 owner,权限和作用域会乱,调试费劲。
- 凭单个 yml 猜配置:多来源叠加,必须 –dump-config 看真相。
- 假设 API 稳定:预览期 ctx API 可能变,pin 版本、读 ADR。
- Hook 里做重活:pre-execute 这类同步钩子别写阻塞逻辑,会拖慢每次调用。
- 忽略 effect 清理:不返回清理函数,热重载会留垃圾(连接/定时器泄漏)。
这些坑大多源于"把预览框架当稳定产品用"。调整预期,它们都不是问题。
十九、给团队的二次开发治理建议
团队要做二次开发,建议立几条规矩:
- 插件集中管理:自建一个内部插件仓/目录,统一 review、统一版本。
- pin 框架版本:全团队锁同一确切版本,避免"我这边能跑你那边挂"。
- 优先 cookbook 样例:改动从官方样例改起,不凭空造。
- 改前读 ADR/architecture:避免和框架哲学打架。
- CI 跑 lint + test:用官方 lefthook/oxlint/vitest,提交前本地过一遍。
- 插件也要审计:第三方/社区插件当供应链风险看,pin 版本、看来源。
二次开发不是"随便写",而是"在框架契约内、有纪律地扩展"。团队越这样管,插件资产越能滚雪球。
二十、一句话总结二次开发
如果只留一句:DSH 的二次开发,不是"fork 改源码",而是"按 Cordis 的 ctx 契约写插件,挂到插件树上"——ctx.plugin/ctx.effect/ctx.on/ctx.waterfall 是把手,defineTool 造工具,cordis.yml 做组合,–dump-config 看真相,cookbook 给样例,ADR 讲为什么。框架把"扩展点"做成了干净接缝,你只管往里插。
二十一、实战系列收官感言
写到这,十篇实战系列就收尾了。我们走过的路:
从"这是什么"到"我能改它",这条线走完,你不再是 DSH 的使用者,而是能塑造它的人。
二十二、插件作用域与 owner 机制深解
前面提了工具注册要写 owner,这里讲透"为什么"。DSH 的插件/工具是带作用域的——谁注册、挂在树的哪个位置,决定了它的可见范围和权限边界。
owner 字段把工具"绑定"到注册它的插件。这带来几个好处:
- 权限隔离:某个预设/会话可以只允许调用特定 owner 的工具,避免"所有工具对所有人可见"。
- 责任清晰:工具出问题,能立刻定位是哪个插件注册的。
- 热重载安全:卸载插件时,它注册的工具随 owner 一起回收,不会残留。
所以写工具时认真填 owner(用你插件名),别图省事留空或乱填。这是"一切皆插件"能保持整洁的工程细节——每个能力都有明确的"户口"。
二十三、fiber 状态机与插件生命周期
Cordis 内核里有个叫 fiber(纤程) 的状态机概念,描述插件的生命周期:从 PENDING(挂起/待激活)→ ACTIVE(激活中)→ 终态 DISPOSED(已释放)或 FAILED(失败)。你写的插件,从被加载到被卸载,就走这条状态链。
理解它有什么用?
- 插件在 PENDING 时还没拿到完整 ctx,别在那时做重活。
- ACTIVE 时你的 setup 跑完、effect 生效,能力在线。
- 卸载时走向 DISPOSED,你的 effect 清理函数被调用;若清理出错或 setup 抛错,可能 FAILED。
- FAILED 的插件不该拖累整个框架——DSH 的设计是让单个插件失败不影响全局(理想情况下)。
把插件当"有生命周期的对象"而非"一段静态代码",你写 effect 清理、处理异步初始化时,就会更顺。热重载(改配置断开重连)之所以丝滑,背后就是 fiber 状态机在正确回收与重建。
二十四、调试插件:从日志看插件行为
插件写完了不工作,怎么查?回到我们反复强调的武器:会话日志 + –dump-config。
- 插件没生效?先 –dump-config 看它到底有没有被加载、挂在树的哪。
- 工具没被调用?看日志里模型的工具选择,确认你的工具名/描述是否被模型"看见"(描述写清楚,模型才懂何时用)。
- Hook 没触发?确认你监听的事件名对不对(tools/pre-execute、agent/session-start 等),事件名错一个字母都不触发。
- 运行时报错?日志里会有插件 setup/execute 的堆栈。
DSH 的"日志即真相"在二次开发时尤其救命——你所有的扩展行为都写在不可变日志里,debug 不是猜,是看。这也再次印证系列一贯的主题:把会话当档案,debug 当考古。
二十五、插件与预设的版本管理
把插件纳入版本管理,是团队能持续积累资产的前提:
- 你写的插件,放 git 仓,和 cordis.yml/preset 一起纳管。
- pin 框架版本(确切版本号),别让某次 pnpm update 把预览期框架升到一个破坏性版本,导致所有插件挂掉。
- 插件本身的 package.json 也锁依赖版本,尤其是 @deepseek-ai/* 相关包。
- 用 preset 把"插件组合"固化,团队共享同一套能力面,行为一致。
二次开发的资产价值,在于"可复用、可升级、可共享"。版本管理是把这个价值落地的纪律——否则你的插件就是"本地能跑、别人跑不了、下周自己也跑不了"的临时物。
二十六、用 vitest 测你的插件
别只靠手跑验证插件。DSH 本身用 vitest 做测试,你写插件也该写单测:
- 工具的 execute 是纯函数(接参数、返结果),最容易单测:构造参数、断言返回。
- Hook 监听可以 mock 一个 call 事件,断言你的 deny/abstain 逻辑正确。
- 插件 setup 里涉及 ctx 交互的,可以用框架提供的测试工具 mock 一个 ctx。
提交前本地 pnpm test 过一遍,比"手动点几下界面"可靠得多,也方便 CI 把关。预览期 API 会变,单测还能当"回归护栏"——某次框架升级悄悄改了 ctx.tools.register 的签名,你的测试会立刻红,而不是上线才发现。
二十七、从"工具"到"完整 Agent 行为"的插件
工具是最简单的扩展,但插件能力远不止工具。通过 ctx.on 监听不同事件,你能改变 Agent 的整体行为:
- 监听 agent/session-start:每次新会话自动注入一段系统提示/加载上下文。
- 监听 tools/post-execute:对所有工具结果做脱敏/改写(呼应安全篇的 guard)。
- 监听 tools/pre-execute:做审批/限流(安全篇讲过)。
- 注册 ctx.waterfall:让多个插件对同一数据依次加工(比如多个后处理依次跑)。
也就是说,插件能"包裹"Agent 的整个生命周期,而不只是"加个工具"。当你想要"让 Agent 在所有会话都遵守某条规则"“对所有输出都做某处理”,写的就是这类行为型插件,而非工具型插件。这是"一切皆插件"的真正纵深——你能改的,是 Agent 的行为本身。
二十八、社区插件生态现状
客观说,DSH 还是开发者预览,社区插件生态正在成形但远未繁荣。目前能看到的有:
- 官方桥接包:dsh-hooks-claude-code、dsh-hooks-codex(桥接别家的 hook)。
- 社区 workaround:dsh-skillport(跨框架加载 SKILL.md)。
- 各类自定义工具/Provider,散落在社区分享里。
所以别指望"应用商店里挑插件"。当前阶段,二次开发更多是"自己写自己用",而不是"装现成的"。这反而是机会:早期生态空缺,你写的实用插件更容易被社区需要。把我们这篇的套路用熟,你就是能填坑的那批人。
二十九、把内部系统接成 Provider/插件
企业二次开发最常见的诉求,是把"内部系统"接进 DSH。两条路:
- 接成工具:用 defineTool 包一层内部 API(比如查订单系统、查内部知识库),模型就能"调内部系统"。适合"单向查询/操作"。
- 接成子 Agent Provider:如果内部系统本身是个 Agent/服务,按 ACP 或自定义 Provider 协议接进来,主 Agent 能委派任务给它(呼应多 Agent 篇)。适合"委派一段活"。
选哪条看形态:内部系统是"一个 API"→ 工具;内部系统是"一个能自己干活的智能体"→ Provider。DSH 的开放架构让两者都可行,你按内部系统的实际形态选就好。
三十、二次开发的性能考量
写插件别忘性能,几条实务:
- pre-execute/post-execute 这类钩子会被每次调用触发,里面别写重活、别做网络阻塞,否则每次工具调用都变慢。
- ctx.effect 里开的连接/定时器,务必在清理函数关掉,否则热重载泄漏,跑久了内存涨。
- 工具 execute 里的外部调用,加超时,别让一个慢 API 拖死整个 Agent 循环。
- 日志别打太猛,高频插桩日志会让会话日志膨胀、界面卡(呼应 Web UI 篇的"投影≠真相")。
性能是"能跑"和"能生产"的分水岭。预览期你写的插件可能只自己用,但一旦进团队流水线,这些细节决定它是资产还是负担。
三十一、迁移自别家:hooks/skills 桥接实战
如果你从 Claude Code / Codex 迁过来,最现实的二次开发不是"重写",而是"桥接":
- hooks:用官方 dsh-hooks-claude-code / dsh-hooks-codex,把你已有的 hooks.json 直接翻译进 DSH。你之前投在 hook 脚本上的资产,零重写复用。
- skills:用社区 dsh-skillport 发现你机器上各家的 SKILL.md,加载进 DSH(注意这是社区方案,非官方兼容保证)。
- 工具/命令:Claude Code 的 commands、Codex 的类似物,需要按 DSH 的 defineTool 重写——这部分没有自动桥,但逻辑平移不难。
迁移策略建议:hook 先桥(省力),skill 用社区桥(过渡),工具/命令手动迁(必要)。别追求"一天全搬",先让核心资产在 DSH 里跑起来,再逐步丰富。
三十二、给初学者的二次开发路线
如果你刚接触 DSH 二次开发,按这条路线走最稳:
别一上来就"写个改变 Agent 行为的复杂插件"。从工具起步,逐步加深,这部"电梯"坐稳了,二次开发就不吓人。
三十三、常见的"不要这么做"
列几条反面教材,帮你避坑:
- 不要改 node_modules 里的源码来"扩展":升级即丢,且破坏可维护性。写插件,别改核心。
- 不要裸用 latest 跑生产插件:预览期破坏性变更频繁,锁版本。
- 不要在所有会话全局挂重插件:影响面太大,先用预设限定范围。
- 不要在同步钩子里 await 网络:会阻塞 Agent 循环,体验崩。
- 不要忽略 effect 清理:内存/连接泄漏,跑久了出事。
- 不要假设 API 永久不变:预览期,读 ADR、跟版本。
这些"不要"的本质,都是"把预览框架当稳定产品用"的变种。调整预期,二次开发就很愉快。
三十四、最后的提醒
把最关键的一句话留到最后:DSH 的二次开发,扩展点很干净,但地基还在动。你写的插件是树上分枝,优雅、可复用;但预览期的 ctx API、配置结构都可能变。所以写插件时,把"易适配"当成质量要求——逻辑别和某个易变 API 绑死,关键依赖处留好注释和适配层。这样框架一升级,你改几行就能续上,而不是推倒重来。
三十五、插件与多 Agent 的交汇
二次开发和多 Agent(我们第九篇)其实在底层汇合:你写的子 Agent Provider,本质就是一个"能委派执行的插件"。也就是说,“把内部系统接成子 Agent”(第二十九节)和"写一个普通插件",走的是同一套 ctx 契约——区别只是你实现的扩展点是"可委派执行体"而非"工具/Hook"。
这再次印证"一切皆插件"的统一性:工具、Hook、MCP 桥、Skill、子 Agent Provider、预设……全都是 ctx 上的不同扩展点。你学通一套 ctx API,就能在任意扩展形态间游走。这也是为什么本篇放在系列最后——它是前面所有能力的"元能力":当你会写插件,前面九篇讲的每一个能力,你都能改、能扩、能造自己的版本。
三十六、给团队的一份"二次开发规范"草案
把前面散落的纪律,汇成一份可落地的团队规范草稿:
这份草案不复杂,但每条都对应一个我们讲过的坑。团队把二次开发当"工程"而非"个人玩具",插件资产才能复利。
三十七、本篇一句话收尾
如果只留一句给离开本篇的你:DSH 的二次开发,不是 fork 改源码的苦役,而是按 ctx 契约写插件的创作——地基还在动,但接缝很干净;你今天写的每一段插件,都是在给未来的自己攒可复用的能力。 从用框架到造框架,差的不是天赋,是愿意掀开机壳、按契约接一根分枝。
三十八、从十篇到"能改造框架"的跨越
回看整个系列,前九篇你在"用"DSH:用它的模型、工具、UI、安全、多 Agent。本篇你在"改"DSH:按它的契约写插件。这个跨越,才是开源框架真正的礼物。
封闭产品给你的是"它做好的功能",你只能在它划的框里用;开源框架给你的是"可塑的运行时",你能按自己的需要长出新功能。前者省事但受限,后者前期要学但自由。DSH 用"一切皆插件"把后者的门槛降得很低——你不用懂整个内核,只要会用 ctx 几个 API、会写 cordis.yml 组合,就能扩展。
所以如果你读到这里有点手痒,别停。打开 docs/cookbook/extension-cookbook.md,抄一个最小插件,跑起来。那一步"我写的插件真的被 Agent 用上了"的快感,是读十篇文档都换不来的。
三十九、给本系列读者的临别话
十篇写尽,我想对你说句掏心话:技术文章会过时(尤其开发者预览期的 DSH,rc 版本一个个发,命令和 API 都在变),但"怎么学一个框架"的方法不会过时——抓架构心智模型、读官方文档与 ADR、动手跑最小样例、用日志当真相、把扩展当插件而非补丁。这套学法,你既能用来看 DSH,也能用来看任何新出的 Agent 框架。
希望这十篇不只是"DSH 说明书",更是你建立"Agent 框架学习框架"的脚手架。框架会换,能力长在你身上。
四十、写在最后的一行字
如果你只读得下本篇最后一行,请记住:真正属于你的技术能力,从来不是"会用某个框架",而是"框架不够用时,你能不能自己长出一个"——DSH 把这门手艺的入门,放到了一个插件、几行 ctx 的距离。 这十篇是引子,你动手写的第一个插件,才是正文。
结语
二次开发实战,是实战系列的收官,也是你从"用框架"走向"造框架"的起点。DSH 用"一切皆插件"把扩展点做成了干净接缝——你写的不是补丁,而是树上的一根分枝;升级框架时它大概率还在,团队积累时它能滚雪球。这,正是开源 Agent 框架相对封闭产品最深的护城河:你不仅能用它,还能长它。当一个框架愿意把"改它"的权利也交给你,它就不只是工具,而是你能力的一部分。愿这十篇,成为你长出那份能力的起点。
如果这十篇帮你把 DSH 从"听说过"变成"能上手、能改造",点个关注。实战系列到这里告一段落,但 DSH 还在快速演进——rc 版本一个个发,新能力不断涌现。后续我会持续跟踪更新,把值得讲的新东西带给你。有问题、有想让我展开的主题,评论区见。从用框架到造框架,你只差"掀开机壳写一段插件"这一脚。下个 rc 出来,我们再接着聊新东西。感谢这十篇的同行。
本文基于 deepseek-ai/deepseek-harness 官方仓库(Cordis 内核、docs/cookbook/extension-cookbook.md、ADR 0009/0010、@deepseek-ai/dsh-tools、CONTRIBUTING/AGENTS/CLAUDE/architecture 文档)、社区实践整理,截至 2026-08。dsh 处于开发者预览阶段,API 与命令以你安装版本为准,二次开发请 pin 确切版本并先读 cookbook 与 ADR。





![[AI工程] 基于DeepAgents深度搜索项目-171主机测评](https://www.171host.com/wp-content/uploads/2026/10/20261025003612-6add4efc576b6-220x150.png)
![[AI工程] 基于DeepAgents深度搜索项目-171主机测评](https://www.171host.com/wp-content/uploads/2026/10/20261024202007-6add12f73a661-220x150.png)