DSH(DeepSeek Harness)使用及插件开发完整教程

DSH(DeepSeek Harness)使用及插件开发完整教程

归档说明:本篇为 2026-09-05 会话产出的完整教程(基于 deepseek-ai/deepseek-harness master 分支官方文档整理),按 SCHEMA 回流(file-back)操作存入 qa/。概念页见 DeepSeek Harness(dsh);原始素材见 2026-09-06 DSH 官方文档存档(deepseek-harness 17篇)。

本文基于官方仓库 deepseek-ai/deepseek-harness 的官方文档(README、开发指南、CLI 参考、Cordis 教程系列、实操手册)整理,所有代码示例均来自官方一手资料。

⚠️ dsh 目前处于 开发者预览(Developer Preview) 阶段,迭代很快,未来会出现破坏兼容性的变更。


目录

  1. DSH 是什么
  2. 安装与准备
  3. 基本使用
  4. Profile 与配置体系
  5. 插件管理:找、装、管
  6. 核心架构:一切皆插件
  7. 插件开发实战
  8. 插件的挂载与分发
  9. 调试与质量保障
  10. 常见坑速查
  11. 功能 → 插件机制速查表
  12. 资源索引

1. DSH 是什么

DeepSeek Harness(dshDeepSeek AI 开发的开源 agent harness(智能体框架),MIT 许可证。

核心理念是 Agent = Model + Harness:模型负责”想”,Harness 负责”做”——把模型能力接入文件系统、终端、Web 和工具链。

它构建于 “一切皆插件”(Everything is a Plugin) 的架构之上,底层由 Cordis 插件框架驱动(以 vendor 方式内置),设计思想见论文 A Programming Paradigm for Spatiotemporal Composability

所谓”一切皆插件”,是指产品里没有任何特权核心:模型适配器、工具注册表、会话日志、持久化、任务调度、UI 乃至 agent 主循环本身,都是可以由配置层替换或扩展的插件。


2. 安装与准备

2.1 前置条件

依赖 版本要求
Node.js 22.19+ 或 24+(CI 覆盖 22.19、24、26)
pnpm 仅从源码运行/插件开发需要;仓库固定 pnpm@11.7.0,建议先 corepack enable
Git 2.26+(仅源码开发需要)
DeepSeek API Key 可选:真实模型对话需要;纯链路演示、单测、工具插件开发均不需要

2.2 方式一:通过 npm 直接运行(推荐日常使用)

1
npx @deepseek-ai/dsh web

该命令默认在 http://127.0.0.1:3080 启动 Web UI,本机启动时会自动用默认浏览器打开页面。加 --no-open 可只启动服务器不打开浏览器。

也可以全局安装(便于日常反复调用 dsh 命令):

1
2
3
npm install -g @deepseek-ai/dsh
dsh --version # 验证安装
dsh web # 启动 Web UI

2.3 方式二:从源码运行(插件开发推荐)

1
2
3
4
5
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install # 安装依赖(同时配置 Lefthook git 钩子)
pnpm run build # 构建产物(Web 运行需要已构建产物)
pnpm dsh web # 用构建产物启动

新克隆后先跑一次 pnpm run typecheck,成功即表示搭建完成。

2.4 配置 API Key

真实的 DeepSeek 适配器从环境变量或仓库根目录被 gitignore 的 .env 文件读取凭证:

1
2
DEEPSEEK_API_KEY=sk-...
DEEPSEEK_BASE_URL=https://... # 可选,默认公开 API

未设置 Key 时,依赖真实 API 的 e2e 测试会自动跳过;工具插件开发、Cordis 教程全程不需要 Key。


3. 基本使用

3.1 Web UI 模式(交互使用)

1
2
3
4
5
dsh web                      # 等价于 dsh --profile web
dsh web --no-open # 不自动开浏览器
dsh web --port 8080 # 覆盖端口
dsh web --trusted-host <auth># 添加 /api 信任围栏的具名 authority
dsh web --help # 打印 web 应用自己的帮助
  • 默认地址 http://127.0.0.1:3080;CLI 有意不支持 --host 0.0.0.0(会以用法错误退出)。
  • 通过 SSH 启动时只打印宿主机 URL,不打开浏览器(本地转发地址由 SSH 客户端持有)。
  • 基于 base 的 profile 以运行命令时所在目录作为默认 workspace 根目录,并加载适用的 AGENTS.md / CLAUDE.md 指令(渲染预算 65,536 字节)。

3.2 Headless 一次性任务(脚本/自动化)

1
dsh --profile headless "summarize this workspace"
  • 需要 DEEPSEEK_API_KEY(环境变量或 .env)。
  • 提供方推理分片以 dsh: reasoning: 标题流式写入 stderrstdout 只打印最终文本。
  • 任务原因为 completed 时退出码 0,否则 1。不挂载浏览器、HTTP 服务器,不开监听端口,适合脚本化。

3.3 内置 Profile 一览

Profile 用途 随附参数
web Web UI 交互(base + web-app) --host --port --trusted-host --no-open
headless 一次性任务(base + headless) 任务文本(位置参数)
sdk SDK / JSON-RPC stdio 接入 stdio 携带 JSON-RPC
sdk-minimal 极简 SDK(无指令发现/SQLite,固定 danger-full-access 同上
acp Agent Client Protocol 接入(编辑器等) stdio 携带 ACP

这五个 profile 首次使用时会从随附模板自动初始化;其他名字的 profile 会显式报错,提示 dsh plugin --profile <name> add <package>

3.4 权限与沙箱

  • base 系 profile 中,新会话默认 workspace-write 权限预设:Bash 和文件系统修改仅限会话 workspace 与平台临时根目录;读取和网络不受限制。
  • 沙箱后端:bwrap(私有 PID 命名空间,隐藏宿主进程)、Landlock、Seatbelt。
  • 环境变量 DSH_PERMISSION_MODE 可改进程后备值;Web 会话可在 General settings 中存储权限设置。
  • 已启用的 web_search / web_fetch 调用在所有沙箱与审批模式下执行,无需逐次确认;提供方会拒绝非公开目的地址。

3.5 常用环境变量

变量 作用
DEEPSEEK_API_KEY 模型与搜索凭证
DEEPSEEK_BASE_URL 可选,自定义 API 地址
DSH_HOME Profile 与凭据的家目录
DSH_PERMISSION_MODE 权限模式后备值
DSH_TOOLS_MODE native / ptc / both,选择工具呈现方式
DSH_TELEMETRY_MODE FULL(全量 OTLP 上报)/ DISABLED(全部留在本地);默认按反馈门控(用户记录 /feedback 前不上传任何数据)
DSH_TELEMETRY_DISABLED 非空即遥测强制关闭(最终效力)
HTTP_PROXY / HTTPS_PROXY 出站代理,无需额外开关即生效

凭证解析顺序:继承环境 → $DSH_HOME/.credentials.yaml → 调用目录 .env$DSH_HOME/.env

3.6 进程生命周期

  • 收到 SIGINT/SIGTERM 先优雅排空,插件树最多 5 秒完成 dispose;第二次信号强制退出。
  • SIGTERM 以 0 退出;SIGINT 报告 130。
  • 启动器参数边界:dsh 自身的 flag 必须写在最前面;从第一个无法识别的 token 起的所有内容原样交给 profile 内的应用。要传字面量 -- 给应用需写成 -- --

4. Profile 与配置体系

这是理解 dsh 的关键一节:同一份 dsh,靠 profile 组合出完全不同的插件集。

4.1 配置分层(后者覆盖前者)

生效配置树以空根节点为起点,依次叠加四层:

1
2
3
4
1. 组合包 patch          —— profile manifest 中 dsh.profile.bundles 列出的各 bundle 的 cordis.patch.yml
2. profile 自身 —— $DSH_HOME/profiles/<name>/cordis.patch.yml
3. home 级(机器本地) —— $DSH_HOME/cordis.patch.yml(各 profile 共享的偏好)
4. 命令行 overlay —— --patch <path> 指定(可多个,按 argv 顺序)

重要语义:patch 对同一配置行是整体替换该行的 config 值,不是深度合并;patch 也可以插入新行。

4.2 查看生效配置(不启动)

1
2
dsh --profile web --dump-default-config   # 只打印组合包各层
dsh --profile web --dump-config # 额外加 profile/home/--patch 层

输出带注释,标明每行由哪个文件提供、被哪些 overlay 修改过;!!js 表达式保持未求值。

4.3 热重载

  • 自定义 profile 省略 dsh.profile.patchReload 时默认 live:编辑 profile 与 home 两级 cordis.patch.yml事务性热重载,无需重启。
  • startup 模式只在启动时应用一次。
  • 例外:Bundle 成员变化(插件安装/卸载/更新)只改磁盘 manifest,必须重启 profile 才生效

4.4 cordis.yml / cordis.patch.yml 的写法

配置文件是一组 Cordis 配置项的 YAML 列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 每项的 name 是模块指定符:相对路径或 npm 包名
- name: '@deepseek-ai/dsh-tools'

# 可携带 config 块,由插件的 Config schema 校验
- name: './my-plugin.ts'
config:
greeting: '你好'

# 支持 !!js 表达式(仅在 config 与 disabled 字段内有效)
- name: './env-aware.ts'
config:
greeting: !!js process.env.DEMO_GREETING ?? 'Hello'
disabled: !!js process.platform === 'win32' # 按环境门控一整行

各项并发启动,列表顺序不决定加载先后——顺序由 inject 服务依赖决定。配置校验、模块解析或插件启动失败会报错并以非零状态退出。


5. 插件管理:找、装、管

5.1 安装插件(dsh plugin 命令)

1
2
3
4
dsh plugin --profile <name> add <package-or-git-spec>
dsh plugin --profile <name> remove <package>
dsh plugin --profile <name> update <package>
dsh plugin --profile <name> why <package>

原理:dsh plugin 在 profile 缺失时先初始化它,然后以 profile 目录为工作目录把参数转发给 pnpm(需要 pnpm 在 PATH 上)。

实例(装一个 Git 托管的社区 UI 插件并启动):

1
2
dsh plugin --profile tui add github:deepseek-harness/turtle-ui
dsh --profile tui

装官方 subagent 提供方(Codex / Claude Code 子代理,可独立增删):

1
2
dsh plugin --profile <name> add @deepseek-ai/dsh-subagent-codex
dsh plugin --profile <name> add @deepseek-ai/dsh-subagent-claude-code

5.2 Bundle(组合包)机制

插件包通过在 package.json 中声明即可自动接入配置层栈:

1
2
3
4
5
{
"dsh": {
"bundle": { "patch": "./cordis.patch.yml" }
}
}

dsh plugin add 成功后,dsh 检查依赖闭包中的 bundle 声明:声明了 patch 的依赖自动加入 dsh.profile.bundles 配置层栈;update 后获得声明的也会随即激活。没有 bundle 声明的依赖仍作为普通依赖保留(一次性警告);remove 则从层栈删除。

内置组合包(@deepseek-ai/dsh-basedsh-web-appdsh-headlessdsh-sdk-appdsh-sdk-minimaldsh-acp-app)始终来自当前 dsh 安装;树外组合包来自 profile 中 pnpm 管理的 node_modules

5.3 注意事项

  • 相对路径 spec.../pluginfile:link:)先锚定到调用目录:在插件 checkout 里 add . 安装的是该 checkout 本身。
  • pnpm ≥10 的 allowBuilds:Git 托管且随源码发布的插件靠 prepare 脚本构建,pnpm 默认拦截。首次 add 会失败并给出提示——把输出的键复制进该 profile 的 pnpm-workspace.yaml 后重跑即可。安装已构建的 tarball 或本地 checkout 无需此步。
  • 添加/移除/更新 Bundle 后须重启 profile(运行中的 profile 保留本次启动时的 Bundle 集合)。
  • 某些 Bundle(如 subagent 提供方)装好后还须在复制出的 Preset 中单独启用对应工具行,新 Agent 才能看到该工具。

5.4 去哪找插件

  • GitHub topic:dsh-plugin(给你的插件仓库加上该话题便于被发现)
  • 社区精选列表:awesome-dsh-plugin
  • MCP:CLI 随附 @deepseek-ai/dsh-mcp-client 可供 patch 层使用,但默认不启用任何 MCP 服务器(每条服务器命令都是沙箱外的受信任可执行代码)。

6. 核心架构:一切皆插件

6.1 Cordis 的五个核心概念

概念 含义
插件 实现 Service 的对象:带可选 injectapply(ctx) 的函数、带 apply 方法的对象,或 Service 子类
上下文(Context) 服务容器。服务占据稳定的 ctx.<key>(如 ctx.toolsctx.llmctx.agents);其他插件按 key 查找服务,而非导入具体实现
inject 声明服务依赖。插件等待依赖就绪才启动;加载顺序由依赖表达,无需手动编排。加载后仍持续跟踪:依赖消失则插件随之卸载,恢复后重载
类型化事件 服务通过 TypeScript 声明合并注册事件名,以 emit/waterfall/parallel/serial/bail 分发
可逆注册 提示词片段、工具 schema、适配器、监听器通过 ctx.effect() / ctx.on() 安装;reload 与 teardown 时按预期撤销

6.2 事件分发模式

模式 是否 await 分发顺序 返回值
emit 按注册顺序观察
waterfall 按注册顺序,环绕式
parallel 全部并行
serial 按注册顺序
bail 直到某监听器返回 bail 值

waterfall 语义(最重要的一个):监听器收到 (...args, next),是环绕中间件

  • 调用 next() → 执行下游监听器,其返回值经当前层包装后向外返回;
  • 不调用 next() 直接返回 → 短路整条链
  • 拥有决策权的策略监听器可以短路;仅做观察/标注的监听器必须委托(调用 next());
  • 仅当必须在普通注册之前运行时才用 prepend: true

6.3 实践规则

  • 行为归类:工具流水线事件挂在 ctx.tools,模型流式输出挂在 ctx.llm,agent 协调挂在 ctx.agents
  • 拦截和策略优先用事件;直接能力调用优先用服务方法。
  • 每个注册都应有对应的 disposer:从 ctx.effect() 返回,或用 Cordis 辅助方法自动处理。
  • 服务名共用一个扁平命名空间:harness 已占用 toolsllm 等普通名,自有服务请加有辨识度的前缀。

7. 插件开发实战

以下逐步走通从最小插件到各类真实插件形态。全程不需要 API Key。

7.0 环境准备

1
2
3
4
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
mkdir -p tmp/cordis-tutorial && cd tmp/cordis-tutorial # tmp/ 已被 git 忽略

官方教程系列(docs/cordis-tutorial/,共 7 章,每章可运行):

  1. 第一个插件 → 2. 生命周期与 effect → 3. 服务 → 4. 事件 → 5. 配置 → 6. 组合与 HMR → 7. 进入 harness

每章从 tmp/cordis-tutorial 运行同一条命令(单文件启动器:创建根 Context、挂载 Loader,Loader 加载当前目录 ./cordis.yml):

1
node --import tsx ../../vendor/cordis/bin.js

7.1 第一个插件

hello.ts

1
2
3
4
5
6
7
import type { Context } from '@deepseek-ai/cordis'

export const name = 'hello' // 可选:诊断信息中标识插件的显示名

export function apply(ctx: Context) {
console.log('hello from my first plugin')
}

cordis.yml

1
- name: './hello.ts'

运行 node --import tsx ../../vendor/cordis/bin.js,输出 hello from my first plugin。你的文件里没有任何框架启动代码:插件只描述自己的贡献,cordis.yml 负责组合应用

三种插件形态(需要公开服务前一律用函数形态):

1
2
3
4
5
6
7
8
// 1. 函数插件(最常用)—— 注意:必须命名导出,不能用默认导出!
export function apply(ctx: Context) {}

// 2. 对象插件
export const objectPlugin = { name: 'object-plugin', apply(ctx: Context) {} }

// 3. 类插件(Service 子类,见 7.3)
export class MyService extends Service {}

7.2 四个标准导出

导出 必需 作用
name 显示名(诊断用)
inject 必需服务数组;loader 让插件等待这些服务存在才启动
Config Standard Schema 校验器(本仓库用 Schemastery),运行 apply 前验证配置;配置不完整时插件绝不启动
apply(ctx, config) 插件主体

⚠️ 函数插件必须命名导出。用默认导出会丢失 inject 元数据(官方事故复盘 docs/postmortem/0001 的主题)。

带配置校验的插件(config-demo.ts):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import type { Context } from '@deepseek-ai/cordis'
import Schema from '@deepseek-ai/schemastery'

export const name = 'config-demo'

export interface Config {
greeting: string
targets: string[]
}

// 同名的 interface + schema:消费方得到类型,Cordis 得到验证器
export const Config: Schema<Config> = Schema.object({
greeting: Schema.string().default('Hello'),
targets: Schema.array(String).default(['world']),
})

export function apply(ctx: Context, config: Config) {
for (const target of config.targets) {
console.log(`${config.greeting}, ${target}!`)
}
}
1
2
3
- name: './config-demo.ts'
config:
targets: ['alpha', 'beta']

配置无效时会得到精确报错并进入 FAILED 状态:

1
2
ValidationError: invalid config:
- $.targets expected array but got not-an-array (at targets)

7.3 服务插件(对外提供能力)

greeter.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { Service, type Context } from '@deepseek-ai/cordis'

// 编译时:声明合并把 greeter 加入 Context 接口(不生成代码,只提供类型安全)
declare module '@deepseek-ai/cordis' {
interface Context {
greeter: GreeterService
}
}

export class GreeterService extends Service {
constructor(ctx: Context) {
super(ctx, 'greeter') // 运行时:以名称 greeter 注册实例
}
greet(who: string) {
return `Hello, ${who}!`
}
}

export const name = 'greeter'

export function apply(ctx: Context) {
ctx.plugin(GreeterService) // 注册属于 effect,卸载提供方时自动移除
}

消费方(consumer.ts)——只指定能力名,不导入实现:

1
2
3
4
5
6
7
8
import type { Context } from '@deepseek-ai/cordis'

export const name = 'consumer'
export const inject = ['greeter'] // PENDING 直到 greeter 就绪

export function apply(ctx: Context) {
console.log(ctx.greeter.greet('world'))
}

要点:

  • cordis.yml 中两行交换顺序输出不变——启动时机由依赖决定。
  • 彻底移除 greeter:consumer 保持 PENDING,不崩溃也不运行一半;PENDING 的 fiber 不保持事件循环活跃,若无其他运行项,进程会静默以 0 退出(这正是需要诊断的隐形失败,见第 9 节)。
  • 可选依赖:不写 inject,用 ctx.get('greeter') 探测(无提供方时为 undefined)。

7.4 工具插件(给模型加工具)⭐ 最常用

工具挂在 ctx.tools 上,schema 自动流入系统提示词组装greet-tool.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'

export const name = 'greet-tool'
export const inject = ['tools']

export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'greet',
description: 'Greet the named person.', // 模型可见
parameters: {
name: { type: 'string', required: true, description: 'Who to greet' },
limit: { type: 'number' }, // 缺省即可选
},
output: {
schema: { type: 'string' }, // 规范返回值类型
render: (_args, value) => [{ type: 'text', text: value }],
},
async execute(args, exec) {
// args 已按 schema 校验且有类型:{ name: string; limit?: number }
return `Hello, ${args.name}!`
},
}))
}

