dsh-desktop:DeepSeek Harness 的本地优先桌面应用

dsh-desktop:DeepSeek Harness 的本地优先桌面应用

一个基于 Go + Wails v3 构建的 Windows 桌面应用,把 DeepSeek Harness 的 agent 运行时与 Web UI 封装成开箱即用、完全离线的桌面产品。

项目地址: https://github.com/ydhcxh/dsh-desktop


一、项目定位

DeepSeek Harness 是一个功能完整的 agent 运行时,自带 Web 界面。但它以 Node.js CLI 工具的形式分发,普通用户要使用它,需要先安装 Node、手动拉起服务、记住本地端口、处理日志与进程生命周期------这些工程细节对最终用户来说并不友好。

dsh-desktop 不重新实现 Harness,而是在其之上补齐一个"桌面产品"所需的全部宿主能力:

能力 说明
免 CLI 启动 双击即可进入 Harness,无需手动敲命令
完全离线 内置 Node 运行时与预装依赖,目标机器零安装
数据隔离 应用专属启动目录,用户数据与安装目录分离
进程托管 统一管理子进程的启动、就绪检查、日志与退出
版本更新 可选的 GitHub Releases 版本检测与升级引导

换言之,dsh-desktop 负责"宿主",Harness 负责"智能"。两者各司其职,组合成一个可分发、可维护的桌面应用形态。


二、核心特性

1. 启动即用,无落地页

应用启动后直接导航到 Harness 的 Web UI,不设置多余的中转页面。启动期间仅显示一个简洁的加载页,就绪后自动跳转。

2. 随机端口,避免冲突

每次启动默认监听一个随机的 127.0.0.1 空闲端口,天然规避多实例与端口占用问题;需要固定端口时,可通过命令行参数或环境变量指定。

3. 完全离线的运行时

应用通过内置的 node.exe 直接运行预装在 runtime/ 目录中的 dsh 入口脚本 ,运行期不依赖网络、不走 npx 下载,目标机器无需安装 Node.js

4. 应用专属启动目录

Harness 的工作目录被指向 %LOCALAPPDATA%\dsh-desktop\launch-root,其 profiles / sessions / plugins 等用户数据与安装目录分离,升级程序不会误删用户数据

5. 健壮的进程生命周期管理

  • 退出时优雅终止 Harness 子进程;
  • 通过 Windows Job ObjectKILL_ON_JOB_CLOSE)兜底,即使主进程被强杀,整棵子进程树也会被系统强制回收,杜绝"僵尸进程"。

6. 可选的版本检测

构建时注入 GitHub 仓库后,应用会在启动后、每 6 小时以及手动触发时检查最新 Release,发现新版弹出对话框并一键打开下载页。


三、运行时架构

#mermaid-svg-Bckx6uLboOYlaxgQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Bckx6uLboOYlaxgQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Bckx6uLboOYlaxgQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Bckx6uLboOYlaxgQ .error-icon{fill:#552222;}#mermaid-svg-Bckx6uLboOYlaxgQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Bckx6uLboOYlaxgQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Bckx6uLboOYlaxgQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Bckx6uLboOYlaxgQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Bckx6uLboOYlaxgQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Bckx6uLboOYlaxgQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Bckx6uLboOYlaxgQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Bckx6uLboOYlaxgQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Bckx6uLboOYlaxgQ .marker.cross{stroke:#333333;}#mermaid-svg-Bckx6uLboOYlaxgQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Bckx6uLboOYlaxgQ p{margin:0;}#mermaid-svg-Bckx6uLboOYlaxgQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Bckx6uLboOYlaxgQ .cluster-label text{fill:#333;}#mermaid-svg-Bckx6uLboOYlaxgQ .cluster-label span{color:#333;}#mermaid-svg-Bckx6uLboOYlaxgQ .cluster-label span p{background-color:transparent;}#mermaid-svg-Bckx6uLboOYlaxgQ .label text,#mermaid-svg-Bckx6uLboOYlaxgQ span{fill:#333;color:#333;}#mermaid-svg-Bckx6uLboOYlaxgQ .node rect,#mermaid-svg-Bckx6uLboOYlaxgQ .node circle,#mermaid-svg-Bckx6uLboOYlaxgQ .node ellipse,#mermaid-svg-Bckx6uLboOYlaxgQ .node polygon,#mermaid-svg-Bckx6uLboOYlaxgQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Bckx6uLboOYlaxgQ .rough-node .label text,#mermaid-svg-Bckx6uLboOYlaxgQ .node .label text,#mermaid-svg-Bckx6uLboOYlaxgQ .image-shape .label,#mermaid-svg-Bckx6uLboOYlaxgQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-Bckx6uLboOYlaxgQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Bckx6uLboOYlaxgQ .rough-node .label,#mermaid-svg-Bckx6uLboOYlaxgQ .node .label,#mermaid-svg-Bckx6uLboOYlaxgQ .image-shape .label,#mermaid-svg-Bckx6uLboOYlaxgQ .icon-shape .label{text-align:center;}#mermaid-svg-Bckx6uLboOYlaxgQ .node.clickable{cursor:pointer;}#mermaid-svg-Bckx6uLboOYlaxgQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Bckx6uLboOYlaxgQ .arrowheadPath{fill:#333333;}#mermaid-svg-Bckx6uLboOYlaxgQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Bckx6uLboOYlaxgQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Bckx6uLboOYlaxgQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bckx6uLboOYlaxgQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Bckx6uLboOYlaxgQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bckx6uLboOYlaxgQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Bckx6uLboOYlaxgQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Bckx6uLboOYlaxgQ .cluster text{fill:#333;}#mermaid-svg-Bckx6uLboOYlaxgQ .cluster span{color:#333;}#mermaid-svg-Bckx6uLboOYlaxgQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Bckx6uLboOYlaxgQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Bckx6uLboOYlaxgQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-Bckx6uLboOYlaxgQ .icon-shape,#mermaid-svg-Bckx6uLboOYlaxgQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bckx6uLboOYlaxgQ .icon-shape p,#mermaid-svg-Bckx6uLboOYlaxgQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Bckx6uLboOYlaxgQ .icon-shape .label rect,#mermaid-svg-Bckx6uLboOYlaxgQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bckx6uLboOYlaxgQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Bckx6uLboOYlaxgQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Bckx6uLboOYlaxgQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 应用数据目录 %LOCALAPPDATA%\\dsh-desktop\\
内置运行时 (随 exe 分发)
dsh-desktop (Go + Wails v3)
http://127.0.0.1:<随机端口>
WebView2 窗口

