夜雨聆风学习资料网

ARTICLE · 1340013

DSH 插件开发:从 Cordis 原理到 Bundle 分发的实战指南

DSH 插件开发:从 Cordis 原理到 Bundle 分发的实战指南

从 Cordis 插件机制,到工具、服务、事件、配置、打包与发布。

DeepSeek Harness 开发者学习手册 | 官方仓库与文档调研 | 资料核对日期:2026 年 11 月 9 日

本手册解决三个问题

  • DSH 插件究竟是什么,运行时如何被加载、依赖、调用和卸载?
  • 如何从零写一个能被模型调用的工具插件,并完成本地调试?
  • 如何把开发完成的插件打包成 Bundle,安装到 Profile 并交付给其他人?
先看一个重要结论DSH 的插件不是单一的“工具函数扩展”。它建立在 Cordis 插件运行时之上:插件可以注册模型工具、提供服务、监听事件或扩展 Web 客户端。对初学者,建议先从 Node 侧工具插件入手,再学习服务、事件和打包。 

目录

  • 01 先理解 DSH:它是什么,插件解决什么问题
  • 02 插件的整体架构:Cordis、Context、Fiber、Profile 与 Bundle
  • 03 插件加载和运行原理:依赖注入、注册、卸载与热替换
  • 04 DSH 插件的几种形态:工具、服务、事件、模型适配器、Web UI
  • 05 开发环境准备:版本、仓库与启动方式
  • 06 第一个可运行插件:添加 greet 工具
  • 07 配置、事件和资源生命周期:从 Demo 到可维护插件
  • 08 打包、安装与分发:Bundle / Profile 工作流
  • 09 Web 客户端插件:Host 与 Browser 双端结构
  • 10 测试、调试与常见问题
  • 11 插件安全:必须认真对待的边界
  • 12 DSH Plugin、MCP、Skill 的区别与选型
  • 13 建议学习路线与项目练习
  • 附录 A 常用命令速查
  • 附录 B 官方资料与源码入口
阅读顺序建议先看 1–4 章建立概念,再按第 5–8 章做出一个真正可安装的插件。第 9 章属于 UI 扩展进阶内容;第 11 章建议在任何安装第三方插件之前阅读。 
01 先理解 DSH:它是什么,插件解决什么问题

DSH 是 DeepSeek Harness 的命令行名称,由 DeepSeek AI 开源。它不是一个新的大模型,也不只是一个聊天前端,而是运行在模型外部的一层 Agent Harness:负责组织模型请求、工具调用、会话、权限与审批、文件和命令执行、模型接入以及用户界面。官方 README 将其定义为基于 Cordis 的“一切皆插件”架构。[1]

可以把大模型理解为“负责理解与生成的决策引擎”,把 Harness 理解为“让模型能够在真实环境里完成工作的运行时”。DSH 中的很多能力并不写死在一个大核心里,而是由插件组合出来。模型适配器、工具注册表、会话日志和 Agent Loop 等都通过插件机制参与运行。[2]

1.1 插件不是只有 Tool

在常见的 Agent 框架中,开发者经常把插件理解为“给模型加一个 function calling 工具”。DSH 的插件范围更广:

Tool 插件
做什么:向模型注册可调用的工具,模型生成参数后由运行时执行
典型例子:检索内部知识库、查询数据库、读取业务系统、执行计算
Service 插件
做什么:向其他插件提供服务接口,通过 ctx 上的服务名访问
典型例子:提供统一缓存、内部 API 客户端、业务数据服务
Event / Hook 插件
做什么:监听、记录、转换或拦截运行时事件
典型例子:记录工具调用、加策略门禁、处理异常、统计耗时
Model Adapter 插件
做什么:接入或替换模型调用后端
典型例子:自建模型网关、私有部署模型、路由到不同推理服务
Bundle / Profile 扩展
做什么:把一个或多个插件配置打包成可安装组合层
典型例子:交付一套面向研发或企业场景的 DSH 配置
Client UI 插件
做什么:扩展 DSH Web 客户端的界面、组件与浏览器服务
典型例子:侧边栏、面板、消息卡片、交互入口

因此,“写一个 DSH 插件”之前,第一步不是立刻写代码,而是先确认你要扩展的是工具能力、运行时服务、事件行为,还是浏览器界面。普通的内部 API 封装通常从 Tool 插件起步;需要多个插件共用的能力,才抽象成 Service。

1.2 插件、MCP 和 Skill 不是一回事
DSH Plugin
本质:运行在 DSH 插件系统中的可执行扩展代码
适合解决的问题:扩展 DSH 内部运行时:工具、服务、事件、模型适配器、UI
MCP
本质:进程或网络边界上的工具/资源协议
适合解决的问题:连接独立服务、外部工具服务器或跨 Agent 客户端复用能力
Skill
本质:给 Agent 的任务说明、规则、流程或领域知识
适合解决的问题:告诉模型“应该怎样做”,而不是直接提供一个新的运行时服务
选型经验如果能力需要在 DSH 进程内直接注册事件、服务或工具,选择 Plugin;如果已有独立服务希望被多个 Agent 复用,优先考虑 MCP;如果只是补充操作步骤、提示词或领域方法,Skill 通常更合适。可以在 Plugin 内部调用 MCP,也可以让 Plugin 提供工具封装,但不要把三者混为一谈。 
02 插件的整体架构:Cordis、Context、Fiber、Profile 与 Bundle

