honghuachen/deepseekharness-desktop 预览 preview

honghuachen/deepseekharness-desktop

一键安装的跨平台桌面容器(macOS / Windows),完整运行官方 DeepSeek Harness Web 壳,内置兼容性守护机制,支持第三方插件管理、更新与移除,并自动拉取官方内核最新版本。 | One-click cross-platform desktop container (macOS / Windows) running the official DeepSeek Harness Web shell, with built-in compatibility safeguards, third-party plugin management (update & remove), and automatic updates to the official runtime.

Project Overview项目介绍

This is a lightweight cross-platform desktop container for the official DeepSeek Harness web shell. It automatically checks and updates to the latest official release on every launch. Use it for a native desktop DSH experience without manual updates. The unsigned Windows build triggers a SmartScreen prompt on first run.

这是一款运行官方DeepSeek Harness网页端的轻量跨平台桌面容器,核心能力是每次启动自动检查更新到最新官方版本。适合想要以桌面端方式使用官方DSH,又不想手动更新的用户。注意Windows版首次运行会触发SmartScreen安全提示,需手动允许运行。

Or use CLI install (for developers)或使用命令行安装(适合开发者)

CLI Install命令行安装

dsh plugin --profile web add github:honghuachen/deepseekharness-desktop

honghuachen/deepseekharness-desktop 加入你的 DSH 配置(web profile)即可启用。

READMEREADME

English | 中文

DSH Web — Desktop Container for the Official DeepSeek Harness

An extremely thin, cross-platform (macOS + Windows) desktop container: the window always runs the official deepseek-ai/deepseek-harness web shell — the container itself ships no business UI of its own.

Core capability: it automatically checks for and updates to the latest official release on every launch.

┌─────────────────────────────────────────────────────┐
│  DSH Web.app (thin Electron shell)                   │
│                                                       │
│  1. Query npm registry: @deepseek-ai/dsh latest      │
│  2. New version found → pnpm-install into versions/  │
│  3. Atomically flip the `current` symlink → rollback │
│  4. Launch the official `dsh web` service            │
│     (127.0.0.1:<port>)                               │
│  5. Window loads the official page ← 100% official   │
└─────────────────────────────────────────────────────┘

Design principles

Decision Rationale
The shell has zero business logic — only "update + launch + open window" The upstream project iterates fast; a thin shell never conflicts with it
Each runtime version installs into its own directory, switched via a symlink An upgrade that fails rolls back instantly; old versions stay around for rollback
pnpm's build-script allowlist (allowBuilds) is pre-configured Only known native modules (node-pty/koffi, etc.) are allowed to run install scripts
A portable Node v22 ships with the app No dependency on the system Node version; ABI stays consistent with native modules
The data directory is independent of the shell Session data lives in ~/.dsh; upgrading or reinstalling the shell never touches it

Quick start

Development mode

npm install          # install Electron and other devDependencies
npm run fetch-tools  # download vendor/pnpm + vendor/node (portable Node)
npm start

The first launch downloads the official runtime (roughly 1–2 minutes, depending on your network); afterwards it only downloads again when a new version is available.

Building the app

npm run dist         # produces macOS (dmg/zip) + Windows (NSIS installer/zip)

One build covers both platforms (electron-builder cross-builds the Windows target directly on macOS):

Platform Artifacts
macOS arm64 DSH Web-<ver>-arm64.dmg / -mac.zip
Windows x64 DSH-Web-Setup-<ver>-x64.exe / -win.zip

Windows notes:

  • Unsigned — the first run triggers a SmartScreen prompt ("Run anyway" is fine); the installer runs in per-user mode with a selectable install directory.
  • A portable node.exe and pnpm ship inside the package; version switching uses NTFS junctions (no admin privileges needed), falling back to a pointer file in restricted environments.
  • The task-completion badge shows up as a taskbar overlay number; parsing session logs for it depends on the system zstd command (if missing, the badge feature silently degrades — install it with scoop install zstd if you want it).

Update mechanism

