DeepSeek Harness 部署与插件开发实战指南

DeepSeek Harness(dsh)是基于 Cordis 插件架构的 AI Agent 运行时,支持 Web UI、插件扩展、主题皮肤定制。本文覆盖从安装部署到自定义插件/主题开发的完整流程,附大量踩坑记录。


目录

  1. 什么是 DeepSeek Harness
  2. 架构概览
  3. 环境准备与安装
  4. 首次启动与配置
  5. 插件系统详解
  6. 开发自定义插件
  7. 主题与皮肤系统
  8. Bundle 安装与管理
  9. 踩坑记录与解决方案
  10. 常用命令速查

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 组件           │      │
│  │  - 工具执行  │  │  - 主题/皮肤         │      │
│  │  - 文件操作  │  │  - 设置面板          │      │
│  └─────────────┘  └─────────────────────┘      │
└─────────────────────────────────────────────────┘

配置层叠顺序(优先级从低到高)

  1. Bundle 层:profile 的 dsh.profile.bundles 列出的包,按安装顺序叠加
  2. Profile patch:profile 目录下的 cordis.patch.yml
  3. 用户级 patch$DSH_HOME/cordis.patch.yml(所有 profile 共享)
  4. 命令行覆盖--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

首次启动会:

  1. 自动创建 $DSH_HOME/profiles/web/ 目录
  2. 安装基础 bundle(dsh-base + dsh-web-app
  3. 启动 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 :5173taskkill /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"
    }
  }
}

加载流程:

  1. dsh-client-modules(Node 侧)扫描 Loader 中声明了 dsh.client 的包
  2. 动态编译 /plugins/<id>/client.js
  3. 注入 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),提供 lightdark 两种模式。

两个核心 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.logEADDRINUSE

原因:旧进程没杀干净,新进程绑定端口失败直接退出。

解决

netstat -ano | findstr :5173
# 找到 PID 后
taskkill /PID <pid> /F
# 然后重启
dsh web

9.4 Client 插件不生效

排查顺序

  1. lib/client.js 存在吗?(没构建 = 不加载)
  2. cordis.patch.yml 的 id 唯一吗?(重复 = 替换旧行)
  3. 重启进程了吗?(插件集变化需要重启)
  4. 端口冲突吗?(新进程根本没起来)

9.5 peerDependencies 不能用 workspace:^

问题dsh plugin add 在 profile 独立环境运行,不认 workspace: 协议。

解决peerDependencies 用普通版本号(^4.0.1),devDependencies 保持 workspace:^

9.6 GitHub tarball 下载失败

问题:国内网络连接 GitHub 不稳定,dsh plugin addECONNRESET / 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.yamlpackages/*/* 已自动包含新目录。


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         # 检查端口占用

附录:皮肤插件快速上手模板

  1. 复制 templates/skin-plugin/ 目录,改名为你的皮肤名
  2. 修改 package.json 中的包名
  3. 修改 src/client/index.ts 中的 token 值和 CSS
  4. 构建:pnpm --filter @my-org/dsh-skin-xxx bundle
  5. 安装:dsh plugin --profile web add ./packages/client/ui-skin-xxx
  6. 重启:dsh web

最后更新:2026-08-24
适用版本:DeepSeek Harness dsh v0.1.0-rc.6+
仓库地址:https://github.com/deepseek-ai/deepseek-harness

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