deepseek-harness
deepseek-ai
DeepSeek Harness: Everything is a Plugin.
PROJECT TOPICS
PROJECT README
中文 | English
一个树外(out-of-tree)DeepSeek Harness 插件:在运行中的实例上直接完成插件的热安装、热卸载、禁用/启用——无需重启, 并提供 Web 设置页标签页:
github:owner/repo / file: 路径)
安装插件 bundle,立即热生效于运行中的 dsh web,且重启后保留。┌────────────────────────────── 浏览器(React)──────────────────────────────┐
│ 设置 → 插件 → "插件管理"标签页(槽位 settings.plugins.tab) │
│ │ fetch('/plugin-manager/*') │
└────────┼──────────────────────────────────────────────────────────────────┘
▼
┌──────────────────────────── dsh web 进程 ──────────────────────────────────┐
│ webserver 路由(node:http)──▶ manager 核心 │
│ ① pnpm add/remove(profile 内的真实依赖) │
│ ② 行写入 profile 用户补丁层(cordis.patch.yml) │
│ ③ 根 Include entry.update → Loader 挂载/卸载 fiber │
│ 网关:TypertRemoteService(源码模式反射,无代码生成) │
└────────────────────────────────────────────────────────────────────────────┘
包是双面的:
lib/index.js,tsc 编译)——PluginManagerGateway,一个注册为
pluginManager 的 TypertRemoteService。api-gateway 可通过源码模式反射
分发它的 @Remote 方法(无需生成产物);同时它在 webServer 服务上注册浏览器
HTTP 传输。lib/client.js,esbuild 打包)——注册进 settings.plugins.tab
槽位(id manager)的 Web 设置标签页,由 dsh-client-modules 发现,并以普通
模块表 bundle(window.__ModuleLoader__.load)形式服务。全部复用 launcher 已有的机制,因此插件不需要对 dsh checkout 做任何改动:
pnpm add <spec>。包成为真实依赖(进入
profile 的 node_modules 与 package.json,pnpm 不会剪除它)。管理器读取
该 bundle 的 dsh.bundle.patch 行,写入 profile 的用户补丁层
(cordis.patch.yml)。entry.update(携带新补丁列表)——
与 launcher 用户补丁 watcher 相同的调用。管理器自己应用自己的写入,因为文件
watcher 可能漏掉紧跟其后的写入事件。dsh.profile.bundles(也就不会双组合行)。pnpm remove / pnpm update
(registry 包)或 remove + re-add(file:/link:——pnpm 的增量重拷不会
物化新增文件)。{ id, disabled }),按 id 覆盖 bundle 行而不
产生重复。依赖语义透传给 pnpm。 管理器不解释被安装 bundle 的 manifest:
optionalDependencies 与可选 peer 的行为完全遵循 pnpm 的定义(失败的可选
依赖被跳过、安装继续;profile 使用 autoInstallPeers: false,peer 从不被
自动安装,运行时从 dsh 安装闭包解析)。插件若 import 非闭包 peer,其可用性
由 bundle 作者负责。
浏览器半通过插件自有的同源 HTTP 传输(/plugin-manager/*)与 host 通信,
而不是 Typert remote 管线:npm 上的 dsh-api-gateway 包装不了(其若干依赖
dsh-type-meta、dsh-compact 等未发布),树外无法挂载 client contribution。
host 网关仍注册 Typert 绑定,供进程内消费者使用。
唯一硬性要求:一个 npm 包,其 package.json 声明
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }(标准 DSH 插件
bundle)。满足即装,插件内部形态不限。
来源形式(spec 白名单,底层透传 pnpm add,pnpm 能解析的规范基本都能用):
@kyorakuyk/dsh-plugin-manager、@scope/name@^1.0.0https://github.com/x/y.git、git+ssh://…github:owner/repo、owner/repofile:C:/path/to/bundle、link:…(开发/离线最常用)插件内部可以是什么(补丁行由根 Include 的插值机制统一处理):
dsh.client 包(浏览器半经 dsh-client-modules 可用)dsh-skill-filesystem、dsh-mcp-client 的 bundleinsert / id-targeted 覆盖 / 禁用行 / group / !!js 表达式optionalDependencies 的 bundle(缺失的可选依赖被跳过、安装继续——已实测)装不了的(会明确拒绝):
dsh.bundle.patch 的普通库 → 拒绝并自动 pnpm remove 回滚,不留残渣.dsh-plugin / config.yaml 安装格式 → 不支持(DSH 核心已整体移除该路径)边界提醒:
dsh plugin CLI 不要对同一 bundle 混用(卸载会自动清理 CLI
残留的 bundle 层条目)前置条件:Node ≥ 22、pnpm ≥ 10、已安装 dsh 并初始化过目标 profile。
① 直接从 GitHub 安装(推荐)——无需克隆本仓库:
dsh plugin --profile web add "github:kyorakuyk/dsh-hotreload-plugin-manager"
# 或完整 git URL:
# dsh plugin --profile web add "https://github.com/kyorakuyk/dsh-hotreload-plugin-manager.git"
② 或者克隆后本地安装:
git clone https://github.com/kyorakuyk/dsh-hotreload-plugin-manager.git
dsh plugin --profile web add "file:$(pwd)/dsh-hotreload-plugin-manager"
③ 或者从 npm 安装(发布后可用):
dsh plugin --profile web add "@kyorakuyk/dsh-plugin-manager"
装完后必须重启 dsh web 实例(新 bundle 需要重启才挂载;仓库已包含构建产物 lib/,无需先构建),浏览器硬刷新一次(Ctrl+Shift+R):
dsh --profile web
然后打开 Web UI → 设置 → 插件 → 插件管理,按 spec 安装其他插件——npm 包名、
git URL、github:owner/repo、或 file:/link: 路径均可。
npm 已发布:
dsh plugin --profile web add "@kyorakuyk/dsh-plugin-manager"。
设置 → 插件 → 插件管理:
| 方法 | 路径 | Body | 结果 |
|---|---|---|---|
| GET | /plugin-manager/list |
— | { ok, value: Bundle[] } |
| POST | /plugin-manager/install |
{ spec } |
{ ok, message, exitCode?, tail? } |
| POST | /plugin-manager/uninstall |
{ packageName } |
同上 |
| POST | /plugin-manager/update |
{ packageName } |
同上 |
| POST | /plugin-manager/setEnabled |
{ entryId, enabled } |
同上 |
Bundle 形如 { packageName, version?, spec, rowIds, disabled }。
pnpm install
pnpm build # tsc(Node 半)+ esbuild(浏览器 bundle)
pnpm test # vitest:REAL-composition / HTTP / bundle 契约共 22 个测试
pnpm typecheck
发布走 GitHub Actions OIDC 信任发布——无需 token、无需 OTP/恢复码:
npm version 0.1.5 --no-git-tag-version # 升版本
git add package.json pnpm-lock.yaml && git commit -m "pkg: bump to 0.1.5"
git push
git tag v0.1.5 && git push origin v0.1.5 # 触发 .github/workflows/publish.yml
workflow 会:typecheck → build → test → npm publish --provenance(OIDC)
→ 自动创建 GitHub Release。发布失败可重跑:
gh workflow run publish.yml --ref main -f publish_ref=refs/tags/v0.1.5
前置:npm 账号需把本仓库 +
publish.yml注册为该包的 Trusted Publisher (npmjs.com → 包设置 → Trusted Publisher)。
目录结构:
src/ host 半(tsc → lib/*.js)
├── index.ts PluginManagerGateway(TypertRemoteService + @Remote)
├── manager.ts 核心生命周期操作
├── http.ts webserver 路由处理器
└── spec|pnpm|patch-layer|state|types.ts
src/client/index.tsx 浏览器半(esbuild → lib/client.js)
scripts/build-client.mjs esbuild 配置(模块表闭包契约)
tests/ vitest 套件 + fixtures/ 测试夹具
依赖按"运行时共享安装内单一 Cordis 实例"分类:@deepseek-ai/* 包为
peerDependencies(从 dsh 安装闭包解析),js-yaml 是唯一运行时依赖,构建与
测试工具放在 devDependencies。
/plugin-manager/* 路由对任何能触达服务器的人都开放。Web 面
默认仅回环绑定(launcher 拒绝 --host 0.0.0.0),但暴露到局域网时不能依赖
/api 信任围栏的保护;按可信权威校验为待办。dsh.profile.bundles
(行在用户补丁层)。之后若再运行 dsh plugin CLI,reconcile 会把声明
dsh.bundle 的依赖并入 bundle 层,下一次启动时双组合这些行。每个 bundle 只用
一种机制(卸载时会自动清理 CLI 残留的 bundle 层条目)。node_modules
中识别出包名,会以 "could not resolve the installed package name" 明确失败。pnpm update
(registry)或 remove + re-add(file:/link:)刷新。minimumReleaseAge(24h)会拒绝
"刚发布"的包(ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION)。管理器执行
pnpm 时统一附加 --config.minimumReleaseAge=0 跳过冷却(安装是用户主动
操作),并把违规包的 scope 追加到 profile pnpm-workspace.yaml 的
minimumReleaseAgeExclude(例如 @kyorakuyk/*)作为第二道防线。带
构建脚本的 git 依赖(prepare 等)会被 allowBuilds 白名单拦截
(ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED / ERR_PNPM_IGNORED_BUILDS):
管理器会从 pnpm 输出提取其要求的精确 spec(包名@git+url#sha),
自动写入 allowBuilds 并重试一次。手动处理时把对应 scope/spec 加入
pnpm-workspace.yaml 对应区块即可。无:管理器不注册任何 prompt、工具、消息或 provider 请求。
无;本包从不组装模型输入。
MIT
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: 无有效分类标签。