execute() 契约(官方”工具编写参考”的硬性规则):

  1. 参数已为你校验defineTool 在运行前按统一 ParameterSchemaSpec 校验模型生成的 arguments,execute 内的 args 与 InferArgs 类型匹配。显式对象节点必须声明 additionalProperties: true | false。schema DSL 表达不了的约束(非空字符串、正数、跨字段规则)仍需手动检查。
  2. 返回一个规范 JSON 值execute 只返回 output.schema 声明形状的值,注册表将其快照为无损 JSON、校验冻结后,再交给 output.render(args, value) 渲染模型可见内容。不要返回内容块,也不要迫使调用方解析自然语言。
  3. 执行身份受保护exec 携带不可变的 callIdnameargumentsagenttoken 和必填的 signal。把 args 当只读输入。
  4. 抛异常 = isError:基础设施故障抛异常;”不理想的领域结果”(如进程非零退出)应作为正常规范值返回,由渲染器解释。
  5. **遵守 exec.signal**:信号触发时取消进行中的工作(如 readFile(args.path, { signal: exec.signal }))。
  6. 只注册一次;需要热替换工具时 dispose 旧副作用再注册替代品。

后台长任务:通过 producer 配置启用 run_in_background,用 ctx.jobs.start({ kind, label, owner: exec.agent, run }) 注册;成功分支返回 { kind: 'background', jobId }

