🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

桌面端 yxccai/dsh-desktop

DeepSeek Harnessv1.1.3
yxccai73 天前有提交已提供中文文档扫描:含敏感能力

DeepSeek Harness 的非官方开源桌面应用,目标是提供更简单、更顺手的桌面体验。

37/100
综合分
11
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/9/18 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

自动兼容检查未通过:要求 dsh 0.1.0-rc.6,而最新版为 0.2.1-rc.5;仓库 package.json 标记 private,未发布到 npm,需从源码安装。以下命令仍可尝试,但可能失败。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add yxccai/dsh-desktop
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:需留意
是什么
dsh 原生插件 · desktop
装得上吗
静态安装检查未通过,可能需要源码安装
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 73 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装

要求 dsh 0.1.0-rc.6,而最新版为 0.2.1-rc.5;仓库 package.json 标记 private,未发布到 npm,需从源码安装

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-desktop-shell @ 1.1.3
✓Node 引擎要求 >=20 · 基线 Node 22.19 满足
✗dsh CLI 依赖要求 0.1.0-rc.6 · 最新 0.2.1-rc.5 不兼容
✓入口文件main/exports/bin 已声明

源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/2 20:48:56

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 183 处,展示前 118 条)
  • 写入本地文件
    src/main.js:73
    fs.mkdirSync(path.dirname(file), { recursive: true });
  • 写入本地文件
    src/main.js:75
    fs.writeFileSync(temp, JSON.stringify(value, null, 2), 'utf8');
  • 写入本地文件
    src/main.js:76
    fs.renameSync(temp, file);
  • 写入本地文件
    src/main.js:82
    fs.mkdirSync(app.getPath('userData'), { recursive: true });
  • 写入本地文件
    src/main.js:83
    fs.appendFileSync(userFile('dsh-desktop.log'), line);
  • 读取本地文件
    src/main.js:200
    try { const meta = JSON.parse(fs.readFileSync(file, 'utf8')); const image = userFile(path.join('themes', meta.file)); if (!fs.existsSync(image)) retur
  • 读取本地文件
    src/main.js:205
    const source = result.filePaths[0]; const stat = fs.statSync(source); if (!stat.isFile() || stat.size > 20 * 1024 * 1024) throw new Error('背景图片必须小于 20
  • 写入本地文件
    src/main.js:207
    const themes = userFile('themes'); fs.mkdirSync(themes, { recursive: true }); const name = `custom-background${ext}`; const target = path.join(themes,
  • 读取本地文件
    src/main.js:208
    return { name: path.basename(source), dataUrl: `data:${mime};base64,${fs.readFileSync(target).toString('base64')}`, opacity: 100 };
  • 读取本地文件
    src/main.js:214
    const meta = JSON.parse(fs.readFileSync(file, 'utf8'));
  • 读取本地文件
    src/main.js:218
    if (!fs.existsSync(image)) return null;
  • 读取本地文件
    src/main.js:219
    return { name: meta.name, dataUrl: `data:${meta.mime};base64,${fs.readFileSync(image).toString('base64')}`, opacity };
  • 执行外部命令来自配置/变量
    src/runtime-manager.js:88
    return spawn(comspec, ['/d', '/s', '/c', line], options);
  • 执行外部命令来自配置/变量
    src/runtime-manager.js:90
    return spawn(spec.command, spec.args, options);
  • 执行外部命令固定命令
    src/runtime-manager.js:334
    const killer = spawn('taskkill.exe', args, { windowsHide: true, stdio: 'ignore' });
  • 读取本地文件
    src/config.js:43
    const value = JSON.parse(fs.readFileSync(file, 'utf8'));
  • 写入本地文件
    src/config.js:47
    fs.mkdirSync(path.dirname(file), { recursive: true });
  • 写入本地文件
    src/config.js:48
    fs.writeFileSync(file, `${JSON.stringify(defaults, null, 2)}\n`);
  • 读取本地文件
    src/environment-detector.js:8
    try { return fs.statSync(file).isFile(); } catch { return false; }
  • 执行外部命令来自配置/变量
    src/environment-detector.js:30
    const child = spawn(spec.command, spec.args, { windowsHide: true, stdio: 'ignore' });
  • 读取本地文件
    src/pnpm-runtime.js:32
    try { return fs.statSync(file).isFile(); } catch { return false; }
  • 读取本地文件
    src/pnpm-runtime.js:71
    if (fs.readFileSync(file, 'utf8') === content) return false;
  • 写入本地文件
    src/pnpm-runtime.js:74
    fs.writeFileSync(temp, content, { encoding: 'utf8', mode });
  • 写入本地文件
    src/pnpm-runtime.js:75
    fs.renameSync(temp, file);
  • 写入本地文件
    src/pnpm-runtime.js:95
    fs.mkdirSync(dir, { recursive: true });
  • 写入本地文件
    src/pnpm-runtime.js:100
    try { fs.chmodSync(path.join(dir, 'pnpm'), 0o755); } catch { }
  • 读取本地文件
    src/plugin-manager.js:18
    const raw = fs.readFileSync(target);
  • 读取本地文件
    src/plugin-manager.js:21
    return fs.readFileSync(target);
  • 读取本地文件
    src/plugin-manager.js:25
    try { return JSON.parse(fs.readFileSync(file, 'utf8')); }
  • 读取本地文件
    src/plugin-manager.js:30
    try { return fs.statSync(target).isDirectory(); }
  • 读取本地文件
    src/plugin-manager.js:35
    try { return fs.readdirSync(root, { withFileTypes: true }); }
  • 读取本地文件
    src/plugin-manager.js:80
    const stat = fs.lstatSync(target);
  • 写入本地文件
    src/plugin-manager.js:166
    fs.mkdirSync(this.enabledRoot, { recursive: true });
  • 写入本地文件
    src/plugin-manager.js:170
    fs.writeFileSync(path.join(temp, MARKER_FILE), JSON.stringify({ owner: 'dsh-desktop', id, version: plugin.version, digest: plugin.digest, installedAt:
  • 写入本地文件
    src/plugin-manager.js:171
    fs.renameSync(temp, enabled);
  • 删除本地文件
    src/plugin-manager.js:173
    fs.rmSync(temp, { recursive: true, force: true });
  • 写入本地文件
    src/plugin-manager.js:189
    fs.mkdirSync(toRoot, { recursive: true });
  • 写入本地文件
    src/plugin-manager.js:190
    fs.renameSync(current.dir, target);
  • 写入本地文件
    src/plugin-manager.js:202
    fs.renameSync(current.dir, trash);
  • 删除本地文件
    src/plugin-manager.js:203
    fs.rmSync(trash, { recursive: true, force: true });
  • 读取本地文件
    src/web-market-manager.js:59
    try { return JSON.parse(fs.readFileSync(file, 'utf8')); }
  • 读取本地文件
    src/web-market-manager.js:64
    try { return fs.statSync(target).isDirectory(); }
  • 读取本地文件
    src/web-market-manager.js:69
    try { return fs.readdirSync(root, { withFileTypes: true }); }
  • 读取本地文件
    src/web-market-manager.js:90
    const stat = fs.lstatSync(target);
  • 读取本地文件
    src/web-market-manager.js:105
    const data = fs.readFileSync(file.target);
  • 读取本地文件
    src/web-market-manager.js:148
    valid: manifest?.name === PACKAGE_NAME && fs.existsSync(path.join(dir, 'lib', 'host.js')) && fs.existsSync(path.join(dir, 'lib', 'client.js')),
  • 写入本地文件
    src/web-market-manager.js:206
    fs.mkdirSync(this.dshHome, { recursive: true });
  • 写入本地文件
    src/web-market-manager.js:208
    fs.writeFileSync(temp, next, { encoding: 'utf8', mode: 0o600 });
  • 写入本地文件
    src/web-market-manager.js:209
    fs.renameSync(temp, this.patchPath);
  • 写入本地文件
    src/web-market-manager.js:226
    fs.mkdirSync(path.dirname(this.packageDir), { recursive: true });
  • 写入本地文件
    src/web-market-manager.js:230
    fs.writeFileSync(path.join(temp, MARKER_FILE), JSON.stringify({ owner: 'dsh-desktop', package: PACKAGE_NAME, version: manifest.version, digest, instal
  • 写入本地文件
    src/web-market-manager.js:231
    fs.renameSync(temp, this.packageDir);
  • 删除本地文件
    src/web-market-manager.js:233
    fs.rmSync(temp, { recursive: true, force: true });
  • 删除本地文件
    src/web-market-manager.js:240
    try { fs.rmSync(this.packageDir, { recursive: true, force: true }); } catch {}
  • 删除本地文件
    src/web-market-manager.js:277
    try { fs.rmSync(trash, { recursive: true, force: true }); } catch {}
  • 读取本地文件
    src/dsh-integration.js:57
    if (!fs.existsSync(file)) continue;
  • 读取本地文件
    src/dsh-integration.js:59
    hash.update(fs.readFileSync(file));
  • 读取本地文件
    src/dsh-integration.js:68
    const sourceManifest = JSON.parse(fs.readFileSync(path.join(source, 'package.json'), 'utf8'));
  • 读取本地文件
    src/dsh-integration.js:70
    const currentManifest = fs.existsSync(path.join(target, 'package.json'))
  • 读取本地文件
    src/dsh-integration.js:71
    ? JSON.parse(fs.readFileSync(path.join(target, 'package.json'), 'utf8'))
  • 读取本地文件
    src/dsh-integration.js:74
    if (fs.existsSync(target) && currentManifest?.name !== sourceManifest.name) throw new Error('DSH_HOME 中存在冲突的插件中心包');
  • 写入本地文件
    src/dsh-integration.js:75
    fs.mkdirSync(path.dirname(target), { recursive: true });
  • 写入本地文件
    src/dsh-integration.js:81
    fs.renameSync(temp, target);
  • 删除本地文件
    src/dsh-integration.js:82
    fs.rmSync(backup, { recursive: true, force: true });
  • 删除本地文件
    src/dsh-integration.js:84
    fs.rmSync(temp, { recursive: true, force: true });
  • 写入本地文件
    src/dsh-integration.js:94
    fs.mkdirSync(home, { recursive: true });
  • 写入本地文件
    src/dsh-integration.js:100
    fs.writeFileSync(temp, next, { encoding: 'utf8', mode: 0o600 });
  • 写入本地文件
    src/dsh-integration.js:101
    fs.renameSync(temp, patchPath);
  • 写入本地文件
    src/project-panel-manager.js:18
    fs.mkdirSync(path.dirname(file), { recursive: true });
  • 写入本地文件
    src/project-panel-manager.js:20
    fs.writeFileSync(temp, JSON.stringify(value, null, 2), 'utf8');
  • 写入本地文件
    src/project-panel-manager.js:21
    fs.renameSync(temp, file);
  • 读取本地文件
    src/project-panel-manager.js:41
    const data = JSON.parse(fs.readFileSync(registry, 'utf8'));
  • 读取本地文件
    src/project-panel-manager.js:53
    if (!fs.statSync(root).isDirectory()) throw new Error('工作区不是目录');
  • 读取本地文件
    src/project-panel-manager.js:64
    if (fs.existsSync(target)) {
  • 读取本地文件
    src/project-panel-manager.js:85
    if (fs.existsSync(path.join(cursor, '.git'))) {
  • 读取本地文件
    src/project-panel-manager.js:98
    for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
  • 读取本地文件
    src/project-panel-manager.js:103
    const stat = fs.statSync(target);
  • 写入本地文件
    src/project-panel-manager.js:193
    fs.writeFileSync(temp, content, { encoding: 'utf8', mode: stat.mode });
  • 写入本地文件
    src/project-panel-manager.js:194
    fs.renameSync(temp, target);
  • 删除本地文件
    src/project-panel-manager.js:265
    if (change.untracked) fs.rmSync(target, { recursive: true, force: false });
  • 写入本地文件
    src/project-panel-manager.js:289
    fs.mkdirSync(path.dirname(tempIndex), { recursive: true });
  • 删除本地文件
    src/project-panel-manager.js:306
    } finally { fs.rmSync(tempIndex, { force: true }); }
  • 删除本地文件
    src/project-panel-manager.js:342
    finally { fs.rmSync(tempRoot, { recursive: true, force: true }); }
  • 删除本地文件
    src/project-panel-manager.js:364
    if (!entry) fs.rmSync(target, { recursive: true, force: true });
  • 删除本地文件
    src/project-panel-manager.js:378
    try { await this.git(root, ['cat-file','-e',`${current.tree}:${clean}`]); fs.rmSync(target, { recursive: true, force: true }); }
  • 删除本地文件
    src/project-panel-manager.js:386
    for (const relative of Object.keys(current)) if (!target.files?.[relative]) fs.rmSync(this.resolvePath(root, relative).target, { force: true });
  • 写入本地文件
    src/ownership-marker.js:22
    fs.mkdirSync(path.dirname(file), { recursive: true });
  • 写入本地文件
    src/ownership-marker.js:24
    fs.writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
  • 写入本地文件
    src/ownership-marker.js:25
    fs.renameSync(temp, file);
  • 读取本地文件
    src/ownership-marker.js:38
    const value = JSON.parse(fs.readFileSync(file, 'utf8'));
  • 删除本地文件
    src/ownership-marker.js:51
    fs.rmSync(file, { force: true });
  • 读取本地文件
    src/ownership-marker.js:55
    for (const name of fs.readdirSync(dir)) {
  • 删除本地文件
    src/ownership-marker.js:56
    if (name.startsWith(`${base}.tmp-`)) fs.rmSync(path.join(dir, name), { force: true });
  • 执行外部命令固定命令
    src/ownership-marker.js:93
    const out = execFileSync('tasklist.exe', ['/FI', `PID eq ${pid}`, '/FO', 'CSV', '/NH'], {
  • 执行外部命令固定命令
    src/ownership-marker.js:100
    const out = execFileSync('powershell.exe',
  • 执行外部命令固定命令
    src/ownership-marker.js:108
    const out = execFileSync('ps', ['-p', String(pid), '-o', 'comm='], { encoding: 'utf8', timeout: 3000 });
  • 执行外部命令固定命令
    src/ownership-marker.js:114
    const out = execFileSync('ps', ['-p', String(pid), '-o', 'args='], { encoding: 'utf8', timeout: 3000 });
  • 发起网络请求
    resources/market-plugin/lib/client.js:32
    return fetch('/api/dsh-market', {
  • 读取本地文件
    resources/market-plugin/lib/host.js:84
    if (existsSync(probe)) shim = candidate
  • 读取本地文件
    resources/market-plugin/lib/host.js:106
    const yaml = readFileSync(join(profileDir(profile), 'node_modules', '.modules.yaml'), 'utf8')
  • 读取本地文件
    resources/market-plugin/lib/host.js:193
    try { if (existsSync(cand)) return cand } catch {}
  • 读取本地文件
    resources/market-plugin/lib/host.js:227
    if (existsSync(cand)) return { file: process.execPath, args: [cand], cwd: undefined }
  • 执行外部命令固定命令
    resources/market-plugin/lib/host.js:362
    spawn('taskkill', ['/pid', String(child.pid), '/T', '/F'], { stdio: 'ignore' })
  • 读取本地文件
    resources/market-plugin/lib/host.js:406
    if (!existsSync(yamlPath)) return []
  • 读取本地文件
    resources/market-plugin/lib/host.js:408
    const yaml = readFileSync(yamlPath, 'utf8')
  • 写入本地文件
    resources/market-plugin/lib/host.js:441
    writeFileSync(yamlPath, next)
  • 执行外部命令来自配置/变量
    resources/market-plugin/lib/host.js:614
    const child = spawn(op.inv.file, [...op.inv.args, 'plugin', '--profile', op.profile, op.kind === 'uninstall' ? 'remove' : 'add', op.target], {
  • 发起网络请求
    resources/market-plugin/lib/host.js:761
    const r = await fetch(`${base}/${owner}/${repo}/HEAD/package.json`, {
  • 写入本地文件
    resources/market-plugin/lib/host.js:800
    mkdirSync(profileDir, { recursive: true })
  • 写入本地文件
    resources/market-plugin/lib/host.js:801
    writeFileSync(join(profileDir, 'package.json'), JSON.stringify({
  • 写入本地文件
    resources/market-plugin/lib/host.js:807
    writeFileSync(join(profileDir, 'cordis.patch.yml'), '[]\n')
  • 写入本地文件
    resources/market-plugin/lib/host.js:808
    writeFileSync(join(profileDir, 'pnpm-workspace.yaml'), 'packages:\n - .\n\nnodeLinker: hoisted\nautoInstallPeers: false\n')
  • 删除本地文件递归
    resources/market-plugin/lib/host.js:831
    try { rmSync(home, { recursive: true, force: true, maxRetries: 3 }) } catch {}
  • 执行外部命令来自配置/变量
    resources/market-plugin/lib/host.js:838
    const child = spawn(exe, args, { cwd, env, shell: shell === true, stdio: ['ignore', 'pipe', 'pipe'] })
  • 写入本地文件
    resources/market-plugin/lib/host.js:885
    writeFileSync(snap, readFileSync(p, 'utf8'))
  • 发起网络请求
    resources/market-plugin/lib/host.js:1011
    const r = await fetch(REGISTRY_URL, { redirect: 'follow', signal: AbortSignal.timeout(CATALOG_FETCH_TIMEOUT_MS) })
  • 删除本地文件递归
    resources/market-plugin/lib/host.js:1080
    try { rmSync(profileDir(profile) + '/.dsh-market', { force: true, recursive: true, maxRetries: 3 }) } catch {}
  • 发起网络请求
    resources/market-plugin/lib/host.js:1478
    const res = await fetch(url, {
扫描于 2026/11/2(1 天前) · 范围 入口闭包 + 全包扫描 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis-plugin-group@deepseek-ai/dsh@deepseek-ai/dsh-invariants
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

界面预览

6 张 · 点击查看大图

功能亮点

  • 非官方桌面应用
  • 内置运行时

非官方 DeepSeek Harness 桌面应用,支持 Windows 和 macOS,内置运行时,自动复用已有 DSH 环境。

README

由 DeepSeek 最新模型翻译生成
DSH Desktop DeepSeek Harness 的非官方开源桌面应用,目标是提供更简单、更顺手的桌面体验。 亮点 官方 DSH Web 外面套一层桌面外壳 —— 功能照单全收,安全,还容易跟上新版本 DSH Desktop 不重新实现任何功能,只是把官方 DSH Web 装进原生桌面窗口: - 一键启动:不用开浏览器标签页,不用记 URL,不用敲命令行 —— 双击桌面图标,进入的就是官方 Web 那套运行时环境。 - 功能继承:官方 DSH Web 作为宿主运行时,加载它完整的 UI 和插件系统,会话、工作区数据、插件生态全部保留;内置插件里的 Web 插件市场组件,一处就能同时管理桌面版和 Web 版的插件。 - 安全隔离:渲染进程拿不到 Node.js、文件系统或任意命令的直接访问权限;Electron 沙箱、contextIsolation、仅限回环地址的导航,再加一层白名单 IPC,共同构成隔离边界。 - 升级简单:官方 DSH 发布新版本时,桌面版只需升个版本号、重新打包。原有的 DSH_HOME、会话、Agent Presets 和持久化插件原样沿用,不受影响。 底子保持精简 —— 只加几项实用的桌面增强 基础版本不堆功能,保留官方 Web 的体验,只补上桌面场景里最有用的几项: - 项目面板:在聊天旁边列出每次回复实际改动或生成的文件。
展开完整文档(README 剩余 1 节)
DSH Desktop

DeepSeek Harness 的非官方开源桌面应用,目标是提供更简单、更顺手的桌面体验。

亮点

官方 DSH Web 外面套一层桌面外壳 —— 功能照单全收,安全,还容易跟上新版本

DSH Desktop 不重新实现任何功能,只是把官方 DSH Web 装进原生桌面窗口:

- 一键启动:不用开浏览器标签页,不用记 URL,不用敲命令行 —— 双击桌面图标,进入的就是官方 Web 那套运行时环境。
- 功能继承:官方 DSH Web 作为宿主运行时,加载它完整的 UI 和插件系统,会话、工作区数据、插件生态全部保留;内置插件里的 Web 插件市场组件,一处就能同时管理桌面版和 Web 版的插件。
- 安全隔离:渲染进程拿不到 Node.js、文件系统或任意命令的直接访问权限;Electron 沙箱、contextIsolation、仅限回环地址的导航,再加一层白名单 IPC,共同构成隔离边界。
- 升级简单:官方 DSH 发布新版本时,桌面版只需升个版本号、重新打包。原有的 DSH_HOME、会话、Agent Presets 和持久化插件原样沿用,不受影响。

底子保持精简 —— 只加几项实用的桌面增强

基础版本不堆功能,保留官方 Web 的体验,只补上桌面场景里最有用的几项:

- 项目面板:在聊天旁边列出每次回复实际改动或生成的文件。多标签预览支持 Markdown、HTML、源代码、diff、CSV、PDF、图片和文本,可分屏编辑,保存时按原子写入并处理冲突。Git 工作区显示真实状态,支持按文件丢弃改动;非 Git 目录改用内容快照(不会 git init,也不会往项目里写任何东西)。每次回复都会记录一份可恢复的快照,恢复前总会先把当前状态存成恢复点。
- 内置插件(可选安装):内置插件以可选目录的形式提供,不预装。目前已上架:多套配色主题(深海蓝、极光绿紫、玫瑰晚霞粉、暖沙琥珀,每套都有浅色和深色模式)、自定义图片背景(原生文件选择器,透明度可调,随时关闭),以及轻量写作助手(起草、改写、总结、翻译)。都支持按需安装、停用和卸载。
- Windows 系统通知(可选):智能体等待审批、完成任务或出错停止时,弹出原生通知 —— 不用一直盯着页面。随时可在「设置 → 插件 → 内置插件」里开关,默认关闭。

可扩展、有创意 —— 精简底座上的完整插件生态

底座保持轻量,扩展能力直接可用:

- 内置插件持续增加:官方精选插件通过可信渠道分发,带完整性校验,安装、禁用、卸载都不会碰你的内容。
- 社区插件市场:内置一份固定且经过校验的社区市场组件。启用后,进入「设置 → 插件 → 插件市场」即可浏览 awesome-dsh-plugin.com —— 集中安装、更新、卸载社区插件,自带 pnpm,无需全局安装任何东西。安装内容进入共享的 DSH_HOME,桌面版和浏览器版都能用。
- 按需扩展:需要时从内置目录和社区目录添加能力;不需要时,基础体验保持精简。

下载

两种使用方式

| 方式 | 适合谁 | 你会得到什么 |
| 一键安装包 | 大多数用户 | 双击即可安装运行,无需环境。数据是你自己的(DSH_HOME、会话、插件),但 DSH 由桌面版自带的运行时启动 |
| 连接模式 | 装了 Node.js、想用完整官方 DSH 的用户 | 不只是数据 —— DSH Web 本身也由你用系统 Node 启动(npx 拉取最新版本);桌面版只作为外壳连接,绝不使用自带运行时 |

两种方式共用同一份数据(DSH_HOME、会话、插件),可以随时切换。开发者也可以从源码运行;npm start 使用 auto 模式,优先用系统环境。

一键安装包

从 GitHub Releases 下载:

- Windows x64 Setup
- SHA-256 校验和

macOS(测试版)

Apple Silicon(M 系列):

- macOS arm64 DMG
- macOS arm64 ZIP

Intel(x64)版本会在 runner 跑完后出现在 Releases 页面。

首次启动时,在 Finder 里按住 Control 点击应用,选择 打开。如果 macOS 仍然拦截,去 系统设置 → 隐私与安全性 → 仍要打开。签名和公证计划在后续版本中提供。

截图

感知会话的项目面板

带完整上下文文件变更的项目面板

会话快照与按文件回滚

会话快照与回滚控件

托管插件中心

DSH Desktop Plugin Center

社区插件市场

内置的社区市场组件挂载到共享的 DSH_HOME 之后,会管理桌面端和 Web 端已经装好的插件,还能随时浏览并安装新加入的社区插件:

Community plugin market - plugin management

Community plugin market - plugin install

工作原理

DSH Desktop 会自动挑一个可用的运行时:

1. 连接已有的 DSH 服务。
2. 复用全局安装的 dsh。
3. 复用系统的 npx/npm 环境。
4. 回退到内置的 DSH 运行时。

老用户通常可以继续用原来的 DSH_HOME、会话、Agent Presets 和持久化插件。新用户不用先装 DSH 或 Node.js。

项目面板

对话真正改动或生成的文件,会显示在对应回复的下方。点一下,就在右侧多标签预览里打开,带文件名和关闭按钮;聊天布局会收窄,而不是被盖住。预览支持 Markdown、HTML、源代码、diff、CSV、PDF、图片和文本,还支持分屏编辑,保存是原子的,并且能识别冲突。Git 工作区会给出真实的状态 / diff / 丢弃操作;普通的非 Git 目录则改用 DSH Desktop 的内容 blob 快照,不会执行 git init,也不会往项目里写元数据。两种模式都保留回合改动、全上下文 diff、单文件撤销和可恢复快照;恢复快照前,会先把当前状态记成一个恢复点。

Office 文件目前调用系统应用打开;原生 DOCX/XLSX/PPTX 渲染还在计划中。

Web 插件市场(可选)

DSH Desktop 内置了社区插件市场 @sanqi-normal/dsh-webui-market-plugin(MIT,源码放在 resources/market-plugin),按需挂载到共享的 DSH_HOME,于是 DSH Web GUI 多出一个 Settings → Plugins → Plugin Market 标签页,可以浏览 awesome-dsh-plugin.com,把社区插件装进 web 配置档或从中卸载。

- 两个地方都能管:桌面端 Plugin Center 窗口(Web 插件市场标签页)和 Web 设置桥(GUI 里的内置插件标签卡片)。安装、启用、禁用、卸载都是对 $DSH_HOME/cordis.patch.yml 加 $DSH_HOME/node_modules/@sanqi-normal/dsh-webui-market-plugin 的互斥事务操作;$DSH_HOME/profiles/web 永远不会被改动。
- 没有全局 pnpm 也能用:应用自带 pnpm,并提供一个 PATH shim(src/pnpm-runtime.js),所有 DSH 进程都会继承它。所以即使在 Windows 上从没装过全局 pnpm,网页内安装也能成功(npx/全局/自带启动都一样)。从终端手动启动的 DSH 服务,也会回退到 $DSH_HOME/bin 下的自带 pnpm。如果 pnpm 确实不可用,市场会给出清楚的提示,而不是甩一堆控制台乱码。
- 常见安装问题自动处理:允许 git 托管的构建脚本(dangerously-allow-all-builds);迁移过的配置档复用其记录的 pnpm store(npm_config_store_dir);lockfile 缺少 tarball 完整性信息时(旧版 pnpm 写下的裸 GitHub archive URL),改写成 github: 语法并重试一次。
- 第三方警告:市场插件由社区作者编写,运行在 DSH 进程内,拥有宿主权限(文件、网络、命令)。只安装你信任的来源。桌面应用自带一份固定、经过摘要校验的副本,拒绝操作外来或被篡改的内容,但对第三方插件的行为不承担责任。
- 需要重启:改动在 DSH web 服务重启后生效;正在进行的会话不会中断。
- 来源与复现:resources/market-plugin/VENDORED.md —— Host 部分是上游代码加上两个 DSH Desktop 可移植性补丁(自带 pnpm 的 PATH shim 和人类可读的 pnpm 错误信息),Client 部分是 MIT 协议下重新设计的原创 UI。

插件兼容性

基于官方 DSH/Cordis 服务、Host 工具、UI 插槽、主题和持久化 Presets 构建的插件,兼容性通常最好。依赖固定 DOM 结构、浏览器扩展或开发 HMR 的插件,需要单独测试。

连接模式(用完整的官方 DSH Web,而不只是你的数据)

一键安装器会保留你的数据(DSH_HOME、会话、插件),但 DSH 进程由桌面自带的固定版本(rc.6)以 Electron-as-Node 启动。连接模式更进一步:DSH Web 本身也由你用自己系统的 Node 启动(npx 拉取最新官方版本),桌面只作为外壳连接 —— 数据、进程、版本全由你掌控。

DSH Desktop 只是一个外壳 —— 默认按 auto 顺序挑选运行时(连接已有服务 → 全局 dsh → 系统 npx → 自带运行时)。如果你想让 DSH Web 由自己系统的 Node 启动,就用连接模式:

完整步骤

1. 安装 Node.js(连接模式的前提):从 nodejs.org 下载 LTS 版本并安装。

2. 安装桌面应用(二选一):
- 下载一键安装器并安装;
- 或者从源码运行(开发者)。

3. 用系统 Node 启动 DSH Web(在终端里操作;保持窗口打开):

npx @deepseek-ai/dsh web --host 127.0.0.1 --port 3080

4. 把桌面端配置成只连接、不启动服务: json { "launchMode": "connect", "url": "http://127.0.0.1:3080" }

配置文件位于 <userData>\config.json(Windows 默认路径:C:\Users\<username>\AppData\Roaming\dsh-desktop-shell\config.json;如果设置了 DSH_DESKTOP_HOME,则以它指向的位置为准)。

5. 启动桌面端 —— 它会检测 3080 端口上的 DSH 服务并直接连接,跳过内置运行时。

这样你的 DSH 就跑在真正的 Node 上(process.execPath 是 node.exe),桌面端只负责提供窗口、项目面板、内置插件和系统通知 —— 不再有 Electron-as-Node 内置运行时,上游一发布更新你就能拿到。

注意:在 connect 模式下,DSH Web 由你的终端启动和管理 —— 关掉终端服务就停了;桌面端不会替你启动它。

从源码运行 powershell
git clone https://github.com/yxccai/dsh-desktop.git
cd dsh-desktop
npm ci
npm run check
npm test
npm start

npm start 默认使用 auto 模式:优先用你系统里的 dsh 或 npx,所以同样不会强制使用内置运行时。

平台支持

- Windows x64:提供 Setup 构建,已测试。
- macOS Intel / Apple Silicon:未签名的测试构建以预发布形式发布。首次启动时,在 Finder 里按住 Control 点击应用,选择 打开;如果 macOS 仍然拦截,去 系统设置 → 隐私与安全性 → 仍要打开。签名与公证已在计划中。
- 后续版本可能会考虑支持更多平台。

开发 powershell
npm ci
npm run check
npm test
npm start

更多信息:

- 更新日志
- 安全
- 贡献指南
- 运行时准备

许可

本项目采用 MIT 许可证。DeepSeek Harness、Electron 及其他依赖仍遵循各自的许可证。
预览分享卡片长什么样 ↓yxccai/dsh-desktop
分享 · DeepSeek Harness Hub
yxccai/dsh-desktop

DeepSeek Harness 的非官方开源桌面应用,目标是提供更简单、更顺手的桌面体验。

回到顶部