理解 DSH 插件,核心是理解 Cordis。Cordis 是 DSH 底层的插件框架。插件向共享 Context 贡献服务、类型化事件以及可撤销的注册动作;框架负责依赖关系、加载生命周期和资源清理。[2][3]

2.1 五个核心对象
Plugin
可以怎样理解:一段遵循 Cordis 约定的模块/类
开发时关注什么:name、inject、apply(ctx, config)、可选 Service 类
Context(ctx)
可以怎样理解:插件可见的服务容器与运行环境
开发时关注什么:通过 ctx.tools、ctx.llm 等访问服务,不要硬绑具体实现
Service
可以怎样理解:一个插件对外暴露的命名能力
开发时关注什么:定义稳定接口;消费者通过 inject 声明依赖
Fiber
可以怎样理解:一次插件挂载实例及其生命周期作用域
开发时关注什么:插件注册的监听器、工具、资源归属到 Fiber
Effect
可以怎样理解:插件挂载时产生、卸载时撤销的副作用
开发时关注什么:用 ctx 注册资源;自定义资源用 ctx.effect() 返回清理函数
2.2 一个运行中的 DSH 如何组成

DSH 不是简单地“扫描一个 plugins 文件夹,然后把里面的 JS 全部加载”。一个启动中的实例由 Profile 组织,Profile 按顺序堆叠 Bundle,并在上面再应用用户自己的 Patch。官方架构文档给出的典型层次是:[2]

配置分层示意

空配置根节点
  └─ Profile 中按顺序声明的 Bundle patch
       └─ Profile 自己的 cordis.patch.yml
            └─ DSH_HOME 级别的 cordis.patch.yml
                 └─ 命令行传入的 --patch overlay
                      ↓
                 Loader 装配最终插件树
                      ↓
                 Cordis 根据服务依赖加载各插件

Patch 通过条目的 id 定位插件行,可以插入新行,也可以替换某行的配置。需要注意:后层优先级更高;Patch 对目标条目的 config 采用整块替换语义,并非所有字段都做深度合并。修改已有插件配置时,不能只写一个字段就假设旧 config 的其他字段一定保留。[2]

2.3 Bundle 与 Profile 的区别
Bundle
它回答的问题:这个可分发包贡献了什么插件层?
典型文件:npm package.json 中的 dsh.bundle;cordis.patch.yml;插件代码
Profile
它回答的问题:当前启动的这套 DSH 由哪些 Bundle 组成?
典型文件:$DSH_HOME/profiles/<name>/package.json;cordis.patch.yml

Bundle 是开发者编写、版本化和分发的东西;Profile 是用户启动的一份运行组合。两者都可能在 package.json 的 dsh 字段中声明,但声明类型不同:Bundle 用 dsh.bundle,Profile 用 dsh.profile。官方文档明确强调,一个对象不是同时承担这两种角色。[4]

03 插件加载和运行原理:依赖注入、注册、卸载与热替换
3.1 apply(ctx) 是插件的入口

最常见的插件形态是导出 name、可选 inject,以及 apply(ctx, config)。当插件依赖的服务准备好后,Cordis 会执行 apply。一个只有日志输出的最小插件可以写成:

最小插件

import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello-dsh'
export function apply(ctx: Context) {
  console.log('[hello-dsh] plugin loaded')
}

name 是插件在组合树中的稳定名称。apply 是挂载入口,config 是由配置层传入并经过 schema 处理的运行参数。开发工具插件时,通常会显式声明 inject: ['tools']。

3.2 inject:依赖服务就绪后再启动

假设插件需要注册工具,它依赖工具注册表服务。写 inject: ['tools'] 后,Cordis 会等 tools 服务可用时才执行 apply。若服务尚未准备好,插件会处于等待状态,而不是在 apply 内部盲目访问一个可能不存在的 ctx.tools。[3]

依赖注入

export const name = 'greet-tool'
export const inject = ['tools']
export function apply(ctx: Context) {
  // 到这里时,ctx.tools 已经就绪
  ctx.tools.register(/* tool definition */)
}
3.3 Fiber 生命周期

插件状态机

PENDING → LOADING → ACTIVE
             ↘ FAILED
ACTIVE → UNLOADING → DISPOSED
PENDING
含义:插件已声明,但 inject 所需服务尚未就绪
LOADING
含义:依赖已满足,正在执行 apply
ACTIVE
含义:插件正常运行
FAILED
含义:apply 执行失败
UNLOADING
含义:插件正在退出并释放资源
DISPOSED
含义:插件已彻底卸载

如果插件依赖的服务后来消失,例如某个服务提供者被替换,依赖它的插件也可能被卸载;服务恢复后,再按依赖关系重新加载。这让能力替换可以在插件树层面完成,而不必强行维持某个固定加载顺序。[3]