DeepSeek Harness Web UI
Harness 子进程生命周期

启动 / 就绪检查 / 重启 / 关闭
Windows Job Object

强杀兜底回收进程树
runtime/node/node.exe
runtime/node_modules/@deepseek-ai/dsh/...
launch-root/

profiles / sessions / plugins
logs/dsh-desktop.log

启动流程

  1. 解析端口--port 参数 > DSH_PORT 环境变量 > 自动挑选空闲端口。
  2. 清理残留:先停止上一次启动可能遗留的自身子进程。
  3. 探测复用:若服务已在运行(如用户手动启动过),直接复用,不再重复拉起。
  4. 启动子进程 :优先用内置 node.exe 运行预置的 dsh 入口(离线模式);未预置时回退到 npx 在线安装。
  5. 就绪轮询:以 400ms 间隔轮询 HTTP 端点,直至就绪、子进程退出或超时(180 秒)。
  6. 导航窗口:就绪后把 WebView2 窗口指向本地地址;失败则展示错误页并附日志路径。

进程回收机制

子进程启动后立即被挂入一个 KILL_ON_JOB_CLOSE 的 Job Object:

  • 正常退出路径:调用 taskkill /F /T 终止整棵进程树;
  • 异常退出路径(强杀主进程):Job 句柄随主进程关闭,系统自动终止 Job 内所有进程。

双保险设计保证了无论哪种退出方式,都不会留下孤立的后台服务。


四、技术选型与依赖

组件 角色 版本
Go 宿主程序开发语言 1.21+(go.mod 为 1.26.4)
Wails v3 桌面窗口与 WebView2 封装 v3.0.0-beta.8
golang.org/x/sys Windows Job Object / 进程 API v0.46.0
Node.js 内置运行时(仅构建时下载) 24.16.0
DeepSeek Harness 上游 agent 运行时与 Web UI 0.1.0-rc.6
WebView2 Runtime 目标机器运行依赖 Windows 10/11 已内置

值得注意的是,Wails v3 使用纯 Go 加载器加载 WebView2,构建与运行均无需 gcc / CGO,大大降低了 Windows 下的交叉编译门槛。


五、构建与分发

构建命令

powershell 复制代码
cd dsh-desktop
.\build.ps1                             # 安装依赖 + 编译 + 打包 node 与 dsh runtime
.\build.ps1 -Version 1.2.3              # 指定版本号
.\build.ps1 -DshVersion 0.1.0-rc.6      # 指定 dsh 版本
.\build.ps1 -NodeVersion 24.16.0        # 指定内置 Node 版本
.\build.ps1 -UpdateRepo "owner/repo"    # 启用 GitHub Releases 版本检测
.\build.ps1 -SkipDsh                    # 跳过 npm 安装(.dsh-runtime 已存在时)

构建脚本依次完成四件事:

  1. 安装 dsh 依赖npm install 到本地 .dsh-runtime
  2. 编译主程序go build,通过 -ldflags 注入版本号与更新源,并使用 -H=windowsgui -s -w 生成无窗口的精简 GUI 二进制;
  3. 镜像运行时 :把 .dsh-runtime\node_modules 同步到 bin\runtime
  4. 内置 Node :下载并解压 Node Windows 压缩包,仅保留 node.exe

