dsh-plugin-installer
English | 简体中文
DeepSeek Harness(dsh)的插件安装器:一个可直接安装的 bundle,让你在 Web GUI 里直接安装/卸载 profile 插件——不用打开终端,装完默认免重启立即生效。
- 宿主半侧
src/installer.ts在 web 服务器上注册POST /dsh-plugin-installer/api,等价于在终端执行dsh plugin --profile <name> add/remove(转发给 profile 目录里的pnpm+ 对账dsh.profile.bundles层列表); - 浏览器半侧
src/client.ts(含src/installer-client.ts)在 设置 → 插件 里注册「安装」tab:安装输入框、当前 bundle 层(带"运行中 / 需重启"徽标)、逐个卸载按钮。
本包按官方 bundle 分发模型 组织:包内声明 dsh.bundle 与 cordis.patch.yml,用户 dsh plugin add 后即作为配置层生效。
目录结构
dsh-plugin-installer/
├── package.json # npm 包清单 + dsh.bundle / dsh.client 声明 + prepare 构建脚本
├── tsconfig.json # 严格模式类型检查配置(tsc --noEmit)
├── tsdown.config.ts # 构建配置:Node 库(lib/)+ 客户端 bundle(lib/client.js),自包含、供 git 安装时 prepare 使用
├── cordis.patch.yml # bundle 配置层:客户端发现载体行 + 安装器宿主行
├── dev/cordis.yml # 本地开发 overlay(指向源码,配合 dsh web --patch;仅 host 半边)
├── src/
│ ├── index.ts # 包名入口(client 半边发现载体):空实现,host 侧无事可做
│ ├── installer.ts # 安装器宿主半侧:webServer 路由 → pnpm add/remove + bundle 层对账 + 免重启热激活 + 禁用/启用
│ ├── client.ts # 浏览器半边入口:在 settings.plugins.tab 插槽注册「安装」tab
│ └── installer-client.ts # 安装 tab 的 React 实现(同源 fetch 调宿主 API)
└── test/smoke.mjs # 构建产物冒烟测试(路由 + API 方法 + 热激活)快速开始
作为 bundle 安装(给用户用)
把本包装进带 GUI 的 web profile:
# 本地目录(先构建:本地目录安装不会自动跑 prepare)
cd /path/to/dsh-plugin-installer && pnpm build
dsh plugin --profile web add /path/to/dsh-plugin-installer
# 或直接从 GitHub 安装(git 安装会由 pnpm 自动跑 prepare 构建)
dsh plugin --profile web add github:you/dsh-plugin-installerGitHub 安装时 pnpm ≥10 首次会拒绝执行 git 依赖的 prepare,把 pnpm 打印的包名加进 profile 的 pnpm-workspace.yaml 后重试:
allowBuilds:
dsh-plugin-installer: true> 该 allowlist 相当于授权在安装时执行该包的代码,只应允许你信任的源码,并建议锁定 commit:github:you/dsh-plugin-installer#<sha>。
装完重启一次 dsh web(安装器本身要进树后才能工作),打开 http://127.0.0.1:3080 → 左下角 设置 → 插件 → 安装 tab。
> --patch overlay 只加载插件的 host 半边(模块路径解析不到包级声明),因此看不到浏览器半边的「安装」tab;要完整功能必须走上面的 profile 安装。
本地开发
在 deepseek-harness 源码根目录,用 overlay 直接加载本仓库源码(免安装、免构建,只跑宿主半侧):
pnpm dsh web --patch /absolute/path/to/dsh-plugin-installer/dev/cordis.yml把 dev/cordis.yml 里的 name 改成这个仓库在你机器上的绝对路径。
开发循环内自己跑检查:
pnpm install --ignore-workspace # 本包独立于 harness workspace,需单独安装依赖
pnpm typecheck
pnpm build
node test/smoke.mjs安装器怎么用
# 把本包装进 web profile 并启动 GUI 后:
# dsh plugin --profile web add /path/to/dsh-plugin-installer && dsh web
# 打开 http://127.0.0.1:3080 → 设置 → 插件 → 安装安装源支持:npm 包名(dsh-my-plugin)、github:user/repo、file: 链接、本地目录/tarball 的绝对路径(相对路径会被拒绝——浏览器没有可锚定的工作目录)。
禁用 / 启用
每个非内置 bundle 行都有「禁用 / 启用」按钮:安装器在 profile 的 cordis.patch.yml 里维护一段带标记的托管块(# >>> dsh-plugin-installer: managed …),对 bundle 的每个配置行写 { id, disabled: true/false } 覆盖。这个文件既是持久化(启动时作为用户层组合进配置树)也是热重载入口(dsh 的配置 HMR 监听它),所以禁用/启用同样免重启。徽标三态:运行中 / 已禁用 / 需重启。
内置 bundle(@deepseek-ai/*,即 harness 核心 dsh-base、GUI 本身 dsh-web-app)带「内置」徽标,不开放禁用和卸载——它们不是 profile 的依赖,卸载/禁用会让 profile 无法启动。想精细控制内置功能(如禁用其中某个工具行),请直接编辑 profile 的 cordis.patch.yml,对目标行写 disabled: true。
免重启:安装/卸载即时生效
装完默认无需重启:安装器在 pnpm add/remove + 对账成功后,会直接改写运行中配置树的根 include 补丁列表——把新 bundle 的补丁行追加进去(或把已卸载 bundle 的补丁行过滤掉),Loader 事务性应用后新插件即刻激活/销毁。这是安装器自包含实现,不需要改 harness 源码(dsh 只对 cordis.patch.yml 做配置热重载,bundle 层在启动时冻结,所以仅改 package.json 无法免重启)。
- 安装成功返回
hot: true时,状态行显示"已热生效,无需重启",bundle 行徽标变为"运行中"; - 若运行中的树无法热应用(无 Loader / 找不到根 include / 补丁更新失败),自动回退为旧行为:"重启 dsh 后生效",bundle 行显示"需重启"——重启总是最终兜底,重启后由
dsh.profile.bundles接管。
已知局限(重启即可恢复):
- 热装的 bundle 行是追加在运行中补丁列表末尾的;如果你在热装之后、重启之前手工编辑
cordis.patch.yml,dsh 的配置热重载会以启动时冻结的 bundle 层重算整棵树,热装的行会暂时退出运行(bundle 行退回"需重启"),重启后恢复。 - 热装后、重启前,
cordis.patch.yml里针对该 bundle 行的 id 覆盖(如disabled)不会生效——补丁按顺序应用,用户层在热装行之前。重启后由 bundle 层顺序接管,覆盖正常生效。 - 带浏览器半边的插件(声明了
dsh.client)热激活后,其 UI 需要刷新页面才会被发现(client-modules 按页面加载时扫描)。
> 热激活失败时,状态行会透传 Loader 的错误——请先分辨这是不是与热激活无关的插件问题(例如工具名/路由冲突:同一份模板装两份、两个插件都注册 greet)。这类冲突重启同样会遇到,先解决冲突再装。
注意:
- dsh 进程的 PATH 上要有 pnpm。 git 安装会运行包的
prepare脚本;pnpm ≥10 首次会拒绝,错误信息会透传 pnpm 输出,里面就有要加进该 profilepnpm-workspace.yaml的allowBuilds包键。 - 不需要改 harness 源码——安装 tab 走公开的
settings.plugins.tab插槽 + 普通 webServer 路由,未修改的 harness 检出新代码即可用。 - 宿主半侧只在挂了
webServer服务的 profile(即 web profile)里激活;无头 profile 下自动禁用,不会导致启动失败。 - 路由仅绑定回环(web 服务器默认
127.0.0.1),执行的是你机器上的pnpm——信任边界与你自己跑dsh plugin add相同。
浏览器半边(client)是怎么工作的
package.json声明dsh.client: { platform: "web" }+exports["./client"]→ dsh 的 client-modules 发现lib/client.js并作为浏览器插件加载;- 发现载体:client-modules 按 Loader 条目名解析 package.json(
require.resolve('<条目名>/package.json')),因此cordis.patch.yml里必须有一行name等于包名本身(dsh-plugin-installer,即src/index.ts,空实现)——浏览器才会加载lib/client.js; src/client.ts在settings.plugins.tab插槽注册「安装」tab;src/installer-client.ts通过同源fetch调/dsh-plugin-installer/api与宿主半侧通信;- 宿主半侧
src/installer.ts通过ctx.webServer注册该路由,转发 pnpm + 对账 bundle 层 + 免重启热激活; - 运行时 client 半边只依赖
react(浏览器平台模块表提供),其余一律走 ctx 服务,不 import 任何@deepseek-ai客户端包——改代码时请保持这个纪律。
改成你自己的安装器(fork)
- 改包名:
package.json的name、src/index.ts的name、cordis.patch.yml里的两行name/id保持一致。改包名后还要同步与浏览器半边有关的地方:tsdown.config.ts里 client bundle 的id(__ModuleLoader__.load({ id }))、package.json的dsh.client、src/client.ts的插槽 id。 - 换安装目录/换安装命令:改
src/installer.ts里runPnpm的调用与ROUTE_PREFIX。 - 记得
declare module '@deepseek-ai/cordis'合并Context/Events类型,跨包边界才类型安全。
发布
- npm:
pnpm publish(files已包含构建产物与补丁,无需额外步骤) - tarball:
pnpm pack,用户dsh plugin --profile web add ./dsh-plugin-installer-0.1.0.tgz - git:用户
dsh plugin add github:you/dsh-plugin-installer(配合上面的allowBuilds)