3.4 自动清理:为什么注册动作要通过 ctx

通过 ctx.on() 注册的事件监听器、通过 ctx.tools.register() 注册的工具,以及通过 ctx.effect() 注册的自定义资源,都会归属于当前插件 Fiber。Fiber 卸载时,框架会撤销这些注册。对于数据库连接、WebSocket、文件 watcher 等资源,应在 ctx.effect() 中返回释放函数。[3]

自定义资源的清理

export function apply(ctx: Context) {
  ctx.effect(() => {
    const connection = createConnection()
    // 插件卸载或热替换时执行
    return () => connection.close()
  })
}
不要绕过生命周期插件不要在 apply() 里启动一个永久运行、却没有释放方式的后台任务。也不要把插件注册的 handler 放到全局 EventEmitter 后忘记 removeListener。优先使用 ctx 提供的注册接口;必须管理外部资源时,让 ctx.effect() 明确知道如何清理。 
3.5 HMR 热替换

在启用 DSH 的 HMR 支持后,文件变化可以触发旧插件卸载、清理注册、重新加载模块并再次执行 apply。因为注册动作属于 Fiber 的 effect,正确使用 ctx 注册资源时,热替换不会累积一堆旧 listener 或重复工具。[3] 但 HMR 不会替代测试:文件可重载,不代表资源清理或外部 API 调用就一定正确。

04 DSH 插件的几种形态:工具、服务、事件、模型适配器、Web UI
4.1 Tool:把外部能力变成模型可调用的函数

这是最适合入门的扩展方式。Tool 通过 ctx.tools.register() 注册到工具运行时。工具描述和参数 schema 会参与模型请求的工具定义;模型返回工具调用后,运行时校验参数,再调用 execute(),最后按 output 的约定把规范结果渲染成模型可读的 content。[5][6]

一个 Tool 需要重点设计四件事:

  • name:稳定、简洁、能表达动作的工具名。
  • description:模型看到的工具说明。写清楚用途、输入、限制和何时调用。
  • parameters:声明参数类型、必填项、描述和必要约束。
  • execute + output:执行真实逻辑,并返回符合 output.schema 的规范结果;render 决定结果如何转换成模型可读内容。
特别注意defineTool 会按声明的 Parameter Schema 校验参数,但仍需在 execute 内检查 schema 无法表达的业务条件,例如字符串不能为空、数值范围、跨字段约束以及用户是否有权访问目标数据。[6] 
4.2 Service:把共用能力抽成服务

当多个插件都需要调用同一个内部能力时,不应该把 HTTP 请求、鉴权、重试和缓存逻辑复制到每个 Tool 中。可以把它们封装为一个 Service,挂载在 ctx 的稳定 key 上,再由消费者通过 inject 声明依赖。DSH 文档中的典型服务包括 ctx.tools、ctx.llm 和 ctx.agents。[7]

更进一步的工程化拆分是把能力分成三层:Service Definition(接口契约)、Service Provider(真实实现)和 Consumer(调用方)。这样可以在不修改消费插件的情况下替换本地实现、云端实现或测试 Mock。

4.3 Event / Hook:观察或改变其他插件的行为

事件是 Cordis 插件之间松耦合通信的主要方式。基础形式是 ctx.on(event, handler) 监听和 ctx.emit(event, payload) 广播。Cordis 还提供 bail、serial 和 waterfall 等不同调用语义。[8]

emit
执行语义:广播:所有监听器执行,忽略返回值
典型用途:日志、指标、通知
bail
执行语义:短路:遇到第一个有效返回值即结束
典型用途:判断是否允许、选择一个处理结果
serial
执行语义:按注册顺序串行执行并等待异步结果
典型用途:有顺序要求的阶段处理
waterfall
执行语义:每个监听器包装下游结果,组成处理流水线
典型用途:请求改写、策略网关、结果转换

waterfall 的监听器必须调用 next() 才会继续下游;如果不调用,流水线会被短路。这是有意设计,不是普通的事件广播。[8]

工具结果观察示例

ctx.on('tools/result', (exec, result) => {
  console.log(`[tool] ${exec.name}`)
  const text = result.content
    .map(block => block.type === 'text' ? block.text : '')
    .join('')
  console.log(text.slice(0, 120))
})