构建产物

复制代码
bin/
├── dsh-desktop.exe      # 主程序(约 12 MB)
└── runtime/
    ├── node/            # 内置 Node 运行时(node.exe,约 92 MB)
    └── node_modules/    # 预装的 dsh 依赖(约 250 MB)

分发方式

dsh-desktop.exeruntime/ 置于同一目录 ,打包为 zip 或安装器(总计约 350 MB)即可分发。最终用户双击 dsh-desktop.exe 即用,首次进入后在 Settings → Models 配置 DeepSeek API Key,并用 Choose workspace 选择项目目录。


六、配置与数据

端口配置(优先级从高到低)

  1. 命令行参数:dsh-desktop.exe --port 3090
  2. 环境变量:$env:DSH_PORT="3090"
  3. 默认:随机空闲端口

数据与日志

内容 路径
用户数据(profiles / sessions / plugins) %LOCALAPPDATA%\dsh-desktop\launch-root\
运行日志 %LOCALAPPDATA%\dsh-desktop\logs\dsh-desktop.log

菜单项 Harness → Restart Harness 用于重启 dsh 服务,Harness → Open Log 用记事本打开运行日志。

版本检测

  • 更新源:GitHub Releases API(https://api.github.com/repos/{owner}/{repo}/releases/latest
  • 注入方式:构建时 -UpdateRepo "owner/repo",留空则禁用
  • 检查节奏:启动后 15~30 秒首次检查,此后每 6 小时一次,另有手动检查入口
  • 版本比较:对形如 1.2.3 的语义化版本逐段比较,忽略预发布后缀

七、健壮性设计

dsh-desktop 在"把上游 CLI 变成可靠桌面产品"这件事上,有几处值得称道的工程细节:

  1. 先探测、后启动:启动前先探测目标端口是否已有服务,避免重复拉起;同时先清理可能残留的旧子进程。
  2. 回退机制 :内置运行时缺失时自动回退到 npx 在线安装,开发与分发两种形态都能工作。
  3. 优雅 + 强制双层回收taskkill 正常回收与 Job Object 兜底并存,杜绝进程泄漏。
  4. 错误可观测:启动失败会展示目标地址与日志路径,日志文件贯穿子进程整个生命周期,排错有据可查。

八、局限与展望

  • 平台限制:当前仅面向 Windows(依赖 WebView2 与 Windows Job Object)。
  • 上游不稳定 :DeepSeek Harness 处于开发者预览阶段、快速迭代,锁定版本 0.1.0-rc.6,可能出现兼容性破坏的变更。
  • 体积偏大:内置 Node 与依赖合计约 350 MB,对轻量分发场景不够友好。

未来的可演进方向包括:跨平台支持(Linux / macOS 对应 API)、增量更新与自更新能力、安装器(MSIX / NSIS)打包、以及更细粒度的端口与进程诊断面板。


九、小结

dsh-desktop 是一个小而完整的工程范例:它没有重复造轮子去实现 agent 能力,而是精准地解决了"如何把 CLI 工具交付为桌面产品"这一实际问题------离线运行时、进程托管、数据隔离、更新引导。通过 Go + Wails v3 的轻量组合,它在约 12 MB 的主程序体积内,为 DeepSeek Harness 提供了稳健、可分发、零门槛的桌面载体。

项目地址:https://github.com/ydhcxh/dsh-desktop

项目许可证:MIT(本项目);DeepSeek Harness 及其依赖遵循各自的上游许可证。

相关推荐
怕浪猫8 小时前
第1章:认识 DeepSeek Harness——一个插件化的 Agent Runtime 平台
agent·natural language toolkit·deepseek
特立独行的猫a10 小时前
一切皆插件:DeepSeek Harness 的架构哲学,以及与主流 Agent 的对比
人工智能·架构·agent·deepseek·harness
圆奋奋10 小时前
DeepSeek Harness:尝鲜
ai·deepseek
晚安code15 小时前
DeepSeek Harness 插件推荐:4 款开源神器让写代码直接起飞
deepseek
朋克洛德的码农16 小时前
Go并发-sync包四剑客:Mutex、RWMutex、WaitGroup、Once-从入门到原理
开发语言·后端·golang
圣殿骑士-Khtangc16 小时前
Go泛型编程实战从类型约束到泛型数据结构
golang
ovO17 小时前
给 DeepSeek Harness 装一个任务面板:DSH Task Board 快速上手
开源·deepseek
兮动人17 小时前
DeepSeek 把模型的“马具“开源了:拆解 Harness
gpt·deepseek·harness·dsh
FfHUCisI18 小时前
Golang - 信号量模式(Semaphore Pattern)
开发语言·后端·golang