UI 卡片(可选):工具的 UI 展示是独立关注点,通过 presentCall(args)(PENDING 卡片,可选 generic / terminal / diff)和 presentResult(args, { content, isError, meta? })(完成后卡片,另有 read / search / web)声明。硬性规则:这两个方法必须是纯函数(不做 I/O、不读时钟/随机数——它们会在实时流式输出和会话回放时反复运行);UI 格式永不进入模型结果。没有展示方法的工具回退到通用卡片。

PTC mode 自动触达:在 PTC 模式下每个已注册工具自动可通过 await tools.<name>(args) 程序化调用,重新进入正常执行流水线。所以请把 output.schema 设计成实用的程序化 API。

注册即 effect:dispose 插件 fiber 即自动注销工具,无需手写清理。

7.5 钩子插件(拦截与策略)

ctx.on() 拦截扩展点。以官方权限门禁示例为例——从 tools/pre-execute 的 waterfall 返回类型化决策:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import type { Context } from '@deepseek-ai/cordis'
import type { PreToolDecision, ToolExecution } from '@deepseek-ai/dsh-tools'

declare function isAllowed(exec: ToolExecution): Promise<boolean>

export const name = 'permission-gate'

export function apply(ctx: Context) {
ctx.on('tools/pre-execute', async (exec, next): Promise<PreToolDecision> => {
if (!(await isAllowed(exec))) {
return { kind: 'deny', reason: 'Denied by policy.' } // 拥有决策权 → 短路
}
return next() // 仅观察 → 必须委托
})
}

