DeepSeek Harness 部署与插件开发实战指南
DeepSeek Harness 部署与插件开发实战指南
DeepSeek Harness(dsh)是基于 Cordis 插件架构的 AI Agent 运行时,支持 Web UI、插件扩展、主题皮肤定制。本文覆盖从安装部署到自定义插件/主题开发的完整流程,附大量踩坑记录。
目录
1. 什么是 DeepSeek Harness
DeepSeek Harness(简称 dsh)是 DeepSeek 推出的插件式 AI Agent 运行时框架。核心理念:everything is a plugin——所有功能(模型调用、工具执行、Web UI、主题外观)都以插件形式加载和组合。
核心特性:
- 插件架构:基于 Cordis 依赖注入框架,所有组件可插拔
- Web UI:内置浏览器端 React 前端,支持热更新
- 主题系统:13 个可覆盖 token,支持自定义皮肤
- Profile 隔离:不同 profile 加载不同插件组合
- Bundle 机制:插件可打包为 npm 包,一键安装到任意 profile
- Client 插件:浏览器端插件动态编译,无需重建整个前端
2. 架构概览
┌─────────────────────────────────────────────────┐
│ DeepSeek Harness │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Bundle │ │ Bundle │ │ Bundle │ │
│ │ (base) │ │ (web-app)│ │ (custom) │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ Cordis 插件树 │ │
│ │ 按序叠加:bundle → profile patch → │ │
│ │ 用户级 patch → --patch 覆盖 │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Node 侧 │ │ Browser 侧 │ │
│ │ (服务端) │ │ (Client 插件) │ │
│ │ - 模型调用 │ │ - UI 组件 │ │
│ │ - 工具执行 │ │ - 主题/皮肤 │ │
│ │ - 文件操作 │ │ - 设置面板 │ │
│ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────┘
配置层叠顺序(优先级从低到高)
- Bundle 层:profile 的
dsh.profile.bundles列出的包,按安装顺序叠加 - Profile patch:profile 目录下的
cordis.patch.yml - 用户级 patch:
$DSH_HOME/cordis.patch.yml(所有 profile 共享) - 命令行覆盖:
--patch <path>参数
每层通过 id 定位配置行,后层按 id 替换前层的整行配置(不深合并)。
关键目录结构
$DSH_HOME/ # Windows: %USERPROFILE%\.dsh
├── cordis.patch.yml # 用户级全局 patch
├── profiles/
│ ├── web/ # web profile
│ │ ├── package.json # 依赖清单 + bundles 列表
│ │ ├── cordis.patch.yml # profile 级 patch
│ │ ├── pnpm-workspace.yaml
│ │ └── node_modules/ # 插件安装位置
│ └── headless/ # 无 UI profile
└── skin-backgrounds/ # 皮肤背景图存储
3. 环境准备与安装
3.1 系统要求
| 依赖 | 版本要求 | 说明 |
|---|---|---|
| Node.js | ≥ 22 | 需要 node:sqlite 等新特性 |
| pnpm | ≥ 10 | 包管理器 |
| Git | 任意版本 | 部分插件从 GitHub 安装 |
| 操作系统 | Windows / macOS / Linux | 本文以 Windows 为主 |
3.2 安装 Node.js
Windows 推荐:下载 Node.js 22+ 的 zip 包,解压到固定目录(如 D:\node24-win),添加到 PATH。
# 验证
node --version # v22.x 或更高
pnpm --version # 10.x
⚠️ 不要用 Node.js 20:dsh 依赖
node:sqlite,Node 20 不支持。
3.3 安装 DeepSeek Harness
方式一:npm 全局安装(推荐)
npm install -g @deepseek-ai/dsh
npm 国内镜像(加速):
npm config set registry https://registry.npmmirror.com
npm install -g @deepseek-ai/dsh
安装后在任意目录都能使用 dsh 命令。
方式二:从源码安装
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm dsh # 通过 pnpm 调用 dsh CLI
⚠️ 源码方式在 monorepo 内运行
pnpm dsh plugin会触发全量依赖检查,可能下载大包超时。建议用全局安装方式。
3.4 验证安装
dsh --version
4. 首次启动与配置
4.1 启动 Web UI
# 首次运行会自动初始化 web profile
dsh web
首次启动会:
- 自动创建
$DSH_HOME/profiles/web/目录 - 安装基础 bundle(
dsh-base+dsh-web-app) - 启动 Web 服务(默认端口 5173)
浏览器访问 http://localhost:5173 即可。
4.2 后台启动(Windows)
创建 start_web_bg.ps1 脚本:
# start_web_bg.ps1 — 后台启动 dsh web,自动杀旧实例
$port = 5173
# 杀掉占用端口的旧进程
$lines = netstat -ano | Select-String ":$port\s.*LISTENING"
foreach ($line in $lines) {
$pid = ($line -split '\s+')[-1]
if ($pid -match '^\d+$') {
Write-Host "Stopping old web instance: PID $pid"
taskkill /PID $pid /F 2>$null
}
}
# 后台启动
Start-Process -FilePath "dsh" -ArgumentList "web" -WindowStyle Hidden `
-RedirectStandardError "web.err.log" -RedirectStandardOutput "web.out.log"
Start-Sleep -Seconds 5
Write-Host "OK - service ready"
⚠️ EADDRINUSE 问题:如果
web.err.log里有EADDRINUSE,说明旧进程没杀干净。手动netstat -ano | findstr :5173→taskkill /PID <pid> /F。
4.3 查看当前配置
# 查看 web profile 的完整插件树
dsh --profile web --dump-config
输出会显示所有层的组合结果,包括 bundle 层的注释标记。
5. 插件系统详解
5.1 两种插件类型
| 类型 | 运行位置 | 说明 |
|---|---|---|
| Node 插件 | 服务端 | 模型调用、文件操作、API 路由等 |
| Client 插件 | 浏览器端 | UI 组件、主题、设置面板等 |
一个包可以同时包含 Node 侧和 Client 侧代码(双面插件)。
5.2 Bundle 机制
Bundle 是一种特殊的 npm 包,在 package.json 中声明 dsh.bundle:
{
"name": "@my-org/my-plugin",
"dsh": {
"bundle": {
"patch": "./cordis.patch.yml"
}
}
}
安装 bundle 时,dsh 会自动将其 cordis.patch.yml 内容追加到 profile 的配置层中。
5.3 Client 插件加载机制
Client 插件通过 dsh.client 声明:
{
"dsh": {
"client": {
"inject": ["@deepseek-ai/dsh-client-runtime"],
"platform": "web"
}
}
}
加载流程:
dsh-client-modules(Node 侧)扫描 Loader 中声明了dsh.client的包- 动态编译
/plugins/<id>/client.js - 注入
window.__DSH_BOOT__供前端加载
关键点:Client 插件不需要重建整个前端 dist,只需自身先 bundle 产出 lib/。
6. 开发自定义插件
6.1 包结构模板
packages/client/my-plugin/
├── package.json
├── tsconfig.json
├── tsdown.config.ts
├── cordis.patch.yml # bundle 声明(可选)
├── src/
│ ├── index.ts # Node 侧(host half)
│ └── client/
│ └── index.ts # Browser 侧(client half)
└── lib/ # 构建产物(bundle 后生成)
├── index.js
├── client.js
└── types/
6.2 package.json 模板
{
"name": "@my-org/dsh-my-plugin",
"version": "0.1.0",
"type": "module",
"exports": {
".": {
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./client": {
"types": "./lib/types/client/index.d.ts",
"default": "./lib/client.js"
},
"./package.json": "./package.json"
},
"files": [
"lib/index.js",
"lib/client.js",
"lib/types/**/*.d.ts",
"cordis.patch.yml"
],
"dsh": {
"bundle": {
"patch": "./cordis.patch.yml"
},
"client": {
"inject": [
"@deepseek-ai/dsh-client-runtime",
"@deepseek-ai/dsh-client-ui-theme"
],
"platform": "web"
}
},
"scripts": {
"bundle": "tsdown",
"watch": "tsdown --watch"
},
"peerDependencies": {
"@deepseek-ai/cordis": "^4.0.1",
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.5"
},
"devDependencies": {
"@deepseek-ai/cordis": "workspace:^",
"@deepseek-ai/dsh-client-runtime": "workspace:^",
"tsdown": "latest",
"typescript": "latest"
}
}
⚠️ 关键坑:
peerDependencies必须用普通版本号(如^4.0.1),不能用workspace:^。dsh plugin add在 profile 独立环境运行,不认 workspace 协议。devDependencies可以保持workspace:^(仓库内构建用)。
6.3 tsconfig.json
{
"extends": "../../../tsconfig.base.client.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib/types"
},
"include": ["src"]
}
6.4 tsdown.config.ts
import { clientBundle } from '../tsdown.client.ts';
export default clientBundle('my-plugin', ['lib/types/index.js']);
6.5 Client 侧代码(browser half)
// src/client/index.ts
export const name = 'dsh-my-plugin';
// 声明注入的 ctx 服务
export const inject = ['theme'];
// 插件入口
export function apply(ctx: any) {
// 注册主题覆盖
ctx.effect(() => {
const dispose = ctx.theme.overrideTokens('my-plugin', {
'--dsw-alias-brand-primary': { light: '#0D9488', dark: '#2DD4BF' },
});
return dispose;
}, 'my-plugin: theme override');
}
6.6 Node 侧代码(host half)
// src/index.ts
export const name = 'dsh-my-plugin';
export function apply(ctx: any) {
// 服务端逻辑(如果不需要可以空实现)
}
6.7 cordis.patch.yml(Bundle 声明)
- insert:
- id: my-plugin
name: '@my-org/dsh-my-plugin'
6.8 构建与安装
# 构建单包
pnpm --filter @my-org/dsh-my-plugin bundle
# 安装到 web profile
dsh plugin --profile web add ./packages/client/my-plugin
# 验证
dsh --profile web --dump-config
7. 主题与皮肤系统
7.1 主题运行时
dsh 内置 ThemeRuntime(@deepseek-ai/dsh-client-ui-theme),提供 light 和 dark 两种模式。
两个核心 API:
// 1. 立即叠加覆盖层(推荐,无需 UI 入口)
const dispose = ctx.theme.overrideTokens('my-skin', {
'--dsw-alias-brand-primary': { light: '#0D9488', dark: '#2DD4BF' },
});
// 调用 dispose() 即还原
// 2. 注册可切换主题
ctx.theme.register({
id: 'ocean',
colorScheme: 'dark',
tokens: { '--dsw-alias-brand-primary': '#2DD4BF' },
});
// 编程切换:ctx.theme.setTheme('ocean')
7.2 可覆盖 Token 全表
| Token | 说明 |
|---|---|
--dsw-alias-brand-primary |
品牌主色 |
--dsw-alias-brand-hover |
品牌悬停色 |
--dsw-alias-brand-active |
品牌激活色 |
--dsw-alias-bg-base |
基础背景色 |
--dsw-alias-bg-elevated |
浮层背景色 |
--dsw-alias-bg-surface |
表面背景色 |
--dsw-alias-text-primary |
主文本色 |
--dsw-alias-text-secondary |
次文本色 |
--dsw-alias-text-tertiary |
三级文本色 |
--dsw-alias-border-default |
默认边框色 |
--dsw-alias-border-strong |
强调边框色 |
--dsw-alias-shadow-default |
默认阴影 |
--dsw-alias-radius-base |
基础圆角 |
⚠️ 每个 token 必须同时提供 light 和 dark 两个值,否则 ThemeTokenModes 校验会抛错。
7.3 皮肤插件完整示例
目录结构:
packages/client/ui-skin-ocean/
├── assets/
│ ├── ocean-bg.svg
│ └── ocean-logo.svg
├── src/
│ ├── index.ts # Node 侧:注册图片路由
│ └── client/
│ └── index.ts # Browser 侧:CSS 注入
└── package.json
Node 侧(图片路由):
import { fileURLToPath } from 'node:url';
import { readFile } from 'node:fs/promises';
import { normalize, join, extname } from 'node:path';
export const name = 'dsh-skin-ocean';
export const inject = ['webServer'];
const ASSETS_DIR = fileURLToPath(new URL('../assets/', import.meta.url));
const MIME: Record<string, string> = {
'.svg': 'image/svg+xml', '.png': 'image/png',
'.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
'.gif': 'image/gif', '.webp': 'image/webp', '.ico': 'image/x-icon',
};
export function apply(ctx: any) {
const prefix = '/skin-ocean/assets';
ctx.effect(() => ctx.webServer.register({
kind: 'prefix',
path: prefix,
handler: async (req: any, res: any) => {
const url = new URL(req.url, 'http://localhost');
const rel = normalize(url.pathname.slice(prefix.length + 1));
const abs = join(ASSETS_DIR, rel);
// 防目录穿越
if (!abs.startsWith(ASSETS_DIR)) {
res.statusCode = 403;
res.end('Forbidden');
return;
}
try {
const data = await readFile(abs);
const ext = extname(abs).toLowerCase();
res.writeHead(200, {
'Content-Type': MIME[ext] || 'application/octet-stream',
'Cache-Control': 'no-cache',
});
res.end(data);
} catch {
res.statusCode = 404;
res.end('Not Found');
}
},
}), 'skin-ocean: asset route');
}
Browser 侧(CSS 注入):
export const name = 'dsh-skin-ocean';
export const inject = ['theme'];
export function apply(ctx: any) {
ctx.effect(() => {
// 标记作用域
document.documentElement.setAttribute('data-skin-ocean', '');
// 注入 CSS
const style = document.createElement('style');
style.textContent = `
[data-skin-ocean] {
--dsw-alias-brand-primary: #0D9488;
--dsw-alias-bg-base: rgba(13, 148, 136, 0.05);
}
[data-skin-ocean] body {
background-image: url('/skin-ocean/assets/ocean-bg.svg');
background-size: cover;
background-attachment: fixed;
}
`;
document.head.appendChild(style);
// 清理函数
return () => {
document.documentElement.removeAttribute('data-skin-ocean');
style.remove();
};
}, 'skin-ocean: css injection');
}
⚠️ Node 侧用了
node:fs/promises等模块时,tsconfig 必须加"types": ["node"]和"lib": ["ES2024", "DOM", "DOM.Iterable"]。
8. Bundle 安装与管理
8.1 安装来源
| 来源 | 命令示例 | 说明 |
|---|---|---|
| npm | dsh plugin --profile web add @org/pkg |
从 npm registry |
| GitHub | dsh plugin --profile web add github:user/repo |
git 协议,需 allowBuilds |
| GitHub tarball | dsh plugin --profile web add https://github.com/.../tar.gz |
推荐,无需构建权限 |
| 本地目录 | dsh plugin --profile web add ./my-plugin |
开发调试用 |
| 本地 tarball | dsh plugin --profile web add D:\pkg-0.1.0.tgz |
离线安装 |
8.2 管理命令
# 安装
dsh plugin --profile web add <package>
# 卸载
dsh plugin --profile web remove <package>
# 升级
dsh plugin --profile web update <package>
# 查看依赖关系
dsh plugin --profile web why <package>
8.3 验证安装(三步法)
# ① 清单层:检查 package.json
type $env:USERPROFILE\.dsh\profiles\web\package.json
# ② 文件层:检查 node_modules(关键判据)
dir $env:USERPROFILE\.dsh\profiles\web\node_modules\<pkg>
# → 有 lib\client.js、lib\index.js = 真的装上了
# → "找不到路径" = 清单写了但文件没下载(网络问题)
# ③ 运行时层:重启后 UI 里找插件入口
dsh --profile web --dump-config
8.4 Git 安装的 allowBuilds
从 Git 安装的插件如果有 prepare 脚本(需要构建),需要在 profile 的 pnpm-workspace.yaml 中允许:
allowBuilds:
my-plugin: true
⚠️ 这等于允许装包时执行该包的代码,只对信得过的包开。建议锁 commit:
dsh plugin add github:user/repo#<sha>。
9. 踩坑记录与解决方案
9.1 不要在 monorepo 根目录跑 pnpm dsh plugin
问题:在源码仓库内运行 pnpm dsh plugin add,pnpm 会先做依赖状态检查,触发全量 pnpm install,下载大包超时失败。
解决:用全局安装的 dsh 命令,在任意不在仓库内的目录运行。
9.2 Node.js 版本必须 ≥ 22
问题:Node 20 缺少 node:sqlite 等特性,dsh 启动失败。
解决:升级到 Node.js 22+。
9.3 EADDRINUSE 端口被占
问题:重启 dsh web 后浏览器看到的还是旧页面,web.err.log 有 EADDRINUSE。
原因:旧进程没杀干净,新进程绑定端口失败直接退出。
解决:
netstat -ano | findstr :5173
# 找到 PID 后
taskkill /PID <pid> /F
# 然后重启
dsh web
9.4 Client 插件不生效
排查顺序:
lib/client.js存在吗?(没构建 = 不加载)cordis.patch.yml的 id 唯一吗?(重复 = 替换旧行)- 重启进程了吗?(插件集变化需要重启)
- 端口冲突吗?(新进程根本没起来)
9.5 peerDependencies 不能用 workspace:^
问题:dsh plugin add 在 profile 独立环境运行,不认 workspace: 协议。
解决:peerDependencies 用普通版本号(^4.0.1),devDependencies 保持 workspace:^。
9.6 GitHub tarball 下载失败
问题:国内网络连接 GitHub 不稳定,dsh plugin add 报 ECONNRESET / ETIMEDOUT。
现象:package.json 里已有依赖声明,但 node_modules 里没有实际文件。
解决:先在能访问 GitHub 的环境下载 tarball,再本地安装:
# 在能访问 GitHub 的环境下载
curl -sL -o plugin.tar.gz "https://github.com/user/repo/archive/refs/tags/v0.1.0.tar.gz"
# 本地安装
dsh plugin --profile web add D:\plugin.tar.gz
9.7 GitHub URL 混入中文中点
问题:用户手打 URL 时把 github.com 打成 github·com(U+00B7 中点),DNS 解析失败。
识别:报错 host 是 xn-- 开头的 punycode。
解决:提供可复制的命令,明说"直接复制别手打"。
9.8 主题 token 必须双模式
问题:只给 light 值不给 dark 值,ThemeTokenModes 校验报错。
解决:每个 token 都提供 { light: '...', dark: '...' } 对象。
9.9 pnpm install 大包下载卡住
问题:pnpm install 卡在 @openai/codex(131MB)或 @anthropic-ai/claude-agent-sdk(85MB)下载。
原因:pnpm 无断点续传,超时后从头下载。
解决:
# 方案1:拉长超时 + 串行下载
pnpm config set fetch-timeout 600000
pnpm config set fetch-retries 10
pnpm config set network-concurrency 1
pnpm install
# 方案2:手动下载 tarball 后 pnpm store add
# (在能访问的环境下载,复制到本地)
pnpm store add D:\pnpm-tarballs\*.tgz
pnpm install --offline
9.10 不要修改仓库现有代码
原则:所有自定义插件放在 packages/client/<新目录>/,不编辑现有包文件。pnpm-workspace.yaml 的 packages/*/* 已自动包含新目录。
10. 常用命令速查
# === 启动 ===
dsh web # 启动 Web UI
dsh web --port 8080 # 指定端口
dsh --profile headless # 无 UI 模式
# === 配置 ===
dsh --profile web --dump-config # 查看完整插件树
dsh setup # 初始配置向导
# === 插件管理 ===
dsh plugin --profile web add <pkg> # 安装插件
dsh plugin --profile web remove <pkg># 卸载插件
dsh plugin --profile web update <pkg># 升级插件
dsh plugin --profile web why <pkg> # 查看依赖关系
# === 开发 ===
pnpm --filter @my-org/my-plugin bundle # 构建单包
pnpm --filter @my-org/my-plugin watch # 监听模式
# === 调试 ===
dsh --profile web --dump-config # 验证插件层
type web.err.log # 查看错误日志
netstat -ano | findstr :5173 # 检查端口占用
附录:皮肤插件快速上手模板
- 复制
templates/skin-plugin/目录,改名为你的皮肤名 - 修改
package.json中的包名 - 修改
src/client/index.ts中的 token 值和 CSS - 构建:
pnpm --filter @my-org/dsh-skin-xxx bundle - 安装:
dsh plugin --profile web add ./packages/client/ui-skin-xxx - 重启:
dsh web
最后更新:2026-08-24
适用版本:DeepSeek Harness dsh v0.1.0-rc.6+
仓库地址:https://github.com/deepseek-ai/deepseek-harness
更多推荐



所有评论(0)