详情

首页手游攻略 DeepSeek Harness中插件开发新手指南实用指南

DeepSeek Harness中插件开发新手指南实用指南

佚名 2026-09-01 09:20:01

平时做技术实践时,很多问题不是概念不会,而是细节没串起来。拿“DeepSeek Harness中插件开发新手指南”来说,它看着像小点,放到项目里常会牵出环境、配置、兼容性和维护成本。下面按实际采用顺序,把思路、关键写法和容易踩坑的地方讲清楚,便于大家直接对照操作。

目录
  • 一、为什么现在是风口
  • 二、核心概念:一切皆插件
    • 2.1 没有「内核 + 插件」的分层
    • 2.2 底层框架 Cordis:五个必须记住的想法
    • 2.3 Profile 与 Bundle:两个关键概念
  • 三、开发环境准备
    • 3.1 前置要求
    • 3.2 安装 dsh
    • 3.3 建议建一个隔离的调试 profile
  • 四、编写你的第一个插件
    • 4.1 最小函数插件
    • 4.2 插件的三种形态
    • 4.3 正式插件的四个导出
  • 五、插件开发的硬性规则
    • 5.1 注册可逆性
    • 5.2 模型可见性规则
    • 5.3 设置规则
    • 5.4 工具的 execute() 契约
    • 5.5 waterfall 事件最易踩的坑
  • 六、实战一:模型可见的工具插件
    • 七、实战二:拦截事件的钩子插件
      • 八、声明式设置(Config)
        • 8.1 定义 schema
        • 8.2 在 cordis.yml 里装配
      • 九、把插件挂载到 dsh(三条路径)
        • 9.1 方式一:正式安装(需 pnpm)
        • 9.2 方式二:临时 overlay(本地开发,不需 pnpm)
        • 9.3 验证插件已挂载
      • 十、如何上传 / 发布插件
        • 10.1 发布到 npm(建议给普通用户分发的首选)
        • 10.2 交付 tarball
        • 10.3 Git 安装(最灵活,但有一道坎)
        • 10.4 让别人发现你的插件
      • 十一、打包规范与发布前检查清单
        • 十二、测试与质量门
          • 十三、常用问题排查(FAQ)

            实际处理时,面向已有 Node.js / TypeScript 基础的开发者。读完本文,你能够独立完成:开发一个 DSH 插件 → 本地调试挂载 → 发布到社区同时被他人安装。

            版本说明:DeepSeek Harness 目前处于开发者预览(Developer Preview)阶段在这个场景下,,迭代很快,官方明确声明会有兼容性破坏变更。下文机制基于 @deepseek-ai/dsh 0.1.0-rc.x 时代的公开资料整理,动手前请以官方仓库文档(GitHub:deepseek-ai/deepseek-harness)和 dsh --dump-config 的实际输出为准。

            一、为什么现在是风口

            在这个场景下,DeepSeek Harness(命令行简称 dsh)是 DeepSeek 开源的 Agent 框架(agent harness),架构上「一切皆插件」:模型适配器、工具注册表、会话日志、甚至 Agent 主循环本身都是插件,整个产品就是启动时从若干设置层组合出来的一棵插件树。

            对开发者友好的几点现状:

            • 核心仓库暂不接受外部 PR结合项目来看,,官方把贡献路径明确指向生态:发布插件、写教程、答社区问题、报 issue。
            • 插件就是普通 npm 包,没有专门的注册中心,发布门槛极低。
            • 从实现思路看,官方指定的发现渠道只是给 GitHub 仓库打一个 dsh-plugin 话题(Topic),就会被社区聚合目录收录。
            • 理解这一步时,社区内测期间已出现数百个公开插件(awesome 目录统计 900+),但大量细分领域仍是空白。

            二、核心概念:一切皆插件

            2.1 没有「内核 + 插件」的分层

            安装目录下的约 195 个 @deepseek-ai/*全部是 Cordis 插件在这个场景下,——工具、LLM 适配器、会话持久化、Web 服务器、前端 UI、沙箱策略都不例外。你写的插件和官方的 dsh-tool-bash 地位完全相同,没有「插件 API」和「内核 API」之分。

            • 加能力 = 往组合里加一行
            • 改行为 = 用 patch 覆盖已有的行

            2.2 底层框架 Cordis:五个必须记住的想法

            想法含义
            插件落到代码里,一个实现了 Service 的对象:最常用是带 apply(ctx) 的函数,也能够是带 inject 的对象或 Service 子类
            上下文(Context)服务的仓库。服务挂到稳定的 ctx.<key>(如 ctx.toolsctx.llmctx.sessions),插件之间借助 key 找服务,不 import 具体实现
            inject结合项目来看,声明插件需的必需服务。loader 会等待这些服务存在后再执行插件,加载顺序由依赖决定而不是文件顺序
            类型化事件在这个场景下,服务借助声明合同时定义事件,用 emit / waterfall / parallel / serial 分发给者
            可逆的注册实际处理时,工具 schema、器等都借助 ctx.effect() / ctx.on() 注册;插件卸载(HMR、热重载、关停)时一切自动回滚

            理解这一步时,扩展点(事件 / 服务)就是 dsh 的「API」。改行为时优先挂在扩展点上,不要去改主循环。

            2.3 Profile 与 Bundle:两个关键概念

            概念manifest回答的问题
            bundle(插件分发单元)dsh.bundle(指向 patch 文件)从实现思路看,「这个包贡献什么」——一个设置层(cordis.patch.yml),由 npm 包分发
            profile(可运行组合)dsh.profile(bundles 列表)「哪些 bundle 按什么顺序组成这个运行实例」

            bundle 是作者分发的单元,profile 是用户启动的单元,dsh plugin 命令负责维护 profile。

            启动时设置层的叠加顺序(后层覆盖前层):

            1. profile 清单里列出的各 bundle(按顺序)
            2. profile 自己的 cordis.patch.yml
            3. 家目录级 $DSH_HOME/cordis.patch.yml(对本机所有 profile 生效)
            4. 命令行 --patch <path> 覆盖(按 argv 顺序)

            查看你的机器实际组合出的插件树:

            dsh --profile web --dump-config

            打印出来的任何一行,都能够用你自己的 patch 替换。 patch 按行的 id 定位:要么整行替换其 config(不是深合同时),要么插入新行。

            三、开发环境准备

            3.1 前置要求

            Node.js:官方声明范围 ^22.19.0 || >=24.0.0,不确定时直接用 Node 24。

            pnpmdsh plugin 子命令会把参数原样转发给 profile 目录里的 pnpm,没有 pnpm 会直接报错:

            npm install -g pnpm

            DeepSeek API Key(运行真实模型时需):把 DEEPSEEK_API_KEY 放进根目录 .envpnpm dsh 会自动加载。没有 Key 也能够先写代码、跑单元测试和 --dump-config 验证。

            3.2 安装 dsh

            # 方式一:直接从 npm 运行(推荐普通开发者)
            npx @deepseek-ai/dsh web # 默认在 http://127.0.0.1:3080 启动 Web UI

            # 方式二:克隆源码开发(推荐要深度调试的开发者)
            git clone https://github.com/deepseek-ai/deepseek-harness.git
            cd deepseek-harness
            pnpm install
            pnpm run build # 不要省!只装依赖不构建会导致 Web 页面缺产物
            pnpm dsh web

            3.3 建议建一个隔离的调试 profile

            实际处理时,开发期间用一个独立 profile(如 --profile dev)安装开发中的插件,日常采用的 web profile 保持稳定,两者互不干扰。

            四、编写你的第一个插件

            4.1 最小函数插件

            新建 hello.ts

            import type { Context } from '@deepseek-ai/cordis'
            export const name = 'hello'
            export function apply(ctx: Context) {
              ctx.logger.info('hello from my first plugin')
            }

            再新建 cordis.yml

            - name: './hello.ts'

            在仓库内能够用 vendored 的 Cordis 启动器直接跑通最小挂载链路(不需 API Key):

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

            4.2 插件的三种形态

            import { Service, type Context } from '@deepseek-ai/cordis'
            // 1. 函数插件(最常见,推荐默认用它)
            export function apply(ctx: Context) {}
            // 2. 对象插件:带 apply 方法的对象
            export const objectPlugin = {
              name: 'object-plugin',
              apply(ctx: Context) {},
            }
            // 3. 类插件:Service 子类(适合对外提供一个 ctx.<key> 服务)
            export class MyService extends Service {
              constructor(ctx: Context) {
                super(ctx, 'myService')
              }
            }

            4.3 正式插件的四个导出

            一个正式的函数插件通常导出四个东西:

            import type { Context } from '@deepseek-ai/cordis'
            import z from '@deepseek-ai/schemastery'
            /** 插件显示名,仅用于诊断。 */
            export const name = 'my-plugin'
            /** 声明依赖的必需服务;loader 会等它们存在再执行 apply。 */
            export const inject = ['tools']
            /** 部署期配置的 schemastery 校验 schema(可省略)。 */
            export interface Config {
              greeting: string
            }
            export const Config: z<Config> = z.object({
              greeting: z.string(),
            })
            /** 插件主体:注册一切贡献,并只注册为可逆 effect。 */
            export function apply(ctx: Context, config: Config) {
              ctx.logger.info(config.greeting)
            }

            要点:

            • inject 只声明必需服务;可选服务用 ctx.get(name) 读取。
            • 函数插件必须命名导出理解这一步时,,不要混用默认导出,否则 Loader 会丢掉 inject 元数据。
            • apply 签名:有 Config 导出时是 (ctx, config),没有时是 (ctx)
            • 设置错误要 fail loud:加载失败会明确报错,不会静默跳过。

            五、插件开发的硬性规则

            结合项目来看,这一节汇总官方文档与社区实践中反复强调的规则,违反任何一条都可能导致插件加载失败或行为异常。

            5.1 注册可逆性

            • 结合项目来看,一切注册(工具、器、prompt 段)必须借助 ctx.effect() / ctx.on() 等机制完成,插件卸载时自动回滚ctx.effect() 中注册的东西必须有 teardown,否则重载或切换 profile 时会留下重复器或资源。
            • 工具只注册一次:注册借用的是只读 definition,不要事后改 schema;想换工具就释放所属 effect 再注册。

            5.2 模型可见性规则

            • 模型能看到的任何东西都必须能从会话日志重建从实现思路看,(模型可见 ⟺ 已记录)。要给模型加新的可见输入,就扩展 SessionEventMap 加一种新事件类型、从日志渲染,而不是绕过日志。
            • durable 会话事件(turn/*step/*tool/* 等)追加进会话日志,重启后可重建;live 事件(agent/*tools/*)只做运行期协调。两者分工不能乱。

            5.3 设置规则

            • 「两个部署环境可能需不同的值」都必须做成 Config 字段,不能写死在代码里。
            • cordis.yml!!js 只允许出现在插件 config 和条目 disabled 下;按环境选插件要用 overlay,不要滥用 !!js
            • patch 覆盖是整行替换理解这一步时,(不是深合同时),覆盖时必须保留行的 id

            5.4 工具的 execute() 契约

            • args 自动校验defineTool 会在 execute 前校验模型生成的参数。
            • 只得到一个规范 JSON 值output.schema 定义得到值;抛异常 = isError;领域内的失败结果(如非零退出码)也要放进规范值得到。
            • 遵守 exec.signal:取消信号触发时必须中止进行中的工作。
            • UI 卡片与模型看到的内容分离:模型看到的由 output.render 决定,UI 卡片由 presentCall / presentResult 得到渲染意图(generic / terminal / diff)。
            • 后台长任务借助 ctx.jobs.start() 注册,模型侧得到带 jobId 的规范句柄,且开关必须由部署设置控制。

            5.5 waterfall 事件最易踩的坑

            tools/pre-execute 等 waterfall 事件的器收到 (...args, next):调用 next() 才把结果传给下一个器;不调 next() 直接 return 就是短路,截断整条链。这是写钩子插件时最容易犯的错。

            六、实战一:模型可见的工具插件

            落到代码里,工具是插件最常用的用途。工具注册在 ctx.tools 上,schema 会自动进入 prompt 组装,模型就能「看到」它。

            import { readFile } from 'node:fs/promises'
            import type { Context } from '@deepseek-ai/cordis'
            import { defineTool } from '@deepseek-ai/dsh-tools'
            export const name = 'demo-tool'
            export const inject = ['tools']
            export function apply(ctx: Context) {
              ctx.tools.register(defineTool({
                name: 'read_file',
                description: 'Read a file from disk.', // 模型看到的能力描述,要写清前置条件与副作用
                parameters: {
                  path: { type: 'string', required: true, description: 'Absolute path' },
                  limit: { type: 'number' }, // 可选参数
                },
                output: {
                  schema: { type: 'string' },
                  render: (_args, value) => [{ type: 'text', text: value }],
                },
                async execute(args, exec) {
                  // args 已被 defineTool 按 schema 校验并推导类型
                  return readFile(args.path, { encoding: 'utf8', signal: exec.signal })
                },
              }))
            }

            工具描述(description)的写作要求:说明何时调用、必要前置条件、失败语义与副作用。

            七、实战二:拦截事件的钩子插件

            落到代码里,不需新工具、只想在某个环节插一脚时,用事件器。主循环是事件驱动的,钩子插件就是在这些事件上挂器。

            权限门示例——在 tools/pre-execute 上拦截每一次工具调用:

            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,得到 {kind:'deny'} / {kind:'ask'}
            工具调用必须被最后否决、不可撤销ctx.tools.guard()
            包裹工具执行生命周期(超时/重试/指标)tools/execute
            显式改写工具结果或呈现内容tools/post-execute
            只观察最后结果(审计/捕获)tools/result
            改写模型请求设置agent/request(waterfall)
            改写/拒绝进入 step 的消息agent/pre-step(waterfall)

            八、声明式设置(Config)

            8.1 定义 schema

            @deepseek-ai/schemastery(它也是 Cordis 的校验器),类型和运行时校验合一:

            import z from '@deepseek-ai/schemastery'
            export interface Config {
              allowParallelInProgress: boolean
            }
            export const Config: z<Config> = z.object({
              allowParallelInProgress: z.boolean().required(),
            })

            8.2 在 cordis.yml 里装配

            - id: todo
              name: '@deepseek-ai/dsh-tool-todo'
              config:
                allowParallelInProgress: true

            九、把插件挂载到 dsh(三条路径)

            路径适用场景做法
            外置插件(建议大多数场景)自研、开源、单独发布独立 npm 包,用 dsh plugin add 安装进 profile;package.json 声明 dsh.bundle 可自动进 bundle 层
            临时 overlay调试、演示dsh --profile <name> --patch ./overlay.yml "任务"
            仓库内包给 dsh 本身贡献代码packages/<group>/<pkg>(预览期核心仓库暂不接受外部 PR)

            9.1 方式一:正式安装(需 pnpm)

            dsh plugin 把参数原样转发给 profile 目录里的 pnpm,动词在最后:

            dsh plugin --profile web add /path/to/my-plugin      # 本地路径
            dsh plugin --profile web add github:you/my-plugin # Git 仓库
            dsh plugin --profile web add my-plugin # npm 包
            dsh plugin --profile web add ./my-plugin-0.1.0.tgz # tarball
            dsh plugin --profile web remove my-plugin # 卸载

            • 相对路径锚定到命令行所在目录。
            • 包声明了 dsh.bundle 的,会自动追加进该 profile 的 dsh.profile.bundles 层栈;没声明的包只会作为普通依赖安装同时收到警告。

            9.2 方式二:临时 overlay(本地开发,不需 pnpm)

            # my-overlay.yml
            - insert:
                - id: my-plugin
                  name: '/绝对路径/my-plugin/index.js'

            dsh --profile headless --patch ./my-overlay.yml "任务"

            9.3 验证插件已挂载

            dsh --profile web --dump-config        # 应看到 # == your-plugin 层和对应 id 行
            dsh plugin --profile web why <package> # 确认依赖关系

            十、如何上传 / 发布插件

            DSH 没有专门的插件注册中心——发布 DSH 插件 ≈ 发布一个 npm 包,只是包内容遵循插件约定。官方提供三种分发途径,核心区别在于是否分发预构建产物

            方式用户安装命令安装到的是什么是否需构建授权
            npm 发布dsh plugin add your-package预构建的 lib/ 代码不需
            tarball 交付dsh plugin add ./hello-0.1.0.tgzpnpm pack 打出的包不需
            Git 安装dsh plugin add github:you/repo源码(不是构建产物)需(pnpm ≥ 10)

            10.1 发布到 npm(建议给普通用户分发的首选)

            # 1. 准备 npm 账户并登录
            npm login

            # 2. 先构建再发布(prepublishOnly 里做构建也行)
            pnpm build
            npm publish # 或 pnpm publish

            # 3. 验证:在某个 profile 里安装,确认能挂载
            dsh plugin --profile dev add your-plugin
            dsh --profile dev --dump-config

            发布前检查:入口正确导出 name / inject / applyinject 里依赖的服务提供方要声明进 package.json;版本从 0.x 起步同时遵循语义化版本;选择明确的开源协议(MIT / Apache-2.0 常用)。

            10.2 交付 tarball

            # 作者侧:打出 tgz
            pnpm pack

            # 用户侧:直接安装 tarball 文件,零授权
            dsh plugin add ./hello-plugin-0.1.0.tgz

            10.3 Git 安装(最灵活,但有一道坎)

            Git 安装拉取的是源码在这个场景下,,没有任何环节替你运行 build 脚本——TypeScript 包到手没有 lib/ 输出,加载会失败。所以:

            • 作者侧:必须提供自包含的 prepare 脚本在这个场景下,(pnpm 在 git 安装后运行它完成构建)。它不能假设仅开发环境存在的上下文(比如旁边有一份 monorepo checkout)。
            • 用户侧:pnpm ≥ 10 首次安装会拒绝运行 git 依赖的构建脚本,需在该 profile 的 pnpm-workspace.yaml 里添加 allowBuilds 授权(按报错提示复制 key 即可)。这等于允许该包的代码在你机器上执行。
            • 安全建议:git 安装时锁定 commit——dsh plugin add github:you/repo#<完整commit-sha>,避免后续推送改变实际运行的代码。

            10.4 让别人发现你的插件

            落到代码里,官方指定的发现渠道很轻松——给插件仓库加上 GitHub 话题 dsh-plugin。加了话题的仓库会被社区聚合目录(awesome 清单、各类插件索引站)自动收录。

            其他渠道:

            • GitHub Discussions 社区板块分享插件与反馈
            • DeepSeek Harness Discord 社区
            • 第三方插件聚合站(会被 dsh-plugin 话题自动索引)

            十一、打包规范与发布前检查清单

            一个**标准 DSH 社区插件包(bundle)**的结构:

            your-plugin/
              package.json # 声明 "dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
                                  # main/types/exports 指向真实生成的 lib/
                                  # files 只收录运行入口/声明/许可证/README/组合层
                                  # Cordis 与 Service Definition 包放 peer + dev deps,自有实现放 dependencies
              cordis.patch.yml # bundle 的 patch 层:按行 id 插入插件行,插件按包名解析
              src/index.ts # 函数插件:命名导出 name/inject/Config/apply
              README.md # 服务 API、事件、扩展点、安装命令、Known Limitations
              LICENSE

            package.json 关键片段:

            {
              "name": "dsh-your-plugin",
              "version": "0.1.0",
              "type": "module",
              "main": "./lib/index.js",
              "types": "./lib/index.d.ts",
              "exports": { ".": "./lib/index.js" },
              "dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
            }

            cordis.patch.yml

            - insert:
                - id: your-plugin
                  name: 'dsh-your-plugin' # 用包名,不要用 checkout 相对路径

            发布前检查清单(可直接复制进 PR 描述):

            • 架构:能力缝三角色(Service Definition / Provider / Consumer)是否设计完整
            • 导出与依赖:name/inject/Config/apply 命名导出完整;inject 的服务提供方已声明依赖
            • 生命周期:所有注册可逆,HMR 下释放 fiber 后注册消失
            • Config 与错误:部署期可变项全部做成设置字段;误设置 fail loud
            • 工具与 UI:execute() 契约遵守;output.render 与 UI 卡片分离
            • 测试与文档:行为测试、真实组合测试、README 含 Model Experience 段
            • 构建打包安装:pnpm pack 产物在干净 profile 里能 dsh plugin add 成功同时出现在 --dump-config

            十二、测试与质量门

            理解这一步时,在仓库内开发时,新增/修改包后逐级往上跑(本地只跑受影响的,CI 才全量):

            pnpm run constraints   # workspace 约束
            pnpm run typecheck # strict 类型检查,无 any 逃逸
            pnpm run lint # oxlint
            pnpm run build
            pnpm run hygiene # knip + publint + NodeNext 消费检查
            pnpm run test # vitest 单元测试

            测试方针要点:

            • 行为测试描述行为;改行为要同步改测试并说明原因。
            • 产品可见的插件要有一个真实组合测试:借助 Loader 启动 cordis.yml,而不是只用手搭的 ctx.plugin(...) 单测。
            • 注册可逆性用 HMR 安全测试验证。

            十三、常用问题排查(FAQ)

            Q:dsh plugin 报错找不到 pnpm?

            dsh plugin 是把参数转发给 profile 目录里的 pnpm 执行的。先 npm install -g pnpm

            Q:Git 安装的 TypeScript 插件加载失败?

            理解这一步时,Git 安装拉的是源码,没人替你跑 build。插件作者要提供自包含 prepare 脚本;用户侧 pnpm ≥ 10 还需在 profile 的 pnpm-workspace.yaml 里加 allowBuilds 授权。不想折腾就改用 npm 包或 tarball。

            Q:怎么确认插件真的挂载了?

            dsh --profile <name> --dump-config,应该能看到 # == your-plugin 层和你的插件行 id。

            Q:插件异常导致 dsh 无法启动,如何临时禁用?

            在 profile 的 cordis.patch.yml 里加一行即可,无需卸载:

            - id: your-plugin
              disabled: true

            Q:patch 覆盖了设置但没生效?

            patch 是整行替换落到代码里,而非深合同时——覆盖时必须保留行的 id,且被替换字段要全部重述。

            Q:bundle 插件安装后还要手动 insert 吗?

            不要。声明了 dsh.bundle 的包会被自动注册进 bundle 层栈;再往 profile 的 cordis.patch.yml 手动 insert 同 id 会报 duplicate loader entry id 导致无法启动。

            Q:核心 API 会变吗?

            理解这一步时,会。开发者预览阶段官方明确声明有兼容性破坏变更。建议:pin 住你实验用的仓库 commit 或包版本;以官方文档和 --dump-config 实际输出为准。

            实际处理时,以上就是DeepSeek Harness中插件开发新手指南的详细内容,更多关于DeepSeek Harness插件开发的资料请关注脚本之家其它相关文章!

            相关资讯
            点击查看更多
            游戏推荐
            推荐专题
            热门阅读
            推荐下载