工具流水线的扩展点分工(选错点会做无用功):

扩展点 用途
tools/pre-execute 允许/拒绝/询问(ask + ctx.approval)策略;ctx.tools.guard() 设置后续不可撤销的单调最终拒绝
tools/execute 包裹分发:超时、重试、指标(包装层可替换 exec.signal 施加截止时间)
tools/post-execute 变换结果/阻止结果/附加模型可见上下文
tools/result 只读观察不可变的归一化结果
agent/requestagent/pre-stepagent/session-startagent/turn-stopping agent 循环各阶段的拦截点

「原生钩子」就是这些拦截点上的普通 Cordis 插件,不需要任何外部协议;dsh-hooks-claude-code / dsh-hooks-codex 桥接器正是把既有钩子配置文件映射到这些扩展点上。

7.6 UI 插件

UI 插件组合两类输入:持久的 session/event 记录 + 实时 agent/assistant-stream 帧,并通过 agent.followup() / agent.steer() 把输入驱动回去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import type { Context } from '@deepseek-ai/cordis'
import { brandString } from '@deepseek-ai/dsh-brand'
import { createUserMessage } from '@deepseek-ai/dsh-llm'
import type { SessionId } from '@deepseek-ai/dsh-session'

export const name = 'my-ui'
export const inject = ['agents']