On every launch, unless a kernel version has been manually pinned (see below):

  1. GET https://registry.npmjs.org/@deepseek-ai/dsh/latest to fetch the latest version;
  2. Compare it against the currently active local version using semver (pre-release ordering like -rc.N is supported);
  3. If newer: download and install into <data dir>/runtime/versions/v<version>/, then atomically flip the runtime/current symlink over to it on success; on failure, the previous version stays active;
  4. Auto-cleanup: only the 2 most recent versions are kept (plus a pinned version, if any — see below).

Offline: the check is skipped and the already-installed version is used directly; an error is only raised if there's no installed version at all and the machine is offline.

The About DSH Web menu item shows both the container's own version and the "kernel" version (the currently active version of the official @deepseek-ai/dsh runtime).

Check for Updates window

Menu DSH Web → Check for Updates… (⌘U) opens a dedicated window covering both halves of the app:

Check for Updates window: a container card showing the current version and whether a newer GitHub Release is available, and a kernel card listing every published npm version with tags, publish time, and a switch button

  • Container (the shell app itself): compares the current version against the latest tag on GitHub Releases for this repo.
    • Windows: wired up to electron-updater — "Download & Install" fetches the update, then "Restart & Install" applies it in place. Because the installer isn't code-signed, Authenticode verification is disabled at runtime (win.verifyUpdateCodeSignature: false in electron-builder.yml). Every release must include the latest.yml/.blockmap files electron-builder generates alongside the installer, or the updater has nothing to compare against.
    • macOS: unsigned/unnotarized, and Squirrel.Mac hard-requires signing to auto-update, so this stays detection-only — just a button to jump to the release page.
  • Kernel (@deepseek-ai/dsh): lists every version ever published to npm, newest first, tagged alpha / rc / (once the official package ships one) a true stable, with the version behind npm's latest dist-tag marked "Recommended". You can switch to any version in the list; switching briefly restarts the background service and streams the install log live.
  • Switching to a version pins it: from then on, launch-time auto-update is skipped for that version, and prune() won't clean it up even if it falls outside the "2 most recent" window. Click "Resume following the latest recommended version" to unpin and go back to auto-updating to npm latest on every launch.
  • The container and kernel checks are independent — a GitHub API hiccup only shows an inline "Retry" on that one card, the other keeps working.

"Recommended" here means npm's latest dist-tag, which the official package has maintained since day one — it is not the semver notion of "no pre-release suffix". As of writing, every published version of @deepseek-ai/dsh still carries a -rc.N/-alpha.N suffix, so there's no true GA release yet; the UI intentionally avoids the word "stable" for that reason.

Directory layout

<data dir>/                        # ~/Library/Application Support/DSH Web when packaged; .data/ under the repo in dev
├── runtime/
│   ├── versions/v0.1.1-rc.2/     # each version gets its own full node_modules
│   └── current -> versions/v0.1.1-rc.2
├── pnpm-store/                    # content-addressed store, deduplicated across versions
├── logs/app-YYYY-MM-DD.log        # runtime logs
└── settings.json                  # user configuration

Configuration

settings.json (openable from the menu):

{
  "port": 43130,             // web service port; auto-probes forward if taken
  "channel": "latest",       // update channel (currently only npm's `latest`)
  "autoCheckUpdates": true,  // disable to skip the update check on every launch
  "dshHome": "",             // empty = the official standard ~/.dsh; point elsewhere to isolate data
  "taskBadge": true,         // show a completed-task count badge on the Dock/taskbar icon
  "pinnedKernelVersion": ""  // empty = auto-update to npm `latest`; set via the Check for Updates window to pin a specific kernel version
}

Data relationship with third-party shells

By default the app uses the official standard data directory ~/.dsh, so session history is fully interoperable with the official CLI.

Third-party containers (e.g. the older DSH Desktop) may push community plugins into a profile through a plugin marketplace. These are often incompatible with newer official releases and can crash the web service on startup (showing up as a blank white window). To address that, this app provides:

