# DeepSeek Harness (dsh) 插件创作机制指南 > 本文档基于上游仓库 `deepseek-ai/deepseek-harness`(克隆于本目录 `deepseek-harness/`,已加入 `.gitignore`)的官方文档整理,作为本仓库(dsh-plugins)开发 dsh 插件的参考。 > > 上游处于 developer preview 阶段,**存在破坏性变更风险**。本文所有路径引用均相对于 `deepseek-harness/`。 ## 1. 项目概览 DeepSeek Harness(`dsh`)是 DeepSeek AI 开发的开源 Agent Harness(智能体运行框架),底层基于 [Cordis](https://github.com/cordiverse/cordis) 插件框架(以 vendor 方式引入为 `@deepseek-ai/cordis`)。 核心架构理念:**一切皆插件**。模型适配器、工具注册表、会话日志、甚至 agent loop 本身都是插件,因此每一部分都可以从配置替换。不存在需要打补丁的特权内核——扩展 dsh 的方式是把插件挂载到其他插件旁边。 关键目录: | 路径 | 内容 | |---|---| | `docs/user/develop/` | **面向插件作者的教程**(入门、工具、配置、发布、框架) | | `docs/cookbook/` | 扩展实操手册(添加包/工具/LLM 适配器/Chat 节点/设置卡片) | | `docs/architecture.zh.md` | 整体架构与扩展点归属映射 | | `docs/cordis-primer.zh.md` | Cordis 框架入门 | | `packages/` | 全部核心包(monorepo,pnpm workspace) | | `examples/` | 可运行示例(mcp-memory、headless-agent、web-schedule 等) | ## 2. Cordis 五个核心概念 理解插件机制的基础(详见 `docs/cordis-primer.zh.md`): 1. **插件是实现 Service 的对象**——可以是带可选 `inject` 和 `apply(ctx)` 字段的函数,也可以是 `Service` 子类,生命周期由 Cordis 管理。 2. **上下文是服务的容器**——一个服务占据一个稳定的 `ctx.`(如 `ctx.tools`、`ctx.llm`、`ctx.sessions`);其他插件通过 key 查找服务,而非导入具体实现。 3. **通过 `inject` 声明服务依赖**——插件等待所需服务就绪后才启动;加载顺序由服务依赖表达,无需手动编排。 4. **类型化事件用于通信**——通过 TypeScript 声明合并注册事件名,以 `emit`、`waterfall`、`parallel`、`serial`(另有 `bail`)分发。 5. **注册是可逆的副作用**——工具、监听器等通过 `ctx.effect()` 或 `ctx.on()` 安装,reload/teardown 时自动撤销。 ## 3. 插件的基本形态 插件是导出 `apply` 函数的 TypeScript/JavaScript 模块(详见 `docs/user/develop/basic/index.zh.md`): ```ts import type { Context } from '@deepseek-ai/cordis' export const name = 'hello-plugin' export const inject = ['tools'] // 可选:声明依赖的服务,就绪后才执行 apply export function apply(ctx: Context) { // ctx.tools 已就绪,在此注册能力 } ``` 三种等价形态: - **函数形式**(最常用):如上。 - **对象形式**:`export default { name, inject, apply(ctx) {} }`。 - **类形式**(需要向其他插件提供服务时):`export default class MyService extends Service`,构造函数中 `super(ctx, 'myService')` 占据 `ctx.myService`。 ### 生命周期(Fiber 状态机) ``` PENDING → LOADING → ACTIVE ↘ FAILED ACTIVE → UNLOADING → DISPOSED ``` - 声明 `inject` 的插件在依赖服务就绪后加载;依赖消失时自动卸载,恢复后重新加载。 - **自动清理**:`ctx.on()`、`ctx.tools.register()`、`ctx.llm.registerAdapter()` 等注册随插件卸载自动撤销;自定义资源用 `ctx.effect(() => disposer)` 声明清理逻辑。 - 处置器按注册逆序调用,但异步处置器并发执行;有顺序依赖的清理须放进同一个 effect。 - 支持 HMR:修改 `cordis.yml` 配置或(通过 `cordis-plugin-hmr`)源文件会卸载旧实例、加载新实例。 ## 4. 插件配置 导出 `Config`(Schemastery schema)即可让用户在 `cordis.yml` 中传配置(详见 `docs/user/develop/basic/config.zh.md`): ```ts import Schema from '@deepseek-ai/schemastery' export interface Config { greeting: string maxRetries: number } export const Config: Schema = Schema.object({ greeting: Schema.string().default('Hello'), maxRetries: Schema.number().default(3), }) export function apply(ctx: Context, config: Config) { console.log(config.greeting) } ``` 设计约定: - **无硬编码可调参数**:凡不同部署可能需要不同值的参数,必须定义为配置字段。检验标准:能否不改代码、只改 `cordis.yml` 就改变该值? - **配置错误要响亮**:在 schema 中表达完备约束,使无效配置在加载时即失败。 - 不要导出普通对象作为 `Config`,必须满足 Standard Schema 接口。 ## 5. 工具开发 面向模型的工具通过 `defineTool` + `ctx.tools.register()` 注册(详见 `docs/user/develop/basic/tool.zh.md` 与 `docs/cookbook/adding-a-tool.zh.md`): ```ts import type { Context } from '@deepseek-ai/cordis' import { defineTool } from '@deepseek-ai/dsh-tools' export const name = 'my-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' }, // 规范 JSON 值的 schema render: (_args, value) => [{ type: 'text', text: value }], // 模型可见内容 }, async execute(args, exec) { // args 已按 schema 自动校验并推导类型 // exec.signal 用于取消;exec.agent 可发送异步通知 return readFile(args.path, { encoding: 'utf8', signal: exec.signal }) }, })) } ``` 核心约定: - **参数自动校验**:`defineTool` 在 `execute` 前按 `parameters` 校验模型生成的参数;schema DSL 表达不了的约束(非空、正数、跨字段)需手动检查。 - **返回规范 JSON 值**:`execute` 只返回 `output.schema` 声明的值;不要返回内容块或迫使调用方解析自然语言。抛异常或返回无效值即 `isError`。 - **遵守 `exec.signal`**:信号触发时取消进行中的工作。 - **schema 自动流入系统提示词**:注册即生效,无需额外集成;Code Mode 中每个可见工具自动可经 `await tools.(args)` 调用。 - **UI 卡片独立声明**(可选):`presentCall(args)` / `presentResult(args, result)` 返回渲染意图(`generic` / `terminal` / `diff` / `search` / `web` 卡片)。必须是纯函数(实时流式与日志回放都会执行,禁止 I/O、时钟、随机数)。 - **后台任务**:producer 配置 `run_in_background`,用 `ctx.jobs.start({ kind, label, owner: exec.agent, run })` 注册;返回 `{ kind: 'background', jobId }` 类型的规范句柄。 - **执行策略不内建到工具**:用 `tools/pre-execute`(允许/拒绝/询问)、`tools/execute`(截止时间/重试/指标)、`tools/post-execute`(改写展示或结果)、`tools/result`(观测不可变结果)等事件实现。 生产级参考实现:`packages/shell/tool-bash`(三包结构)、`packages/core/fs` 中的工具。 ## 6. 事件系统 事件是插件间松耦合通信的核心机制(详见 `docs/user/develop/framework/events.zh.md`): | 模式 | 语义 | |---|---| | `emit` | 广播;监听器按注册顺序同步观察,无返回值 | | `bail` | 短路;第一个非空返回值成为最终结果 | | `serial` | 顺序执行,await 异步结果,非空返回值终止后续 | | `waterfall` | 中间件链;监听器收到 `(...args, next)`,**必须调用 `next()`** 才能委托下游,不调用即短路(用于拦截/网关) | | `parallel` | 所有监听器并行观察 | - 事件名遵循 `namespace/action` 约定(如 `agent/pre-step`、`tools/result`、`session/event`)。 - 通过声明合并获得类型安全: ```ts declare module '@deepseek-ai/cordis' { interface Events { 'my-plugin/ready': (payload: { id: string }) => void } } ``` - 注意区分:`turn/*`、`step/*`、`tool/call`、`tool/result` 等是**持久化会话事件类型**(日志事实),观察它们要监听 `session/event` 并检查 `event.type`;`agent/*`、`tools/*` 才是实时 Cordis 事件。 - 单决策事件(如 `agent/pre-step`、`agent/request`、`llm/stream`、`tools/*` 三事件)是 waterfall;`agent/turn-stopping` 是 serial。 ## 7. 打包与分发 两个核心概念(详见 `docs/user/develop/basic/publish.zh.md`): - **组合包(bundle)**:附带一个配置层的 npm 包,是你编写并分发的单位。`package.json` 中 `dsh.bundle` 指向其 patch 文件。 - **profile**:位于 `$DSH_HOME/profiles/` 的可启动组装目录,`dsh.profile.bundles` 按序列出组合包。由 `dsh plugin` 命令维护,无需手写。 ### 组合包最小结构 ``` hello-plugin/ ├── package.json # 声明 dsh.bundle ├── cordis.patch.yml # 被列入 profile 时应用的配置层 └── index.js # 插件入口 ``` ```json { "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" } } } ``` `cordis.patch.yml`(插件行按**包名**引用,让 Node 模块解析找到已安装代码): ```yaml - insert: - id: hello name: dsh-hello-plugin ``` ### 本地开发(--patch overlay) 无需打包,直接以绝对路径插入本地源码: ```yaml - insert: - id: hello name: '/absolute/path/to/my-plugin/src/my-plugin.ts' ``` ```sh dsh web --patch ./cordis.yml # 仓库源码内用 pnpm dsh web ``` ### 安装与加载顺序 ```sh dsh plugin --profile demo add ./hello-plugin # 本地路径 dsh plugin --profile demo add github:you/hello-plugin # git(需 prepare 脚本 + allowBuilds 授权) dsh plugin --profile demo add your-package # npm(预构建,无需授权) dsh plugin --profile demo remove dsh-hello-plugin dsh --profile demo --dump-config # 查看实际生效的配置树 ``` 生效配置按以下顺序逐层叠加(后应用的层按行胜出,patch 替换目标行**整个** config,不做深度合并): 1. profile 的 `dsh.profile.bundles` 所列各组合包 patch(按列表顺序,首个为 `@deepseek-ai/dsh-base`) 2. profile 自己的 `cordis.patch.yml` 3. home 级 `$DSH_HOME/cordis.patch.yml` 4. 各 `--patch ` overlay(按 argv 顺序) 推论:你的 patch 可按 `id` 覆盖前面层的行(须重述该行全部键);用户可在自己 profile 的 patch 中覆盖你的行(因此优先给出合理默认值)。 ### git 安装的构建授权 git 安装拉取源码而非构建产物,作者需提供自包含的 `prepare` 脚本(参考 [turtle-ui](https://github.com/deepseek-harness/turtle-ui));用户需在 profile 的 `pnpm-workspace.yaml` 中授权(`allowBuilds: { dsh-hello-plugin: true }`)——这等于允许该包代码在安装时于本机执行,只对可信源码授权并锁定 commit。免授权的替代:发布到 npm(预构建 `lib/`)或交付 tarball(`pnpm pack`)。 ## 8. 扩展点速查表 新行为附加到已有扩展点(完整版见 `docs/architecture.zh.md` § 新行为的归属位置): | 目标 | 机制 | |---|---| | 添加模型提供方 | 在 `ctx.llm` 上注册适配器 | | 添加面向模型的能力(工具) | `ctx.tools.register()`;schema 自动加入提示词组装 | | 添加用户命令(无需模型轮次) | `ctx.commands` | | 添加后台工作 | `ctx.jobs`;`job_*` 工具收集/停止 | | 添加 shell / 终端执行 | 注册 `ctx.shell` 后端 / `ctx.terminals` 后端 | | 添加文件系统访问或策略 | 注册 `ctx.fs` 提供方,或监听 `fs/*` 事件 | | 限制所启动的进程 | `ctx.sandbox` 后端 | | 拦截请求、工具或轮次 | `agent/*` 或 `tools/*` 事件;`agent/turn-stopping` 停止轮次 | | 添加模型可见上下文 | `agent.inject()`(落入下一次获准的请求) | | 添加 UI / Web Chat 节点 | 驱动 `ctx.agents` 并从 `session/event` 渲染;注册 `ConversationNodeDefinition` | | 添加持久会话状态 | 扩展 `SessionEventMap`,从日志渲染和回放 | | 生成会话标题 | 注册唯一的 `ctx.sessionTitle` 提供方 | | fork 活跃会话 | `ctx.sessions.fork(source, boundary?)` | | 将注册项限定到单个 agent | 使用该 agent 的 `agent.ctx` | ### 能力 seam(可替换能力) 一个 **seam** 包含三种角色,可按需拆分为不同包: - **Service Definition**:声明接口 - **Service Provider**:实现接口(替换提供方即改变整个产品,如把 `fs`/进程指向远程沙箱) - **Consumer**:使用能力(通常是面向模型的工具) ### 命名与包拓扑 - 单一 engine/runtime/policy/controller/resolver/store 用单数 `ctx` key;registry 用复数。 - 接口包用能力名称;实现包加机制/协议/环境/厂商限定词。 - 向上游贡献 workspace 包的完整清单见 `docs/cookbook/adding-a-package.zh.md`(本仓库开发树外插件无需遵循其 workspace 不变式,但角色命名规范值得借鉴)。 ## 9. 关键工作流速记 ```sh # 克隆并从源码运行(探索/调试上游) git clone https://github.com/deepseek-ai/deepseek-harness.git cd deepseek-harness && pnpm install && pnpm run build pnpm dsh web --patch ./my-overlay/cordis.yml # 快速验证插件层 dsh --profile --dump-config # 发布插件 pnpm build && pnpm publish # 预构建 lib/ 后发布 npm ``` ## 10. 上游文档索引 | 主题 | 路径(相对 `deepseek-harness/`) | |---|---| | 第一个插件 | `docs/user/develop/basic/index.zh.md` | | 开发一个工具(教程) | `docs/user/develop/basic/tool.zh.md` | | 插件配置 | `docs/user/develop/basic/config.zh.md` | | 打包与安装插件 | `docs/user/develop/basic/publish.zh.md` | | 插件与生命周期 | `docs/user/develop/framework/index.zh.md` | | 事件系统 | `docs/user/develop/framework/events.zh.md` | | 服务与依赖 | `docs/user/develop/framework/service.zh.md` | | 工具编写参考(权威) | `docs/cookbook/adding-a-tool.zh.md` | | LLM 适配器 | `docs/user/develop/practice/llm-adapter.zh.md`、`docs/cookbook/adding-an-llm-adapter.zh.md` | | 架构总览 | `docs/architecture.zh.md` | | Cordis 入门 / 教程 | `docs/cordis-primer.zh.md`、`docs/cordis-tutorial/index.zh.md` | | 扩展实操手册 | `docs/cookbook/extension-cookbook.zh.md` | | 工具目录 / 配置目录(生成) | `docs/tool-catalog.zh.md`、`docs/config-catalog.zh.md` | | 示例集 | `examples/README.zh.md` | 社区:GitHub Discussions;插件仓库可打 `dsh-plugin` topic 提升可发现性;[Discord](https://discord.gg/Ycq5dCaS4)。