export function apply(ctx: Context) {
// 实时渲染流式文本
ctx.on('agent/assistant-stream', ({ frame }) => {
if (frame.type === 'chunk' && frame.chunk.type === 'text-delta') {
render(frame.chunk.text)
}
})
// 用户输入 → 追加到 agent 会话
onUserInput(text => ctx.agents.get(brandString<SessionId>('client-session'))?.followup(createUserMessage({
content: [{ type: 'text', text }],
source: { kind: 'user' },
})))
}

要在内建 Web Client 中贡献自定义聊天业务行,则注册 ConversationNodeDefinitionconversation.chat.node keyed renderer(详见官方 Conversation 子系统参考)。

7.7 其他插件形态

  • 协议驱动:把外部协议对端(如 ACP 编辑器)接入 ctx.agents。官方完整示例是 packages/acp/acp(JSON-RPC over stdio);拆除 agent 用 AgentHandle.dispose() 达到完全停稳。
  • LLM 适配器:通过 registerAdapter 注册 LlmAdapter 子类(官方已有 dsh-llm-deepseekdsh-llm-pi-ai), cookbook 见 docs/cookbook/adding-an-llm-adapter.zh.md
  • MCP 服务器桥:每个 MCP 服务器一个插件——发现工具后逐个 ctx.tools.register()
  • 系统提示词贡献ctx.systemPrompt.section(),支持排序与作用域覆盖(AGENTS.md 加载就是一个 section 提供方)。