Manage Third-Party Plugins window: plugins grouped by profile, tagged official/third-party and where each is referenced (bundle/patch layer), with checkboxes for bulk removal and per-plugin update checks

  1. A plugin manager (menu: DSH Web → Manage Third-Party Plugins…): browse every plugin under a profile, distinguish official vs. third-party and where each is referenced (bundle, patch layer), and remove selected ones precisely. Removal requires confirmation, and the original files are automatically backed up under the profile's .sanitized-backup-* directory.
  2. Automatic cleanup on first launch: if the web profile has been contaminated, it's restored to the official default shape, while user-level configuration for official components (e.g. the Funplay MCP) is preserved.
  3. Crash self-healing: the service auto-restarts if it exits unexpectedly; if it has never been cleaned before, cleanup runs first, then it restarts.

Note: the desktop profile belongs to the older DSH Desktop. Removing plugins from it will break that older shell's marketplace features (that's the intended effect of decontamination) — quit the older shell first if you still use it. To install a plugin, use the official method: dsh plugin --profile <name> add <package>.

Community plugin marketplace

Menu DSH Web → Community Plugin Marketplace… (⌘⇧M, or switch to that tab inside the "Manage Third-Party Plugins" window):

Community plugin marketplace window: browse a curated Top 10 per category, search, jump to a plugin's open-source homepage, and one-click install into a chosen profile

  • Aggregates high-star, high-install plugins from the mainstream community marketplaces (dsh-1024store, dsh-market) into 11 categories (Tools & Capabilities, UI Enhancements, Dev & Runtime, Skill Packs, Conversation & Messaging, Models & Providers, Workflow Automation, Memory Management, Themes, Notifications, Fun & Games), each curated down to a Top 10; duplicate listings of the same GitHub repo/package are merged.
  • Real-time search by plugin name, Chinese description, author, or keyword, plus category filtering.
  • Already-installed plugins are flagged on their card so you don't install them twice; pick a target profile and click "Install" to write the dependency into that profile's package.json and dsh.profile.bundles directly — no manual file editing needed.
  • Click a plugin's name or its "open-source" badge to open its GitHub/npm homepage in the system default browser.
  • Marketplace data is cached locally for 12 hours; if the network is unavailable or the API errors out, it falls back seamlessly to a bundled Top 100 seed dataset, so the list is always usable.

Token usage & cost stats

Menu DSH Web → Token 用量统计… (⌘⇧T) opens a dedicated window that tallies token usage and estimated cost across every session under ~/.dsh/sessions:

Token Usage Stats window: by-model view showing total tokens, estimated cost, cache hit rate, and the input/output/cache breakdown for each model

  • Time range: today / yesterday / this week / this month / all time / a custom range
  • Grouping: by project (folded up to the repository root along .git, worktrees included) / by model / by session (capped at the 50 most recently active)
  • Each row shows total tokens, cache hit rate, and estimated cost; the by-model/by-session views additionally break the cost down into input/output/cache-write/cache-read
  • Clicking a project or session row opens its folder in Finder (Explorer on Windows)
  • The window re-scans automatically every 60 seconds while open

Cost estimates come from a local, editable pricing table. The first time the window opens, the bundled pricing.default.json is copied to pricing.json in the app's data directory; from then on only that user copy is read/written (an app update never overwrites it). DeepSeek Harness can route to any provider/model — real-world usage has included OpenRouter, SenseNova, Zhipu, and Google models, for example — and this table only ships pricing for DeepSeek's own official models; every other model shows as "unknown pricing" until you add its rate using the same JSON shape. DeepSeek's official API also prices differently during UTC peak/off-peak windows; this only uses the (lower) off-peak rate as a single approximation, so the cost shown is an estimate (≈), not an exact bill.

Performance and cross-platform support: decompressing .zstd session logs prefers the system-installed zstd CLI when available (roughly an order of magnitude faster in practice), and falls back to the pure-JS fzstd library otherwise — a case that mainly shows up on Windows machines without extra command-line tools installed. Neither path needs any native compilation, so it works correctly on Windows too. The whole scan/decompress/parse pipeline runs on a dedicated worker thread and never blocks the main process or the UI.

