AEicn
dsh-desktop
将 DeepSeek Harness 封装为 Windows 便携桌面应用。 WebView2 原生窗口,内置 Node 与 dsh(构建时打包、运行时零下载)。
- Stars
- 1
- Language
- C#
- Created
- Aug 14, 2026
- Updated
- Aug 14, 2026
Introduction
DeepSeek Harness 桌面版(DshDesktop)
将 DeepSeek Harness 封装为 Windows 便携桌面应用。 WebView2 原生窗口,内置 Node 与 dsh(构建时打包、运行时零下载)。
✨ 核心功能(保持精简)
| 功能 | 说明 |
|---|---|
| 免装 Node | 内置独立 Node 运行时(构建时打包到 runtime/node,目标机器零依赖) |
| 内置 dsh CLI | 完整打包 @deepseek-ai/dsh 及全部插件(runtime/dsh,离线可用) |
| 无边框窗口 | 自绘 36px 标题栏(⋯ 菜单 + 窗口控制)、标题栏颜色跟随 harness 外观设置(设置页「外观」:浅色/深色/跟随系统)、Win11 圆角、边缘拖拽调整大小(悬停显示缩放光标);⋯ 菜单与托盘菜单(含二级菜单)为 Win11 圆角 |
| 系统托盘常驻 | 关闭默认隐藏到托盘;菜单(颜色跟随主题):显示 / 检查更新 / 日志 / 退出 |
| 退出即清理 | Job Object(KILL_ON_JOB_CLOSE)托管 harness 进程树,正常/崩溃/强杀均无孤儿进程 |
| 便携版 | 数据跟随 exe(data/ 目录),拷到 U 盘即用 |
| 跟随官方更新 | 检测 dsh 新版本 → 同意后 npm overlay 安装(staging 原子切换)→ 失败自动回退内置版 |
| 文件预览 | 「文件」标签页(与 对话/轨迹 同级):VSCode 风格文件树 + 单击预览(文本/图片/HTML)+「本会话修改」次级页(按轮次分组显示本会话改过的文件) |
| 任务/权限提醒 | 会话任务跑完弹托盘通知(点击回到窗口);agent 请求权限(需审批)弹警告通知;agent 询问抉择选项(ask_user_question)时弹提醒(携带问题文本,点击回到窗口作答);子代理任务不通知、30 秒去重 |
| 余额显示 | 对话底部 dock:余额 ¥X(15 分钟刷新,查询失败自动隐藏),悬停仅提示点击跳转用量页面(不展示充值明细),点击用系统默认浏览器打开用量页面 |
🚀 快速开始
使用成品(免构建)
项目根目录 dist\ 即为便携版:
- 进入
dist\,双击DshDesktop.exe - 零下载启动(内置 Node + dsh 已打包)
- 数据生成在
dist\data\,随目录移动即"便携"
从源码构建
需要:.NET SDK 9.0+ + 网络(首次打包内置运行时)
git clone https://github.com/AEicn/dsh-desktop.git && cd dsh-desktop
# 1) 打包内置运行时(Node + dsh,构建机执行一次)
powershell -ExecutionPolicy Bypass -File scripts/fetch-runtime.ps1
# 2) 构建并生成 dist\(自动复制 runtime/assets,清理中间副本)
powershell -ExecutionPolicy Bypass -File scripts/make-dist.ps1
# 或加 -KeepCache 保留 runtime 缓存加速重建
# 3) 更新应用并保留用户数据(dist\data 重建前移出、重建后移回)
# 本地工具(不入库,见 .gitignore);克隆仓库的用户直接跑 make-dist.ps1 即可
powershell -ExecutionPolicy Bypass -File scripts/update-dist.ps1
powershell -ExecutionPolicy Bypass -File scripts/release.ps1
📁 项目文件说明(每个文件的作用)
源码(src/)
| 文件 | 作用 |
|---|---|
Program.cs | 入口:单实例互斥、全局异常捕获、启动主窗体 |
Paths.cs | 便携路径解析:exe 可写 → 数据跟随 exe;否则回退用户目录。定义 RootDir/RuntimeDir/DshHome/WebView2DataDir/LogsDir |
AppConfig.cs | 配置加载/保存(data/config.json):workspace、端口、更新开关、托盘开关 |
Log.cs | 简单日志器:追加写 logs/app.log(UTF-8 无 BOM) |
NodeRuntime.cs | 内置 Node 检查:runtime/node/node.exe 存在性 + 版本自检(构建时打包,运行时零下载) |
DshBundler.cs | 内置 dsh 管理:内置版(只读基线)+ overlay 更新(data/agent,staging 原子切换)+ 版本查询 |
HarnessLauncher.cs | 启动 dsh --profile web --port <free> 子进程(内置 node 直调 bin.js),等待 HTTP 就绪;HarnessInstance 持有进程 + Job |
ProcessJob.cs | Windows Job Object 封装(KILL_ON_JOB_CLOSE):应用退出即终止整个进程树 |
WindowChrome.cs | 无边框窗口外观骨架:Win11 圆角开关、WS_THICKFRAME 清除(白边根源)、WM_NCHITTEST 8 方向 resize 命中、菜单弹窗圆角 |
MainForm.cs | 主窗体:无边框 + 自绘标题栏(拖动/最小化/最大化/关闭/菜单)、WebView2 加载 harness、导航 origin 校验、更新检查 UI;DarkMenuRenderer 深色菜单 |
TrayIcon.cs | 系统托盘:深色菜单(显示/检查更新/日志/退出)、首次隐藏提示一次、自绘鲸鱼图标兜底 |
Updater.cs | 更新器:查询 npm registry 最新版 → 比对 → 调用 DshBundler 更新 → 结果回传 |
PluginSync.cs | 配套插件同步:把 assets/plugins/ 复制到便携 DSH_HOME 的 web profile 并全量重建 cordis.patch.yml(幂等,自动清理旧条目) |
ShellEndpoint.cs | 桌面壳事件端点:监听 127.0.0.1 随机端口,接收宿主插件(dsh-notify 提醒、dsh-appearance 外观同步)POST 的事件,弹托盘通知或切换标题栏/菜单主题 |
Security.cs | WebView2 导航 origin 校验(仅允许本地 harness origin) |
配套插件(assets/plugins/)
| 目录 | 作用 |
|---|---|
dsh-files/ | 文件树宿主插件(运行在 harness 内):注册 /api/dsh-files/list(目录列表)与 /api/dsh-files/read(读文件,类型判定)路由,仅回环可访问 |
dsh-files-client/ | 文件树客户端插件(注入 Web UI):「文件」标签页 —— VSCode 风格懒加载文件树 + 单击预览(文本/图片/HTML iframe)+「本会话修改」次级页(session.history 提取 tool/call 按轮次分组);会话 cwd 从 session.list RPC 获取 |
dsh-notify/ | 提醒宿主插件:监听 harness 事件流(apiProxy host 流:任务跑完 running→false;mux 流:approval/requested 权限请求、question/requested 抉择询问)→ POST 桌面壳事件端点(DSH_DESKTOP_SHELL_URL);子代理任务不通知、30 秒去重,权限请求逐条提醒,询问携带问题标题/正文逐条提醒 |
dsh-appearance/ | 外观同步宿主插件:切换事件(settings/updated/settings/document-updated)只作为检查信号——延迟一拍读 ui-theme 命名空间的当前值(以持久化值为唯一事实,不信任事件载荷)→ 值变化时 POST 桌面壳 {type:"appearance", preference};永久 2 秒轮询兜底自愈;桌面壳据此给标题栏与菜单配色,「跟随系统」读 Windows 应用模式 |
dsh-balance/ | 余额宿主插件:读 DSH_HOME/.credentials.yaml 的 DEEPSEEK_API_KEY → 查询官方 /user/balance → GET /api/dsh-balance/query(仅回环) |
dsh-balance-client/ | 余额客户端插件:注册 conversation.composer.dock(对话底部统计栏),显示「余额 ¥X」(15 分钟刷新,查询失败不显示),悬停仅提示点击跳转用量页面(不展示充值明细),点击跳转 usage 页面(桌面壳转系统浏览器) |
构建脚本(scripts/)
| 文件 | 作用 |
|---|---|
fetch-runtime.ps1 | 构建时打包:下载 Node 24 官方 zip → runtime/node;内置 npm 安装 @deepseek-ai/dsh → runtime/dsh;精简(删 PDB/非 win32 二进制/C++ 源码,注意保留 koffi/src 等 JS 入口) |
make-dist.ps1 | 一键构建:确保 runtime 存在 → dotnet publish → 生成根目录 dist\ → 清理 %TEMP% 构建目录(项目树零中间产物) |
update-dist.ps1 | 本地更新工具(不入库,见 .gitignore):调 make-dist.ps1 -KeepCache 重建 dist\,重建前后把 dist\data 移出/移回,更新应用不丢用户数据(会话/配置/凭据/WebView2 档案) |
release.ps1 | 本地发布工具(不入库,见 .gitignore):commit 全部改动 → fetch 集成远端(rebase/合并,本地优先)→ push → 强制打 v<版本> tag 并推送(触发 CI 构建 dist 并生成 GitHub Release);版本只读 DshDesktop.csproj 的 <Version>,可用 -Message 自定义提交信息、-Token 提供 GitHub PAT;首次运行自动初始化 git(init/remote/分支/身份) |
其他
| 文件 | 作用 |
|---|---|
DshDesktop.csproj | 项目定义:WinForms + WebView2、ApplicationIcon(exe 鲸鱼图标)、发布时复制 runtime/assets、排除非源码目录防误编译、中间产物重定向到 %TEMP% |
Directory.Build.props | MSBuild 属性(Sdk.props 前导入):NuGet restore 文件重定向到 %TEMP%,项目根零 obj/ |
app.manifest | DPI 感知、Win10/11 兼容、asInvoker |
assets/icon.ico | DeepSeek 鲸鱼图标(窗口/托盘/exe) |
docs/ARCHITECTURE.md | 架构总览 |
docs/RULES.md | 开发与构建规则(必须遵守的约定) |
.github/workflows/build.yml | CI:tag 触发 → fetch-runtime → make-dist → 打包 zip 发布 |
📁 运行期目录(dist\ 内自动生成)
dist\
DshDesktop.exe # 主程序
runtime\node\ # 内置 Node
runtime\dsh\ # 内置 dsh(离线)
assets\plugins\ # 配套插件(同步到 web profile)
data\
dsh-home\ # 便携 DSH_HOME(会话/配置/凭据)
agent\ # 更新 overlay(已更新的 dsh)
webview2\ # WebView2 用户数据
config.json # 应用配置
logs\ # 日志
⚙️ 配置(data/config.json)
{
"workspace": "C:\\Users\\you\\Projects",
"port": 0,
"useBundledDsh": true,
"startupTimeoutSeconds": 120,
"minimizeToTray": true,
"autoCheckUpdates": true
}
- 标题栏与菜单颜色跟随 harness 设置页的「外观」选项(浅色 / 深色 / 跟随系统),切换后实时生效
appearancePreference(内部缓存):最近一次 harness 外观偏好,启动即按它渲染配色(无需等待同步);由应用自动读写,无需手改
环境变量:
DSH_DESKTOP_PORTABLE=1强制便携模式DSH_DEBUG_PORT=9333WebView2 远程调试(开发用)DSH_NODE_MIRROR/NPM_CONFIG_REGISTRY构建时镜像(可选)DEEPSEEK_API_KEY/DEEPSEEK_API_BASE余额查询(默认读DSH_HOME/.credentials.yaml)
🔄 更新机制
- 启动后(或菜单「检查 dsh 更新…」)查询 npm registry 最新版
- 发现新版 → 询问是否更新
- 同意后:npm 安装到
data/agent-staging→ 校验 → 原子切换为data/agent(不碰内置包) - 失败保留当前版本;启动时 overlay 优先、内置兜底;重启生效
🛡️ 安全设计
- 导航围栏:WebView2 仅允许本地 harness origin,外部链接交给系统浏览器
- 路由围栏:插件路由仅接受回环(127.0.0.1/::1)请求
- 进程隔离:harness 进程树挂入 Job Object,退出即清理
📄 License
MIT — 见 LICENSE