8. 插件的挂载与分发

写好插件后,有三条挂载路径:

路径一:外置 npm 包(推荐发布方式)

  1. 初始化插件包,在 package.json 声明 bundle:

    1
    2
    3
    4
    {
    "name": "my-dsh-plugin",
    "dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
    }
  2. 包内提供 cordis.patch.yml(把你的插件行加进配置层):

    1
    - name: my-dsh-plugin        # 或指向具体入口文件
  3. 用户安装即自动生效:

    1
    2
    dsh plugin --profile <name> add my-dsh-plugin        # npm 包
    dsh plugin --profile <name> add github:user/repo # Git 托管

路径二:--patch 临时 overlay(本地试验)

不改 profile,临时叠加一层配置:

1
2
dsh web --patch ./extra.cordis.yml
dsh --profile headless --patch ./my-overlay.yml "task"

路径三:仓库内正式包(给 dsh 本体贡献)

packages/<group>/<pkg> 新建正式包并登记进对应 tsconfig aggregate,走官方 包检查清单 与 PR 流程。

发布与传播


9. 调试与质量保障

9.1 检查配置树

1
dsh --profile web --dump-config

注释会标明每行来源与被哪些 overlay 修改;找不到目标的 patch 会报到 stderr。dump 会初始化缺失的 profile 文件,但不运行应用参数提供方(有应用参数的调用会被拒绝)。

