这是本节的多页打印视图。 .
扩展 Y8
- 1: 借助 Creator 开发
- 2: 第一个插件
- 3: 开发一个工具
- 4: 插件配置
- 5: 打包与安装插件
专业插件可以从两个问题开始:领域分析引擎需要处理什么数据、返回什么结果?操作面板需要让用户发起什么操作、检查哪些结果?按需求扩展其中一部分或两部分,复用 DSH 已有的运行能力。背后的分工见产品理念。
选择合适的方式
| 你的需求 | 从这里开始 |
|---|---|
| Y8 已经包含的能力 | 阅读默认插件及其使用前提 |
| 已有其他人维护的能力 | 在插件市场查找,检查 README 和兼容要求 |
| 固定流程、提示词或输出标准 | 编写工作区 Skill;仅有操作说明时无需新建运行时插件 |
| 调整选项或组合现有能力 | 先看设置,再看插件配置和 Bundle 说明 |
| 接入一个已有外部服务 | 先检查该服务是否提供可用的 MCP 集成 |
| 增加操作、服务、设置卡片或界面 | 进入下面的插件开发路径 |
定义一个小需求
把这份提纲复制到对话或项目 README。先填写已知信息,其余可以让助手检查现有项目;提供样例数据,不要填写凭据。
- 要解决的任务:
- 输入内容及来源:
- 期望输出,以及在哪里检查:
- 允许修改的范围,以及禁止执行的操作:
- 需要的账号、服务和配置项:
- 一份样例输入及预期结果:
例如:只读查询团队构建服务的失败任务,返回失败原因和链接,不触发重跑。提供一份脱敏响应作为验收样例。这是扩展需求示例,不表示 Y8 已经接入该服务。
开发并检查结果
- 借助 Creator 开发,查询可用接口,试验一个范围明确的需求。
- 按第一个插件加载可编辑源码。本教程需要准备好的 DSH 源码 checkout,仅有桌面安装包还不够。
- 增加一个工具或配置项,每次修改一种行为并检查可见结果。
- 按打包与安装把源码交付给其他人。临时动态试验不等于已安装的软件包。
专业扩展示例见 Harbor 如何开发。框架、事件和服务细节见框架指南;尚未在本站发布的深入参考会打开 GitHub。
在团队内复用
共享项目或确切的软件包版本、支持的 DSH 版本、安装步骤、配置项名称和验收样例。同事使用自己的账号与目录,再重复执行样例。共享文件不携带私人会话或凭据。本地 checkout 链接只在作者机器上有效,交给他人时使用打包指南。
文档中的步骤失败时,通过故障排查提供具体步骤、版本与错误,帮助区分说明缺失和需要完善的开发能力。
参与 Y8 开发
1 - 借助 Creator 开发
准备环境
本指南使用 DSH 源码 Web 应用。先完成 GitHub 上的源码环境准备,再在仓库根目录运行:
打开命令打印的启动地址,包括其中的认证片段。系统会分配可用端口。选择工作目录并配置模型后再发送请求,在会话的 Agent Preset 选择器中选择“创造模式”(Creator,预设 ID 为 cordis)。如果当前安装的 Y8 未提供该预设,请使用这条源码路径;仅安装 Y8 不代表已具备独立插件开发环境。
描述改动
使用需求提纲,请助手加载 Creator 预设中的插件开发 Skill;涉及组合配置时,再使用该预设中的组合编辑 Skill。
首次试验可以要求一个临时只读工具,返回某项任务的简短检查清单,并要求助手先查询当前工具注册接口。写明预期结果,要求验证后停止试验。模型请求使用你配置的账号。
先查询再实现
Creator 可以列出检查提供方、查询实际接口,并读取已有试验的源码和诊断。请它将操作与服务放在 Host,将界面放在 Client,确有需要时再组合两者。缺少能力时应明确报告,不能猜测 API。
激活前先看源码预览和预期效果。动态代码是普通 JavaScript 函数体,不是 TypeScript 或 JSX 模块。定义试验不等于执行它。按提示处理审批,并检查最终运行状态;等待中或启动中都不代表成功。
检查并停止
调用新增工具或操作改动后的界面,与样例中的预期结果比较。失败时让 Creator 检查该试验的诊断和源码。完成后停止它,确认对应工具或界面贡献消失。停止会移除注册,不会撤销已经写出的文件或外部操作。
保留有用的成果
动态定义保存在进程内,Host 重启后会消失。会话中记录过源码,不代表会自动重新加载。重启前把有用源码和需求保存到项目,再通过第一个插件教程与打包指南建立并验证普通插件。这条路径没有动态代码自动转包功能。
修改已有插件时,继续维护原项目及其身份和配置。自定义预设使用用户自己的副本,不要直接修改随应用提供的 Creator 预设,因为升级会替换它。确切运行规则和诊断说明见 GitHub 上的动态工具参考。
2 - 第一个插件
创建本地项目
在仓库根目录创建本教程使用的临时项目:
插件是什么
在 Harness 中,插件是一个导出 apply 函数的 TypeScript 模块。框架在加载时调用 apply,传入一个 ctx(上下文对象),你通过 ctx 注册能力:
这就是完整配置。
创建插件文件
创建 scratch-plugin/src/my-plugin.ts:
注册到 cordis.yml
在仓库根目录运行 pwd,然后创建 scratch-plugin/cordis.yml,作为插入本地插件的 Web 覆盖层。请将下文的 /absolute/path/to/deepseek-harness 替换为命令打印的路径:
插件路径必须是绝对路径。patch 文件只贡献配置,不会改变 loader 解析模块路径时使用的 profile 目录。
使用该覆盖层启动 Web UI:
打开终端打印的启动地址,包括其中的认证片段。启动期间,终端会打印 [hello-plugin] plugin loaded!。
修改与停止
修改插件中的问候语,使用 Ctrl+C 停止开发命令,再运行同一条命令。检查终端是否打印新的问候语。每次重启使用新打印的启动地址;端口 0 表示由系统分配可用端口。完成后停止开发命令。
自动清理
通过 ctx 注册的任何东西——事件监听、工具、定时器——在插件卸载时都会被自动清理。你不需要手动 removeListener 或 clearInterval。
如果你有需要手动清理的资源(比如一个网络连接),用 ctx.effect() 告诉框架怎么清理:
声明依赖
如果你的插件需要使用其他服务(如 tools、llm),需要声明 inject:
框架会确保依赖的服务就绪后才加载你的插件。
插件的三种形态
除了函数形式,插件还支持对象形式和类形式:
对象形式
类形式
大多数情况下,函数形式足够了。当插件需要向其他插件提供服务时,可使用类形式(见 服务与依赖)。
下一步
- 开发一个工具 — 了解工具定义 DSL
- 插件配置 — 让插件接受用户配置
- Cordis 框架教程 — 底层的插件框架,在临时目录中动手构建,无需 API 密钥
3 - 开发一个工具
创建工具插件
将 scratch-plugin/src/my-plugin.ts 替换为:
inject 让 Cordis 等待工具注册表就绪。defineTool 根据 parameters 推导并校验 args;execute 返回 output.schema 声明的规范值,output.render 再将该值转换为面向模型的内容。
运行并调用工具
如果开发命令未在运行,请重新启动:
打开终端打印的启动地址,包括其中的认证片段,然后输入:Use the greet tool to greet Ada. 模型可以调用 greet,并收到 Hello, Ada! 这一工具结果。
下一步
4 - 插件配置
cordis.yml 中传入的配置。先完成第一个插件。和该教程一样,将下方绝对路径替换为自己的仓库路径;patch 不会改变插件模块的解析目录。
定义 Config 类型
在插件中导出一个 Config 类型和同名的 Schemastery schema;默认值直接写在 schema 中:
在 scratch-plugin/cordis.yml 新插入的本地插件行中添加配置:
插件加载时,Cordis 会通过导出的 schema 校验配置,并填充未提供字段的默认值。不要导出普通对象作为 Config,因为它不满足 Cordis 要求的 Standard Schema 接口。
Schema 校验
对于需要严格校验的场景,使用 Schemastery 定义 schema:
Schema 在插件加载时执行校验。如果配置不合法,插件会加载失败并给出明确错误信息。
设计原则
无硬编码可调参数
Harness 的约定:凡是不同部署可能需要采用不同值的参数,都必须定义为配置字段。
检验标准:能否在 cordis.yml 中改变这个值,而不需要修改代码?
配置错误要响亮
在 schema 中表达自身完备的约束,使无效配置在插件加载时失败。对服务或已注册资源的引用需要依赖注入;服务教程 会介绍这项约定。
应用并验证
修改源码或 patch 后,停止并重启教程命令。使用上面的配置时,终端会打印 Hi there。把 maxRetries: 5 改为 maxRetries: wrong-type,重启后检查插件加载时的校验错误;继续前恢复有效值。这条路径不依赖源码或 overlay 自动重载。
下一步
5 - 打包与安装插件
--patch overlay 加载本地插件。本教程把它打包成可安装的组合包(bundle),用 dsh plugin add 安装进一个 profile,并解释决定组合后配置的层顺序。本文假设 dsh CLI 已安装。请先完成插件配置。如果改用全新的源码 checkout,请先按照从源码运行章节完成准备,将本教程的 hello-plugin 目录放在仓库根目录,并从该目录把下文的 dsh ... 命令改为 pnpm dsh ...。构建与启动器行为见源码执行。
两个概念,两种 manifest
安装机制建立在两个概念之上。二者都由一份 package.json 描述,但它们在 dsh 键下携带的 manifest(元数据清单)种类不同,回答的问题也不同:
- 组合包是附带一个配置层的 npm 包。它的 manifest 声明
dsh.bundle,回答的是"这个包贡献什么?":一个插入或覆盖插件行的 patch 文件。 - profile 是位于
$DSH_HOME/profiles/<name>下、描述一份可启动组合的目录。它的 manifest 声明dsh.profile,回答的是"这套配置由哪些组合包按什么顺序组成?"。
组合包是你编写并分发的东西;profile 是用户用 dsh --profile <name> 启动的东西。没有东西同时是两者。
组合包 manifest
创建包目录:
创建 hello-plugin/package.json:
创建 hello-plugin/index.js,写入插件入口:
创建 hello-plugin/cordis.patch.yml。这个 patch 与你写过的 --patch overlay 一样,是一个 patch 条目的 YAML 数组;区别是插件行按包名而不是相对源码路径引用这个包,这样 Node 的模块解析才能找到已安装的代码:
没有 dsh.bundle 声明的包仍然可以安装,但只作为普通依赖:dsh plugin 会打印警告,且不激活任何层。如果一个库供插件包 import,而不是供用户启用,就使用这种包格式。
profile manifest
profile 目录包含两个文件:
package.json— profile 的树外插件依赖(由 pnpm 管理),加上dsh.profilemanifest 及其有序的bundles列表。cordis.patch.yml— 用户自己的 patch 层,在每个组合包层之后应用。
profile manifest 从不需要手写:dsh --profile <name> --from-default-profile <template> 可以从随附应用模板创建 profile,dsh plugin 则创建一个以 base 为基础的 profile,并维护其中已安装的 bundle 列表。创建规则以 CLI(命令行界面)行为参考为准;下一节展示插件路径。
安装进 profile
dsh plugin --profile <name> <args...> 在 profile 目录内转发给 pnpm,因此所有 pnpm 子命令都可用。在包含 hello-plugin 的目录中安装该包的 checkout:
首次使用会初始化 profile(@deepseek-ai/dsh-base 作为它的第一个组合包),pnpm 链接该 checkout,而 dsh 因为这个包声明了 dsh.bundle,把它追加进 dsh.profile.bundles:
先不启动、只验证该层,再启动:
dsh plugin --profile demo remove dsh-hello-plugin 会同时移除依赖和对应的层。
加载顺序
生效配置在空根之上按以下顺序逐层组合:
- profile 的
dsh.profile.bundles列表所列的各个组合包 patch,按列表顺序——先是@deepseek-ai/dsh-base,然后是每个已安装组合包,按其加入顺序。 - profile 自己的
cordis.patch.yml。 - home 级的
$DSH_HOME/cordis.patch.yml——各 profile 共享的机器本地偏好。 - 每个
--patch <path>overlay,按 argv 顺序。
应用参数不是另一层 patch。表层组合包可以通过下文所述的普通应用自有服务解析它们。
后应用的层按行胜出,且 patch 会替换目标行的整个 config 值,而不是深度合并各键。这给组合包作者带来两个推论:
- 你的 patch 可以按
id覆盖前面各层的行——就像dsh-web-app组合包覆盖dsh-base的行那样——但必须重述该行需要的每一个键,而不是只写改动的那个。 - 用户可以在自己 profile 的
cordis.patch.yml中覆盖你的行,无需改动你的包,所以优先给出用户大概率会保留的配置默认值,其余交给 schema 承担。
内置组合包名称始终从 dsh 安装目录本身解析;pnpm 只管理树外的包,所以你的组合包可以放心依赖 @deepseek-ai/dsh-base 存在且与安装保持一致。
让表层组合包持有自己的命令行
定义了可运行应用的组合包挂载一个普通提供方插件:
该插件导出 inject = ['cmdlineArgs'],使用自己的 commander program 调用 @deepseek-ai/dsh-cmdline 中的 parseCmdline,再在 program 自己的 action 中把应用自有服务提供出去。启动器把自身 flag 之后的同一份不可变参数交给每个插件,因此添加应用专属 flag 无需修改启动器,多个插件也可以解析该快照。Loader 行不需要启动器标记或特殊类型。
受这些参数配置的行会注入提供方服务,并在自己的 !!js 选项中读取它,同时把部署取值写在旁边作为回退:
遇到 --help 时,提供方不会发布该服务,所以这些行不会激活。Loader 只挂载一次组合,等待每一行的普通注入,再基于其已注入的上下文求值该行的 !!js 配置。
从 GitHub 安装:构建脚本这道坎
发布到注册表不是必须的——用户可以直接从 git 托管安装:
但 git 安装拉取的是源码,不是构建产物:没有任何环节运行你的 build 脚本,因此 TypeScript 包到手时没有 lib/ 输出,加载会失败。必须两边各做一件事:
作者提供一个
prepare脚本——pnpm 在 git 安装后运行它——从源码构建出发布入口,且必须自包含:不能假设仅开发环境才有的上下文,例如旁边有一份 monorepo checkout。turtle-ui 是一个可用的例子:它的prepare运行一份专用的 tsdown 配置,直接转译src/,不用项目引用,也不做类型检查。用户为构建授权。pnpm ≥10 在得到显式允许之前拒绝运行 git 依赖的
prepare脚本,所以第一次add会失败;dsh会指出修法——把 pnpm 打印的确切包键复制进该 profile 的pnpm-workspace.yaml:然后重新执行
add。
请把这项授权视为允许该包的代码在安装时于你的机器上执行,且不在 agent 运行的任何沙箱之内。只对源码可信的包授权,并锁定 commit(github:you/hello-plugin#<sha>),让后续推送无法悄悄改变实际运行的内容。
如果不想让用户做这项授权,就改为分发构建产物——以下两种形式都不需要任何构建权限:
- 发布到 npm,在
pnpm publish时构建好lib/;dsh plugin add your-package安装的就是预构建代码。 - 交付 tarball:用
pnpm pack打包;用户执行dsh plugin add ./hello-plugin-0.1.0.tgz。
下一步
- 插件与生命周期 — 插件的完整生命周期
- CLI(命令行界面)行为参考 — 确切的层优先级、flag 与 profile 机制