DeepSeek Harness 部署到桌面的实现步骤
DeepSeek Harness(DSH),作为一个具备可编排能力的AI编码智能体运行时,还自带Web GUI呢。接下来,我就一步一步地教大家,如何利用Electron把它封装成一个真正的
独立桌面应用

一、DeepSeek Harness 是什么
DeepSeek Harness(简称 DSH)是 DeepSeek 开源的 AI 编码智能体框架,核心特点:
- :基于 Cordis,100+ 个
插件化架构
@deepseek-ai/dsh-*包各司其职(工具、界面、存储、会话、沙箱) - :CLI / Web GUI / Headless 三端同源,共用一份配置树
多界面形态
- :子代&理、工作流(workflow)、全新代&理迭代(Ralph)、目标驱动长跑
多智能体编排
- :文件读写检索、PowerShell/Bash、网络搜索、后台作业、技能加载、MCP
丰富工具
其中 Web GUI 是一条本地 HTTP 服务(默认 http://127.0.0.1:3080)。本文的目标,就是把它从"浏览器标签页"升级成"桌面应用"。
二、方案选型:为什么用 Electron
| 方案 | 界面渲染 | 包体 | 是否含浏览器内核 | 结论 |
|---|---|---|---|---|
| 浏览器开 127.0.0.1 | 系统 Chrome/Edge | 0 | 是(现成) | 原生态用法 |
Electron | 自带 Chromium | ~110MB | 是(打包进应用) | ✅ 本文采用 |
| Tauri | 系统 WebView2 | 3~10MB | 是(复用系统) | 更轻,但需 Rust |
| 彻底原生 GUI | 原生控件 | 小 | 否 | 需重写整个前端,不推荐 |
选 Electron 的理由:
复用 DSH 现有 UI,零重写
三、环境准备
| 依赖 | 版本要求 | 说明 |
|---|---|---|
| Node.js | ≥ 20(实测 v24.16.0) | 运行 dsh 与安装依赖 |
| npm | 自带 | 装 Electron 与 dsh |
| DeepSeek API Key | 有余额 | 调模型必需 |
验证环境:
node --version # v24.16.0 npm --version # 11.13.0
四、第一步:搭建项目骨架
新建目录 dsh-desktop,写入 package.json:
{
"name": "dsh-desktop",
"version": "0.1.0",
"private": true,
"description": "DeepSeek Harness 桌面版:用 Electron 把 Web GUI 装进原生窗口,并自包含启动一个 dsh 实例",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"dependencies": {
"@deepseek-ai/dsh": "^0.1.0-rc.6"
},
"devDependencies": {
"electron": "^33.0.0"
}
}
关键点:把 @deepseek-ai/dsh 作为项目依赖,让桌面应用自包含 dsh,不依赖全局命令、也不依赖 npx 缓存。
五、第二步:编写 Electron 主进程
创建 main.js,职责:找空闲端口 → 用系统 node 拉起 dsh web → 轮询等待就绪 → 打开原生窗口 → 退出时回收进程树。
'use strict';
const { app, BrowserWindow, Menu, dialog, shell } = require('electron');
const { spawn } = require('node:child_process');
const path = require('node:path');
const fs = require('node:fs');
const net = require('node:net');
const DEFAULT_PORT = 3081;
const SERVER_TIMEOUT_MS = 60000;
let mainWindow = null;
let dshProc = null;
let serverPort = null;
function log(...args) {
console.log(`[${new Date().toLocaleTimeString()}]`, ...args);
}
// 解析 dsh CLI 入口(优先本项目自包含副本)
function resolveDshEntry() {
const local = path.join(__dirname, 'node_modules', '@deepseek-ai', 'dsh', 'lib', 'bin.js');
if (fs.existsSync(local)) return local;
try { return require.resolve('@deepseek-ai/dsh/lib/bin.js'); } catch { return null; }
}
// 选择空闲端口:先试首选端口,被占用则回退到系统分配的空闲端口
function pickPort() {
const preferred = Number(process.env.DSH_DESKTOP_PORT) || DEFAULT_PORT;
return new Promise((resolve) => {
const probe = (port) => {
const srv = net.createServer();
srv.unref();
srv.once('error', () => { if (port !== 0) { probe(0); } else { resolve(0); } });
srv.listen(port, '127.0.0.1', () => {
const chosen = srv.address().port;
srv.close(() => resolve(chosen));
});
};
probe(preferred);
});
}
// 轮询直到 Web 服务返回响应
async function waitForServer(port, timeoutMs = SERVER_TIMEOUT_MS) {
const url = `http://127.0.0.1:${port}/`;
const start = Date.now();
while (Date.now() - start < timeoutMs) {
try {
const res = await fetch(url);
if (res.status < 500) return true;
} catch { /* not ready yet */ }
await new Promise((r) => setTimeout(r, 500));
}
return false;
}
// 启动 dsh web
function startDsh(port) {
const entry = resolveDshEntry();
if (!entry) throw new Error('未找到 @deepseek-ai/dsh,请先运行 npm install');
const nodeCmd = process.env.DSH_NODE || 'node';
const cwd = process.env.DSH_WORKSPACE || process.cwd();
const args = [entry, 'web', '--port', String(port)];
log(`启动 dsh: ${nodeCmd} ${args.join(' ')}`);
const proc = spawn(nodeCmd, args, {
cwd,
env: { ...process.env, DSH_DESKTOP: '1' },
stdio: ['ignore', 'pipe', 'pipe'],
windowsHide: false,
});
proc.stdout.on('data', (d) => process.stdout.write(`[dsh] ${d}`));
proc.stderr.on('data', (d) => process.stderr.write(`[dsh] ${d}`));
proc.on('error', (err) => log(`dsh 启动失败: ${err.message}`));
proc.on('exit', (code, sig) => log(`dsh 进程退出 code=${code} signal=${sig ?? ''}`));
return proc;
}
// 回收 dsh 进程树
function stopDsh() {
if (!dshProc || dshProc.killed) return;
try {
if (process.platform === 'win32') {
spawn('taskkill', ['/pid', String(dshProc.pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' });
} else {
dshProc.kill('SIGTERM');
}
} catch (err) { log('回收 dsh 失败:', err.message); }
}
function buildMenu() {
const template = [
{ label: '应用', submenu: [
{ role: 'reload', label: '刷新' },
{ role: 'toggleDevTools', label: '开发者工具' },
{ type: 'separator' },
{ role: 'quit', label: '退出' },
] },
{ label: '编辑', submenu: [
{ role: 'undo', label: '撤销' }, { role: 'redo', label: '重做' },
{ type: 'separator' },
{ role: 'cut', label: '剪切' }, { role: 'copy', label: '复制' },
{ role: 'paste', label: '粘贴' }, { role: 'selectAll', label: '全选' },
] },
{ label: '视图', submenu: [
{ role: 'resetZoom', label: '实际大小' },
{ role: 'zoomIn', label: '放大' }, { role: 'zoomOut', label: '缩小' },
{ type: 'separator' },
{ role: 'togglefullscreen', label: '全屏' },
] },
];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
}
function createWindow(port) {
mainWindow = new BrowserWindow({
width: 1280, height: 840,
minWidth: 900, minHeight: 600,
title: 'DeepSeek Harness',
backgroundColor: '#0e1016',
show: false,
autoHideMenuBar: true,
webPreferences: {
contextIsolation: true, nodeIntegration: false, sandbox: true, spellcheck: false,
},
});
mainWindow.loadURL(`http://127.0.0.1:${port}/`);
mainWindow.once('ready-to-show', () => mainWindow.show());
mainWindow.on('closed', () => { mainWindow = null; });
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
if (/^https?:/i.test(url)) { shell.openExternal(url); return { action: 'deny' }; }
return { action: 'allow' };
});
}
const gotLock = app.requestSingleInstanceLock();
if (!gotLock) {
app.quit();
} else {
app.on('second-instance', () => {
if (mainWindow) { if (mainWindow.isMinimized()) mainWindow.restore(); mainWindow.focus(); }
});
app.whenReady().then(async () => {
app.setName('DeepSeek Harness');
buildMenu();
if (!resolveDshEntry()) {
dialog.showErrorBox('启动失败', '未找到 @deepseek-ai/dsh。n请先运行 npm install');
app.quit(); return;
}
let ready = false;
try {
serverPort = await pickPort();
dshProc = startDsh(serverPort);
ready = await waitForServer(serverPort);
} catch (err) {
dialog.showErrorBox('启动失败', `dsh 启动异常:n${err.message}`);
}
if (!ready) {
dialog.showErrorBox('启动失败', `dsh web 未能就绪(端口 ${serverPort})。n请查看终端日志排查。`);
stopDsh(); app.quit(); return;
}
createWindow(serverPort);
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0 && serverPort) createWindow(serverPort);
});
});
}
app.on('window-all-closed', () => app.quit());
app.on('before-quit', () => stopDsh());
六、第三步:安装依赖
cd dsh-desktop npm install --no-audit --no-fund
国内网络建议给 Electron 加镜像,避免二进制下载超时:
# Windows PowerShell $env:ELECTRON_MIRROR='https://npmmirror.com/mirrors/electron/' npm install
已知坑:Electron 二进制解压不完整。
node_modules/electron/dist/ 里缺少 electron.exe(只有 license 文件),手动补解压:
# 找到缓存 zip(通常在 $env:LOCALAPPDATAelectronCache)
Expand-Archive -Path "C:Users<你>AppDataLocalelectronCache...electron-vXX-win32-x64.zip" `
-DestinationPath ".node_moduleselectrondist" -Force
# 补写 path.txt
Set-Content -Path ".node_moduleselectronpath.txt" -Value "electron.exe" -NoNewline
验证:
node_moduleselectrondistelectron.exe --version # 应输出 v33.x
七、第四步:启动桌面应用
npm start
稍等约 10~20 秒(首次要拉起 dsh 服务),就会弹出独立的
DeepSeek Harness
3081)。
八、第五步:创建快捷方式(双击即用)
方式 A:桌面快捷方式(推荐,无黑框)
用 PowerShell 创建 .lnk:
$exe = 'D:pathtodsh-desktopnode_moduleselectrondistelectron.exe'
$app = 'D:pathtodsh-desktop'
$ws = New-Object -ComObject WScript.Shell
$sc = $ws.CreateShortcut("$([Environment]::GetFolderPath('Desktop'))DeepSeek Harness.lnk")
$sc.TargetPath = $exe
$sc.Arguments = '"' + $app + '"'
$sc.WorkingDirectory = $app
$sc.IconLocation = "$exe,0"
$sc.Description = 'DeepSeek Harness 桌面版'
$sc.Sa ve()
方式 B:一键启动脚本
在项目目录放一个 start-dsh.cmd:
@echo off setlocal set "APP=%~dp0" set "ELECTRON=%APP%node_moduleselectrondistelectron.exe" if not exist "%ELECTRON%" ( echo [DSH Desktop] First run: installing dependencies... cd /d "%APP%" call npm.cmd install --no-audit --no-fund ) start "" "%ELECTRON%" "%APP%" endlocal
双击即可启动;依赖丢失时还会自动补装。
九、常见问题 FAQ
Q1:窗口能打开,但发消息报Insufficient Balance/QUOTA
这不是桌面部署的问题
模型 API
余额/额度不足
- 解决:到 platform.deepseek.com 充值/查余额,确认 API Key 正确且有额度;第三方中转则查中转额度。
- 注意:桌面版与浏览器版共用同一份凭据(
~/.dsh/.credentials.yaml),所以浏览器版此刻会报同样的错。
Q2:端口冲突(3081 已被占用)
桌面版默认用 3081(避开浏览器版的 3080);若仍冲突会自动回退到空闲端口。也可手动指定:
$env:DSH_DESKTOP_PORT='3090'; npm start
Q3:怎么固定到任务栏 / 开始菜单
- 启动一次后,任务栏图标上右键 → 「固定到任务栏」
- 对桌面图标右键 → 「固定到"开始"屏幕」
Q4:想换自定义图标
准备一张 .ico,在 main.js 的 BrowserWindow 里加 icon: '路径.ico',并把快捷方式的 IconLocation 指向它。
十、结语
到这儿,你已经成功将DeepSeek Harness从“浏览器标签页”升级成了
双击即可打开的桌面应用
electron-builder 就能打包成绿色版或安装包。
本文配套代码:
main.js+package.json+start-dsh.cmd三件套即可完整复现。