9.2 诊断 PENDING(插件”没效果”的头号原因)

症状:新增配置项毫无反应、进程静默退出 0。排查顺序:

  1. 检查拼写:模块无法解析(路径/包名写错)时,Cordis 经 logger 服务报告而不崩溃,且启动阶段这条报告可能在 console 导出器开始工作前丢失。
  2. 检查 inject 的服务是否有提供方:组合中缺少 toolssystemPrompt 等服务的提供方时,插件会保持 PENDING 且无任何输出。对照 base/headless bundle 层确认你 inject 的服务由谁提供。
  3. 用 HMR live 重载配合日志观察挂载决策。

9.3 官方 Cordis 教程系列

docs/cordis-tutorial/(01–07 章,全部可运行、无需 Key):生命周期与 effect、服务、事件(waterfall 短路实验)、配置校验、组合与 HMR 诊断、接入真实 harness 工具流水线。是理解本文所有概念的实践路径。

9.4 质量门禁(贡献 dsh 本体时)

逐级执行:constraintstypechecklintbuildhygienedoc-synctest。CI 要求 100% 测试覆盖率;产品可见插件需真实组合测试。日常提交只需选择覆盖变更表面的最小检查集(pnpm run check:all 可跑全套本地门禁)。


10. 常见坑速查

