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

桌面客户端 chermye/dsh-desktop

DeepSeek Harnessv0.2.0
chermye67 天前有提交已提供中文文档扫描:高危

在 Windows 上免装 Node 直接运行 DSH 内核

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

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

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

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:需留意
是什么
dsh 原生插件 · security
装得上吗
实装验证未通过(dep_tree_unresolvable),装前请到仓库确认最近更新与 issue
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 67 天前
档位由哪些信号合成

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

验证判据与等级说明 →
⚠︎ 实装验证未通过(dep_tree_unresolvable · 2026/9/27) ——可能是验证环境差异,装前建议到 GitHub 仓库确认最近更新与 issue。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装

仓库 package.json 标记 private,未发布到 npm,需从源码安装

展开查看检查明细 ↓

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

✓npm 包dsh-desktop @ 0.2.0
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖要求 ^0.1.1-rc.2 · 最新 0.2.1-rc.2 兼容
✓入口文件main/exports/bin 已声明

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

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/20 04:29:50

🔴 安全扫描发现高危能力自动 · 静态分析源码
v2.7.0
🔴 扫描:高危尚无人工评估

静态扫描发现安装期即执行代码,或命令参数由运行时变量拼接(参数来源无法静态判定,从严处理)。

安装时即执行脚本命令参数由运行时变量拼接读写/删除本地文件发起外部网络请求
查看扫描证据(共 94 处,展示前 94 条)
  • 读取本地文件
    scripts/patch-picker-worker.mjs:61
    const source = readFileSync(target, "utf8");
  • 写入本地文件
    scripts/patch-picker-worker.mjs:85
    writeFileSync(target, patched);
  • 读取本地文件
    scripts/patch-storage-json.mjs:251
    const source = readFileSync(target, "utf8");
  • 写入本地文件
    scripts/patch-storage-json.mjs:278
    writeFileSync(target, patched);
  • 发起网络请求
    packages/carrier/lib/index.js:65
    sharedFetch = conn !== void 0 ? conn.createSharedFetchHandler("/api", { fetch: (req) => apiFetch.fetch(req) }) : apiFetch;
  • 读取本地文件
    packages/carrier/lib/index.js:117
    await send({ t: "bundle:ok", rid: frame.rid, source: readFileSync(path, "utf8") });
  • 发起网络请求
    packages/carrier/lib/index.js:158
    const resp = await apiFetch.fetch(new Request(`${LOOPBACK_BASE}/api/session.export?sessionId=${encodeURIComponent(sessionId)}`, { method: "GET" }));
  • 发起网络请求
    packages/carrier/lib/index.js:177
    const resp = await sharedFetch.fetch(new Request(`${LOOPBACK_BASE}/api/respond`, {
  • 发起网络请求
    packages/carrier/lib/index.js:186
    const resp = await sharedFetch.fetch(new Request(`${LOOPBACK_BASE}/api/${method}`, {
  • 读取本地文件
    scripts/build-installer.mjs:35
    if (existsSync(sourceZip)) {
  • 读取本地文件
    scripts/build-installer.mjs:43
    if (!existsSync(targetZip)) {
  • 写入本地文件
    scripts/build-installer.mjs:44
    mkdirSync(targetDir, { recursive: true });
  • 写入本地文件
    scripts/build-installer.mjs:45
    copyFileSync(sourceZip, targetZip);
  • 执行外部命令来自配置/变量
    scripts/build-installer.mjs:50
    const child = spawn(process.execPath, [cli, ...process.argv.slice(2)], {
  • 写入本地文件
    scripts/copy-static.mjs:25
    mkdirSync(join(dist, "preload"), { recursive: true });
  • 写入本地文件
    scripts/copy-static.mjs:26
    mkdirSync(join(dist, "transport"), { recursive: true });
  • 写入本地文件
    scripts/copy-static.mjs:27
    mkdirSync(join(dist, "assets"), { recursive: true });
  • 写入本地文件
    scripts/copy-static.mjs:29
    copyFileSync(join(root, "src", "preload", "preload.cjs"), join(dist, "preload", "preload.cjs"));
  • 写入本地文件
    scripts/copy-static.mjs:30
    copyFileSync(join(root, "src", "transport", "transport.js"), join(dist, "transport", "transport.js"));
  • 删除本地文件
    scripts/copy-static.mjs:36
    rmSync(join(dist, "assets", "icon.png"), { force: true });
  • 删除本地文件
    scripts/copy-static.mjs:37
    rmSync(join(dist, "assets", "tray.png"), { force: true });
  • 读取本地文件
    scripts/copy-static.mjs:39
    const officialSvg = readFileSync(require.resolve("@deepseek-ai/dsh-web-frontend/dist/favicon.svg"), "utf8");
  • 读取本地文件
    scripts/copy-static.mjs:62
    const pngBytes = readFileSync(join(dist, "assets", "icon-light.png"));
  • 写入本地文件
    scripts/copy-static.mjs:76
    writeFileSync(join(dist, "assets", "icon.ico"), Buffer.concat([icondir, entry, pngBytes]));
  • 执行外部命令来自配置/变量
    scripts/e2e-recovery.mjs:42
    const child = spawn(exeToRun, [...baseArgs, `--remote-debugging-port=${PORT}`], {
  • 发起网络请求
    scripts/e2e-recovery.mjs:60
    const resp = await fetch(`http://127.0.0.1:${PORT}/json/list`);
  • 执行外部命令固定命令
    scripts/e2e-recovery.mjs:136
    const killer = spawn("taskkill", ["/PID", String(child.pid), "/T", "/F"], { stdio: "ignore" });
  • 删除本地文件递归
    scripts/e2e-recovery.mjs:192
    rmSync(home, { recursive: true, force: true });
  • 删除本地文件递归
    scripts/e2e-recovery.mjs:193
    rmSync(userData, { recursive: true, force: true });
  • 执行外部命令来自配置/变量
    scripts/e2e-smoke.mjs:36
    const child = spawn(exeToRun, args, {
  • 发起网络请求
    scripts/e2e-smoke.mjs:58
    const resp = await fetch(`http://127.0.0.1:${PORT}/json/list`);
  • 写入本地文件
    scripts/e2e-smoke.mjs:283
    writeFileSync(shotPath, Buffer.from(shot.data, "base64"));
  • 执行外部命令固定命令
    scripts/e2e-smoke.mjs:300
    const killer = spawn("taskkill", ["/PID", String(child.pid), "/T", "/F"], { stdio: "ignore" });
  • 删除本地文件递归
    scripts/e2e-smoke.mjs:310
    if (home !== null) rmSync(home, { recursive: true, force: true });
  • 删除本地文件递归
    scripts/e2e-smoke.mjs:311
    rmSync(userData, { recursive: true, force: true });
  • 读取本地文件
    scripts/repair-session-logs.mjs:30
    if (!existsSync(root)) {
  • 读取本地文件
    scripts/repair-session-logs.mjs:36
    if (!existsSync(corePath)) {
  • 执行外部命令来自配置/变量
    scripts/repro-kernel-spawn.mjs:9
    const child = spawn(exe, [bin, "--profile", "desktop"], {
  • 写入本地文件
    scripts/repro-real-home.mjs:16
    writeFileSync(stderrPath, "");
  • 写入本地文件
    scripts/repro-real-home.mjs:17
    writeFileSync(stdoutPath, "");
  • 写入本地文件
    scripts/repro-real-home.mjs:18
    const stderrLine = (line) => appendFileSync(stderrPath, `[stderr] ${line}\n`);
  • 写入本地文件
    scripts/repro-real-home.mjs:21
    appendFileSync(stdoutPath, line + "\n");
  • 执行外部命令来自配置/变量
    scripts/repro-real-home.mjs:28
    const child = spawn(exe, [bin, "--profile", "desktop"], {
  • 写入本地文件
    scripts/repro-real-home.mjs:50
    child.stdout.on("data", (chunk) => appendFileSync(stdoutPath, `[stdout] ${chunk.toString("hex")}`));
  • 读取本地文件
    scripts/scan-all-session-logs.mjs:12
    for (const entry of readdirSync(dir, { withFileTypes: true })) {
  • 读取本地文件
    scripts/scan-all-session-logs.mjs:47
    const raw = readFileSync(file);
  • 读取本地文件
    scripts/scan-all-session-logs.mjs:79
    const kb = (statSync(file).size / 1024).toFixed(0);
  • 写入本地文件
    scripts/setup-profile.mjs:32
    mkdirSync(profileDir, { recursive: true });
  • 读取本地文件
    scripts/setup-profile.mjs:35
    if (!existsSync(manifestPath)) {
  • 写入本地文件
    scripts/setup-profile.mjs:36
    writeFileSync(manifestPath, JSON.stringify({ dsh: { profile: { bundles: DESKTOP_BUNDLES } } }, null, 2) + "\n");
  • 写入本地文件
    scripts/setup-profile.mjs:41
    mkdirSync(fallbackDir, { recursive: true });
  • 写入本地文件
    scripts/setup-profile.mjs:43
    mkdirSync(dirname(link), { recursive: true });
  • 读取本地文件
    scripts/setup-profile.mjs:45
    if (!existsSync(link)) {
  • 执行外部命令来自配置/变量
    scripts/smoke-carrier.mjs:23
    const child = spawn(process.execPath, [dshBin, "--profile", "desktop"], {
  • 删除本地文件递归
    scripts/smoke-carrier.mjs:190
    rmSync(home, { recursive: true, force: true });
  • 读取本地文件
    scripts/verify-repair.mjs:30
    const raw = readFileSync(path);
  • 读取本地文件
    src/main/app-protocol.ts:106
    const html = readFileSync(join(distDir, "index.html"), "utf8");
  • 读取本地文件
    src/main/app-protocol.ts:125
    return new Response(readFileSync(options.transportPath, "utf8"), {
  • 读取本地文件
    src/main/app-protocol.ts:147
    const body = readFileSync(filePath);
  • 发起网络请求
    src/main/carrier-client.ts:66
    const frame = (await this.request({ t: "rpc", method, payload }, timeoutMs)) as { result: { ok: boolean; value?: unknown; error?: unknown } };
  • 发起网络请求
    src/main/carrier-client.ts:72
    const frame = (await this.request({ t: "manifest" })) as { manifest: unknown };
  • 发起网络请求
    src/main/carrier-client.ts:78
    const frame = (await this.request({ t: "bundle", id })) as { source: string };
  • 发起网络请求
    src/main/carrier-client.ts:85
    const frame = (await this.request({ t: "ping", seq }, timeoutMs)) as { t: string; seq: number };
  • 读取本地文件
    src/main/config.ts:20
    const raw = JSON.parse(readFileSync(configPath(), "utf8")) as Partial<AppConfig>;
  • 写入本地文件
    src/main/config.ts:29
    mkdirSync(app.getPath("userData"), { recursive: true });
  • 写入本地文件
    src/main/config.ts:30
    writeFileSync(configPath(), JSON.stringify(next, null, 2) + "\n");
  • 写入本地文件
    src/main/log.ts:14
    mkdirSync(dirname(logPath), { recursive: true });
  • 写入本地文件
    src/main/log.ts:16
    if (statSync(logPath).size > MAIN_LOG_MAX_BYTES) renameSync(logPath, `${logPath}.old`);
  • 读取本地文件
    src/main/log.ts:16
    if (statSync(logPath).size > MAIN_LOG_MAX_BYTES) renameSync(logPath, `${logPath}.old`);
  • 写入本地文件
    src/main/log.ts:20
    return createWriteStream(logPath, { flags: "a" });
  • 读取本地文件
    src/main/profiles.ts:34
    if (!existsSync(manifestPath)) {
  • 写入本地文件
    src/main/profiles.ts:35
    mkdirSync(profileDir, { recursive: true });
  • 写入本地文件
    src/main/profiles.ts:36
    writeFileSync(manifestPath, JSON.stringify({ dsh: { profile: { bundles: DESKTOP_BUNDLES } } }, null, 2) + "\n");
  • 写入本地文件
    src/main/profiles.ts:45
    mkdirSync(fallbackDir, { recursive: true });
  • 写入本地文件
    src/main/profiles.ts:48
    mkdirSync(dirname(link), { recursive: true });
  • 读取本地文件
    src/main/profiles.ts:50
    if (!existsSync(link)) return false;
  • 删除本地文件
    src/main/profiles.ts:59
    rmdirSync(link);
  • 删除本地文件
    src/main/profiles.ts:61
    rmSync(link, { force: true });
  • 读取本地文件
    src/main/profiles.ts:75
    const manifest = JSON.parse(readFileSync(join(profileDir, "package.json"), "utf8")) as {
  • 读取本地文件
    src/main/profiles.ts:88
    if (!existsSync(profilesDir)) return [];
  • 读取本地文件
    src/main/profiles.ts:89
    const names = readdirSync(profilesDir, { withFileTypes: true })
  • 写入本地文件
    src/main/server-manager.ts:141
    mkdirSync(dirname(path), { recursive: true });
  • 写入本地文件
    src/main/server-manager.ts:143
    if (statSync(path).size > KERNEL_LOG_MAX_BYTES) renameSync(path, `${path}.old`);
  • 读取本地文件
    src/main/server-manager.ts:143
    if (statSync(path).size > KERNEL_LOG_MAX_BYTES) renameSync(path, `${path}.old`);
  • 写入本地文件
    src/main/server-manager.ts:147
    this.kernelLog = createWriteStream(path, { flags: "a" });
  • 执行外部命令表达式求值
    src/main/server-manager.ts:163
    private spawn(): Promise<void> {
  • 执行外部命令来自配置/变量
    src/main/server-manager.ts:168
    const child = spawn(process.execPath, [dshBin, "--profile", this.profile], {
  • 读取本地文件
    src/main/session-repair.ts:120
    const raw = readFileSync(path);
  • 读取本地文件
    src/main/session-repair.ts:270
    const mtimeMs = statSync(path).mtimeMs;
  • 写入本地文件
    src/main/session-repair.ts:335
    copyFileSync(path, backupPath);
  • 写入本地文件
    src/main/session-repair.ts:337
    writeFileSync(tmpPath, serializeArtifact(path, scan.header, rows));
  • 写入本地文件
    src/main/session-repair.ts:339
    renameSync(tmpPath, path);
  • 删除本地文件
    src/main/session-repair.ts:341
    rmSync(tmpPath, { force: true });
  • 读取本地文件
    src/main/session-repair.ts:361
    entries = readdirSync(dir, { withFileTypes: true });
扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 GitHub 仓库

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

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis-plugin-group@deepseek-ai/dsh@deepseek-ai/dsh-anonymous-user-id@deepseek-ai/dsh-atomic-write@deepseek-ai/dsh-authorization@deepseek-ai/dsh-bash-local@deepseek-ai/dsh-client-modules@deepseek-ai/dsh-code-runtime@deepseek-ai/dsh-compaction@deepseek-ai/dsh-fs@deepseek-ai/dsh-host-webserver@deepseek-ai/dsh-invariants
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 免装Node运行内核

README

由 DeepSeek 最新模型翻译生成
dsh-desktop Windows 桌面客户端,用于 DeepSeek Harness (DSH)。 - UI 复用官方 Web UI,零修改 - 完全自包含:捆绑 dsh 内核,用 Electron 自带的 Node(ELECTRON_RUN_AS_NODE=1)运行,不要求系统安装 Node/dsh - 零端口:内核不监听任何地址(连 127.0.0.1 都没有),传输全部走私有 stdio 管道(帧协议),攻击面归零 - 菜单栏与托盘跟随「通用设置 → 语言」实时切换(中英双语) - 托盘常驻、单实例、关闭到托盘、多 profile 切换 架构与协议详见 docs/design.md。
展开完整文档(README 剩余 1 节)
dsh-desktop

Windows 桌面客户端,用于 DeepSeek Harness (DSH)。

- UI 复用官方 Web UI,零修改
- 完全自包含:捆绑 dsh 内核,用 Electron 自带的 Node(ELECTRON_RUN_AS_NODE=1)运行,不要求系统安装 Node/dsh
- 零端口:内核不监听任何地址(连 127.0.0.1 都没有),传输全部走私有 stdio 管道(帧协议),攻击面归零
- 菜单栏与托盘跟随「通用设置 → 语言」实时切换(中英双语)
- 托盘常驻、单实例、关闭到托盘、多 profile 切换

架构与协议详见 docs/design.md。

快速开始

npm install # 安装依赖(含 Electron 二进制;国内网络可在 .npmrc 配置镜像)
npm run build # 编译主进程 + 复制静态资源 + 生成图标
npm start # 启动桌面应用
npm run smoke:carrier # 无 Electron 的内核冒烟测试(先跑它验证环境)
npm run smoke:recovery # 内核崩溃/重启恢复能力 E2E("kernel not running" 回归)

首次启动会在 $DSH_HOME/profiles/(默认 ~/.dsh/profiles/)创建 desktop profile,并与你的 dsh CLI 共享会话、配置和凭据。

打包安装器

完整教程(含验证清单、踩坑记录、发版流程)见 docs/package/packaging-guide.md。

npm run pack # 免安装版:release/win-unpacked/(构建后可直接运行验证)
npm run dist # NSIS 安装器:release/DSH-Desktop-Setup-.exe

- 安装器为按用户安装(无需管理员),可选安装目录,带桌面/开始菜单快捷方式。
- 构建脚本 scripts/build-installer.mjs 已内置国内镜像(electron + electron-builder 二进制),无需手动设置环境变量。
- 注意:dsh 的一批运行时包在 npm 锁文件中被标记为 peer 依赖,electron-builder 的依赖收集器会跳过它们;因此这些包被显式声明为本应用的直接依赖(见 package.json 里 @deepseek-ai/dsh- 直依赖)。升级 dsh 版本后如遇内核启动报 Cannot find package,比对 node_modules/@deepseek-ai 与打包产物的包清单补齐。此外 sharp 是内核插件 @deepseek-ai/dsh-attachment-local 的硬依赖,build.files 不得排除它(详见 docs/package/packaging-guide.md 坑 5)。
- 应用未签名,首次运行安装器时 Windows SmartScreen 可能提示「更多信息 → 仍要运行」。

目录结构

src/
main/ Electron 主进程(TS) preload/preload.cjs 页面桥(contextBridge) transport/transport.js __DSH_TRANSPORT__ 注入脚本(官方传输缝隙) packages/carrier/ 内核侧插件 bundle(stdio carrier + 空壳 webServer) scripts/ 构建 / 冒烟 / profile 设置脚本 docs/design.md 设计规格

注意事项

- 依赖 pin 住 @deepseek-ai/dsh 版本;升级后先跑 npm run smoke:carrier 回归。
- 传输契约(上游缝 API)集中在 packages/carrier/lib 和 src/transport/transport.js 两处。
- 会话日志是单写者格式:桌面端与 dsh CLI 共享同一 $DSH_HOME 的会话日志,两个 dsh 进程同时打开同一会话并发送消息会互相覆写 seq,造成该会话日志损坏(corrupt session log: seq gap)。同一会话避免同时在 CLI 与桌面端操作;应用每次启动会自动扫描并修复损坏的会话日志(改写前留 .bak- 备份)。
- 手动修复/体检入口:
node scripts/repair-session-logs.mjs # dry-run:只检测
node scripts/repair-session-logs.mjs --apply # 修复(自动备份,跳过 2 分钟内活跃写入的文件)
node scripts/scan-all-session-logs.mjs "$env:USERPROFILE\.dsh\sessions" # 全量连续性体检
预览分享卡片长什么样 ↓chermye/dsh-desktop
分享 · DeepSeek Harness Hub
chermye/dsh-desktop

在 Windows 上免装 Node 直接运行 DSH 内核

回到顶部