This guide covers local development, validation, patch maintenance, and target-native packaging. For the runtime design, see Architecture. For formal releases, see the release runbook.
- Node.js 22 or later
- npm
- macOS on Apple Silicon or Intel, or Windows x64
This baseline pins @deepseek-ai/dsh@0.2.1-rc.3. Windows packages run Harness in Electron Node mode, while macOS uses an Electron UtilityProcess. Both are independent of the Node.js version used to run development commands.
git clone https://github.com/dataelement/dsh-desktop.git
cd dsh-desktop
npm ci
npm run devnpm ci runs the repository's postinstall hook. It reapplies the tracked patch-package patches, installs DSH brand assets into the pinned Harness frontend, and installs Electron.
Development builds use the separate application name DSH Desktop Dev and the separate user-data directory dsh-desktop-dev, so they do not reuse production DSH Desktop data. Multiple development worktrees still share that development profile by default; avoid running them at the same time when testing profile, plugin, migration, or recovery changes.
To run another worktree concurrently, set a distinct DSH_DEV_PROFILE directory name and unused loopback ports using DSH_DEV_HARNESS_PORT and DSH_DEV_MOBILE_PORT. For example, on macOS:
DSH_DEV_PROFILE=dsh-desktop-dev-workbench DSH_DEV_HARNESS_PORT=43132 DSH_DEV_MOBILE_PORT=43133 npm run devOn PowerShell, set the same names through $env:DSH_DEV_PROFILE, $env:DSH_DEV_HARNESS_PORT and $env:DSH_DEV_MOBILE_PORT before npm run dev. The profile must be a directory name, separate from dsh-desktop; ports must be distinct integers from 1024 to 65535. These overrides are ignored by production builds. Defaults remain dsh-desktop-dev, Harness port 43130 and mobile port 43128.
Workbench Market resolves reviewed catalog targets, but delegates package installation,
updates, selection switches, removal and runtime reconciliation to Plugin Manager.
Its install records describe catalog provenance, not whether a plugin is running.
The inventory endpoint returns Plugin Manager bundle and plugin snapshots; clients
refresh them on plugin-manager/changed rather than treating saved sidebar entries
or installed package files as proof of activation.
Catalog installation requires the generation backend verified-install capability. The backend checks expected package name/version and registry integrity before publication; Release downloads retain SHA-256 validation. An unsupported backend refuses verified installation instead of falling back to unverified pnpm. Updates replace only the expected package and retain manager protections and rollback.
Actual application results determine the next action: applied, restart-required,
failed, cancelled or overridden. No static client/server package classifier
drives these routes. The packaged HMR fallback reports module-comparison rather
than claiming module replacement. This does not add unpublished local workbench
identity support; registration and a mounted panel are separate acceptance stages.
Run the core checks before submitting a change:
npm test
npm run typecheck
npm run buildStatic checks are not a substitute for runtime verification. Changes that affect startup, profiles, plugins, native dialogs, updates, mobile access, or packaging should also be exercised through the corresponding real application flow.
To exercise the Cloudflare-to-Pinggy fallback without disrupting the machine's network, start the development app with Cloudflare failure simulation enabled:
DSH_TUNNEL_FORCE_PINGGY=1 npm run devThen enable the temporary public tunnel from the phone connection screen. The tunnel status should report pinggy, and the generated URL should use a Pinggy hostname. This variable affects only the process started from that command; omit it on the next launch to restore the normal Cloudflare-first behavior.
Automatic fallback still happens only when Cloudflare fails to start. If a Cloudflare pairing link is already showing but the phone cannot open it, use 扫码打不开?换一条线路 / Can't open? Try another link on the pairing page to switch to Pinggy.
src/main/ Electron main process and application orchestration
src/main/runtime/ Harness process lifecycle and diagnostics
src/main/state/ Profile consistency, repair, recovery, and Safe Mode
src/main/mobile/ Paired phone bridge and optional Cloudflare tunnel
src/main/update/ Installed-build update state and lifecycle
src/preload/ Narrow renderer-to-main IPC and desktop UI seams
src/shared/ Shared contracts and desktop menu definitions
packages/ Bundled desktop support packages
patches/ Reproducible patches for the pinned Harness packages
build/ Packaged HTML, icons, loaders, and Harness entry files
scripts/ Build, signing, metadata, and target verification tools
test/ Unit and source-contract regression coverage
.github/workflows/ Native CI, signing, release, and publication workflows
The desktop product intentionally reuses the upstream Harness UI. Desktop-specific provider onboarding, preset transfer, model selection, workspace, branding, and layout changes are captured under patches/ rather than stored as untracked edits in node_modules.
The @deepseek-ai/dsh-host-open-in-app@0.2.1-rc.3 patch carries forward the V0.12.2 adaptation of PR #709 for #686, originally targeting 0.2.0-rc.2. Detached external app launches remove inherited ELECTRON_RUN_AS_NODE names case-insensitively before merging the adapter's explicit environment. GitHub Desktop's Windows CLI adapter keeps its explicit Node mode; the shared subprocess scrub and internal runners remain unchanged. test/open-in-app-environment.test.ts exercises the installed bundle, emitted resolver, and host route; set DSH_TEST_OPEN_IN_ELECTRON=1 to include native Electron checks with disposable data directories and no application window. Run these tests with standalone Node, not Electron in Node mode, because their Node child fixtures use process.execPath. Remove the patch when the pinned upstream package provides equivalent behavior in both artifacts. Validate native Windows Open In when replaying or retiring it.
When upgrading Harness:
- Install the intended upstream version.
- Verify the current Settings, Credentials, Provider Directory, workspace, and preset contracts.
- Reapply or rewrite each desktop customization.
- Regenerate the relevant
patch-packagepatches. - Run the full automated suite.
- Start the real app and exercise every affected user flow.
The node-pty@1.2.0-beta.15 helper-path patch retains the #615/#646 correction alongside the external-helper support: only complete app.asar and node_modules.asar path segments map to their unpacked siblings, and already-unpacked paths remain unchanged. Keep the resolution order (explicit DSH_NODE_PTY_SPAWN_HELPER, executable sibling, default module-relative helper). Remove this part of the patch only when the pinned upstream version preserves these behaviors and passes both test/node-pty-helper-path.test.ts (actual module evaluation with a recorded native call) and test/node-pty-asar-layout.test.ts (real macOS PTY in archived and already-unpacked layouts, with default and external helpers). The path tests do not replace the native or installed-application checks.
Harness includes architecture-specific native dependencies. Build each installer on the operating system and architecture where it will run.
# macOS Apple Silicon, on an Apple Silicon Mac or runner
npm run package:mac:arm64
# macOS Intel, on an Intel Mac or runner
npm run package:mac:x64
# Windows x64 NSIS installer, on a Windows x64 machine or runner
npm run package:winDo not invoke electron-builder --win from macOS or Linux for a distributable Windows package. The target verification scripts intentionally reject host/target mismatches.
For local unsigned development packages, use the corresponding package:dev:* command. Packages run Harness and package commands through the packaged Electron runtime (a utility process and the Helper in Node mode on macOS, the executable in Node mode on Windows); no standalone node_modules/node may be present under app.asar.unpacked. Verify the packaged native-module, pnpm and Harness smokes, then the final signed installer's separate installed-app smoke before handoff. Office preparation is included in dev, build and test: it fetches hash-locked native Python and wheels into .build/office-runtime, outside ASAR, and ships no second Node runtime. See Office runtime. The locked Electron 43.0.0 must remain compatible with the native loader: scripts/verify-target.mjs fails packaging when Electron in Node mode cannot load it, and changing Electron still requires a new Windows package qualification. Before packaging, node scripts/probe-electron-node-runtime.mjs boots Harness from the repository through Electron Node mode with a disposable DSH_HOME and checks an authenticated HTTP response; the Windows CI job runs it on every build.
Formal release artifacts are built, signed, and published by the tag workflow. A local build or pull-request check is not formal release evidence.
- Never include real API keys in issues, logs, screenshots, fixtures, or test data.
- Preserve unrelated worktree changes.
- Keep temporary research, local reports, and internal working documents under the ignored
doc/directory. - Update all localized README files when changing user-visible facts.
The @deepseek-ai/dsh-web-frontend@0.2.1-rc.3 patch carries forward #762: the shell audit exempts only manifest-declared optional workbench entries present in the recorded failure snapshot. Required and unknown entries remain fatal, and failure diagnostics remain available. The rc.3 shell still performs the conflicting audit, so the rc.2 patch is ported to its current asset rather than retired. test/client-shell-failure-isolation.test.ts executes the pinned shell boot, audit and mount functions with their real Cordis dependency closure. Remove this patch when upstream provides equivalent isolation and required-entry handling.