# 正确做法
1 函数插件用默认导出 必须命名导出 apply/inject/Config,否则丢失 inject 元数据
2 插件静默不加载、进程退出 0 inject 的服务没有提供方 → PENDING;或模块路径拼写错误(错误经 logger 报告,可能启动期丢失)。先查拼写,再对照 bundle 层确认服务提供方存在
3 patch 改了没生效 patch 是整行替换 config(非深合并);用字面量替换会一并移除 !!js 运行时读取。Bundle 增删需重启 profile(仅 patch 文件编辑才热重载)
4 waterfall 监听器把下游全短路了 观察/标注型监听器**必须调用 next()**;短路是策略监听器的专属特权
5 装插件报 allowBuilds 错误 pnpm ≥10 默认拦截 prepare 脚本:把提示的键复制进该 profile 的 pnpm-workspace.yaml 后重跑
6 dsh web --help 没启动应用 这是设计:应用参数边界之后,flag 交给应用自己解析。启动器 flag 必须写在最前;传字面量 ---- --
7 --host 0.0.0.0 报错 CLI 有意不支持,以用法错误退出
8 服务名冲突 服务共用扁平命名空间,harness 占用 tools/llm 等普通名;自有服务加前缀
9 presentCall 里读文件/时钟 展示方法必须是纯函数(实时与回放都会反复运行);会话上下文由 UI 适配器提供
10 工具把失败信息塞进自然语言 规范值保持结构化(供程序消费),人类解释放进 output.render;基础设施故障才抛异常
11 后台任务用 exec.signal 取消 任务 id 发布后,生命周期归 job_kill/owner dispose 所有,应使用任务自有取消信号
12 装了 subagent bundle 但模型看不到工具 还须在 Preset 中单独启用对应工具行

11. 功能 → 插件机制速查表

(来自官方实操手册,”没有任何一行修改循环本身”的微内核声明由此可验证)

产品功能 插件机制
钩子系统 agent/session-startagent/pre-stepagent/requesttools/pre-executetools/post-executeagent/turn-stopping 监听器
内置工具 ctx.tools.register()dsh-tool-*:bash、fs、web、subagent、todo)
权限/AskUserQuestion tools/pre-execute 返回 ask + ctx.approval 应答
Plan mode @deepseek-ai/dsh-plan-modeplan/mode 状态 + plan:policy 提示段 + exit_plan_mode 出口
子代理委派 ctx.subagents 提供方注册表 + dsh-tool-subagent
MCP 每服务器一插件 → ctx.tools.register()
上下文压缩 ctx.compaction seam + dsh-compaction-basic,自动检查挂 agent/pre-step
系统提示词 ctx.systemPrompt.section();AGENTS.md 是一个 section 提供方
模型适配器 registerAdapter 注册 LlmAdapter 子类
定时任务 定时器触发 → 空闲时 followup() / 忙碌时 inject() 通知
UI/遥测/回放 agent/assistant-stream + session/event;回放 = sessions.create(id, { seed })
插件热重载 每个注册都是 ctx.effect → HMR 直接生效

12. 资源索引

官方

社区教程(第三方,辅助阅读)


本文整理于 2026-09-05,基于 master 分支官方文档。dsh 处于开发者预览阶段,API 可能变化,请以官方文档为准。


DSH(DeepSeek Harness)使用及插件开发完整教程
https://ldioif.cn/2026/09/06/2026-09-DSH(DeepSeek Harness)使用及插件开发完整教程/
作者
博主(改成你的名字)
发布于
2026年9月6日
许可协议