详情

首页手游攻略 DeepSeek Harness 部署到桌面的实现步骤

DeepSeek Harness 部署到桌面的实现步骤

佚名 2026-08-19 07:59:58

DeepSeek Harness(DSH)是一个可编排的 AI 编码智能体运行时,自带 Web GUI。本文手把手教你用 Electron 把它包成一个独立桌面应用——双击图标即开、无地址栏、不依赖命令行,全程本地部署、数据不出本机。

DeepSeek Harness 部署到桌面的实现步骤

一、DeepSeek Harness 是什么

DeepSeek Harness(简称 DSH)是 DeepSeek 开源的 AI 编码智能体框架,核心特点:

  1. 插件化架构:基于 Cordis,100+ 个 @deepseek-ai/dsh-* 包各司其职(工具、界面、存储、会话、沙箱)
  2. 多界面形态:CLI / Web GUI / Headless 三端同源,共用一份配置树
  3. 多智能体编排:子代&理、工作流(workflow)、全新代&理迭代(Ralph)、目标驱动长跑
  4. 丰富工具:文件读写检索、PowerShell/Bash、网络搜索、后台作业、技能加载、MCP

其中 Web GUI 是一条本地 HTTP 服务(默认 http://127.0.0.1:3080)。本文的目标,就是把它从"浏览器标签页"升级成"桌面应用"。

二、方案选型:为什么用 Electron

方案界面渲染包体是否含浏览器内核结论
浏览器开 127.0.0.1系统 Chrome/Edge0是(现成)原生态用法
Electron自带 Chromium~110MB是(打包进应用)✅ 本文采用
Tauri系统 WebView23~10MB是(复用系统)更轻,但需 Rust
彻底原生 GUI原生控件需重写整个前端,不推荐

选 Electron 的理由:复用 DSH 现有 UI,零重写;把浏览器内核内嵌进桌面壳,换来独立窗口、任务栏图标、无地址栏的原生体验。

三、环境准备

依赖版本要求说明
Node.js≥ 20(实测 v24.16.0)运行 dsh 与安装依赖
npm自带装 Electron 与 dsh
DeepSeek API Key有余额调模型必需

验证环境:

node --version   # v24.16.0npm --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 webfunction 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-desktopnpm 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.txtSet-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.Save()

方式 B:一键启动脚本

在项目目录放一个 start-dsh.cmd

@echo offsetlocalset "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

这不是桌面部署的问题——能打开窗口、能进界面,说明本地部署已成功。该错误来自 DeepSeek 模型 API:账户余额/额度不足

  1. 解决:到 platform.deepseek.com 充值/查余额,确认 API Key 正确且有额度;第三方中转则查中转额度。
  2. 注意:桌面版与浏览器版共用同一份凭据(~/.dsh/.credentials.yaml),所以浏览器版此刻会报同样的错。

Q2:端口冲突(3081 已被占用)

桌面版默认用 3081(避开浏览器版的 3080);若仍冲突会自动回退到空闲端口。也可手动指定:

$env:DSH_DESKTOP_PORT='3090'; npm start

Q3:怎么固定到任务栏 / 开始菜单

  1. 启动一次后,任务栏图标上右键 → 「固定到任务栏」
  2. 对桌面图标右键 → 「固定到"开始"屏幕」

Q4:想换自定义图标

准备一张 .ico,在 main.jsBrowserWindow 里加 icon: '路径.ico',并把快捷方式的 IconLocation 指向它。

十、结语

至此,你把 DeepSeek Harness 从"浏览器标签页"升级成了双击即开的桌面应用:本地部署、数据不出本机、自包含 dsh、干净退出。若想进一步分发,可用 electron-builder 打包成绿色版或安装包。

本文配套代码:main.js + package.json + start-dsh.cmd 三件套即可完整复现。

点击查看更多
推荐专题
热门阅读