常见的事件命名采用 namespace/action 风格,例如 agent/pre-step、agent/request-error、tools/result 和 session/event。要观察持久化的 turn/*、step/*、tool/call 等会话事件时,应监听 session/event 并检查 event.type;它们不等同于同名的 Cordis 事件。[8]

4.4 Model Adapter:接入新的模型后端

如果目标不是增加工具,而是让 DSH 通过新的网关或私有部署模型完成推理,就应该研究 LLM Adapter 接口,而不是把模型调用伪装成一个 Tool。适配器负责把 DSH 的模型调用契约转换为供应商 API 的请求与响应格式。建议在熟悉基础工具插件后再进入这一层。

4.5 Client UI:扩展 Web 界面

Web UI 插件同时涉及 Node Host 和 Browser Client 两个运行面。Host 侧负责识别启用的插件包、组织客户端启动依赖图,并提供客户端 bundle;浏览器侧按需加载 bundle。普通 Node Tool 插件不需要声明 dsh.client;只有确实要扩展浏览器 UI 时才进入这套机制。[9]

05 开发环境准备:版本、仓库与启动方式

官方 README 将 DSH 标为 Developer Preview,明确提醒项目迭代较快,未来可能出现破坏兼容性的变更。[1] 因此,学习时应把 DSH 版本、插件依赖版本和源码 commit 一起记录,不要把某个示例当成跨版本永久稳定的 API。

Node.js
本次核对到的要求/信息:当前主仓库 package.json 要求 node >=24.9.0
建议:先运行 node --version 确认;不要继续使用旧的 Node 22 环境
包管理器
本次核对到的要求/信息:仓库 packageManager 指向 pnpm 11.28.5
建议:按仓库版本使用 Corepack/pnpm
源码仓库
本次核对到的要求/信息:https://github.com/deepseek-ai/deepseek-harness
建议:源码开发建议 clone 官方主仓库
运行入口
本次核对到的要求/信息:npx @deepseek-ai/dsh web
建议:只想先体验产品时使用
源码运行
本次核对到的要求/信息:pnpm install → pnpm run build → pnpm dsh web
建议:需要本地挂载插件、理解代码或使用源码教程时使用

版本依据:当前仓库根 package.json 的 engines.node 为 >=24.9.0,packageManager 为 pnpm@11.28.5;README 的源码运行方式是 clone、pnpm install、pnpm run build,再运行 pnpm dsh web。[1][10]

5.1 只体验 DSH

快速启动

npx @deepseek-ai/dsh web
# 默认 Web UI 地址
http://127.0.0.1:3080

首次启动后,需要先按 DSH 界面完成模型 Provider、API Key 或账户凭据配置,才能验证模型是否会主动调用工具。注意:这个命令用于体验产品;如果希望按官方本地插件教程挂载 TypeScript 源文件,建议采用源码 checkout 开发路径。

5.2 从源码准备开发环境

源码准备

git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
node --version      # 要求 >= 24.9.0
corepack enable
pnpm --version     # 以仓库 packageManager 字段为准
pnpm install
pnpm run build
pnpm dsh web

pnpm run build 会准备仓库运行所需的构建产物;pnpm dsh web 会使用这些已构建产物启动,不会自动重新构建。[1]

06 第一个可运行插件:添加 greet 工具

接下来写一个最小但完整的 Tool 插件:模型接收到“用 greet 工具问候 Ada”后,会调用 greet(name),插件返回一条问候语。这个例子沿用官方工具教程,并额外加入配置 schema,让你同时看到工具注册与插件配置如何配合。[5][11]

6.1 创建目录
# 在 deepseek-harness 仓库根目录执行
mkdir -p scratch-plugin/src

建议先不要自己设计复杂的 package 或构建系统。先通过一个源码目录 + cordis.yml patch,将插件挂载进 Web profile。

6.2 编写插件代码

插件主体:my-plugin.ts

// scratch-plugin/src/my-plugin.ts
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
import Schema from '@deepseek-ai/schemastery'
export const name = 'greet-tool'
export const inject = ['tools']
export interface Config {
  prefix: string
}
export const Config: Schema<Config> = Schema.object({
  prefix: Schema.string().default('Hello'),
})
export function apply(ctx: Context, config: Config) {
  ctx.tools.register(defineTool({
    name: 'greet',
    description: 'Greet someone by name.',
    parameters: {
      name: {
        type: 'string',
        required: true,
        description: 'The name of the person to greet',
      },
    },
    output: {
      schema: { type: 'string' },
      render: (_args, value) => [{ type: 'text', text: value }],
    },
    async execute(args) {
      return `${config.prefix}, ${args.name}!`
    },
  }))
}

这段代码可以拆成四部分理解:

name = greet-tool
职责:Cordis 插件本身的名字,不等于模型调用的工具名
inject = [tools]
职责:声明插件需要工具注册表服务
Config + Schema
职责:声明插件配置结构和默认值,加载时校验
ctx.tools.register(defineTool(...))
职责:将 greet 工具注册到 DSH 工具运行时
6.3 挂载本地插件

在 scratch-plugin/cordis.yml 中写入一个 patch。插件源码路径按官方本地挂载教程使用绝对路径。将下面的路径替换为你自己仓库的真实路径:

本地 patch

# scratch-plugin/cordis.yml
- insert:
    - id: greet-tool-row
      name: '/absolute/path/to/deepseek-harness/scratch-plugin/src/my-plugin.ts'
      config:
        prefix: '你好'

然后从 deepseek-harness 仓库根目录启动:

启动开发实例

pnpm dsh web --patch ./scratch-plugin/cordis.yml

打开 http://127.0.0.1:3080,确认模型 Provider 已配置,然后输入:

验收语句请使用 greet 工具问候 Ada。 

预期模型会选择 greet 工具,传入 name = Ada,execute() 返回“你好, Ada!”,工具结果再交还给 Agent Loop,供模型继续生成最终答复。官方教程使用的问候语是 “Hello, Ada!”;这里将 prefix 改成中文,只是为了演示插件配置如何影响行为。[5]

6.4 一个 Tool 从定义到执行究竟发生了什么
  1. 插件被 Loader 加载,Cordis 检查 inject,等待 ctx.tools 就绪。
  2. apply() 运行,调用 ctx.tools.register(),把工具定义注册进运行时。
  3. DSH 组装模型请求时,工具定义中的 name、description 和 parameters 成为模型可见工具契约。
  4. 模型决定调用 greet,返回结构化参数。运行时先按参数 schema 做校验。
  5. 通过校验后执行 execute(args),返回值必须符合 output.schema。
  6. output.render 将规范结果转换为模型可读 content,Agent Loop 收到结果后继续处理。

这也是为什么工具 description 和 schema 必须认真写:模型并不会“理解插件源代码”,它主要依据对外暴露的工具契约决定是否调用、如何组织参数。

07 配置、事件和资源生命周期:从 Demo 到可维护插件
7.1 配置 Schema:把环境差异从代码中拿出来

如果一个参数在不同部署、环境或客户间可能不同,就应该考虑将其定义为 Config,而不是写死在代码里。官方建议通过 @deepseek-ai/schemastery 定义 schema,并导出与 Config 同名的 schema 供 Cordis 校验。普通 JavaScript 对象不满足这里要求的 Standard Schema 接口。[11]

配置定义

import type { Context } from '@deepseek-ai/cordis'
import Schema from '@deepseek-ai/schemastery'
export const name = 'api-client'
export interface Config {
  baseUrl: string
  timeoutMs: number
  verbose: boolean
}
export const Config: Schema<Config> = Schema.object({
  baseUrl: Schema.string().required(),
  timeoutMs: Schema.number().default(15000),
  verbose: Schema.boolean().default(false),
})
export function apply(ctx: Context, config: Config) {
  // config 已经过 schema 校验,并应用默认值
}

配置里可放基础 URL、超时、重试次数、模式开关等非秘密参数。API Key、访问令牌等敏感数据不要作为普通配置写进仓库或示例文件,应使用 DSH 的凭据管理机制、受控环境变量或外部 Secret 管理方案。

7.2 事件:观察、审计与拦截

适合先做的事件插件包括工具调用日志、错误跟踪、调用耗时统计、审计记录和策略检查。比如监听 tools/result 可以记录一次工具执行的最终结果;如需阻止某次工具执行,应研究对应的 pre-execute / guard 等正式策略扩展点,不要把一个普通观察事件误当成可靠的权限边界。[8][12]

7.3 资源生命周期

插件里创建的资源包括:HTTP 客户端、数据库连接池、定时器、文件 watcher、WebSocket、后台队列和临时文件。应逐一回答:谁创建?谁拥有?何时停止?异常退出时是否清理?重复加载时是否会重复创建?

事件监听器
推荐做法:用 ctx.on() 注册,让 Fiber 统一管理
工具注册
推荐做法:用 ctx.tools.register() 注册,不要私自修改已注册定义
数据库/HTTP/WebSocket 连接
推荐做法:用 ctx.effect() 创建并返回 close/dispose 清理函数
定时任务
推荐做法:使用受 Cordis 生命周期管理的服务/定时器,卸载时停止
长任务
推荐做法:支持 AbortSignal / 超时 / 取消;避免永久阻塞 Agent Loop
7.4 工具执行的工程化约束
  • 给网络请求设置超时,并将执行取消信号传递到底层 fetch / SDK。
  • 参数校验除了类型,还要检查长度、数值范围、白名单和跨字段关系。
  • 限制返回内容大小,不要把大段原始文档无上限塞回上下文。
  • 对外部 API 错误返回可诊断但不泄露密钥、堆栈和内部地址的信息。
  • 写操作默认采用显式确认或审批;先实现只读能力,再逐步增加写权限。
  • 幂等性、重试和限流要按下游 API 的真实契约设计,不能对所有错误盲目重试。
08 打包、安装与分发:Bundle / Profile 工作流

本地 --patch 适合开发试验,但不适合给其他人长期安装。要让插件成为可安装的扩展,需要把代码和 Patch 包成一个 npm 包,并在 package.json 的 dsh.bundle.patch 中声明配置层。官方教程将该产物称为 Bundle。[4]

8.1 Bundle 的目录结构

发布包结构

dsh-hello-plugin/
├── package.json          # 包信息 + dsh.bundle 声明
├── cordis.patch.yml      # 该 Bundle 贡献的配置层
└── index.js              # 插件入口代码
8.2 package.json

最小 Bundle manifest

{
  "name": "dsh-hello-plugin",
  "version": "0.1.0",
  "type": "module",
  "main": "index.js",
  "files": ["index.js", "cordis.patch.yml"],
  "dsh": {
    "bundle": {
      "patch": "./cordis.patch.yml"
    }
  }
}

真实插件通常还要补齐 repository、license、engines.dsh、peerDependencies 和必要的 devDependencies。若插件 import @deepseek-ai/cordis 或 DSH 服务包,需按官方打包规则处理与宿主共享实例的依赖:与宿主共享的 DSH 包应采用 peerDependencies,并用相应 devDependencies 支持本地类型检查和开发;版本范围要与实际测试过的 DSH 版本匹配,不建议无约束地写 *。[4]

8.3 cordis.patch.yml

Bundle patch

- insert:
    - id: hello-plugin
      name: dsh-hello-plugin

这里 name 使用已安装的 npm package 名称,而不是本地源文件的相对路径。Node 模块解析依靠包名找到插件入口。

8.4 安装到一个 Profile

本地 Bundle 安装

# 在包含 dsh-hello-plugin 目录的工作区执行
# 首次使用 demo 时,CLI 会初始化一个 Profile
dsh plugin --profile demo add ./dsh-hello-plugin
# 查看最终装配配置
dsh --profile demo --dump-config
# 启动这个 Profile
dsh --profile demo

第一次向 demo Profile 安装 Bundle 时,CLI 会初始化 Profile,并以 @deepseek-ai/dsh-base 作为基础组合层;当安装包声明了 dsh.bundle 时,CLI 会把它登记到 dsh.profile.bundles。[4]

8.5 更新、卸载和版本兼容
  • 插件更新:通过 profile 的包管理依赖更新对应 Bundle,再核对 dump-config 与运行行为。
  • 插件卸载:使用 dsh plugin --profile demo remove dsh-hello-plugin。
  • 版本约束:推荐声明 engines.dsh 的 SemVer 范围;安装和启动时会检查兼容性。
  • 修改 Patch 后必须重新检查组合结果。Patch 覆盖的是配置条目,不能只凭源码文件存在就判断插件已启用。

对于公开分发,可以通过 npm 包或 Git 仓库固定版本/commit 的方式安装。官方项目还提供实验性的 Custom plugin indexes:索引是一个 JSON 文件,列出可安装 Bundle 的名称、说明、作者、版本及来源;它不是插件运行机制本身,而是发现与分发目录。[13]

09 Web 客户端插件:Host 与 Browser 双端结构

如果要开发侧边栏、设置面板、消息卡片或浏览器交互入口,只有 Node 侧插件是不够的。DSH Web 客户端插件采用 Host + Client 两部分:

Host half
运行位置:Node / DSH 服务端
职责:扫描启用的 Loader 条目,识别 dsh.client,组织客户端启动图,并提供浏览器 bundle
Client half
运行位置:浏览器
职责:加载 bundle,注册客户端服务、store、UI slot 和交互逻辑

官方客户端模块机制会把声明了 dsh.client 的插件包转换成可加载的浏览器 bundle。Host 侧在 Web composition 中提供资源;浏览器侧按需加载,模块副作用在模块真正被物化时才运行。官方说明客户端 bundle 的惰性加载是为了避免启动时执行所有 UI 插件代码。[9]

9.1 什么时候才需要 dsh.client
  • 只给模型增加一个 search_xxx 工具:不需要 dsh.client。
  • 监听工具事件并写入日志:不需要 dsh.client。
  • 要在浏览器里新增面板、菜单、卡片或交互组件:需要研究 dsh.client。
  • 要给 Web 页面同时提供 Host API 和浏览器组件:需要设计两端契约、依赖顺序与数据边界。
进阶建议先把 Node 侧 Tool / Service 插件练熟,再单独开一个 UI 插件实验。客户端插件构建体系、模块入口和组件 slot 规则相对更复杂,不应作为第一个 DSH 插件项目。 
10 测试、调试与常见问题
10.1 建议的最小测试层次
纯函数单测
测试目标:参数处理、业务规则、格式转换
推荐方式:不启动 DSH,直接测试函数
插件挂载测试
测试目标:apply 是否成功,依赖是否就绪,资源是否正确注册
推荐方式:在本地 Profile / scratch overlay 中加载
Tool 契约测试
测试目标:schema、无效参数、输出结构、异常和取消
推荐方式:构造合法与非法调用用例
端到端测试
测试目标:模型是否能发现、选择并正确使用工具
推荐方式:Web UI 中输入自然语言任务并核对工具轨迹
回归测试
测试目标:卸载/HMR 后无残留,更新后兼容
推荐方式:重复加载、卸载、升级、重启、重放典型任务
10.2 调试命令

常用调试命令

# 查看当前 Profile 的最终配置树
dsh web --dump-config
# 查看默认 Profile 的配置树
dsh web --dump-default-config
# 以开发 patch 覆盖层启动
pnpm dsh web --patch ./scratch-plugin/cordis.yml

dump-config 的价值在于验证最终结果,而不是只看自己写的 patch。遇到“插件没生效”,先确认最终组合树中是否出现插件行、是否被 disabled、name 指向是否正确、profile 是否真的安装了 bundle。具体命令参数应以当前安装版本的 CLI help 为准。[14]

10.3 常见问题排查
插件启动时未执行 apply
优先检查:inject 声明的服务是否加载;条目是否进入最终配置
解决思路:用 dump-config 检查;确认服务 key 与插件包兼容
找不到模块
优先检查:Node 版本、依赖安装、peerDependencies、包名解析
解决思路:按官方依赖规则配置;避免引入另一份 Cordis 实例
本地插件文件不加载
优先检查:patch 文件路径是否正确;源码插件路径是否为绝对路径
解决思路:重新 pwd,替换绝对路径;核对 YAML 缩进
工具不被模型调用
优先检查:description 是否具体;参数是否容易构造;模型工具调用是否开启
解决思路:缩小工具职责,写清调用场景,降低歧义
工具调用后报错
优先检查:参数 schema、execute 异常、output.schema 与真实返回值
解决思路:记录脱敏参数和错误类别;逐层验证
改配置后行为没变化
优先检查:Profile 是否更新;配置是否被更高层 patch 覆盖
解决思路:重新 dump-config;检查 patch 的层级与覆盖顺序
热替换后出现重复任务
优先检查:是否绕过 ctx.effect;外部定时器/监听器未清理
解决思路:将资源释放逻辑交给 Fiber,加入重复加载回归测试
11 插件安全:必须认真对待的边界
官方安全提示DeepSeek Harness 当前仍是实验性 Developer Preview,官方 Safety 文档明确指出项目尚未经过安全审计,不能视为安全或生产就绪。项目可执行模型生成的代码与命令、加载第三方插件,并可能访问运行环境提供的网络、进程、凭据和文件。沙箱、审批和权限控制可以降低风险,但不能保证隔离或避免损害。[15] 
11.1 对插件代码采用“可执行程序”标准审查

不要把第三方插件理解成主题皮肤或静态配置。一个插件包会执行代码;安装和启用前,应把它当成一个需要安全评审的软件组件。至少检查:

  • 发布者、仓库地址、Tag/Commit、版本号是否可信且可追溯。
  • 插件入口代码是否读取用户目录、环境变量、SSH 配置、浏览器数据或凭据。
  • 是否存在不必要的网络外发、遥测、下载执行、动态代码执行或子进程启动。
  • package.json 中的依赖和安装脚本是否必要;锁定依赖版本并审查更新差异。
  • Patch 是否插入/替换高权限插件配置,是否改变审批、沙箱、凭据或文件访问相关设置。
  • 卸载后是否仍残留定时任务、后台进程、文件或持久化配置。
11.2 开发和部署时的最低安全基线
开发环境
建议:使用一次性 VM 或隔离容器;不要拿日常办公账号和重要 SSH 凭据做插件试验
文件访问
建议:只挂载测试目录;避免让开发实例直接读取生产文件和患者数据
凭据
建议:使用专用低权限测试 Key;不将密钥写入源码、日志、文档或返回给模型
网络
建议:对外请求设置域名白名单、超时和响应大小上限;防止 SSRF / 任意外联
工具权限
建议:从只读工具开始;写入、删除、执行命令等操作单独授权和审计
发布
建议:固定 DSH 兼容范围和依赖版本;每次升级做差异审查与回归测试

对于医疗场景,尤其不要把真实患者身份信息、病历原文、院内访问令牌或生产数据库权限直接暴露给一个未经安全评审的插件。推荐先在合成数据和隔离测试环境验证,再走医院或企业内部的安全评审、最小权限与审计流程。

12 DSH Plugin、MCP、Skill 的区别与选型
给 DSH 增加一个可被模型调用的本地/内部 API
优先考虑:Tool Plugin
原因:直接使用 ctx.tools 注册工具,参数与输出纳入 DSH 工具运行时
多个插件需要复用统一鉴权、连接池或缓存
优先考虑:Service Plugin
原因:把共用能力抽象成 ctx 服务,其他插件通过 inject 依赖
连接一个已有的独立工具服务器
优先考虑:MCP
原因:外部服务使用标准协议暴露工具/资源,避免把服务代码全塞进 DSH 进程
给模型增加行业流程、操作规范和术语知识
优先考虑:Skill
原因:属于任务指导,不需要执行一个新的运行时 API
扩展 DSH Web 的交互界面
优先考虑:Client UI Plugin
原因:通过 dsh.client 扩展浏览器侧能力

实际工程可能混合使用:例如医院知识检索服务已经有独立 HTTP/MCP 接口,DSH Tool 插件负责将它封装成模型易于调用的工具;检索策略和循证流程放在 Skill;院内统一鉴权、日志与连接复用封装在 Service。这样可把协议层、运行时层和提示知识层分清。

13 建议学习路线与项目练习
第一阶段:入门
学习目标:理解 Plugin / ctx / inject / apply
练习项目:hello 插件 + greet 工具
完成标准:模型能在 Web UI 里实际调用 greet
第二阶段:工程化
学习目标:掌握 Config、错误处理、取消、生命周期
练习项目:HTTP API 查询插件
完成标准:超时、错误、取消、schema 与输出均可测试
第三阶段:运行时扩展
学习目标:掌握 Service / Event / Hook
练习项目:统一 API Service + 工具审计插件
完成标准:两个工具共用 Service;事件可观测且可清理
第四阶段:分发
学习目标:Bundle、Profile、版本约束
练习项目:打包发布一个私有 Bundle
完成标准:新 Profile 可安装、dump-config 正确、可卸载
第五阶段:进阶 UI
学习目标:Host / Client、dsh.client
练习项目:一个只读信息面板
完成标准:客户端资源按契约加载,Host/Browser 边界清晰
13.1 适合做的第一个真实项目:内部知识库检索插件

如果要把学习成果逐步用到真实业务,可以做一个只读的内部知识库检索插件。它比 greet 更接近企业场景,但仍然能保持边界清晰:

  1. 定义模型工具 search_internal_kb(query, top_k, filters),description 里明确用途与限制。
  2. 在 execute 中调用已有的知识检索 HTTP API;不要在插件里重复实现向量数据库、重排和权限系统。
  3. 对 top_k 设置上下限,对 query 限制长度,对 filters 做白名单校验。
  4. 返回结构化、有限长度的摘要、来源标题、来源 ID 和可追溯链接;不要返回整个原始文档。
  5. 将 API Base URL、timeout、top_k 默认值放进 Config;Key 由受控凭据机制提供。
  6. 加入 tools/result 观测插件,记录耗时、命中数与错误类型;日志不得包含令牌和不必要的敏感原文。

这个练习能覆盖 DSH 插件开发中的核心能力:工具契约、配置注入、HTTP 调用、错误处理、结果规范化、审计和安全边界。下一步再扩展成复用 Service,或将检索服务放在 MCP 进程边界外运行。

附录 A 常用命令速查
启动官方 Web UI
命令:npx @deepseek-ai/dsh web
克隆源码
命令:git clone https://github.com/deepseek-ai/deepseek-harness.git
安装依赖并构建
命令:pnpm install && pnpm run build
源码启动 Web
命令:pnpm dsh web
用本地 patch 启动
命令:pnpm dsh web --patch ./scratch-plugin/cordis.yml
查看 Profile 最终配置
命令:dsh --profile <name> --dump-config
查看 Web 默认配置
命令:dsh web --dump-default-config
安装本地 Bundle
命令:dsh plugin --profile <name> add ./my-plugin
卸载 Bundle
命令:dsh plugin --profile <name> remove <package-name>
启动 Profile
命令:dsh --profile <name>
 CLI 参数和 Profile 启动方式可能随预览版本变化。执行前建议先检查当前版本的 dsh --help 和 dsh plugin --help;官方 CLI 行为参考是理解命令语义的入口。[14] 
附录 B 官方资料与源码入口

以下优先列出 DeepSeek Harness 官方 GitHub 仓库中的 README、架构说明和开发教程。由于 DSH 处于 Developer Preview,建议阅读时以当前主分支及正在使用的 npm 版本为准。

[1] 官方仓库 README(中文):项目定位、运行方式、开发者预览状态https://github.com/deepseek-ai/deepseek-harness/blob/master/README.zh.md

[2] 官方架构文档(中文):Cordis、Profile、Bundle、Patch 分层https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/architecture.zh.md

[3] 插件与生命周期(中文):Fiber 状态机、inject、自动清理、HMRhttps://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/framework/index.zh.md

[4] 打包与安装插件(中文):dsh.bundle、Profile、dsh plugin addhttps://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/publish.zh.md

[5] 开发一个工具(中文):defineTool 与 greet 工具示例https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/tool.zh.md

[6] Tool authoring reference:参数校验、execute、output、执行契约https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cookbook/adding-a-tool.md

[7] 服务与依赖(中文):ctx 服务、inject 与 Service 提供者https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/framework/service.zh.md

[8] 事件系统(中文):emit / bail / serial / waterfallhttps://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/framework/events.zh.md

[9] Client 模块(中文):Web Host/Browser 插件加载结构https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/subsystems/client-modules.zh.md

[10] 官方主仓库 package.json:Node / pnpm 版本要求https://github.com/deepseek-ai/deepseek-harness/blob/master/package.json

[11] 插件配置(中文):Schemastery Config schemahttps://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/config.zh.md

[12] 扩展插件实操手册(中文):工具钩子与扩展模式https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cookbook/extension-cookbook.zh.md

[13] Plugin indexes:实验性插件索引的发布和格式https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/guide/plugin-market.md

[14] CLI 行为参考(中文):Profile 启动、插件管理和 dump-confighttps://github.com/deepseek-ai/deepseek-harness/blob/master/apps/cli/reference/README.zh.md

[15] 官方 SAFETY.md:实验性状态、沙箱限制和安全建议https://github.com/deepseek-ai/deepseek-harness/blob/master/SAFETY.md

最后的开发建议把第一个插件控制在一个 Tool、一份 Config、一个外部 API 和一套回归用例之内。先确认模型能正确调用,再扩展成 Service、事件审计和 Bundle 分发。不要一开始就改 Agent Loop 或开发复杂的前端模块。 

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料