Notes

  • Don't run two DSH containers at once (e.g. the older DSH Desktop.app): both share the ~/.dsh session data, and concurrent writes are risky. After migrating to this app, quit or remove the old shell.
  • The default port is 43130, chosen to avoid colliding with the old DSH Desktop's 43120.
  • Native modules (node-pty, koffi) all use official prebuilt binaries — no Xcode command line tools required.
  • To distribute the app to others, configure Apple code signing (the CSC_LINK environment variable) before running npm run dist again.

Project structure

src/main/
├── main.js              # startup orchestration, windows, menu, lifecycle, crash self-healing
├── config.js            # paths and constants (registry URL, build allowlist)
├── updater.js           # update engine: check/install/atomic-swap/cleanup (pure Node, testable)
├── kernel-versions.js   # fetches every published npm version of @deepseek-ai/dsh (pure Node, testable)
├── shell-update.js      # checks GitHub Releases for a newer container version (pure Node, testable)
├── shell-auto-updater.js # container auto-update: wraps electron-updater (Windows only)
├── kernel-switch.js     # kernel version switch state machine: install → activate → persist pin (pure Node, testable)
├── update-window.js     # Check for Updates window (container + kernel) + its IPC handlers
├── runner.js            # official service process management: launch/double health check/graceful exit
├── plugin-guard.js      # third-party plugin guard: surgically restores an official profile
├── plugin-manager.js    # third-party plugin manager window
├── badge.js             # task-completion Dock/taskbar badge watcher
├── semver.js            # pre-release-aware version comparison
├── status-window.js     # startup progress window (plain text, not the product UI)
├── logger.js            # file logging
└── token-usage/         # token usage stats: scan/decompress/parse/dedupe/aggregate/pricing/window
    ├── scanner.js        # recursively find session.jsonl(.zstd)
    ├── decompress.js     # prefers system zstd, falls back to pure-JS fzstd
    ├── parser.js         # parses session events into usage records
    ├── dedup.js          # dedupes by requestId (streaming snapshots repeat the same request)
    ├── aggregator.js     # aggregates by project/model/session + estimates cost
    ├── pricing.js        # pricing table loading (bundled default + user-editable copy)
    ├── cache.js           # incremental cache keyed by file mtime/size
    ├── scan-worker.js     # runs the actual scan on a worker thread, off the main process
    ├── service.js         # orchestrates the steps above
    ├── window.js          # the stats window + its IPC handlers
    └── stats-window.html  # the stats window's UI
scripts/
├── fetch-tools.mjs        # build-time download of pnpm/portable Node into vendor/
├── e2e-update-test.mjs    # headless end-to-end test (hits the real registry, full chain)
├── test-plugin-update.mjs # self-test for third-party plugin update logic
├── test-token-usage.mjs   # self-test for the token usage module (incl. a real-file decompression regression)
├── kernel-versions-test.mjs # self-test for parsing/classifying/sorting the npm version list
├── shell-update-test.mjs  # self-test for the GitHub Releases update check
├── kernel-switch-test.mjs # self-test for the kernel switch state machine (incl. pin/rollback-on-failure)
├── badge-test.mjs         # self-test for the task-completion badge
├── plugin-guard-test.mjs  # self-test for the plugin guard
├── repair-session.mjs     # repairs sequence numbers in a corrupted session.jsonl(.zstd)
└── verify-session-file.cjs # verifies a session file's zstd framing / sequence continuity

Testing

npm test          # fast regression suite: badge / plugin guard / plugin update / token usage / kernel versions / shell update / kernel switch (no network needed)
npm run test:e2e  # end-to-end update flow test (hits the real npm registry, slower)

Contributing

Issues and PRs are welcome. Please read CONTRIBUTING.md first (it covers the dev workflow and the commit message convention).

License

MIT

上一个 Prev dsh-cc-haha-memory 下一个 Next dsh-files-git