非官方 DeepSeek Harness Linux 桌面适配:复用固定官方 Electron、内核与 WebUI…
数据截至 2026/10/29 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
git clone https://github.com/TommyFang2077/dsh-linux-desktop.git状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
安装兼容性检查需源码安装仓库根目录及浅层子目录均无 dsh 插件清单,无法用 dsh 插件安装命令安装
展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/30 00:35:56
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
查看扫描证据(共 189 处,展示前 98 条)
- 读取本地文件electron-builder.config.mjs:6
const desktop = JSON.parse(readFileSync(join(root, 'desktop.json'), 'utf8')) - 读取本地文件electron-builder.config.mjs:7
const officialVersion = JSON.parse(readFileSync(join(root, 'upstream.json'), 'utf8')).version - 读取本地文件electron-builder.config.mjs:19
const stat = lstatSync(path) - 写入本地文件electron-builder.config.mjs:20
chmodSync(path, ((stat.mode & 0o777) | (stat.isDirectory() ? 0o700 : 0o600)) & ~0o022) - 读取本地文件electron-builder.config.mjs:21
if (lstatSync(path).mode & 0o022) throw new Error('Packaging requires a filesystem with Unix permissions') - 读取本地文件electron-builder.config.mjs:28
if (!lstatSync(directory).isDirectory() || lstatSync(directory).isSymbolicLink()) { - 读取本地文件electron-builder.config.mjs:36
for (const entry of readdirSync(pending.pop(), { withFileTypes: true })) { - 写入本地文件electron-builder.config.mjs:64
writeFileSync(file, bytes, { flag: 'wx', mode }) - 写入本地文件electron-builder.config.mjs:65
chmodSync(file, mode) - 写入本地文件electron-builder.config.mjs:67
for (const path of aliases) renameSync(join(temporary, relative(bin, path)), path) - 删除本地文件递归electron-builder.config.mjs:68
} finally { rmSync(temporary, { recursive: true, force: true }) } - 删除本地文件electron-builder.config.mjs:90
rmSync(join(context.appOutDir, 'resources/default_app.asar'), { force: true }) - 读取本地文件updates/core.mjs:76
const metadata = JSON.parse(await fs.readFile(join(root, 'runtime/primary-runtime/runtime.json'), 'utf8')) - 读取本地文件updates/core.mjs:88
for await (const chunk of createReadStream(path)) digest.update(chunk) - 读取本地文件updates/core.mjs:104
const directoryStat = await fs.lstat(directory) - 读取本地文件updates/core.mjs:106
for (const name of (await fs.readdir(directory)).sort()) { - 读取本地文件updates/core.mjs:110
const stat = await fs.lstat(absolute) - 读取本地文件updates/core.mjs:125
const stat = await fs.lstat(manifestPath) - 写入本地文件updates/core.mjs:197
await fs.rename(temporary, path) - 删除本地文件updates/core.mjs:200
} finally { await fs.rm(temporary, { force: true }) } - 写入本地文件updates/core.mjs:217
await fs.mkdir(this.root, { recursive: true, mode: 0o700 }) - 写入本地文件updates/core.mjs:219
await fs.chmod(this.root, 0o700) - 发起网络请求updates/core.mjs:332
const response = await this.fetch(address.href, { redirect: manual ? 'manual' : 'error', signal }) - 写入本地文件updates/core.mjs:457
await fs.mkdir(directory, { recursive: true, mode: 0o700 }) - 写入本地文件updates/core.mjs:466
await fs.rename(temporary, path) - 删除本地文件updates/core.mjs:467
} finally { await fs.rm(temporary, { force: true }) } - 删除本地文件updates/core.mjs:472
await fs.rm(join(directory, name), { force: true }) - 写入本地文件updates/core.mjs:488
await fs.copyFile(cached, destination) - 删除本地文件updates/core.mjs:532
} finally { await fs.rm(staging, { recursive: true, force: true }) } - 删除本地文件updates/core.mjs:555
} finally { await fs.rm(directory, { recursive: true, force: true }) } - 删除本地文件updates/core.mjs:574
} finally { await fs.rm(directory, { recursive: true, force: true }) } - 写入剪贴板updates/desktop.mjs:31
try { clipboard.writeText(details); feedback = '报告已复制。' } - 读取本地文件updates/desktop.mjs:42
const metadata = JSON.parse(await fs.readFile(join(resources, 'desktop.json'), 'utf8')) - 读取本地文件updates/desktop.mjs:43
const config = JSON.parse(await fs.readFile(process.env.DSH_WORKBENCH_UPDATE_CONFIG ?? join(resources, 'updates.json'), 'utf8')) - 读取本地文件updates/desktop.mjs:44
const descriptor = JSON.parse(await fs.readFile(join(options.bundled.dsh, 'desktop-runtime.json'), 'utf8')) - 读取本地文件updates/desktop.mjs:45
const primaryRuntime = JSON.parse(await fs.readFile(join(options.bundled.runtime, 'primary-runtime/runtime.json'), 'utf8')) - 执行外部命令来自配置/变量updates/market.mjs:11
const child = spawn(node, ['--expose-internals', cli, kernel.dsh, kernel.runtime, - 读取本地文件updates/market.mjs:48
try { await fs.access(marker); return 'already-provisioned' } catch (error) { if (error.code !== 'ENOENT') throw error } - 读取本地文件updates/market.mjs:50
const manifest = JSON.parse(await fs.readFile(join(profile, 'package.json'), 'utf8')) - 写入本地文件updates/market.mjs:53
await fs.mkdir(data, { recursive: true, mode: 0o700 }) - 写入本地文件updates/market.mjs:54
await fs.writeFile(marker, JSON.stringify({ name: MARKET_PACKAGE, managedByUser: true }) + '\n', { flag: 'wx', mode: 0o600 }) - 读取本地文件updates/market.mjs:58
const installed = JSON.parse(await fs.readFile(join(profile, 'node_modules/dshmarket/package.json'), 'utf8')) - 写入本地文件updates/market.mjs:60
await fs.mkdir(data, { recursive: true, mode: 0o700 }) - 写入本地文件updates/market.mjs:61
await fs.writeFile(marker, JSON.stringify({ name: installed.name, version: installed.version }) + '\n', { flag: 'wx', mode: 0o600 }) - 读取本地文件updates/migration-processes.mjs:10
const contents = await fs.readFile(`/proc/${pid}/stat`, 'utf8') - 读取本地文件updates/migration-processes.mjs:13
bootId: (await fs.readFile('/proc/sys/kernel/random/boot_id', 'utf8')).trim() } - 读取本地文件updates/migration-processes.mjs:25
for (const name of await fs.readdir('/proc')) { - 读取本地文件updates/migration-processes.mjs:28
if ((await fs.stat(`/proc/${name}`)).uid !== process.getuid()) continue - 读取本地文件updates/migration-processes.mjs:46
return (await fs.readFile(`/proc/${pid}/cmdline`, 'utf8')).split('\0').filter(Boolean) - 读取本地文件updates/migration-processes.mjs:50
const environment = (await fs.readFile(`/proc/${pid}/environ`, 'utf8')).split('\0') - 执行外部命令来自配置/变量updates/migration-processes.mjs:179
const child = spawn(python, ['-I', '-c', signalProcesses], { stdio: ['pipe', 'pipe', 'pipe'] }) - 执行外部命令来自配置/变量updates/migration-progress-launch.mjs:13
child = spawn(process.execPath, ['--dsh-migration-progress', transactionPath], { - 读取本地文件updates/migration-progress.mjs:12
const stat = readFileSync(`/proc/${identity.pid}/stat`, 'utf8') - 读取本地文件updates/migration-progress.mjs:15
&& readFileSync('/proc/sys/kernel/random/boot_id', 'utf8').trim() === identity.bootId - 写入本地文件updates/migration-progress.mjs:23
mkdirSync(profile, { recursive: true, mode: 0o700 }) - 读取本地文件updates/migration-progress.mjs:24
const info = lstatSync(profile) - 写入剪贴板updates/migration-progress.mjs:76
try { clipboard.writeText(`${details}\n${feedback}`); feedback = '报告已复制。' } - 写入剪贴板updates/migration-progress.mjs:114
clipboard.writeText(migrationReportText(report, context.reportPath)) - 读取本地文件updates/migration-report.mjs:19
const info = lstatSync(path) - 读取本地文件updates/migration-report.mjs:22
return JSON.parse(readFileSync(path, 'utf8')) - 读取本地文件updates/migration-report.mjs:37
const info = lstatSync(directory) - 读取本地文件updates/user-install.mjs:17
try { await fs.lstat(path); return true } catch (error) { if (error.code === 'ENOENT') return false; throw error } - 读取本地文件updates/user-install.mjs:21
const stat = await fs.lstat(path) - 读取本地文件updates/user-install.mjs:31
const state = JSON.parse(await fs.readFile(path, 'utf8')) - 读取本地文件updates/user-install.mjs:39
if (await exists(path) && !(await fs.lstat(path)).isSymbolicLink()) fail('安装入口已被其他文件占用') - 删除本地文件updates/user-install.mjs:40
if (value === null) { await fs.rm(path, { force: true }); return } - 写入本地文件updates/user-install.mjs:44
await fs.rename(temporary, path) - 删除本地文件updates/user-install.mjs:45
} finally { await fs.rm(temporary, { force: true }) } - 读取本地文件updates/user-install.mjs:56
home = await fs.realpath(home) - 读取本地文件updates/user-install.mjs:60
const real = await fs.realpath(parent) - 写入本地文件updates/user-install.mjs:65
} else await fs.mkdir(root, { recursive: true, mode: 0o700 }) - 写入本地文件updates/user-install.mjs:73
try { await fs.mkdir(lock, { mode: 0o700 }) } catch (error) { - 删除本地文件updates/user-install.mjs:77
try { return await action() } finally { await fs.rmdir(lock) } - 写入本地文件updates/user-install.mjs:171
await fs.mkdir(join(root, 'versions'), { recursive: true, mode: 0o700 }) - 写入本地文件updates/user-install.mjs:178
} else await fs.rename(tree, destination) - 写入本地文件updates/user-install.mjs:186
await fs.mkdir(dirname(path), { recursive: true, mode: 0o755 }) - 删除本地文件updates/user-install.mjs:194
await fs.rm(journal) - 删除本地文件updates/user-install.mjs:199
for (const path of created) await fs.rm(path) - 删除本地文件updates/user-install.mjs:200
await fs.rm(journal, { force: true }) - 写入本地文件updates/whole-desktop.mjs:16
await fs.mkdir(path, { recursive: true, mode: 0o700 }) - 读取本地文件updates/whole-desktop.mjs:17
const info = await fs.lstat(path) - 读取本地文件updates/whole-desktop.mjs:28
const actual = await fs.realpath(parent) - 读取本地文件updates/whole-desktop.mjs:39
const info = await fs.lstat(request.archive) - 读取本地文件updates/whole-desktop.mjs:76
try { owner = JSON.parse(await fs.readFile(join(path, 'owner.json'), 'utf8')) } - 删除本地文件updates/whole-desktop.mjs:79
await fs.rm(path, { recursive: true }) - 读取本地文件updates/whole-desktop.mjs:92
try { await fs.lstat(gate) } - 读取本地文件updates/whole-desktop.mjs:94
owner = JSON.parse(await fs.readFile(join(gate, 'owner.json'), 'utf8')) - 写入本地文件updates/whole-desktop.mjs:165
await fs.mkdir(parent, { recursive: true, mode: 0o700 }) - 写入本地文件updates/whole-desktop.mjs:171
try { await fs.mkdir(gate, { mode: 0o700 }) } catch (error) { - 写入本地文件updates/whole-desktop.mjs:185
await fs.copyFile(request.archive, transaction.archive, constants.COPYFILE_EXCL) - 写入本地文件updates/whole-desktop.mjs:186
await fs.chmod(transaction.archive, 0o600) - 写入本地文件updates/whole-desktop.mjs:209
await fs.writeFile(transaction.workerPath, worker, { flag: 'wx', mode: 0o600 }) - 删除本地文件updates/whole-desktop.mjs:226
} finally { await fs.rm(candidate, { recursive: true, force: true }) } - 执行外部命令来自配置/变量updates/whole-desktop.mjs:259
child = spawn(transaction.node, [transaction.workerPath, 'run', join(transaction.directory, 'transaction.json')], { - 删除本地文件updates/whole-desktop.mjs:401
} finally { await fs.rm(staging, { recursive: true, force: true }) } - 删除本地文件updates/whole-desktop.mjs:459
await fs.rm(join(transaction.directory, 'backup.staging'), { recursive: true, force: true }) - 执行外部命令来自配置/变量updates/whole-desktop.mjs:481
const child = spawn(result.executable, [], { detached: true, env, stdio: ['ignore', log.fd, log.fd] }) - 执行外部命令来自配置/变量updates/whole-desktop.mjs:545
const child = spawn(transaction.oldExecutable, [], { detached: true, stdio: 'ignore', env: { ...process.env,
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
功能亮点
- Tauri2原生桌面窗口
- 复用官方WebUI
- 内置离线语音输入
展开完整介绍(127 字)↓
官方 DeepSeek Harness(dsh)原生桌面壳:Tauri 2 把官方 WebUI 变成原生桌面窗口,保留会话/工作区/插件/技能。内置 SenseVoice 离线语音输入、插件市场、视觉模型配置;锚定模式解决 DeepSeek 思考模式问题
README
由 DeepSeek 最新模型翻译生成展开完整文档(README 剩余 1 节)
dsh-linux-desktop 基于 DeepSeek Harness 官方源码构建的非官方 Linux 桌面版,支持 Linux x86_64。 程序统一安装到 ~/Applications/dsh-linux-desktop/,不安装到 /opt。升级保留现有配置、会话和插件。 安装 从 Releases 下载适合系统的软件包: - Ubuntu / Debian:下载 .deb。 - Fedora / RHEL:下载 .rpm。 在下载目录打开终端,执行对应命令(文件名换成你下载的版本): Ubuntu / Debian sudo apt install ./dsh-workbench-0.2.1-alpha.2-r1-linux-x64.deb Fedora / RHEL sudo dnf install ./dsh-workbench-0.2.1-alpha.2-r1-linux-x64.rpm 安装后,从应用菜单打开 dsh-workbench。首次启动会自动将程序安装到 HOME,请稍等;不需要手动解压,也不需要另外安装 Node.js 或 Python。 只有安装软件包需要系统授权,打开应用和应用内更新不需要。不要使用 sudo 启动应用。 从旧版升级前,请先通过托盘的「退出」完全关闭应用。 更新 打开 设置 → 通用 → 软件更新,检查并更新桌面或内核。两者独立更新,下载后会校验签名和兼容性。 内核优先跟随官方正式版和预发布 Release。项目自动应用 Linux 适配、构建、测试,并用已发布的旧壳实际升级到新内核;通过后签名发布,失败则保留候选和失败证据供适配。客户端启动完成后及每 6 小时检查、下载内核,安装必须由用户确认;忙或任务状态未知时暂缓,不强停任务。 新的跟随更新器保留原壳,每次安装新内核前,在旧程序完全退出后备份实际配置、会话及程序状态;备份校验通过后才激活。新内核启动失败时保留备份并阻止旧内核读取新数据,恢复须使用对应的整组快照。alpha.5 r2 用户需要先安装一次 alpha.5 r3 更新器过渡包,后续以内核独立升级为主。实现与发布验收状态见 自动更新策略与维护;源码变更不会直接替换本机安装。 alpha.1 → alpha.2 是跨数据兼容边界升级:在「软件更新」里点「检查更新」再点「更新」即可。alpha.1 用户会先更新到 alpha.1 r9 过渡包,再次更新时升级到 alpha.2 r3;旧应用完全退出、自动数据备份验证成功后才切换。仅更新 deb/rpm 引导包不会替换已有 HOME 版本;回退到 alpha.1 必须同时恢复旧格式数据。支持范围与恢复说明见 过渡升级与恢复。 托盘菜单提供「打开」「重启」「退出」。重启或退出前,如果有正在执行的任务,应用会请求确认。 常见问题 程序和数据放在哪里? - 程序:~/Applications/dsh-linux-desktop/ - 配置、会话、插件和相关状态:实际 DSH_HOME,默认 ~/.dsh/,桌面 profile 位于其 profiles/desktop/。 - 桌面设置:实际 Electron userData,默认 ~/.config/dsh-workbench/;XDG 配置可能改变该路径。 不要通过删除数据目录来升级或迁移程序。 GNOME 下没有托盘图标? 安装并启用适配当前 GNOME 版本的 AppIndicator and KStatusNotifierItem Support 扩展,只保留一个同类托盘扩展。优先使用 v66 或包含其修复的发行版版本;更换扩展后注销并重新登录,再启动应用。 应用不会自动修改系统扩展或关闭 Chromium 沙箱。 不使用 deb/rpm:手动安装 tar.gz 仅使用本仓库可信发行版或自己构建的归档。先核对下载来源和校验值,再执行其中的程序;未知来源的归档不能靠自己附带的校验值证明可信。 以下示例使用 r3 归档,在仓库根目录运行,归档位于 dist/: (cd dist && sha256sum --check SHA256SUMS) archive="$PWD/dist/dsh-workbench-0.2.1-alpha.2-r3-linux-x64.tar.gz" checksum=$(sha256sum "$archive" | cut -d ' ' -f 1) temporary=$(mktemp -d) tar -xzf "$archive" -C "$temporary" "$temporary/app/resources/runtime/primary-runtime/dependencies/node/bin/node" \ "$temporary/app/resources/app/workbench/install-user.mjs" \ --archive "$archive" --sha256 "$checksum" --version 0.2.1-alpha.2 ~/.local/bin/dsh-workbench 安装完成后可删除临时解压目录。此方式同样安装到 HOME,之后从应用菜单启动和更新。 从源码构建 需要 Node.js 24+、npm、Git、Python 3.11+、C/C++ 编译器、make、tar、dpkg-deb 和 rpmbuild。首次构建需要联网和数 GB 空间。 make prepare make test make package 产物位于 dist/:deb、rpm、tar.gz、SHA256SUMS 和 build-info.json。 make verify # 检查打包产物 make run # 临时运行,不安装 make gui-smoke # 使用隔离 HOME 验证窗口、图片和沙箱 上游版本固定在 upstream.json,Linux 构建号在 desktop.json,上游适配补丁在 patches/linux.patch。安装机制、签名发布和回滚详见 更新说明。 许可证 本项目构建脚本和 Linux 补丁采用 MIT,官方代码及捆绑依赖保留原许可证。DeepSeek 名称与图案归各自权利人所有,本项目并非 DeepSeek 官方发行版。
预览分享卡片长什么样 ↓
非官方 DeepSeek Harness Linux 桌面适配:复用固定官方 Electron、内核与 WebUI…
相关插件 · 市场 / 管理
这是你的仓库?认领条目