ARTICLE · 1154447
2. 插件打包和安装的必要性

要让某个profile真正用上它,必须经过”编译打包->装进profile的node_modules -> 在patch里声明加载这三步“。
一、核心原因
1、插件的加载方式决定了必须安装
dsh加载插件靠的是cordis.patch.yml里的配置,比如
{ insert: { plugin: '@deepseek-ai/dsh-my-custom-tool' } }这里写的是包名(@deepseek-ai/dsh-my-custom-tool),不是文件路径。Node.js 要能 import 到这个包,它必须存在于 node_modules 里——而进入 node_modules 的唯一方式就是安装。
2、每个profile是独立的小项目
每个 profile 目录下都有自己的 package.json 和 node_modules,比如
$DSH_HOME/profiles/web/├── package.json ← 这个 profile 装了哪些插件├── cordis.patch.yml└── node_modules/ ← 插件实际存在的地方
你给 web profile 装了一个插件,headless profile不会自动有。这种隔离设计要求每个 profile 单独安装自己需要的插件。
3、插件有自己的依赖树
一个插件可能依赖第三方库(比如 axios、zod)。这些依赖需要 pnpm/npm 来解析、下载、放进正确的位置。手动拷贝文件搞不定依赖解析。
4、开发态vs运行态的区别
在 dsh 源码仓库里开发插件,pnpm install 就把所有 workspace 包链接好了,所以感觉"不用安装"。但如果你把插件给别人用,或者装到一个独立的 profile 里,就必须走打包安装流程。
二、打包和安装分别指什么
1、打包build
把ts源码编译成可运行的js,命令是
pnpm build# 产出 lib/ 目录(编译后的 JS + 类型声明)
为什么要打包?因为
运行时(尤其是 Python SDK 打包的 exe)不会跑 TypeScript 源码 发布到 npm 的必须是编译后的产物 减少体积、提升启动速度
2、安装install
把打包好的插件装进目标 profile:
# 方式 1:用 dsh 命令(推荐)dsh plugin --profile web add file:/path/to/your-plugin# 方式 2:手动# 1. 编辑 profile 的 package.json,加上依赖# 2. 在 profile 目录下 pnpm install
安装完成后,还要在 cordis.patch.yml 里 insert 这个插件,它才会被加载。
三、流程图
你写的插件源码(packages/my-plugin/src/)││ pnpm build(打包)▼编译产物(packages/my-plugin/lib/)││ dsh plugin add(安装)▼profile 的 node_modules/@deepseek-ai/dsh-my-plugin/││ cordis.patch.yml 里 insert▼运行时被加载,插件生效
四、最简单的插件

...... [hello-plugin] plugin loaded! dsh web: http://127.0.0.1:3080/?token=rl0JJgdY7pUDGanIiItFHr2NpSoXU62m-Q-XvdCWgFY dsh web: opening the default browser; pass --no-open to disable ...... |
1、命令解释
pnpm | 用 pnpm 执行仓库里定义的脚本/命令(这里走仓库的 dsh 启动器,从源码运行) |
dsh | deepseek-harness 的启动器命令(`apps/cli/src/bin.ts`) |
web | 子命令,等价于 --profile web:启动 web 这个 profile(Web UI 服务) |
--patch ./scratch-plugin/cordis.yml | --patch ./scratch-plugin/cordis.yml |
2、启动时配置怎么叠加的?
web profile 的配置树 = cordis.yml(空根)\ + dsh-base bundle 层 \ + dsh-web-app bundle 层 \ + profile 自己的 cordis.patch.yml(用户层) \ + ./scratch-plugin/cordis.yml ← --patch 加的这一层(插入 hello 插件) |
--patch` 是单次生效的:只对本次启动有效,不会写进 profile 的 `cordis.patch.yml`。想永久生效要改 profile 的用户层或用 `dsh plugin add`
效果:Web UI 正常启动的同时,额外加载你的 hello-plugin,终端打印 `[hello-plugin] plugin loaded!`
五、其他重要内容
自动清理、声明依赖(插件需要使用其他服务,如tools、llm,需要声明inject)、插件还有其他形态
六、自定义tool
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 someone by name.', parameters: { name: { type: 'string', required: true, description: 'The name to greet' }, }, output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: value }], }, async execute(args) { return `Hello, ${args.name}!` }, })) } |
同样的命令启动后,就可以在正常对话时,调用工具了
