Răsfoiți Sursa

Merge pull request #3413 from deepseek-harness/feat/electron

feat(desktop): add managed Electron distribution
07akioni 3 săptămâni în urmă
părinte
comite
ba05b7d49d
100 a modificat fișierele cu 9898 adăugiri și 51 ștergeri
  1. 6 0
      .agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.i18n.yaml
  2. 73 0
      .agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.md
  3. 73 0
      .agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.zh.md
  4. 3 3
      .gitattributes
  5. 2 0
      .gitignore
  6. 12 0
      THIRD_PARTY_NOTICES.md
  7. 2 2
      apps/cli/README.i18n.yaml
  8. 1 1
      apps/cli/README.md
  9. 1 1
      apps/cli/README.zh.md
  10. 8 0
      apps/cli/src/args.ts
  11. 6 0
      apps/cli/tests/args.spec.ts
  12. 34 0
      apps/desktop-host/config/desktop.cordis.patch.yml
  13. 28 0
      apps/desktop-host/package.json
  14. 587 0
      apps/desktop-host/src/index.ts
  15. 184 0
      apps/desktop-host/src/wire.ts
  16. 19 0
      apps/desktop-host/tsconfig.json
  17. 12 0
      apps/desktop-host/tsdown.config.ts
  18. 6 0
      apps/desktop/README.i18n.yaml
  19. 160 0
      apps/desktop/README.md
  20. 160 0
      apps/desktop/README.zh.md
  21. 39 0
      apps/desktop/electron-builder.config.d.mts
  22. 109 0
      apps/desktop/electron-builder.config.mjs
  23. 51 0
      apps/desktop/package.json
  24. 108 0
      apps/desktop/renderer/plugin-manager.css
  25. 35 0
      apps/desktop/renderer/plugin-manager.html
  26. 101 0
      apps/desktop/renderer/plugin-manager.js
  27. 90 0
      apps/desktop/scripts/desktop-auto-update-environment.d.mts
  28. 169 0
      apps/desktop/scripts/desktop-auto-update-environment.mjs
  29. 49 0
      apps/desktop/scripts/desktop-build-paths.d.mts
  30. 70 0
      apps/desktop/scripts/desktop-build-paths.mjs
  31. 61 0
      apps/desktop/scripts/desktop-release-environment.d.mts
  32. 98 0
      apps/desktop/scripts/desktop-release-environment.mjs
  33. 257 0
      apps/desktop/scripts/desktop-upload-plan.ts
  34. 118 0
      apps/desktop/scripts/dev.ts
  35. 120 0
      apps/desktop/scripts/development-project.ts
  36. 413 0
      apps/desktop/scripts/macos-seed-store.ts
  37. 23 0
      apps/desktop/scripts/notarize-macos-disk-images.d.mts
  38. 30 0
      apps/desktop/scripts/notarize-macos-disk-images.mjs
  39. 285 0
      apps/desktop/scripts/package-target.ts
  40. 172 0
      apps/desktop/scripts/prepare-package-set.ts
  41. 107 0
      apps/desktop/scripts/prepare-runtime.ts
  42. 200 0
      apps/desktop/scripts/prepare-seed.ts
  43. 83 0
      apps/desktop/scripts/upload-target.ts
  44. 73 0
      apps/desktop/scripts/verify-macos-signature.d.mts
  45. 186 0
      apps/desktop/scripts/verify-macos-signature.mjs
  46. 17 0
      apps/desktop/scripts/windows-sign.cmd
  47. 91 0
      apps/desktop/scripts/windows-sign.d.mts
  48. 266 0
      apps/desktop/scripts/windows-sign.mjs
  49. 183 0
      apps/desktop/src/core-package-set.ts
  50. 413 0
      apps/desktop/src/host-process.ts
  51. 215 0
      apps/desktop/src/host-protocol.ts
  52. 40 0
      apps/desktop/src/ipc.ts
  53. 97 0
      apps/desktop/src/locale.ts
  54. 397 0
      apps/desktop/src/main.ts
  55. 48 0
      apps/desktop/src/paths.ts
  56. 5 0
      apps/desktop/src/preload-app.ts
  57. 26 0
      apps/desktop/src/preload.ts
  58. 725 0
      apps/desktop/src/project-manager.ts
  59. 35 0
      apps/desktop/src/release.ts
  60. 271 0
      apps/desktop/src/seed-store.ts
  61. 26 0
      apps/desktop/src/single-instance.ts
  62. 95 0
      apps/desktop/src/update-coordinator.ts
  63. 103 0
      apps/desktop/tests/core-package-set.spec.ts
  64. 89 0
      apps/desktop/tests/desktop-auto-update-environment.spec.ts
  65. 50 0
      apps/desktop/tests/desktop-build-paths.spec.ts
  66. 195 0
      apps/desktop/tests/desktop-upload-plan.spec.ts
  67. 89 0
      apps/desktop/tests/development-project.spec.ts
  68. 216 0
      apps/desktop/tests/host-process.spec.ts
  69. 98 0
      apps/desktop/tests/host-protocol.spec.ts
  70. 23 0
      apps/desktop/tests/locale.spec.ts
  71. 245 0
      apps/desktop/tests/macos-seed-store.spec.ts
  72. 171 0
      apps/desktop/tests/macos-signature.spec.ts
  73. 86 0
      apps/desktop/tests/package-target.spec.ts
  74. 79 0
      apps/desktop/tests/prepare-package-set.spec.ts
  75. 392 0
      apps/desktop/tests/project-manager.spec.ts
  76. 167 0
      apps/desktop/tests/seed-store.spec.ts
  77. 32 0
      apps/desktop/tests/single-instance.spec.ts
  78. 102 0
      apps/desktop/tests/update-coordinator.spec.ts
  79. 226 0
      apps/desktop/tests/windows-sign.spec.ts
  80. 11 0
      apps/desktop/tsconfig.json
  81. 30 0
      apps/desktop/tsdown.config.ts
  82. 2 2
      docs/architecture.i18n.yaml
  83. 6 0
      docs/architecture.md
  84. 6 0
      docs/architecture.zh.md
  85. 2 2
      docs/config-catalog.i18n.yaml
  86. 2 2
      docs/config-catalog.md
  87. 2 2
      docs/config-catalog.zh.md
  88. 2 2
      docs/subsystems/client-modules.i18n.yaml
  89. 9 0
      docs/subsystems/client-modules.md
  90. 9 0
      docs/subsystems/client-modules.zh.md
  91. 16 1
      package.json
  92. 2 2
      packages/boot/app-boot/README.i18n.yaml
  93. 1 1
      packages/boot/app-boot/README.md
  94. 1 1
      packages/boot/app-boot/README.zh.md
  95. 1 0
      packages/boot/app-boot/src/index.ts
  96. 44 25
      packages/boot/app-boot/src/profile.ts
  97. 11 0
      packages/boot/app-boot/tests/profile.spec.ts
  98. 2 2
      packages/client/connection/README.i18n.yaml
  99. 1 1
      packages/client/connection/README.md
  100. 1 1
      packages/client/connection/README.zh.md

+ 6 - 0
.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.i18n.yaml

@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# side as of the last confirmed-consistent state. Both languages carry equal authority;
+# after editing either side, bring the other along and re-record with:
+#   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.md
+2026-08-25-electron-desktop-packaging-and-updates.md: 6d22777c8913911dbb1d89c09de9e891636873c8
+2026-08-25-electron-desktop-packaging-and-updates.zh.md: 5108492ba6f029847735f570aa77c2cb505f981c

Fișier diff suprimat deoarece este prea mare
+ 73 - 0
.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.md


Fișier diff suprimat deoarece este prea mare
+ 73 - 0
.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.zh.md


+ 3 - 3
.gitattributes

@@ -1,10 +1,10 @@
 # The repo's canonical text form is LF, enforced at checkout too: no smudge
 # boundary between working tree and repo, so byte-level gates (verify-*
 # comparisons, blob hashing, coverage offsets) see one form on every host.
-# If a file class ever genuinely needs CRLF in the working tree (.bat/.cmd
-# for cmd.exe), add a `*.bat text eol=crlf` override AFTER this line — the
-# in-repo form stays LF; CRLF becomes checkout-time presentation only.
+# Windows command scripts require CRLF in the working tree for cmd.exe; the
+# override after the default keeps the in-repo form normalized as text.
 * text=auto eol=lf
+*.cmd text eol=crlf
 
 *.pdf binary
 

+ 2 - 0
.gitignore

@@ -27,6 +27,8 @@ tmp/
 .idea
 mise.toml
 dist-exe/
+/dist/
+apps/desktop/.desktop-build/
 python/sdk-runtime/src/deepseek_harness_runtime/runtime/deepseek-harness-sdk-runtime-*
 python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/
 python/**/__pycache__/

+ 12 - 0
THIRD_PARTY_NOTICES.md

@@ -68,6 +68,7 @@ External packages that a workspace package resolves at runtime. The tier covers
 | [`compression`](https://github.com/expressjs/compression) | MIT |
 | [`diff`](https://github.com/kpdecker/jsdiff) | BSD-3-Clause |
 | [`e2b`](https://github.com/e2b-dev/e2b) | MIT |
+| [`electron-updater`](https://github.com/electron-userland/electron-builder) | MIT |
 | [`eventsource-parser`](https://github.com/rexxars/eventsource-parser) | MIT |
 | [`fflate`](https://github.com/101arrowz/fflate) | MIT |
 | [`fs-ext`](https://github.com/baudehlo/node-fs-ext) | MIT |
@@ -98,6 +99,7 @@ External packages that a workspace package resolves at runtime. The tier covers
 | [`react-dom`](https://github.com/facebook/react) | MIT |
 | [`readable-stream`](https://github.com/nodejs/readable-stream) | MIT |
 | [`resolve.exports`](https://github.com/lukeed/resolve.exports) | MIT |
+| [`semver`](https://github.com/npm/node-semver) | ISC |
 | [`sharp`](https://github.com/lovell/sharp) | Apache-2.0 |
 | [`shiki`](https://github.com/shikijs/shiki) | MIT |
 | [`supports-color`](https://github.com/chalk/supports-color) | MIT |
@@ -140,7 +142,9 @@ External packages **directly declared** only by repository tooling, test infrast
 
 | Package | License |
 | --- | --- |
+| [`@aws-sdk/client-s3`](https://github.com/aws/aws-sdk-js-v3) | Apache-2.0 |
 | [`@braintree/sanitize-url`](https://github.com/braintree/sanitize-url) | MIT |
+| [`@electron/notarize`](https://github.com/electron/notarize) | MIT |
 | [`@lexical/headless`](https://github.com/facebook/lexical) | MIT |
 | [`@modelcontextprotocol/server-everything`](https://github.com/modelcontextprotocol/servers) | MIT / Apache-2.0 |
 | [`@modelcontextprotocol/server-filesystem`](https://github.com/modelcontextprotocol/servers) | MIT / Apache-2.0 |
@@ -158,6 +162,7 @@ External packages **directly declared** only by repository tooling, test infrast
 | [`@types/react`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT |
 | [`@types/react-dom`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT |
 | [`@types/readable-stream`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT |
+| [`@types/semver`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT |
 | [`@types/spdx-expression-parse`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT |
 | [`@types/turndown`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT |
 | [`@types/use-sync-external-store`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT |
@@ -166,13 +171,17 @@ External packages **directly declared** only by repository tooling, test infrast
 | [`@vitest/coverage-v8`](https://github.com/vitest-dev/vitest) | MIT |
 | [`@yao-pkg/pkg`](https://github.com/yao-pkg/pkg) | MIT |
 | [`@yarnpkg/cli-dist`](https://github.com/yarnpkg/berry) | BSD-2-Clause |
+| [`app-builder-lib`](https://github.com/electron-userland/electron-builder) | MIT |
 | [`cytoscape`](https://github.com/cytoscape/cytoscape.js) | MIT |
 | [`cytoscape-cose-bilkent`](https://github.com/cytoscape/cytoscape.js-cose-bilkent) | MIT |
 | [`dayjs`](https://github.com/iamkun/dayjs) | MIT |
 | [`debug`](https://github.com/debug-js/debug) | MIT |
+| [`electron`](https://github.com/electron/electron) | MIT |
+| [`electron-builder`](https://github.com/electron-userland/electron-builder) | MIT |
 | [`esbuild`](https://github.com/evanw/esbuild) | MIT |
 | [`eslint-plugin-sonarjs`](https://github.com/SonarSource/SonarJS) | LGPL-3.0-only |
 | [`execa`](https://github.com/sindresorhus/execa) | MIT |
+| [`extract-zip`](https://github.com/maxogden/extract-zip) | BSD-2-Clause |
 | [`fast-check`](https://github.com/dubzzz/fast-check) | MIT |
 | [`http-server`](https://github.com/http-party/http-server) | MIT |
 | [`istanbul-lib-report`](https://github.com/istanbuljs/istanbuljs) | BSD-3-Clause |
@@ -181,12 +190,15 @@ External packages **directly declared** only by repository tooling, test infrast
 | [`lefthook`](https://github.com/evilmartians/lefthook) | MIT |
 | [`lightningcss`](https://github.com/parcel-bundler/lightningcss) | MPL-2.0 |
 | [`mermaid`](https://github.com/mermaid-js/mermaid) | MIT |
+| [`msgpackr`](http://github.com/kriszyp/msgpackr) | MIT |
 | [`oxlint`](https://github.com/oxc-project/oxc) | MIT |
 | [`oxlint-tsgolint`](https://github.com/oxc-project/tsgolint) | MIT |
 | [`playwright`](https://github.com/microsoft/playwright) | Apache-2.0 |
+| [`pnpm`](https://github.com/pnpm/pnpm) | MIT |
 | [`publint`](https://github.com/publint/publint) | MIT |
 | [`smol-toml`](https://github.com/squirrelchat/smol-toml) | BSD-3-Clause |
 | [`spdx-expression-parse`](https://github.com/jslicense/spdx-expression-parse.js) | MIT |
+| [`tar`](https://github.com/isaacs/node-tar) | BlueOak-1.0.0 |
 | [`tsdown`](https://github.com/rolldown/tsdown) | MIT |
 | [`typescript-language-server`](https://github.com/typescript-language-server/typescript-language-server) | Apache-2.0 |
 | [`vite`](https://github.com/vitejs/vite) | MIT |

+ 2 - 2
apps/cli/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write apps/cli/README.md
-README.md: 850a9371c1515d1e0219e9a685b638063c498ff4
-README.zh.md: 02de213d7a7158971b445511a00661183b37234c
+README.md: adab66bb1d7ed46039248a57c8722964f5aebd33
+README.zh.md: 4887a67872a569a0a54f0378aaceffe51cdc43c1

+ 1 - 1
apps/cli/README.md

@@ -16,7 +16,7 @@ The `dsh` command is the sole supported Node application launcher: profiles are
 | `dsh web` | Alias of `--profile web`. |
 | `dsh plugin --profile <name> <pnpm args>` | Manage a profile's plugins by forwarding to pnpm in the profile directory. |
 
-The invoking directory is the default workspace root. The `web`, `headless`, `sdk`, `sdk-minimal`, and `acp` profiles auto-initialize on first use from shipped templates; any other profile must be created through `dsh plugin`.
+The invoking directory is the default workspace root. The `web`, `headless`, `sdk`, `sdk-minimal`, and `acp` profiles auto-initialize on first use from shipped templates; any other profile must be created through `dsh plugin`. The `desktop` name is reserved for the Electron-owned profile, so the CLI rejects boot, config-dump, and plugin-management requests for it.
 
 ## App arguments
 

+ 1 - 1
apps/cli/README.zh.md

@@ -16,7 +16,7 @@
 | `dsh web` | `--profile web` 的别名。 |
 | `dsh plugin --profile <name> <pnpm args>` | 通过在 profile 目录中转发给 pnpm 来管理该 profile 的插件。 |
 
-运行命令时所在的目录将作为默认 workspace 根目录。`web`、`headless`、`sdk`、`sdk-minimal` 和 `acp` profile 在首次使用时会从随附模板自动初始化;其他任何 profile 都必须通过 `dsh plugin` 创建。
+运行命令时所在的目录将作为默认 workspace 根目录。`web`、`headless`、`sdk`、`sdk-minimal` 和 `acp` profile 在首次使用时会从随附模板自动初始化;其他任何 profile 都必须通过 `dsh plugin` 创建。`desktop` 名称保留给 Electron 持有的 profile,因此 CLI 会拒绝针对它的启动、配置 dump 和插件管理请求。
 
 ## 应用参数
 

+ 8 - 0
apps/cli/src/args.ts

@@ -60,6 +60,12 @@ interface BootOptions {
  */
 const collect = (value: string, previous: string[] = []): string[] => [...previous, value]
 
+function rejectElectronProfile(program: Command, profile: string): void {
+  if (profile.toLowerCase() === 'desktop') {
+    program.error('error: profile "desktop" is managed exclusively by the Electron application')
+  }
+}
+
 /** The launcher's own help text; each app prints its own. */
 const HELP_EXAMPLES = `
 Examples:
@@ -141,6 +147,7 @@ export function parseDshArgs(argv: readonly string[], version: string): DshInvoc
       }
       const profile = options.profile
       if (profile === '') program.error('error: --profile needs a name')
+      rejectElectronProfile(program, profile)
       resolved = resolveBoot(program, profile, options, args)
     })
 
@@ -176,6 +183,7 @@ export function parseDshArgs(argv: readonly string[], version: string): DshInvoc
     .action((args: string[], options: { profile: string }) => {
       rejectParentOptions('plugin')
       if (options.profile === '') program.error('error: --profile needs a name')
+      rejectElectronProfile(plugin, options.profile)
       if (args.length === 0) program.error('error: plugin needs pnpm arguments to forward (e.g. add <package>)')
       resolved = { mode: 'plugin', profile: options.profile, args }
     })

+ 6 - 0
apps/cli/tests/args.spec.ts

@@ -95,6 +95,12 @@ describe('parseDshArgs', () => {
     expect(exitCode(['plugin', 'add', 'x'])).toBe(1) // --profile required
     expect(exitCode(['plugin', '--profile', 'tui'])).toBe(1) // nothing to forward
     expect(exitCode(['plugin', '--profile', ''])).toBe(1)
+    expect(exitCode(['--profile', 'desktop'])).toBe(1)
+    expect(exitCode(['--profile', 'Desktop'])).toBe(1)
+    expect(exitCode(['--profile', 'DESKTOP'])).toBe(1)
+    expect(exitCode(['--profile', 'desktop', '--dump-config'])).toBe(1)
+    expect(exitCode(['plugin', '--profile', 'desktop', 'add', 'x'])).toBe(1)
+    expect(exitCode(['plugin', '--profile', 'Desktop', 'add', 'x'])).toBe(1)
     expect(exitCode(['--profile', 'x', 'plugin', 'add', 'y'])).toBe(1)
   })
 

+ 34 - 0
apps/desktop-host/config/desktop.cordis.patch.yml

@@ -0,0 +1,34 @@
+# Electron reuses the browser composition without its network and browser-launch rows.
+
+- id: web-startup
+  disabled: true
+
+- id: webserver
+  disabled: true
+
+- id: web-runtime
+  disabled: true
+
+- id: client-hmr
+  disabled: true
+
+- id: open-in-app
+  disabled: true
+
+- id: ui-open-in-app
+  disabled: true
+
+- id: directory-picker
+  disabled: true
+
+- id: connection
+  inject:
+    - credentials
+  config: {}
+
+- insert:
+    - id: directory-picker-native
+      name: '@deepseek-ai/dsh-host-directory-picker-native'
+
+    - id: ui-directory-picker-native
+      name: '@deepseek-ai/dsh-client-ui-directory-picker-native'

+ 28 - 0
apps/desktop-host/package.json

@@ -0,0 +1,28 @@
+{
+  "name": "@deepseek-ai/dsh-desktop-host",
+  "description": "Private upstream-Node host process for the Electron desktop application",
+  "version": "0.1.3-alpha.2",
+  "private": true,
+  "license": "MIT",
+  "type": "module",
+  "main": "lib/index.js",
+  "files": [
+    "lib/index.js",
+    "config/desktop.cordis.patch.yml"
+  ],
+  "dependencies": {
+    "@deepseek-ai/cordis": "workspace:^",
+    "@deepseek-ai/cordis-plugin-include": "workspace:^",
+    "@deepseek-ai/dsh": "workspace:^",
+    "@deepseek-ai/dsh-api-gateway": "workspace:^",
+    "@deepseek-ai/dsh-app-boot": "workspace:^",
+    "@deepseek-ai/dsh-client-connection": "workspace:^",
+    "@deepseek-ai/dsh-client-modules": "workspace:^",
+    "@deepseek-ai/dsh-client-ui-directory-picker-native": "workspace:^",
+    "@deepseek-ai/dsh-cmdline": "workspace:^",
+    "@deepseek-ai/dsh-host-directory-picker-native": "workspace:^",
+    "@deepseek-ai/dsh-host-webserver": "workspace:^",
+    "@deepseek-ai/dsh-launch-environment": "workspace:^",
+    "@deepseek-ai/dsh-web-frontend": "workspace:^"
+  }
+}

+ 587 - 0
apps/desktop-host/src/index.ts

@@ -0,0 +1,587 @@
+/**
+ * Electron child-process entry: boots the desktop project without a listening
+ * socket and carries API plus validated Web assets over framed byte pipes.
+ * @module @deepseek-ai/dsh-desktop-host
+ */
+
+import { createRequire } from 'node:module'
+import { closeSync, createReadStream, createWriteStream, existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs'
+import { once } from 'node:events'
+import { readFile } from 'node:fs/promises'
+import { dirname, extname, join, normalize, resolve, sep } from 'node:path'
+import { fileURLToPath } from 'node:url'
+import type { Context } from '@deepseek-ai/cordis'
+import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include'
+import {
+  boot,
+  composeEntries,
+  loadLayeredEnv,
+  loadProfileDirectory,
+  loadOverlayPatches,
+} from '@deepseek-ai/dsh-app-boot'
+import { provideCmdline } from '@deepseek-ai/dsh-cmdline'
+import { DSH_LAUNCH_ENVIRONMENT_KEY } from '@deepseek-ai/dsh-launch-environment'
+import type {} from '@deepseek-ai/dsh-api-gateway'
+import type { ConnectionFetchHandler } from '@deepseek-ai/dsh-client-connection'
+import type {} from '@deepseek-ai/dsh-client-modules'
+import { renderIndexInjections, type IndexInjection } from '@deepseek-ai/dsh-host-webserver'
+import {
+  DESKTOP_HOST_PROTOCOL_VERSION,
+  DESKTOP_PIPE_CHUNK_BYTES,
+  DESKTOP_REQUEST_PIPE_FD,
+  DESKTOP_RESPONSE_PIPE_FD,
+  DesktopHostRequestDecoder,
+  encodeDesktopResponseData,
+  encodeDesktopResponseEnd,
+  encodeDesktopResponseError,
+  encodeDesktopResponseStart,
+  type DesktopHostRequestFrame,
+} from './wire.ts'
+
+export { DESKTOP_HOST_PROTOCOL_VERSION } from './wire.ts'
+
+/** One request forwarded from Electron's `dsh-app://` handler. */
+export interface DesktopHostFetchCommand {
+  readonly streamId: number
+  readonly request: {
+    readonly url: string
+    readonly method: string
+    readonly headers: readonly [string, string][]
+  }
+}
+
+/** Commands accepted by the desktop child process. */
+export type DesktopHostCommand = {
+  readonly type: 'shutdown'
+}
+
+/** Events emitted by the desktop child process. */
+export type DesktopHostEvent = {
+  readonly type: 'ready'
+  readonly protocolVersion: typeof DESKTOP_HOST_PROTOCOL_VERSION
+  readonly dshVersion: string
+} | {
+  readonly type: 'fatal'
+  readonly message: string
+}
+
+/** Controller returned to tests and the self-executing process entry. */
+export interface DesktopHostController {
+  /** Installed dsh version carried by this host. */
+  readonly dshVersion: string
+  /** Dispatch one custom-protocol request and stream its response to the response pipe. */
+  fetch(command: DesktopHostFetchCommand, body: ReadableStream<Uint8Array> | null): Promise<void>
+  /** Abort one in-flight request. */
+  cancel(streamId: number): void
+  /** Stop accepting messages and await complete host teardown. */
+  dispose(): Promise<void>
+}
+
+function isRecord(value: unknown): value is Record<string, unknown> {
+  return typeof value === 'object' && value !== null
+}
+
+function isDesktopHostCommand(message: unknown): message is DesktopHostCommand {
+  return typeof message === 'object' && message !== null && 'type' in message
+    && (message as Record<string, unknown>).type === 'shutdown'
+}
+
+interface PackageManifest {
+  readonly name?: string
+  readonly version?: string
+}
+
+const DESKTOP_PATCH = fileURLToPath(new URL('../config/desktop.cordis.patch.yml', import.meta.url))
+const ROOT_CONFIG = '# Electron desktop composition root; package transactions own this file.\n[]\n'
+const ROOT_CONFIG_FILENAME = 'desktop.cordis.yml'
+const DESKTOP_STREAM_PATH = '/.dsh/remote-stream'
+
+const DESKTOP_TRANSPORT_SCRIPT = `globalThis.__DSH_TRANSPORT__={
+  ownsHost:true,
+  async *openStream(endpoint,payload,signal){
+    const response=await fetch(${JSON.stringify(DESKTOP_STREAM_PATH)},{
+      method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({endpoint,payload}),signal
+    })
+    if(!response.ok||response.body===null)throw new Error('desktop stream transport failed: HTTP '+response.status)
+    const reader=response.body.getReader(),decoder=new TextDecoder()
+    let pending=''
+    for(;;){
+      const {done,value}=await reader.read()
+      pending+=decoder.decode(value,{stream:!done})
+      let newline
+      while((newline=pending.indexOf('\\n'))!==-1){
+        const line=pending.slice(0,newline);pending=pending.slice(newline+1)
+        if(line!=='')yield JSON.parse(line)
+      }
+      if(done)break
+    }
+    if(pending!=='')yield JSON.parse(pending)
+  }
+}`
+
+const MIME: Readonly<Record<string, string>> = {
+  '.css': 'text/css; charset=utf-8',
+  '.html': 'text/html; charset=utf-8',
+  '.js': 'text/javascript; charset=utf-8',
+  '.json': 'application/json',
+  '.svg': 'image/svg+xml',
+  '.webmanifest': 'application/manifest+json',
+}
+
+function readManifest(path: string): PackageManifest {
+  const value: unknown = JSON.parse(readFileSync(path, 'utf8'))
+  if (!isRecord(value)) throw new Error(`dsh desktop: ${path} must contain a package manifest`)
+  return {
+    ...(typeof value.name === 'string' ? { name: value.name } : {}),
+    ...(typeof value.version === 'string' ? { version: value.version } : {}),
+  }
+}
+
+function packageManifestPath(projectDir: string, packageName: string): string {
+  const path = join(projectDir, 'node_modules', ...packageName.split('/'), 'package.json')
+  if (!existsSync(path)) throw new Error(`dsh desktop: installed package ${JSON.stringify(packageName)} has no manifest`)
+  return path
+}
+
+function isProjectPath(projectDir: string, target: string): boolean {
+  const root = realpathSync(projectDir)
+  const path = realpathSync(target)
+  return path === root || path.startsWith(root + sep)
+}
+
+function desktopPatches(projectDir: string, allowLinkedPackages: boolean): PatchOptions[] {
+  const dshRoot = dirname(packageManifestPath(projectDir, '@deepseek-ai/dsh'))
+  const profile = loadProfileDirectory('dsh desktop', projectDir, join(dshRoot, 'package.json'))
+  for (const layer of profile.layers) {
+    if (!allowLinkedPackages && !isProjectPath(projectDir, layer.packageDir)) {
+      throw new Error(`dsh desktop: profile bundle ${JSON.stringify(layer.packageName)} resolved outside the desktop profile`)
+    }
+  }
+  const layers = [
+    ...profile.layers.map(layer => layer.patches),
+    profile.patches,
+    loadOverlayPatches('dsh desktop', DESKTOP_PATCH),
+  ]
+  const rows = new Map(composeEntries(layers).flatMap(row => typeof row.id === 'string' ? [[row.id, row] as const] : []))
+  const agentPresets = rows.get('agent-presets')
+  if (agentPresets !== undefined) {
+    layers.push([{
+      id: 'agent-presets',
+      config: {
+        ...(agentPresets.config ?? {}) as Record<string, unknown>,
+        roots: [{ path: join(dshRoot, 'config', 'agent-presets'), trust: 'system' }],
+      },
+    }])
+  }
+  return layers.flat()
+}
+
+function dshVersion(projectDir: string): string {
+  const manifest = readManifest(packageManifestPath(projectDir, '@deepseek-ai/dsh'))
+  if (typeof manifest.version !== 'string') throw new Error('dsh desktop: installed dsh manifest has no version')
+  return manifest.version
+}
+
+function assetHandler(ctx: Context, projectDir: string): ConnectionFetchHandler {
+  const require = createRequire(join(projectDir, 'package.json'))
+  const distIndex = require.resolve('@deepseek-ai/dsh-web-frontend/dist/index.html')
+  const distRoot = realpathSync(dirname(distIndex))
+  const renderIndex = async (): Promise<Response> => {
+    const rows: IndexInjection[] = [{ kind: 'script', placement: 'head', text: DESKTOP_TRANSPORT_SCRIPT }]
+    ctx.emit('webserver/index-inject', rows)
+    const body = renderIndexInjections(await readFile(distIndex, 'utf8'), rows)
+    return new Response(body, { headers: { 'content-type': MIME['.html'] ?? 'text/html; charset=utf-8' } })
+  }
+  return {
+    requestBodyMode: () => 'buffered',
+    async fetch(request): Promise<Response> {
+      if (request.method !== 'GET' && request.method !== 'HEAD') return new Response(null, { status: 405 })
+      const url = new URL(request.url)
+      if (url.pathname.startsWith('/plugins/')) return ctx.clientModules.fetchBundle(request)
+      let pathname: string
+      try {
+        pathname = decodeURIComponent(url.pathname)
+      } catch {
+        return new Response(null, { status: 400 })
+      }
+      if (pathname === '/' || pathname === '/index.html') return renderIndex()
+      const target = resolve(normalize(join(distRoot, pathname)))
+      if (target !== distRoot && !target.startsWith(distRoot + sep)) return new Response(null, { status: 403 })
+      try {
+        const realTarget = realpathSync(target)
+        if (realTarget !== distRoot && !realTarget.startsWith(distRoot + sep)) return new Response(null, { status: 403 })
+        return new Response(request.method === 'HEAD' ? null : await readFile(realTarget), {
+          headers: { 'content-type': MIME[extname(realTarget)] ?? 'application/octet-stream' },
+        })
+      } catch {
+        return renderIndex()
+      }
+    },
+  }
+}
+
+function remoteStreamHandler(ctx: Context): ConnectionFetchHandler {
+  return {
+    requestBodyMode: () => 'buffered',
+    async fetch(request): Promise<Response> {
+      if (request.method !== 'POST') return new Response(null, { status: 405 })
+      const gateway = ctx.get('typertGateway')
+      if (gateway === undefined) return new Response('gateway unavailable', { status: 503 })
+      let body: unknown
+      try {
+        body = await request.json()
+      } catch {
+        return new Response('body is not JSON', { status: 400 })
+      }
+      if (!isRecord(body) || typeof body.endpoint !== 'string') {
+        return new Response('invalid stream request', { status: 400 })
+      }
+      const abort = new AbortController()
+      const cancel = (): void => { abort.abort(request.signal.reason) }
+      request.signal.addEventListener('abort', cancel, { once: true })
+      const encoder = new TextEncoder()
+      const stream = new ReadableStream<Uint8Array>({
+        async start(controller) {
+          try {
+            const values = await gateway.wireStream.open(body.endpoint as string, body.payload, abort.signal)
+            for await (const value of values) {
+              controller.enqueue(encoder.encode(`${JSON.stringify(value)}\n`))
+            }
+            controller.close()
+          } catch (error) {
+            controller.error(error)
+          } finally {
+            request.signal.removeEventListener('abort', cancel)
+          }
+        },
+        cancel(reason) {
+          abort.abort(reason)
+          request.signal.removeEventListener('abort', cancel)
+        },
+      })
+      return new Response(stream, { headers: { 'content-type': 'application/x-ndjson' } })
+    },
+  }
+}
+
+interface NodeRequestInit extends RequestInit {
+  readonly duplex?: 'half'
+}
+
+/**
+ * Boot one installed desktop npm project.
+ * @param projectDir - active or staged Electron-owned desktop profile.
+ * @param writeResponse - serialized response-pipe writer that applies byte backpressure.
+ * @param options - development-only allowance for workspace-linked bundle packages.
+ * @returns controller after every Host and client-manifest row is active.
+ */
+export async function runDesktopHost(
+  projectDir: string,
+  writeResponse: (frame: Buffer) => Promise<void>,
+  options: { allowLinkedPackages?: boolean } = {},
+): Promise<DesktopHostController> {
+  const absoluteProject = resolve(projectDir)
+  mkdirSync(absoluteProject, { recursive: true })
+  const rootConfig = join(absoluteProject, ROOT_CONFIG_FILENAME)
+  writeFileSync(rootConfig, ROOT_CONFIG)
+  const environment = loadLayeredEnv('dsh desktop')
+  let current: Context | undefined
+  const ctx = await boot('dsh desktop', rootConfig, structuredClone(desktopPatches(
+    absoluteProject,
+    options.allowLinkedPackages === true,
+  )), (hostCtx) => {
+    current = hostCtx
+    hostCtx.provide(DSH_LAUNCH_ENVIRONMENT_KEY, environment)
+    provideCmdline(hostCtx, { args: [], exit: () => {} })
+  })
+  current = ctx
+  const connection = ctx.get('connection')
+  const clientModules = ctx.get('clientModules')
+  const gateway = ctx.get('typertGateway')
+  if (connection === undefined || clientModules === undefined || gateway === undefined) {
+    await ctx.fiber.dispose()
+    throw new Error('dsh desktop: composition did not provide connection, typertGateway, and clientModules')
+  }
+  const api = connection.createSharedFetchHandler('/api')
+  const assets = assetHandler(ctx, absoluteProject)
+  const streams = remoteStreamHandler(ctx)
+  const requests = new Map<number, AbortController>()
+  let disposing: Promise<void> | undefined
+
+  const dispose = async (): Promise<void> => {
+    disposing ??= (async () => {
+      for (const controller of requests.values()) controller.abort()
+      requests.clear()
+      await current?.fiber.dispose()
+      current = undefined
+    })()
+    await disposing
+  }
+
+  return {
+    dshVersion: dshVersion(absoluteProject),
+    cancel(streamId) {
+      requests.get(streamId)?.abort()
+    },
+    async fetch(command, body) {
+      if (disposing !== undefined) throw new Error('dsh desktop: host is disposing')
+      const controller = new AbortController()
+      requests.set(command.streamId, controller)
+      try {
+        const url = new URL(command.request.url)
+        const init: NodeRequestInit = {
+          method: command.request.method,
+          headers: new Headers(command.request.headers.map(([name, value]) => [name, value] as [string, string])),
+          ...(body === null ? {} : { body, duplex: 'half' }),
+          signal: controller.signal,
+        }
+        const request = new Request(url, init)
+        const response = url.pathname === DESKTOP_STREAM_PATH
+          ? await streams.fetch(request)
+          : url.pathname.startsWith('/api/')
+            ? await api.fetch(request)
+            : await assets.fetch(request)
+        await writeResponse(encodeDesktopResponseStart(command.streamId, {
+          status: response.status,
+          headers: [...response.headers.entries()],
+          hasBody: response.body !== null,
+        }))
+        if (response.body !== null) {
+          for await (const chunk of response.body) {
+            const bytes = Buffer.from(chunk)
+            for (let offset = 0; offset < bytes.byteLength; offset += DESKTOP_PIPE_CHUNK_BYTES) {
+              await writeResponse(encodeDesktopResponseData(
+                command.streamId,
+                bytes.subarray(offset, offset + DESKTOP_PIPE_CHUNK_BYTES),
+              ))
+            }
+          }
+        }
+        await writeResponse(encodeDesktopResponseEnd(command.streamId))
+      } catch (error) {
+        if (!controller.signal.aborted) {
+          await writeResponse(encodeDesktopResponseError(
+            command.streamId,
+            error instanceof Error ? error.message : String(error),
+          ))
+        }
+      } finally {
+        requests.delete(command.streamId)
+      }
+    },
+    dispose,
+  }
+}
+
+async function main(): Promise<void> {
+  const projectDir = process.argv[2]
+  if (projectDir === undefined || process.send === undefined) {
+    throw new Error('dsh desktop: expected project directory, byte pipes, and a Node IPC channel')
+  }
+  const option = process.argv[3]
+  if (option !== undefined && option !== '--allow-linked-profile') {
+    throw new Error(`dsh desktop: unsupported internal option ${JSON.stringify(option)}`)
+  }
+  const requestPipe = createReadStream('', { fd: DESKTOP_REQUEST_PIPE_FD, autoClose: false })
+  const responsePipe = createWriteStream('', { fd: DESKTOP_RESPONSE_PIPE_FD, autoClose: false })
+  let responseWriteTail: Promise<void> = Promise.resolve()
+  const writeResponse = (frame: Buffer): Promise<void> => {
+    const write = responseWriteTail.then(async () => {
+      if (responsePipe.destroyed) throw new Error('dsh desktop: Electron response pipe is unavailable')
+      if (!responsePipe.write(frame)) await once(responsePipe, 'drain')
+    })
+    responseWriteTail = write.catch(() => undefined)
+    return write
+  }
+  const send = (event: DesktopHostEvent): void => {
+    if (process.send === undefined || !process.connected) return
+    try {
+      process.send(event)
+    } catch (error) {
+      // A concurrent parent disconnect owns teardown; only that closed-channel
+      // condition is safe to discard while streamed responses unwind.
+      if ((error as NodeJS.ErrnoException).code !== 'ERR_IPC_CHANNEL_CLOSED') throw error
+    }
+  }
+  const controller = await runDesktopHost(projectDir, writeResponse, { allowLinkedPackages: option !== undefined })
+  send({
+    type: 'ready',
+    protocolVersion: DESKTOP_HOST_PROTOCOL_VERSION,
+    dshVersion: controller.dshVersion,
+  })
+  const decoder = new DesktopHostRequestDecoder()
+  const requestBodies = new Map<number, ReadableStreamDefaultController<Uint8Array>>()
+  const blockedRequests = new Set<number>()
+  const discardedRequestBodies = new Set<number>()
+  const runs = new Set<Promise<void>>()
+  let lastStreamId = 0
+  let requestedExitCode = 0
+  let stopping: Promise<void> | undefined
+
+  const resumeRequestPipe = (): void => {
+    if (blockedRequests.size === 0) requestPipe.resume()
+  }
+
+  const stop = (exitCode = 0): Promise<void> => {
+    requestedExitCode = Math.max(requestedExitCode, exitCode)
+    stopping ??= (async () => {
+      requestPipe.pause()
+      requestPipe.removeAllListeners('data')
+      const stopped = new Error('dsh desktop: Host is stopping')
+      for (const body of requestBodies.values()) body.error(stopped)
+      requestBodies.clear()
+      blockedRequests.clear()
+      discardedRequestBodies.clear()
+      requestPipe.destroy()
+      closeSync(DESKTOP_REQUEST_PIPE_FD)
+      await controller.dispose()
+      await Promise.allSettled([...runs])
+      await responseWriteTail.catch(() => undefined)
+      if (!responsePipe.destroyed) {
+        await new Promise<void>((resolvePromise) => { responsePipe.end(resolvePromise) })
+        responsePipe.destroy()
+      }
+      closeSync(DESKTOP_RESPONSE_PIPE_FD)
+      if (process.connected) process.disconnect()
+      process.exitCode = requestedExitCode
+    })()
+    return stopping
+  }
+
+  const failTransport = (error: unknown): void => {
+    const message = error instanceof Error ? error.message : String(error)
+    send({ type: 'fatal', message })
+    void stop(1)
+  }
+
+  const beginRequest = (frame: Extract<DesktopHostRequestFrame, { type: 'start' }>): void => {
+    if (frame.streamId <= lastStreamId) {
+      throw new Error(`dsh desktop: Electron reused or reordered request stream ${String(frame.streamId)}`)
+    }
+    lastStreamId = frame.streamId
+    let body: ReadableStream<Uint8Array> | null = null
+    if (frame.hasBody) {
+      body = new ReadableStream<Uint8Array>({
+        start(controllerOfBody) {
+          requestBodies.set(frame.streamId, controllerOfBody)
+        },
+        pull() {
+          blockedRequests.delete(frame.streamId)
+          resumeRequestPipe()
+        },
+        cancel() {
+          requestBodies.delete(frame.streamId)
+          blockedRequests.delete(frame.streamId)
+          controller.cancel(frame.streamId)
+          resumeRequestPipe()
+        },
+      })
+    }
+    const run = controller.fetch({
+      streamId: frame.streamId,
+      request: {
+        url: frame.url,
+        method: frame.method,
+        headers: frame.headers,
+      },
+    }, body)
+    runs.add(run)
+    void run.catch(failTransport).finally(() => {
+      runs.delete(run)
+      const openBody = requestBodies.get(frame.streamId)
+      if (openBody === undefined) return
+      openBody.error(new Error('dsh desktop: response completed before the request body ended'))
+      requestBodies.delete(frame.streamId)
+      blockedRequests.delete(frame.streamId)
+      discardedRequestBodies.add(frame.streamId)
+      resumeRequestPipe()
+    })
+  }
+
+  const handleRequestFrame = (frame: DesktopHostRequestFrame): void => {
+    switch (frame.type) {
+      case 'start':
+        beginRequest(frame)
+        return
+      case 'data': {
+        const body = requestBodies.get(frame.streamId)
+        if (body === undefined) {
+          if (discardedRequestBodies.has(frame.streamId)) return
+          throw new Error(`dsh desktop: Electron sent body data for inactive stream ${String(frame.streamId)}`)
+        }
+        body.enqueue(frame.data)
+        if ((body.desiredSize ?? 0) <= 0) {
+          blockedRequests.add(frame.streamId)
+          requestPipe.pause()
+        }
+        return
+      }
+      case 'end': {
+        const body = requestBodies.get(frame.streamId)
+        if (body === undefined) {
+          if (discardedRequestBodies.delete(frame.streamId)) return
+          throw new Error(`dsh desktop: Electron ended inactive body stream ${String(frame.streamId)}`)
+        }
+        body.close()
+        requestBodies.delete(frame.streamId)
+        blockedRequests.delete(frame.streamId)
+        resumeRequestPipe()
+        return
+      }
+      case 'cancel': {
+        if (frame.streamId > lastStreamId) {
+          throw new Error(`dsh desktop: Electron canceled unknown stream ${String(frame.streamId)}`)
+        }
+        const body = requestBodies.get(frame.streamId)
+        body?.error(new Error('dsh desktop: Electron canceled the request'))
+        requestBodies.delete(frame.streamId)
+        blockedRequests.delete(frame.streamId)
+        discardedRequestBodies.delete(frame.streamId)
+        controller.cancel(frame.streamId)
+        resumeRequestPipe()
+        return
+      }
+      default:
+        frame satisfies never
+    }
+  }
+
+  requestPipe.on('data', (chunk: string | Buffer) => {
+    try {
+      for (const frame of decoder.push(Buffer.from(chunk))) handleRequestFrame(frame)
+    } catch (error) {
+      failTransport(error)
+    }
+  })
+  requestPipe.once('end', () => {
+    if (stopping !== undefined) return
+    try {
+      decoder.finish()
+      failTransport(new Error('dsh desktop: Electron request pipe ended'))
+    } catch (error) {
+      failTransport(error)
+    }
+  })
+  requestPipe.once('error', failTransport)
+  responsePipe.once('error', failTransport)
+  process.on('message', (message: unknown) => {
+    if (!isDesktopHostCommand(message)) {
+      send({ type: 'fatal', message: 'dsh desktop: invalid Electron IPC command' })
+      void stop(1)
+      return
+    }
+    void stop()
+  })
+  process.once('disconnect', () => { void stop() })
+  process.once('SIGTERM', () => { void stop() })
+  process.once('SIGINT', () => { void stop() })
+}
+
+if (import.meta.main) {
+  main().catch((error: unknown) => {
+    const message = error instanceof Error ? error.message : String(error)
+    if (process.send !== undefined) process.send({ type: 'fatal', message } satisfies DesktopHostEvent)
+    else process.stderr.write(`dsh desktop: ${message}\n`)
+    process.exitCode = 1
+  })
+}

+ 184 - 0
apps/desktop-host/src/wire.ts

@@ -0,0 +1,184 @@
+/** Framed request and response bytes for the Electron Desktop Host transport. */
+
+/** Protocol version shared with the Electron shell. */
+export const DESKTOP_HOST_PROTOCOL_VERSION = 3 as const
+
+/** Child descriptor that receives Electron request frames. */
+export const DESKTOP_REQUEST_PIPE_FD = 3
+
+/** Child descriptor that emits Host response frames. */
+export const DESKTOP_RESPONSE_PIPE_FD = 4
+
+/** Maximum raw body bytes carried by one data frame. */
+export const DESKTOP_PIPE_CHUNK_BYTES = 64 * 1024
+
+const FRAME_MAGIC = 0x44534833
+const FRAME_HEADER_BYTES = 13
+const MAX_CONTROL_PAYLOAD_BYTES = 1024 * 1024
+
+const REQUEST_FRAME_START = 1
+const REQUEST_FRAME_DATA = 2
+const REQUEST_FRAME_END = 3
+const REQUEST_FRAME_CANCEL = 4
+
+const RESPONSE_FRAME_START = 1
+const RESPONSE_FRAME_DATA = 2
+const RESPONSE_FRAME_END = 3
+const RESPONSE_FRAME_ERROR = 4
+type ResponseFrameType = typeof RESPONSE_FRAME_START | typeof RESPONSE_FRAME_DATA
+  | typeof RESPONSE_FRAME_END | typeof RESPONSE_FRAME_ERROR
+
+/** One validated request-pipe frame. */
+export type DesktopHostRequestFrame = {
+  readonly type: 'start'
+  readonly streamId: number
+  readonly url: string
+  readonly method: string
+  readonly headers: readonly [string, string][]
+  readonly hasBody: boolean
+} | {
+  readonly type: 'data'
+  readonly streamId: number
+  readonly data: Buffer
+} | {
+  readonly type: 'end' | 'cancel'
+  readonly streamId: number
+}
+
+function isRecord(value: unknown): value is Record<string, unknown> {
+  return typeof value === 'object' && value !== null
+}
+
+function isHeaders(value: unknown): value is readonly [string, string][] {
+  return Array.isArray(value) && value.every(header => Array.isArray(header) && header.length === 2
+    && typeof header[0] === 'string' && typeof header[1] === 'string')
+}
+
+function assertStreamId(streamId: number): void {
+  if (!Number.isInteger(streamId) || streamId < 1 || streamId > 0xffff_ffff) {
+    throw new Error(`dsh desktop: invalid pipe stream id ${String(streamId)}`)
+  }
+}
+
+function encodeFrame(type: ResponseFrameType, streamId: number, payload: Buffer): Buffer {
+  assertStreamId(streamId)
+  const limit = type === RESPONSE_FRAME_DATA ? DESKTOP_PIPE_CHUNK_BYTES : MAX_CONTROL_PAYLOAD_BYTES
+  if (payload.byteLength > limit) {
+    throw new Error(`dsh desktop: response pipe frame exceeds the ${String(limit)}-byte limit`)
+  }
+  const frame = Buffer.allocUnsafe(FRAME_HEADER_BYTES + payload.byteLength)
+  frame.writeUInt32BE(FRAME_MAGIC, 0)
+  frame.writeUInt8(type, 4)
+  frame.writeUInt32BE(streamId, 5)
+  frame.writeUInt32BE(payload.byteLength, 9)
+  payload.copy(frame, FRAME_HEADER_BYTES)
+  return frame
+}
+
+function encodeJsonFrame(type: ResponseFrameType, streamId: number, value: unknown): Buffer {
+  return encodeFrame(type, streamId, Buffer.from(JSON.stringify(value), 'utf8'))
+}
+
+/** Encode response metadata before any body frames. */
+export function encodeDesktopResponseStart(
+  streamId: number,
+  response: {
+    readonly status: number
+    readonly headers: readonly [string, string][]
+    readonly hasBody: boolean
+  },
+): Buffer {
+  return encodeJsonFrame(RESPONSE_FRAME_START, streamId, response)
+}
+
+/** Encode one bounded raw response-body chunk. */
+export function encodeDesktopResponseData(streamId: number, data: Uint8Array): Buffer {
+  return encodeFrame(RESPONSE_FRAME_DATA, streamId, Buffer.from(data))
+}
+
+/** Encode normal response completion. */
+export function encodeDesktopResponseEnd(streamId: number): Buffer {
+  return encodeFrame(RESPONSE_FRAME_END, streamId, Buffer.alloc(0))
+}
+
+/** Encode one response failure without exposing an Error object across processes. */
+export function encodeDesktopResponseError(streamId: number, message: string): Buffer {
+  return encodeJsonFrame(RESPONSE_FRAME_ERROR, streamId, { message })
+}
+
+/** Incrementally decode validated request frames from the Electron byte pipe. */
+export class DesktopHostRequestDecoder {
+  private buffer: Buffer = Buffer.alloc(0)
+
+  /**
+   * Append bytes and return every complete request frame.
+   * @param chunk - next bytes read from the Electron request pipe.
+   * @returns complete frames in pipe order.
+   */
+  push(chunk: Buffer): DesktopHostRequestFrame[] {
+    this.buffer = this.buffer.byteLength === 0 ? chunk : Buffer.concat([this.buffer, chunk])
+    const frames: DesktopHostRequestFrame[] = []
+    for (;;) {
+      const frame = this.next()
+      if (frame === undefined) return frames
+      frames.push(frame)
+    }
+  }
+
+  /** Reject EOF that splits a frame. */
+  finish(): void {
+    if (this.buffer.byteLength !== 0) throw new Error('dsh desktop: Electron request pipe ended inside a frame')
+  }
+
+  private next(): DesktopHostRequestFrame | undefined {
+    if (this.buffer.byteLength < FRAME_HEADER_BYTES) return undefined
+    if (this.buffer.readUInt32BE(0) !== FRAME_MAGIC) throw new Error('dsh desktop: invalid Electron request frame marker')
+    const rawType = this.buffer.readUInt8(4)
+    const streamId = this.buffer.readUInt32BE(5)
+    const payloadLength = this.buffer.readUInt32BE(9)
+    assertStreamId(streamId)
+    const limit = rawType === REQUEST_FRAME_DATA ? DESKTOP_PIPE_CHUNK_BYTES : MAX_CONTROL_PAYLOAD_BYTES
+    if (payloadLength > limit) {
+      throw new Error(`dsh desktop: Electron request frame exceeds the ${String(limit)}-byte limit`)
+    }
+    const frameLength = FRAME_HEADER_BYTES + payloadLength
+    if (this.buffer.byteLength < frameLength) return undefined
+    const payload = this.buffer.subarray(FRAME_HEADER_BYTES, frameLength)
+    this.buffer = this.buffer.subarray(frameLength)
+    switch (rawType) {
+      case REQUEST_FRAME_START:
+        return this.parseStart(streamId, payload)
+      case REQUEST_FRAME_DATA:
+        return { type: 'data', streamId, data: payload }
+      case REQUEST_FRAME_END:
+        if (payloadLength !== 0) throw new Error('dsh desktop: Electron request end frame carried a payload')
+        return { type: 'end', streamId }
+      case REQUEST_FRAME_CANCEL:
+        if (payloadLength !== 0) throw new Error('dsh desktop: Electron request cancel frame carried a payload')
+        return { type: 'cancel', streamId }
+      default:
+        throw new Error(`dsh desktop: unknown Electron request frame type ${String(rawType)}`)
+    }
+  }
+
+  private parseStart(streamId: number, payload: Buffer): DesktopHostRequestFrame {
+    let value: unknown
+    try {
+      value = JSON.parse(payload.toString('utf8')) as unknown
+    } catch (error) {
+      throw new Error(`dsh desktop: Electron request start payload is not JSON: ${error instanceof Error ? error.message : String(error)}`)
+    }
+    if (!isRecord(value) || typeof value.url !== 'string' || typeof value.method !== 'string'
+      || !isHeaders(value.headers) || typeof value.hasBody !== 'boolean') {
+      throw new Error('dsh desktop: invalid Electron request start payload')
+    }
+    return {
+      type: 'start',
+      streamId,
+      url: value.url,
+      method: value.method,
+      headers: value.headers,
+      hasBody: value.hasBody,
+    }
+  }
+}

+ 19 - 0
apps/desktop-host/tsconfig.json

@@ -0,0 +1,19 @@
+{
+  "extends": "../../tsconfig.base.json",
+  "compilerOptions": {
+    "rootDir": "src",
+    "outDir": "lib/types"
+  },
+  "include": ["src"],
+  "references": [
+    { "path": "../../vendor/cordis" },
+    { "path": "../../vendor/include" },
+    { "path": "../../packages/api/gateway/tsconfig.host.json" },
+    { "path": "../../packages/boot/app-boot" },
+    { "path": "../../packages/boot/cmdline" },
+    { "path": "../../packages/client/connection/tsconfig.host.json" },
+    { "path": "../../packages/client/modules" },
+    { "path": "../../packages/host/webserver" },
+    { "path": "../../packages/util/launch-environment" }
+  ]
+}

+ 12 - 0
apps/desktop-host/tsdown.config.ts

@@ -0,0 +1,12 @@
+import { defineConfig } from 'tsdown'
+
+export default defineConfig({
+  entry: ['lib/types/index.js'],
+  outDir: 'lib',
+  format: ['esm'],
+  platform: 'node',
+  target: 'es2024',
+  fixedExtension: false,
+  dts: false,
+  clean: false,
+})

+ 6 - 0
apps/desktop/README.i18n.yaml

@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# side as of the last confirmed-consistent state. Both languages carry equal authority;
+# after editing either side, bring the other along and re-record with:
+#   pnpm run verify-translation-pairing --write apps/desktop/README.md
+README.md: cf350a9d5e9577f5cf52c4371b32f16e278133f9
+README.zh.md: df48eb5903e21cae9ff041626c28d41d12e5c632

Fișier diff suprimat deoarece este prea mare
+ 160 - 0
apps/desktop/README.md


Fișier diff suprimat deoarece este prea mare
+ 160 - 0
apps/desktop/README.zh.md


+ 39 - 0
apps/desktop/electron-builder.config.d.mts

@@ -0,0 +1,39 @@
+/** Electron-builder fields asserted by the Desktop release tests. */
+export interface DesktopElectronBuilderConfig {
+  readonly appId: string
+  readonly directories: {
+    readonly output: string
+  }
+  readonly extraResources: readonly [
+    { readonly from: string, readonly to: 'runtime' },
+    { readonly from: string, readonly to: 'seed' },
+  ]
+  readonly mac: {
+    readonly identity: string | undefined
+    readonly forceCodeSigning: boolean
+    readonly notarize: boolean
+  }
+  readonly dmg: {
+    readonly sign: boolean
+    readonly writeUpdateInfo: boolean
+  }
+  readonly artifactBuildCompleted: (artifact: { readonly file: string }) => Promise<void> | undefined
+  readonly publish: readonly [{ readonly provider: 'generic', readonly url: string }]
+}
+
+/**
+ * Create electron-builder configuration from one release environment.
+ * @param env - Packaging environment.
+ * @param hostPlatform - Build-host platform used when no explicit target is present.
+ * @param hostArch - Build-host architecture used when no explicit target is present.
+ * @returns electron-builder configuration.
+ */
+export function createElectronBuilderConfig(
+  env?: NodeJS.ProcessEnv,
+  hostPlatform?: NodeJS.Platform,
+  hostArch?: string,
+): DesktopElectronBuilderConfig
+
+declare const electronBuilderConfig: DesktopElectronBuilderConfig
+
+export default electronBuilderConfig

+ 109 - 0
apps/desktop/electron-builder.config.mjs

@@ -0,0 +1,109 @@
+import {
+  resolveDesktopAppId,
+  resolveMacOSNotarizationEnvironment,
+  resolveMacOSSigningEnvironment,
+} from './scripts/desktop-release-environment.mjs'
+import { notarizeMacOSDiskImageArtifact } from './scripts/notarize-macos-disk-images.mjs'
+import { verifyMacOSSignatureAfterSign } from './scripts/verify-macos-signature.mjs'
+import {
+  createWindowsTokenSigner,
+  installWindowsNsisBootstrapSigner,
+} from './scripts/windows-sign.mjs'
+import { resolveDesktopAutoUpdateConfig } from './scripts/desktop-auto-update-environment.mjs'
+import { desktopTargetBuildPaths } from './scripts/desktop-build-paths.mjs'
+
+/**
+ * Create electron-builder configuration from one release environment.
+ * @param {NodeJS.ProcessEnv} env - Packaging environment.
+ * @param {NodeJS.Platform} hostPlatform - Build-host platform used when no explicit target is present.
+ * @param {string} hostArch - Build-host architecture used when no explicit target is present.
+ * @returns {object} electron-builder configuration.
+ */
+export function createElectronBuilderConfig(
+  env = process.env,
+  hostPlatform = process.platform,
+  hostArch = process.arch,
+) {
+  const appId = resolveDesktopAppId(env)
+  const targetPlatform = env.DSH_DESKTOP_TARGET_PLATFORM
+  const resolvedPlatform = targetPlatform ?? hostPlatform
+  const resolvedArch = env.DSH_DESKTOP_TARGET_ARCH ?? hostArch
+  const packagesMacOS = targetPlatform === 'darwin' || (targetPlatform === undefined && hostPlatform === 'darwin')
+  const packagesWindows = targetPlatform === 'win32'
+  const macOSSigning = packagesMacOS ? resolveMacOSSigningEnvironment(env) : undefined
+  if (packagesMacOS) resolveMacOSNotarizationEnvironment(env)
+  const windowsSigner = packagesWindows
+    ? createWindowsTokenSigner({
+        certificateFile: env.DSH_DESKTOP_WINDOWS_CER_FILE,
+        signTool: env.DSH_DESKTOP_WINDOWS_SIGNTOOL,
+        tokenPin: env.DSH_DESKTOP_WINDOWS_TOKEN_PIN,
+        keyContainer: env.DSH_DESKTOP_WINDOWS_KEY_CONTAINER,
+      })
+    : undefined
+  if (windowsSigner !== undefined) {
+    installWindowsNsisBootstrapSigner({ sign: windowsSigner })
+  }
+  const update = resolveDesktopAutoUpdateConfig(env, resolvedPlatform, resolvedArch)
+  const buildPaths = desktopTargetBuildPaths(update.target)
+  return {
+    appId,
+    productName: 'DeepSeek Harness',
+    artifactName: 'deepseek-harness-${version}-${os}-${arch}.${ext}',
+    directories: { output: buildPaths.artifacts },
+    asar: true,
+    files: [
+      'lib/*.js',
+      'lib/*.cjs',
+      'renderer/**/*',
+      'package.json',
+    ],
+    extraResources: [
+      { from: buildPaths.runtime, to: 'runtime' },
+      { from: buildPaths.seed, to: 'seed' },
+    ],
+    mac: {
+      category: 'public.app-category.developer-tools',
+      identity: macOSSigning?.signingIdentity,
+      forceCodeSigning: true,
+      hardenedRuntime: true,
+      notarize: true,
+      target: ['dmg', 'zip'],
+    },
+    dmg: {
+      sign: true,
+      writeUpdateInfo: false,
+    },
+    afterSign: context => {
+      if (context.electronPlatformName !== 'darwin') return
+      verifyMacOSSignatureAfterSign(context, macOSSigning ?? resolveMacOSSigningEnvironment(env))
+    },
+    artifactBuildCompleted: artifact => {
+      if (!artifact.file.endsWith('.dmg')) return
+      return notarizeMacOSDiskImageArtifact(
+        artifact,
+        env,
+        macOSSigning ?? resolveMacOSSigningEnvironment(env),
+      )
+    },
+    win: {
+      forceCodeSigning: true,
+      signtoolOptions: {
+        sign: windowsSigner,
+        signingHashAlgorithms: ['sha256'],
+      },
+      target: ['nsis'],
+    },
+    linux: {
+      category: 'Development',
+      target: ['AppImage'],
+    },
+    nsis: {
+      oneClick: false,
+      allowToChangeInstallationDirectory: true,
+      differentialPackage: true,
+    },
+    publish: [{ provider: 'generic', url: update.publicUrl }],
+  }
+}
+
+export default createElectronBuilderConfig()

+ 51 - 0
apps/desktop/package.json

@@ -0,0 +1,51 @@
+{
+  "name": "@deepseek-ai/dsh-desktop",
+  "description": "Electron desktop shell for an isolated pnpm-installed dsh runtime",
+  "version": "0.1.3-alpha.2",
+  "private": true,
+  "license": "MIT",
+  "type": "module",
+  "main": "lib/main.js",
+  "scripts": {
+    "build": "tsc -b && tsdown",
+    "dev": "tsx scripts/dev.ts",
+    "start": "tsx scripts/dev.ts --skip-build",
+    "prepare:runtime": "tsx scripts/prepare-runtime.ts",
+    "prepare:packages": "tsx scripts/prepare-package-set.ts",
+    "prepare:seed": "tsx scripts/prepare-seed.ts",
+    "prepare:package": "tsx scripts/package-target.ts --prepare-only",
+    "verify:mac-signature": "node scripts/verify-macos-signature.mjs",
+    "package": "tsx scripts/package-target.ts",
+    "package:dir": "tsx scripts/package-target.ts --dir",
+    "package:mac:arm64": "tsx scripts/package-target.ts mac-arm64",
+    "package:mac:arm64:dir": "tsx scripts/package-target.ts mac-arm64 --dir",
+    "package:mac:x64": "tsx scripts/package-target.ts mac-x64",
+    "package:mac:x64:dir": "tsx scripts/package-target.ts mac-x64 --dir",
+    "package:win:x64": "tsx scripts/package-target.ts win-x64",
+    "package:win:x64:dir": "tsx scripts/package-target.ts win-x64 --dir",
+    "upload:mac:arm64": "tsx scripts/upload-target.ts mac-arm64",
+    "upload:mac:x64": "tsx scripts/upload-target.ts mac-x64",
+    "upload:win:x64": "tsx scripts/upload-target.ts win-x64"
+  },
+  "dependencies": {
+    "electron-updater": "^6.8.9",
+    "semver": "^7.8.5"
+  },
+  "devDependencies": {
+    "@aws-sdk/client-s3": "3.1067.0",
+    "@deepseek-ai/dsh-home-paths": "workspace:^",
+    "@electron/notarize": "2.5.0",
+    "@types/js-yaml": "^4.0.9",
+    "@types/node": "^22.20.0",
+    "@types/semver": "^7.8.0",
+    "app-builder-lib": "26.15.3",
+    "electron": "^44.0.0",
+    "electron-builder": "^26.15.3",
+    "extract-zip": "^2.0.1",
+    "js-yaml": "^4.2.0",
+    "msgpackr": "2.0.4",
+    "pnpm": "11.7.0",
+    "tar": "^7.5.0",
+    "typescript": "^6.0.3"
+  }
+}

+ 108 - 0
apps/desktop/renderer/plugin-manager.css

@@ -0,0 +1,108 @@
+:root {
+  color-scheme: light dark;
+  font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+  background: Canvas;
+  color: CanvasText;
+}
+
+body {
+  margin: 0;
+}
+
+main {
+  max-width: 680px;
+  margin: 0 auto;
+  padding: 32px;
+}
+
+header,
+.install-row,
+li {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+header {
+  justify-content: space-between;
+}
+
+h1,
+h2,
+p {
+  margin-top: 0;
+}
+
+header p {
+  color: GrayText;
+}
+
+form,
+section {
+  margin-top: 28px;
+}
+
+label {
+  display: block;
+  margin-bottom: 8px;
+  font-weight: 600;
+}
+
+input {
+  flex: 1;
+  min-width: 0;
+  padding: 9px 11px;
+  border: 1px solid ButtonBorder;
+  border-radius: 7px;
+  background: Field;
+  color: FieldText;
+}
+
+button {
+  padding: 9px 14px;
+  border: 1px solid ButtonBorder;
+  border-radius: 7px;
+  background: AccentColor;
+  color: AccentColorText;
+  cursor: pointer;
+}
+
+button.quiet,
+li button {
+  background: ButtonFace;
+  color: ButtonText;
+}
+
+button:disabled,
+input:disabled {
+  opacity: 0.55;
+  cursor: wait;
+}
+
+#status {
+  min-height: 1.5em;
+  margin-top: 16px;
+  color: GrayText;
+}
+
+ul {
+  margin: 0;
+  padding: 0;
+  list-style: none;
+}
+
+li {
+  justify-content: space-between;
+  padding: 12px 0;
+  border-bottom: 1px solid ButtonBorder;
+}
+
+.package-version {
+  color: GrayText;
+  margin-left: 8px;
+}
+
+.package-actions {
+  display: flex;
+  gap: 8px;
+}

+ 35 - 0
apps/desktop/renderer/plugin-manager.html

@@ -0,0 +1,35 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self'; connect-src 'none'; img-src 'self' data:">
+    <title id="page-title"></title>
+    <link rel="stylesheet" href="plugin-manager.css">
+  </head>
+  <body>
+    <main>
+      <header>
+        <div>
+          <h1 id="title"></h1>
+          <p id="description"></p>
+        </div>
+        <button id="refresh" class="quiet" type="button"></button>
+      </header>
+      <form id="install-form">
+        <label id="package-label" for="package-spec"></label>
+        <div class="install-row">
+          <input id="package-spec" name="package-spec" autocomplete="off" placeholder="@scope/plugin@1.2.3" required>
+          <button id="install" type="submit"></button>
+        </div>
+      </form>
+      <p id="status" role="status" aria-live="polite"></p>
+      <section aria-labelledby="installed-heading">
+        <h2 id="installed-heading"></h2>
+        <ul id="plugins"></ul>
+        <p id="empty"></p>
+      </section>
+    </main>
+    <script src="plugin-manager.js"></script>
+  </body>
+</html>

+ 101 - 0
apps/desktop/renderer/plugin-manager.js

@@ -0,0 +1,101 @@
+const api = window.dshDesktop
+
+async function main() {
+  const locale = await api.locale()
+  const messages = locale.messages
+  const message = (key, values = {}) => messages[key].replaceAll(/\{([^{}]+)\}/gu, (placeholder, name) => values[name] ?? placeholder)
+  document.documentElement.lang = locale.id
+  document.querySelector('#page-title').textContent = messages.pluginManagerTitle
+  document.querySelector('#title').textContent = messages.pluginManagerTitle
+  document.querySelector('#description').textContent = messages.pluginManagerDescription
+  document.querySelector('#refresh').textContent = messages.refresh
+  document.querySelector('#package-label').textContent = messages.npmPackage
+  document.querySelector('#install').textContent = messages.install
+  document.querySelector('#installed-heading').textContent = messages.installed
+  document.querySelector('#empty').textContent = messages.noPlugins
+
+  const list = document.querySelector('#plugins')
+  const empty = document.querySelector('#empty')
+  const status = document.querySelector('#status')
+  const form = document.querySelector('#install-form')
+  const input = document.querySelector('#package-spec')
+  const refresh = document.querySelector('#refresh')
+
+  function setBusy(busy, statusMessage = '') {
+    for (const control of document.querySelectorAll('button, input')) control.disabled = busy
+    status.textContent = statusMessage
+  }
+
+  async function render() {
+    const plugins = await api.plugins.list()
+    list.replaceChildren(...plugins.map(plugin => {
+      const item = document.createElement('li')
+      const identity = document.createElement('span')
+      const version = document.createElement('span')
+      version.className = 'package-version'
+      version.textContent = plugin.version
+      identity.append(document.createTextNode(plugin.name), version)
+      const remove = document.createElement('button')
+      remove.type = 'button'
+      remove.textContent = messages.remove
+      remove.addEventListener('click', () => void run(
+        () => api.plugins.remove(plugin.name),
+        message('removing', { name: plugin.name }),
+      ))
+      const update = document.createElement('button')
+      update.type = 'button'
+      update.textContent = messages.update
+      update.addEventListener('click', () => {
+        const next = window.prompt(message('targetVersion', { name: plugin.name }), plugin.version)?.trim()
+        if (next === undefined || next === '' || next === plugin.version) return
+        void run(() => api.plugins.update(plugin.name, next), message('updating', { name: plugin.name }))
+      })
+      const actions = document.createElement('span')
+      actions.className = 'package-actions'
+      actions.append(update, remove)
+      item.append(identity, actions)
+      return item
+    }))
+    empty.hidden = plugins.length !== 0
+  }
+
+  async function run(operation, statusMessage) {
+    setBusy(true, statusMessage)
+    try {
+      await operation()
+      await render()
+      status.textContent = messages.operationComplete
+    } catch (error) {
+      status.textContent = error instanceof Error ? error.message : String(error)
+    } finally {
+      setBusy(false, status.textContent)
+    }
+  }
+
+  async function load(statusMessage, success) {
+    setBusy(true, statusMessage)
+    try {
+      await render()
+      status.textContent = success
+    } catch (error) {
+      status.textContent = error instanceof Error ? error.message : String(error)
+    } finally {
+      setBusy(false, status.textContent)
+    }
+  }
+
+  form.addEventListener('submit', (event) => {
+    event.preventDefault()
+    const spec = input.value.trim()
+    if (spec === '') return
+    void run(async () => {
+      await api.plugins.add(spec)
+      input.value = ''
+    }, message('installing', { spec }))
+  })
+  refresh.addEventListener('click', () => void load(messages.refreshing, messages.refreshed))
+
+  await load(messages.loadingPlugins, '')
+}
+
+void main()

+ 90 - 0
apps/desktop/scripts/desktop-auto-update-environment.d.mts

@@ -0,0 +1,90 @@
+/** Environment variable that selects the Desktop update deployment. */
+export const DESKTOP_AUTO_UPDATE_ENV: 'DSH_DESKTOP_AUTO_UPDATE_ENV'
+
+/** Supported Desktop update deployment. */
+export type DesktopAutoUpdateEnvironment = 'test' | 'production'
+
+/** Directory name of one supported Desktop release target. */
+export type DesktopAutoUpdateTarget = 'mac-arm64' | 'mac-x64' | 'win-x64'
+
+/** Public updater URL for one release target. */
+export interface DesktopAutoUpdateConfig {
+  readonly environment: DesktopAutoUpdateEnvironment
+  readonly target: DesktopAutoUpdateTarget
+  readonly origin: string
+  readonly publicUrl: string
+  readonly keyPrefix: string
+}
+
+/** Public updater URL and private COS destination for one upload target. */
+export interface DesktopUploadConfig extends DesktopAutoUpdateConfig {
+  readonly bucket: string
+  readonly secretIdEnvName: string
+  readonly secretKeyEnvName: string
+}
+
+/**
+ * Resolve the update deployment, defaulting local release work to test.
+ * @param env - Packaging or upload environment.
+ * @returns Validated deployment name.
+ */
+export function resolveDesktopAutoUpdateEnvironment(
+  env: NodeJS.ProcessEnv,
+): DesktopAutoUpdateEnvironment
+
+/**
+ * Resolve one supported platform and architecture to its update directory.
+ * @param platform - Target Node.js platform.
+ * @param arch - Target Node.js architecture.
+ * @returns Update target directory.
+ */
+export function resolveDesktopAutoUpdateTarget(
+  platform: NodeJS.Platform,
+  arch: string,
+): DesktopAutoUpdateTarget
+
+/**
+ * Return the local completion record filename for one packaged target.
+ * @param target - Supported release target.
+ * @returns Filename stored beside electron-builder artifacts.
+ */
+export function desktopBuildRecordFilename(target: DesktopAutoUpdateTarget): string
+
+/**
+ * Return the electron-builder channel metadata filename for an application version.
+ * @param version - Desktop semantic version.
+ * @param platform - Target platform.
+ * @returns Channel metadata filename emitted for the target.
+ */
+export function desktopUpdateMetadataFilename(
+  version: string,
+  platform: NodeJS.Platform,
+): string
+
+/**
+ * Resolve the public updater URL for one release target.
+ * @param env - Packaging or upload environment.
+ * @param platform - Target Node.js platform.
+ * @param arch - Target Node.js architecture.
+ * @returns Resolved updater configuration.
+ * @throws When the test deployment lacks a valid HTTPS origin.
+ */
+export function resolveDesktopAutoUpdateConfig(
+  env: NodeJS.ProcessEnv,
+  platform: NodeJS.Platform,
+  arch: string,
+): DesktopAutoUpdateConfig
+
+/**
+ * Resolve the public updater URL and private COS destination for one upload target.
+ * @param env - Upload environment.
+ * @param platform - Target Node.js platform.
+ * @param arch - Target Node.js architecture.
+ * @returns Resolved upload configuration.
+ * @throws When the selected deployment lacks a required origin or bucket, or the test origin is not HTTPS.
+ */
+export function resolveDesktopUploadConfig(
+  env: NodeJS.ProcessEnv,
+  platform: NodeJS.Platform,
+  arch: string,
+): DesktopUploadConfig

+ 169 - 0
apps/desktop/scripts/desktop-auto-update-environment.mjs

@@ -0,0 +1,169 @@
+/** Resolve the Desktop auto-update channel and its Tencent COS destination. */
+
+import { prerelease, valid } from 'semver'
+
+/** Environment variable that selects the Desktop update deployment. */
+export const DESKTOP_AUTO_UPDATE_ENV = 'DSH_DESKTOP_AUTO_UPDATE_ENV'
+
+const UPDATE_ENVIRONMENTS = {
+  test: {
+    originEnvName: 'DOWNLOAD_TEST_ORIGIN',
+    fixedOrigin: undefined,
+    bucketEnvName: 'DOWNLOAD_TEST_COS_BUCKET',
+    secretIdEnvName: 'DOWNLOAD_TEST_COS_SECRET_ID',
+    secretKeyEnvName: 'DOWNLOAD_TEST_COS_SECRET_KEY',
+  },
+  production: {
+    originEnvName: undefined,
+    fixedOrigin: 'https://download.deepseek.com',
+    bucketEnvName: 'DOWNLOAD_PROD_COS_BUCKET',
+    secretIdEnvName: 'DOWNLOAD_PROD_COS_SECRET_ID',
+    secretKeyEnvName: 'DOWNLOAD_PROD_COS_SECRET_KEY',
+  },
+}
+
+const UPDATE_TARGETS = new Set(['mac-arm64', 'mac-x64', 'win-x64'])
+
+/**
+ * Resolve the update deployment, defaulting local release work to test.
+ * @param {NodeJS.ProcessEnv} env - Packaging or upload environment.
+ * @returns {'test' | 'production'} Validated deployment name.
+ */
+export function resolveDesktopAutoUpdateEnvironment(env) {
+  const value = env[DESKTOP_AUTO_UPDATE_ENV]?.trim() || 'test'
+  if (value !== 'test' && value !== 'production') {
+    throw new Error(`desktop auto-update: ${DESKTOP_AUTO_UPDATE_ENV} must be "test" or "production"`)
+  }
+  return value
+}
+
+/**
+ * Resolve one supported platform and architecture to its update directory.
+ * @param {NodeJS.Platform} platform - Target Node.js platform.
+ * @param {string} arch - Target Node.js architecture.
+ * @returns {'mac-arm64' | 'mac-x64' | 'win-x64'} Update target directory.
+ */
+export function resolveDesktopAutoUpdateTarget(platform, arch) {
+  const os = platform === 'darwin' ? 'mac' : platform === 'win32' ? 'win' : platform
+  const target = `${os}-${arch}`
+  if (!UPDATE_TARGETS.has(target)) {
+    throw new Error(`desktop auto-update: unsupported target ${target}`)
+  }
+  return target
+}
+
+/**
+ * Return the local completion record filename for one packaged target.
+ * @param {'mac-arm64' | 'mac-x64' | 'win-x64'} target - Supported release target.
+ * @returns {string} Filename stored beside electron-builder artifacts.
+ */
+export function desktopBuildRecordFilename(target) {
+  if (!UPDATE_TARGETS.has(target)) {
+    throw new Error(`desktop auto-update: unsupported target ${target}`)
+  }
+  return `${target}-release.json`
+}
+
+/**
+ * Return the electron-builder channel metadata filename for an application version.
+ * @param {string} version - Desktop semantic version.
+ * @param {NodeJS.Platform} platform - Target platform.
+ * @returns {string} Channel metadata filename emitted for the target.
+ */
+export function desktopUpdateMetadataFilename(version, platform) {
+  if (valid(version) === null) {
+    throw new Error(`desktop auto-update: invalid Desktop version ${JSON.stringify(version)}`)
+  }
+  if (platform !== 'darwin' && platform !== 'win32') {
+    throw new Error(`desktop auto-update: unsupported metadata platform ${platform}`)
+  }
+  const release = prerelease(version)
+  const channel = release === null ? 'latest' : String(release[0])
+  return `${channel}${platform === 'darwin' ? '-mac' : ''}.yml`
+}
+
+/**
+ * Read one required release setting without accepting whitespace-only values.
+ * @param {NodeJS.ProcessEnv} env - Packaging or upload environment.
+ * @param {string} name - Environment variable to read.
+ * @returns {string} Trimmed setting.
+ */
+function requiredEnvironmentValue(env, name) {
+  const value = env[name]?.trim()
+  if (value === undefined || value === '') {
+    throw new Error(`desktop auto-update: ${name} must be set to a non-empty value`)
+  }
+  return value
+}
+
+/**
+ * Normalize an HTTPS origin and reject paths or credentials.
+ * @param {string} value - Candidate origin.
+ * @param {string} name - Environment variable used in diagnostics.
+ * @returns {string} Normalized HTTPS origin without a trailing slash.
+ */
+function httpsOrigin(value, name) {
+  let parsed
+  try {
+    parsed = new URL(value)
+  }
+  catch {
+    throw new Error(`desktop auto-update: ${name} must be an absolute HTTPS origin`)
+  }
+  if (parsed.protocol !== 'https:'
+    || parsed.username !== ''
+    || parsed.password !== ''
+    || parsed.pathname !== '/'
+    || parsed.search !== ''
+    || parsed.hash !== '') {
+    throw new Error(`desktop auto-update: ${name} must be an absolute HTTPS origin without a path, credentials, query, or fragment`)
+  }
+  return parsed.origin
+}
+
+/**
+ * Resolve the public updater URL for one release target.
+ * @param {NodeJS.ProcessEnv} env - Packaging or upload environment.
+ * @param {NodeJS.Platform} platform - Target Node.js platform.
+ * @param {string} arch - Target Node.js architecture.
+ * @returns {{ environment: 'test' | 'production', target: 'mac-arm64' | 'mac-x64' | 'win-x64', origin: string, publicUrl: string, keyPrefix: string }} Resolved updater configuration.
+ * @throws {Error} When the test deployment lacks a valid HTTPS origin.
+ */
+export function resolveDesktopAutoUpdateConfig(env, platform, arch) {
+  const environment = resolveDesktopAutoUpdateEnvironment(env)
+  const target = resolveDesktopAutoUpdateTarget(platform, arch)
+  const deployment = UPDATE_ENVIRONMENTS[environment]
+  let origin = deployment.fixedOrigin
+  if (origin === undefined) {
+    const { originEnvName } = deployment
+    if (originEnvName === undefined) throw new Error('desktop auto-update: selected deployment has no origin')
+    origin = httpsOrigin(requiredEnvironmentValue(env, originEnvName), originEnvName)
+  }
+  const keyPrefix = `_/harness/desktop/stable/${target}`
+  return {
+    environment,
+    target,
+    origin,
+    keyPrefix,
+    publicUrl: `${origin}/${keyPrefix}/`,
+  }
+}
+
+/**
+ * Resolve the public updater URL and private COS destination for one upload target.
+ * @param {NodeJS.ProcessEnv} env - Upload environment.
+ * @param {NodeJS.Platform} platform - Target Node.js platform.
+ * @param {string} arch - Target Node.js architecture.
+ * @returns {{ environment: 'test' | 'production', target: 'mac-arm64' | 'mac-x64' | 'win-x64', origin: string, publicUrl: string, keyPrefix: string, bucket: string, secretIdEnvName: string, secretKeyEnvName: string }} Resolved upload configuration.
+ * @throws {Error} When the selected deployment lacks a required origin or bucket, or the test origin is not HTTPS.
+ */
+export function resolveDesktopUploadConfig(env, platform, arch) {
+  const update = resolveDesktopAutoUpdateConfig(env, platform, arch)
+  const deployment = UPDATE_ENVIRONMENTS[update.environment]
+  return {
+    ...update,
+    bucket: requiredEnvironmentValue(env, deployment.bucketEnvName),
+    secretIdEnvName: deployment.secretIdEnvName,
+    secretKeyEnvName: deployment.secretKeyEnvName,
+  }
+}

+ 49 - 0
apps/desktop/scripts/desktop-build-paths.d.mts

@@ -0,0 +1,49 @@
+import type { DesktopAutoUpdateTarget } from './desktop-auto-update-environment.mjs'
+
+/** Mutable target directories plus the shared immutable download cache. */
+export interface DesktopTargetBuildPaths {
+  readonly root: string
+  readonly artifacts: string
+  readonly runtime: string
+  readonly packageSet: string
+  readonly seed: string
+  readonly seedPnpm: string
+  readonly nodeExtract: string
+  readonly packedDsh: string
+  readonly packedVendor: string
+  readonly packedLandlock: string
+  readonly downloads: string
+}
+
+/**
+ * Resolve the fixed build target selected by a packaging environment.
+ * @param env - Packaging environment.
+ * @param hostPlatform - Build-host platform used when no target override exists.
+ * @param hostArch - Build-host architecture used when no target override exists.
+ * @returns Supported Desktop target name.
+ */
+export function resolveDesktopBuildTarget(
+  env?: NodeJS.ProcessEnv,
+  hostPlatform?: NodeJS.Platform,
+  hostArch?: string,
+): DesktopAutoUpdateTarget
+
+/**
+ * Return the mutable preparation and artifact directories owned by one release target.
+ * @param target - Supported Desktop target name.
+ * @returns Target paths plus the shared immutable download cache.
+ */
+export function desktopTargetBuildPaths(target: DesktopAutoUpdateTarget): DesktopTargetBuildPaths
+
+/**
+ * Resolve the paths owned by the target selected in a packaging environment.
+ * @param env - Packaging environment.
+ * @param hostPlatform - Build-host platform used when no target override exists.
+ * @param hostArch - Build-host architecture used when no target override exists.
+ * @returns Selected target paths.
+ */
+export function resolveDesktopTargetBuildPaths(
+  env?: NodeJS.ProcessEnv,
+  hostPlatform?: NodeJS.Platform,
+  hostArch?: string,
+): DesktopTargetBuildPaths

+ 70 - 0
apps/desktop/scripts/desktop-build-paths.mjs

@@ -0,0 +1,70 @@
+/** Resolve build-owned Desktop paths without sharing mutable state across release targets. */
+
+import { join, resolve } from 'node:path'
+
+const APP_ROOT = resolve(import.meta.dirname, '..')
+const BUILD_ROOT = join(APP_ROOT, '.desktop-build')
+const SUPPORTED_TARGETS = new Set(['mac-arm64', 'mac-x64', 'win-x64'])
+
+/**
+ * Resolve the fixed build target selected by a packaging environment.
+ * @param {NodeJS.ProcessEnv} env - Packaging environment.
+ * @param {NodeJS.Platform} hostPlatform - Build-host platform used when no target override exists.
+ * @param {string} hostArch - Build-host architecture used when no target override exists.
+ * @returns {'mac-arm64' | 'mac-x64' | 'win-x64'} Supported Desktop target name.
+ */
+export function resolveDesktopBuildTarget(
+  env = process.env,
+  hostPlatform = process.platform,
+  hostArch = process.arch,
+) {
+  const platform = env.DSH_DESKTOP_TARGET_PLATFORM ?? env.npm_config_platform ?? hostPlatform
+  const arch = env.DSH_DESKTOP_TARGET_ARCH ?? env.npm_config_arch ?? hostArch
+  const os = platform === 'darwin' ? 'mac' : platform === 'win32' || platform === 'win' ? 'win' : platform
+  const target = `${os}-${arch}`
+  if (!SUPPORTED_TARGETS.has(target)) {
+    throw new Error(`desktop build paths: unsupported target ${target}`)
+  }
+  return /** @type {'mac-arm64' | 'mac-x64' | 'win-x64'} */ (target)
+}
+
+/**
+ * Return the mutable preparation and artifact directories owned by one release target.
+ * @param {'mac-arm64' | 'mac-x64' | 'win-x64'} target - Supported Desktop target name.
+ * @returns {{ root: string, artifacts: string, runtime: string, packageSet: string, seed: string, seedPnpm: string, nodeExtract: string, packedDsh: string, packedVendor: string, packedLandlock: string, downloads: string }} Target paths plus the shared immutable download cache.
+ */
+export function desktopTargetBuildPaths(target) {
+  if (!SUPPORTED_TARGETS.has(target)) {
+    throw new Error(`desktop build paths: unsupported target ${String(target)}`)
+  }
+  const root = join(BUILD_ROOT, 'targets', target)
+  const packed = join(root, 'packed')
+  return {
+    root,
+    artifacts: join(root, 'artifacts'),
+    runtime: join(root, 'runtime'),
+    packageSet: join(root, 'package-set'),
+    seed: join(root, 'seed'),
+    seedPnpm: join(root, 'seed-pnpm'),
+    nodeExtract: join(root, 'node-extract'),
+    packedDsh: join(packed, 'dsh'),
+    packedVendor: join(packed, 'vendor'),
+    packedLandlock: join(packed, 'landlock'),
+    downloads: join(BUILD_ROOT, 'downloads'),
+  }
+}
+
+/**
+ * Resolve the paths owned by the target selected in a packaging environment.
+ * @param {NodeJS.ProcessEnv} env - Packaging environment.
+ * @param {NodeJS.Platform} hostPlatform - Build-host platform used when no target override exists.
+ * @param {string} hostArch - Build-host architecture used when no target override exists.
+ * @returns {ReturnType<typeof desktopTargetBuildPaths>} Selected target paths.
+ */
+export function resolveDesktopTargetBuildPaths(
+  env = process.env,
+  hostPlatform = process.platform,
+  hostArch = process.arch,
+) {
+  return desktopTargetBuildPaths(resolveDesktopBuildTarget(env, hostPlatform, hostArch))
+}

+ 61 - 0
apps/desktop/scripts/desktop-release-environment.d.mts

@@ -0,0 +1,61 @@
+/** Environment variable that supplies the Electron application identifier. */
+export const DESKTOP_APP_ID_ENV: 'DSH_DESKTOP_APP_ID'
+
+/** Environment variable that supplies electron-builder's macOS certificate qualifier. */
+export const MACOS_SIGNING_IDENTITY_ENV: 'DSH_DESKTOP_MACOS_SIGNING_IDENTITY'
+
+/** Environment variable that supplies the expected Apple Developer Team ID. */
+export const MACOS_TEAM_ID_ENV: 'DSH_DESKTOP_MACOS_TEAM_ID'
+
+/** Public identity expected on a macOS release. */
+export interface MacOSSigningEnvironment {
+  readonly signingIdentity: string
+  readonly teamId: string
+}
+
+/** Apple ID credentials accepted by notarytool. */
+export interface MacOSAppleIdNotarizationEnvironment {
+  readonly appleId: string
+  readonly appleIdPassword: string
+  readonly teamId: string
+}
+
+/** App Store Connect API credentials accepted by notarytool. */
+export interface MacOSApiKeyNotarizationEnvironment {
+  readonly appleApiKey: string
+  readonly appleApiKeyId: string
+  readonly appleApiIssuer: string
+}
+
+/** Keychain profile accepted by notarytool. */
+export interface MacOSKeychainNotarizationEnvironment {
+  readonly keychainProfile: string
+  readonly keychain?: string
+}
+
+/** One complete credential strategy accepted by notarytool. */
+export type MacOSNotarizationEnvironment =
+  | MacOSAppleIdNotarizationEnvironment
+  | MacOSApiKeyNotarizationEnvironment
+  | MacOSKeychainNotarizationEnvironment
+
+/**
+ * Resolve and validate the application identifier shared by every platform target.
+ * @param env - Packaging environment.
+ * @returns Reverse-DNS application identifier.
+ */
+export function resolveDesktopAppId(env: NodeJS.ProcessEnv): string
+
+/**
+ * Resolve and validate the public identity expected on a macOS release.
+ * @param env - Packaging environment.
+ * @returns Expected certificate qualifier and Team ID.
+ */
+export function resolveMacOSSigningEnvironment(env: NodeJS.ProcessEnv): MacOSSigningEnvironment
+
+/**
+ * Resolve one complete credential set accepted by Apple's notary service.
+ * @param env - Packaging environment.
+ * @returns Notary credentials without the submitted artifact path.
+ */
+export function resolveMacOSNotarizationEnvironment(env: NodeJS.ProcessEnv): MacOSNotarizationEnvironment

+ 98 - 0
apps/desktop/scripts/desktop-release-environment.mjs

@@ -0,0 +1,98 @@
+/** Resolve public release identifiers supplied by the packaging environment. */
+
+/** Environment variable that supplies the Electron application identifier. */
+export const DESKTOP_APP_ID_ENV = 'DSH_DESKTOP_APP_ID'
+
+/** Environment variable that supplies electron-builder's macOS certificate qualifier. */
+export const MACOS_SIGNING_IDENTITY_ENV = 'DSH_DESKTOP_MACOS_SIGNING_IDENTITY'
+
+/** Environment variable that supplies the expected Apple Developer Team ID. */
+export const MACOS_TEAM_ID_ENV = 'DSH_DESKTOP_MACOS_TEAM_ID'
+
+const APPLE_API_KEY_ENV = 'APPLE_API_KEY'
+const APPLE_API_KEY_ID_ENV = 'APPLE_API_KEY_ID'
+const APPLE_API_ISSUER_ENV = 'APPLE_API_ISSUER'
+const APPLE_ID_ENV = 'APPLE_ID'
+const APPLE_APP_SPECIFIC_PASSWORD_ENV = 'APPLE_APP_SPECIFIC_PASSWORD'
+const APPLE_TEAM_ID_ENV = 'APPLE_TEAM_ID'
+const APPLE_KEYCHAIN_ENV = 'APPLE_KEYCHAIN'
+const APPLE_KEYCHAIN_PROFILE_ENV = 'APPLE_KEYCHAIN_PROFILE'
+
+/**
+ * Read one required non-empty environment variable.
+ * @param {NodeJS.ProcessEnv} env - Packaging environment.
+ * @param {string} name - Required variable name.
+ * @returns {string} Trimmed variable value.
+ */
+function requireEnvironmentValue(env, name) {
+  const value = env[name]?.trim()
+  if (value === undefined || value === '') {
+    throw new Error(`desktop release environment: ${name} must be set to a non-empty value`)
+  }
+  return value
+}
+
+/**
+ * Resolve and validate the application identifier shared by every platform target.
+ * @param {NodeJS.ProcessEnv} env - Packaging environment.
+ * @returns {string} Reverse-DNS application identifier.
+ */
+export function resolveDesktopAppId(env) {
+  const appId = requireEnvironmentValue(env, DESKTOP_APP_ID_ENV)
+  if (!/^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/u.test(appId)) {
+    throw new Error(`desktop release environment: ${DESKTOP_APP_ID_ENV} must be a reverse-DNS identifier`)
+  }
+  return appId
+}
+
+/**
+ * Resolve and validate the public identity expected on a macOS release.
+ * @param {NodeJS.ProcessEnv} env - Packaging environment.
+ * @returns {{ signingIdentity: string, teamId: string }} Expected certificate qualifier and Team ID.
+ */
+export function resolveMacOSSigningEnvironment(env) {
+  const signingIdentity = requireEnvironmentValue(env, MACOS_SIGNING_IDENTITY_ENV)
+  if (signingIdentity.startsWith('Developer ID Application:')) {
+    throw new Error(`desktop release environment: ${MACOS_SIGNING_IDENTITY_ENV} must omit the "Developer ID Application:" prefix`)
+  }
+  const teamId = requireEnvironmentValue(env, MACOS_TEAM_ID_ENV)
+  if (!/^[A-Z0-9]{10}$/u.test(teamId)) {
+    throw new Error(`desktop release environment: ${MACOS_TEAM_ID_ENV} must contain 10 uppercase letters or digits`)
+  }
+  return { signingIdentity, teamId }
+}
+
+/**
+ * Resolve one complete credential set accepted by Apple's notary service.
+ * @param {NodeJS.ProcessEnv} env - Packaging environment.
+ * @returns {{ appleId: string, appleIdPassword: string, teamId: string } | { appleApiKey: string, appleApiKeyId: string, appleApiIssuer: string } | { keychainProfile: string, keychain?: string }} Notary credentials without the submitted artifact path.
+ */
+export function resolveMacOSNotarizationEnvironment(env) {
+  const appleIdValues = [env[APPLE_ID_ENV], env[APPLE_APP_SPECIFIC_PASSWORD_ENV], env[APPLE_TEAM_ID_ENV]]
+  if (appleIdValues.some(value => value !== undefined)) {
+    return {
+      appleId: requireEnvironmentValue(env, APPLE_ID_ENV),
+      appleIdPassword: requireEnvironmentValue(env, APPLE_APP_SPECIFIC_PASSWORD_ENV),
+      teamId: requireEnvironmentValue(env, APPLE_TEAM_ID_ENV),
+    }
+  }
+
+  const apiKeyValues = [env[APPLE_API_KEY_ENV], env[APPLE_API_KEY_ID_ENV], env[APPLE_API_ISSUER_ENV]]
+  if (apiKeyValues.some(value => value !== undefined)) {
+    return {
+      appleApiKey: requireEnvironmentValue(env, APPLE_API_KEY_ENV),
+      appleApiKeyId: requireEnvironmentValue(env, APPLE_API_KEY_ID_ENV),
+      appleApiIssuer: requireEnvironmentValue(env, APPLE_API_ISSUER_ENV),
+    }
+  }
+
+  const keychainProfile = env[APPLE_KEYCHAIN_PROFILE_ENV]?.trim()
+  if (keychainProfile !== undefined && keychainProfile !== '') {
+    const keychain = env[APPLE_KEYCHAIN_ENV]?.trim()
+    return keychain === undefined || keychain === ''
+      ? { keychainProfile }
+      : { keychainProfile, keychain }
+  }
+
+  throw new Error('desktop release environment: macOS packaging requires APPLE_API_KEY, APPLE_API_KEY_ID, and APPLE_API_ISSUER; APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, and APPLE_TEAM_ID; or APPLE_KEYCHAIN_PROFILE')
+}

+ 257 - 0
apps/desktop/scripts/desktop-upload-plan.ts

@@ -0,0 +1,257 @@
+/** Validate packaged Desktop update artifacts before any network upload begins. */
+
+import { createHash } from 'node:crypto'
+import { createReadStream } from 'node:fs'
+import { readFile, stat } from 'node:fs/promises'
+import { basename, join, resolve } from 'node:path'
+import { load } from 'js-yaml'
+import type { DesktopPackageTargetName } from './package-target.ts'
+import {
+  desktopBuildRecordFilename,
+  desktopUpdateMetadataFilename,
+  resolveDesktopUploadConfig,
+} from './desktop-auto-update-environment.mjs'
+import { desktopTargetBuildPaths } from './desktop-build-paths.mjs'
+
+const APP_ROOT = resolve(import.meta.dirname, '..')
+const REPOSITORY_ROOT = resolve(APP_ROOT, '..', '..')
+const TARGETS = {
+  'mac-arm64': { platform: 'darwin', arch: 'arm64', os: 'mac' },
+  'mac-x64': { platform: 'darwin', arch: 'x64', os: 'mac' },
+  'win-x64': { platform: 'win32', arch: 'x64', os: 'win' },
+} as const satisfies Record<DesktopPackageTargetName, {
+  readonly platform: NodeJS.Platform
+  readonly arch: string
+  readonly os: string
+}>
+
+/** One local file and its final object metadata. */
+export interface DesktopUploadArtifact {
+  readonly path: string
+  readonly filename: string
+  readonly key: string
+  readonly contentType: string
+  readonly cacheControl: string
+  readonly channelMetadata: boolean
+}
+
+/** A fully validated upload operation with channel metadata ordered last. */
+export interface DesktopUploadPlan {
+  readonly environment: 'test' | 'production'
+  readonly target: DesktopPackageTargetName
+  readonly version: string
+  readonly publicUrl: string
+  readonly bucket: string
+  readonly secretIdEnvName: string
+  readonly secretKeyEnvName: string
+  readonly artifacts: readonly DesktopUploadArtifact[]
+}
+
+/** Filesystem and environment inputs used to validate one upload. */
+export interface DesktopUploadPlanOptions {
+  readonly environment?: NodeJS.ProcessEnv
+  readonly repositoryRoot?: string
+  readonly appRoot?: string
+  readonly artifactsRoot?: string
+}
+
+interface UpdateFileInfo {
+  readonly filename: string
+  readonly size: number
+  readonly sha512: string
+}
+
+function object(value: unknown, label: string): Record<string, unknown> {
+  if (typeof value !== 'object' || value === null || Array.isArray(value)) {
+    throw new Error(`desktop upload: ${label} must be an object`)
+  }
+  return value as Record<string, unknown>
+}
+
+function stringField(value: unknown, label: string): string {
+  if (typeof value !== 'string' || value === '') {
+    throw new Error(`desktop upload: ${label} must be a non-empty string`)
+  }
+  return value
+}
+
+function numberField(value: unknown, label: string): number {
+  if (typeof value !== 'number' || !Number.isSafeInteger(value) || value <= 0) {
+    throw new Error(`desktop upload: ${label} must be a positive integer`)
+  }
+  return value
+}
+
+async function jsonFile(path: string, label: string): Promise<Record<string, unknown>> {
+  let parsed: unknown
+  try {
+    parsed = JSON.parse(await readFile(path, 'utf8'))
+  }
+  catch (error) {
+    throw new Error(`desktop upload: cannot read ${label} at ${path}: ${error instanceof Error ? error.message : String(error)}`)
+  }
+  return object(parsed, label)
+}
+
+async function manifestVersion(path: string, label: string): Promise<string> {
+  return stringField((await jsonFile(path, label)).version, `${label}.version`)
+}
+
+function updateFileInfo(value: unknown, label: string, expectedFilename: string): UpdateFileInfo {
+  const info = object(value, label)
+  const filename = stringField(info.url ?? info.path, `${label}.url`)
+  if (filename !== basename(filename) || filename !== expectedFilename) {
+    throw new Error(`desktop upload: ${label} must reference ${expectedFilename}, received ${filename}`)
+  }
+  return {
+    filename,
+    size: numberField(info.size, `${label}.size`),
+    sha512: stringField(info.sha512, `${label}.sha512`),
+  }
+}
+
+async function sha512(path: string): Promise<string> {
+  const hash = createHash('sha512')
+  for await (const chunk of createReadStream(path)) hash.update(chunk)
+  return hash.digest('base64')
+}
+
+async function verifyChecksummedArtifact(
+  artifactsRoot: string,
+  info: UpdateFileInfo,
+): Promise<string> {
+  const path = join(artifactsRoot, info.filename)
+  const details = await stat(path).catch(() => undefined)
+  if (details === undefined || !details.isFile()) {
+    throw new Error(`desktop upload: missing artifact ${path}`)
+  }
+  if (details.size !== info.size) {
+    throw new Error(`desktop upload: ${info.filename} size ${details.size} does not match update metadata ${info.size}`)
+  }
+  const actual = await sha512(path)
+  if (actual !== info.sha512) {
+    throw new Error(`desktop upload: ${info.filename} SHA-512 does not match update metadata`)
+  }
+  return path
+}
+
+async function requireArtifact(artifactsRoot: string, filename: string): Promise<string> {
+  const path = join(artifactsRoot, filename)
+  const details = await stat(path).catch(() => undefined)
+  if (details === undefined || !details.isFile() || details.size === 0) {
+    throw new Error(`desktop upload: missing or empty artifact ${path}`)
+  }
+  return path
+}
+
+function uploadArtifact(
+  path: string,
+  keyPrefix: string,
+  contentType: string,
+  channelMetadata = false,
+): DesktopUploadArtifact {
+  const filename = basename(path)
+  return {
+    path,
+    filename,
+    key: `${keyPrefix}/${filename}`,
+    contentType,
+    cacheControl: channelMetadata
+      ? 'no-cache'
+      : 'public, max-age=31536000, immutable',
+    channelMetadata,
+  }
+}
+
+/**
+ * Validate the completed package record, dsh version, update metadata, hashes, and target files.
+ * @param targetName - Fixed platform and architecture selected by the upload command.
+ * @param options - Optional filesystem roots and environment for tests or release automation.
+ * @returns An upload plan whose mutable channel metadata is the final entry.
+ */
+export async function createDesktopUploadPlan(
+  targetName: DesktopPackageTargetName,
+  options: DesktopUploadPlanOptions = {},
+): Promise<DesktopUploadPlan> {
+  const target = TARGETS[targetName]
+  if (target === undefined) {
+    throw new Error(`desktop upload: unsupported target ${String(targetName)}`)
+  }
+  const environment = options.environment ?? process.env
+  const repositoryRoot = options.repositoryRoot ?? REPOSITORY_ROOT
+  const appRoot = options.appRoot ?? APP_ROOT
+  const artifactsRoot = options.artifactsRoot ?? desktopTargetBuildPaths(targetName).artifacts
+  const dshVersion = await manifestVersion(join(repositoryRoot, 'package.json'), 'dsh package')
+  const desktopVersion = await manifestVersion(join(appRoot, 'package.json'), 'desktop package')
+  if (dshVersion !== desktopVersion) {
+    throw new Error(`desktop upload: desktop version ${desktopVersion} does not match current dsh version ${dshVersion}`)
+  }
+
+  const update = resolveDesktopUploadConfig(environment, target.platform, target.arch)
+  const buildRecord = await jsonFile(
+    join(artifactsRoot, desktopBuildRecordFilename(targetName)),
+    `${targetName} package completion record`,
+  )
+  if (buildRecord.schemaVersion !== 1
+    || buildRecord.target !== targetName
+    || buildRecord.version !== dshVersion
+    || buildRecord.environment !== update.environment
+    || buildRecord.publicUrl !== update.publicUrl) {
+    throw new Error(`desktop upload: ${targetName} package completion record does not match dsh ${dshVersion} and ${update.environment} update destination`)
+  }
+
+  const metadataFilename = desktopUpdateMetadataFilename(dshVersion, target.platform)
+  const metadataPath = join(artifactsRoot, metadataFilename)
+  let metadataValue: unknown
+  try {
+    metadataValue = load(await readFile(metadataPath, 'utf8'))
+  }
+  catch (error) {
+    throw new Error(`desktop upload: cannot read update metadata at ${metadataPath}: ${error instanceof Error ? error.message : String(error)}`)
+  }
+  const metadata = object(metadataValue, metadataFilename)
+  const metadataVersion = stringField(metadata.version, `${metadataFilename}.version`)
+  if (metadataVersion !== dshVersion) {
+    throw new Error(`desktop upload: ${metadataFilename} version ${metadataVersion} does not match current dsh version ${dshVersion}`)
+  }
+  if (!Array.isArray(metadata.files) || metadata.files.length !== 1) {
+    throw new Error(`desktop upload: ${metadataFilename}.files must contain exactly one target update file`)
+  }
+
+  const base = `deepseek-harness-${dshVersion}-${target.os}-${target.arch}`
+  const updaterExtension = target.platform === 'darwin' ? 'zip' : 'exe'
+  const updaterInfo = updateFileInfo(metadata.files[0], `${metadataFilename}.files[0]`, `${base}.${updaterExtension}`)
+  const updaterPath = await verifyChecksummedArtifact(artifactsRoot, updaterInfo)
+  const artifacts: DesktopUploadArtifact[] = []
+
+  if (target.platform === 'darwin') {
+    const dmgPath = await requireArtifact(artifactsRoot, `${base}.dmg`)
+    const blockmapPath = await requireArtifact(artifactsRoot, `${base}.zip.blockmap`)
+    artifacts.push(
+      uploadArtifact(dmgPath, update.keyPrefix, 'application/x-apple-diskimage'),
+      uploadArtifact(updaterPath, update.keyPrefix, 'application/zip'),
+      uploadArtifact(blockmapPath, update.keyPrefix, 'application/octet-stream'),
+    )
+  }
+  else {
+    const blockMapSize = object(metadata.files[0], `${metadataFilename}.files[0]`).blockMapSize
+    numberField(blockMapSize, `${metadataFilename}.files[0].blockMapSize`)
+    artifacts.push(uploadArtifact(
+      updaterPath,
+      update.keyPrefix,
+      'application/vnd.microsoft.portable-executable',
+    ))
+  }
+
+  artifacts.push(uploadArtifact(metadataPath, update.keyPrefix, 'application/yaml', true))
+  return {
+    environment: update.environment,
+    target: targetName,
+    version: dshVersion,
+    publicUrl: update.publicUrl,
+    bucket: update.bucket,
+    secretIdEnvName: update.secretIdEnvName,
+    secretKeyEnvName: update.secretKeyEnvName,
+    artifacts,
+  }
+}

+ 118 - 0
apps/desktop/scripts/dev.ts

@@ -0,0 +1,118 @@
+/** Build and launch the unpackaged Electron shell against the current workspace. */
+
+import { spawn } from 'node:child_process'
+import { existsSync, readFileSync } from 'node:fs'
+import { createRequire } from 'node:module'
+import { join, resolve } from 'node:path'
+import { parseArgs } from 'node:util'
+import { DESKTOP_HOST_PROTOCOL_VERSION } from '../src/host-protocol.ts'
+import type { DesktopRelease } from '../src/release.ts'
+import { prepareDevelopmentProject } from './development-project.ts'
+
+const APP_ROOT = resolve(import.meta.dirname, '..')
+const REPOSITORY_ROOT = resolve(APP_ROOT, '..', '..')
+const BUILD_ROOT = join(APP_ROOT, '.desktop-build')
+const DEVELOPMENT_ROOT = join(BUILD_ROOT, 'development')
+
+interface PackageManifest {
+  readonly version?: string
+}
+
+function packageVersion(path: string, subject: string): string {
+  const manifest = JSON.parse(readFileSync(path, 'utf8')) as PackageManifest
+  if (typeof manifest.version !== 'string') throw new Error(`desktop development: ${subject} has no version`)
+  return manifest.version
+}
+
+function debugPort(name: string, fallback: number): number {
+  const value = process.env[name]
+  if (value === undefined || value === '') return fallback
+  const port = Number(value)
+  if (!Number.isSafeInteger(port) || port < 1 || port > 65_535) {
+    throw new Error(`desktop development: ${name} must be an integer from 1 through 65535`)
+  }
+  return port
+}
+
+async function run(command: string, args: readonly string[], cwd: string, environment = process.env): Promise<void> {
+  await new Promise<void>((resolvePromise, reject) => {
+    const child = spawn(command, args, { cwd, env: environment, stdio: 'inherit' })
+    child.once('error', reject)
+    child.once('exit', (code, signal) => {
+      if (code === 0) resolvePromise()
+      else reject(new Error(`desktop development: ${args.join(' ')} exited with ${String(code ?? signal)}`))
+    })
+  })
+}
+
+async function runPackageScript(script: string, cwd: string): Promise<void> {
+  const packageManager = process.env.npm_execpath
+  if (packageManager === undefined || packageManager === '') {
+    throw new Error('desktop development: invoke this launcher through pnpm run dev:desktop or start:desktop')
+  }
+  await run(process.execPath, [packageManager, 'run', script], cwd)
+}
+
+async function launchElectron(projectDir: string): Promise<void> {
+  const require = createRequire(import.meta.url)
+  const electron: unknown = require('electron')
+  if (typeof electron !== 'string') throw new Error('desktop development: electron executable is unavailable')
+  const mainPort = debugPort('DSH_DESKTOP_MAIN_INSPECT_PORT', 9229)
+  const rendererPort = debugPort('DSH_DESKTOP_RENDERER_DEBUG_PORT', 9222)
+  const hostPort = debugPort('DSH_DESKTOP_HOST_INSPECT_PORT', 9230)
+  const home = resolve(process.env.DSH_HOME ?? join(DEVELOPMENT_ROOT, 'home'))
+  const userData = join(DEVELOPMENT_ROOT, 'electron-user-data')
+  const environment: NodeJS.ProcessEnv = {
+    ...process.env,
+    DSH_HOME: home,
+    DSH_DESKTOP_DEV_PROJECT_DIR: projectDir,
+    DSH_DESKTOP_HOST_INSPECT_PORT: String(hostPort),
+    DSH_DESKTOP_NODE_BINARY: process.execPath,
+    DSH_DESKTOP_OPEN_DEVTOOLS: process.env.DSH_DESKTOP_OPEN_DEVTOOLS ?? '1',
+    ELECTRON_ENABLE_LOGGING: process.env.ELECTRON_ENABLE_LOGGING ?? '1',
+  }
+  console.log(`desktop development: DSH_HOME=${home}`)
+  console.log(`desktop development: inspectors main=${String(mainPort)}, renderer=${String(rendererPort)}, host=${String(hostPort)}`)
+  await run(electron, [
+    `--inspect=127.0.0.1:${String(mainPort)}`,
+    `--remote-debugging-port=${String(rendererPort)}`,
+    `--user-data-dir=${userData}`,
+    APP_ROOT,
+  ], APP_ROOT, environment)
+}
+
+async function main(): Promise<void> {
+  const { values } = parseArgs({ options: { 'skip-build': { type: 'boolean', default: false } } })
+  if (!values['skip-build']) {
+    await runPackageScript('build', REPOSITORY_ROOT)
+    await runPackageScript('build', APP_ROOT)
+  }
+  for (const path of [
+    join(APP_ROOT, 'lib', 'main.js'),
+    join(REPOSITORY_ROOT, 'apps', 'desktop-host', 'lib', 'index.js'),
+  ]) {
+    if (!existsSync(path)) throw new Error(`desktop development: missing built artifact ${path}`)
+  }
+  const version = packageVersion(join(APP_ROOT, 'package.json'), 'desktop package')
+  const pnpmVersion = packageVersion(join(APP_ROOT, 'node_modules', 'pnpm', 'package.json'), 'pnpm package')
+  const release: DesktopRelease = {
+    schemaVersion: 1,
+    version,
+    hostProtocolVersion: DESKTOP_HOST_PROTOCOL_VERSION,
+    nodeVersion: process.versions.node,
+    pnpmVersion,
+  }
+  const projectDir = prepareDevelopmentProject({
+    projectDir: join(DEVELOPMENT_ROOT, 'project'),
+    cliDir: join(REPOSITORY_ROOT, 'apps', 'cli'),
+    hostDir: join(REPOSITORY_ROOT, 'apps', 'desktop-host'),
+    dependencyDir: join(REPOSITORY_ROOT, 'node_modules', '.pnpm', 'node_modules'),
+    release,
+  })
+  await launchElectron(projectDir)
+}
+
+main().catch((error: unknown) => {
+  console.error(error instanceof Error ? error.message : error)
+  process.exitCode = 1
+})

+ 120 - 0
apps/desktop/scripts/development-project.ts

@@ -0,0 +1,120 @@
+/** Prepare the disposable npm-project view used by an unpackaged Electron shell. */
+
+import {
+  existsSync,
+  lstatSync,
+  mkdirSync,
+  readFileSync,
+  readdirSync,
+  realpathSync,
+  rmSync,
+  symlinkSync,
+  unlinkSync,
+} from 'node:fs'
+import { dirname, join } from 'node:path'
+import { createDevelopmentProjectMetadata } from '../src/project-manager.ts'
+import type { DesktopRelease } from '../src/release.ts'
+
+interface PackageManifest {
+  readonly name?: string
+  readonly version?: string
+}
+
+/** Inputs whose locations differ between the launcher and isolated tests. */
+export interface DevelopmentProjectOptions {
+  /** Directory replaced with the generated development project. */
+  readonly projectDir: string
+  /** Current workspace's `apps/cli` package directory. */
+  readonly cliDir: string
+  /** Current workspace's private Desktop Host application directory. */
+  readonly hostDir: string
+  /** pnpm's workspace-wide virtual-hoist directory. */
+  readonly dependencyDir: string
+  /** Release identity written into the disposable project metadata. */
+  readonly release: DesktopRelease
+}
+
+function readManifest(path: string): PackageManifest {
+  return JSON.parse(readFileSync(path, 'utf8')) as PackageManifest
+}
+
+function removeOwnedPath(path: string): void {
+  let stat: ReturnType<typeof lstatSync>
+  try {
+    stat = lstatSync(path)
+  } catch (error) {
+    if ((error as NodeJS.ErrnoException).code === 'ENOENT') return
+    throw error
+  }
+  if (stat.isSymbolicLink()) {
+    unlinkSync(path)
+    return
+  }
+  if (stat.isDirectory()) {
+    rmSync(path, { recursive: true })
+    return
+  }
+  unlinkSync(path)
+}
+
+function linkDirectory(source: string, destination: string): void {
+  mkdirSync(dirname(destination), { recursive: true })
+  symlinkSync(realpathSync(source), destination, process.platform === 'win32' ? 'junction' : 'dir')
+}
+
+function mirrorDependencyLinks(sourceRoot: string, destinationRoot: string): void {
+  for (const entry of readdirSync(sourceRoot, { withFileTypes: true })) {
+    if (entry.name === '.bin') continue
+    const source = join(sourceRoot, entry.name)
+    if (entry.name.startsWith('@') && (entry.isDirectory() || entry.isSymbolicLink())) {
+      mkdirSync(join(destinationRoot, entry.name), { recursive: true })
+      for (const scoped of readdirSync(source, { withFileTypes: true })) {
+        if (!scoped.isDirectory() && !scoped.isSymbolicLink()) continue
+        linkDirectory(join(source, scoped.name), join(destinationRoot, entry.name, scoped.name))
+      }
+      continue
+    }
+    if (entry.isDirectory() || entry.isSymbolicLink()) linkDirectory(source, join(destinationRoot, entry.name))
+  }
+}
+
+/**
+ * Replace one disposable project with links to the current built workspace.
+ * @param options - Project destination, CLI package, and release identity.
+ * @returns the absolute project directory supplied by the caller.
+ */
+export function prepareDevelopmentProject(options: DevelopmentProjectOptions): string {
+  const cliManifest = readManifest(join(options.cliDir, 'package.json'))
+  if (cliManifest.name !== '@deepseek-ai/dsh' || cliManifest.version !== options.release.version) {
+    throw new Error(
+      `desktop development: apps/cli must be @deepseek-ai/dsh@${options.release.version}, found `
+      + `${String(cliManifest.name)}@${String(cliManifest.version)}`,
+    )
+  }
+  if (!existsSync(options.dependencyDir)) {
+    throw new Error('desktop development: workspace dependency links are missing; run pnpm install')
+  }
+  const hostManifest = readManifest(join(options.hostDir, 'package.json'))
+  if (hostManifest.name !== '@deepseek-ai/dsh-desktop-host' || hostManifest.version !== options.release.version) {
+    throw new Error(
+      `desktop development: apps/desktop-host must be @deepseek-ai/dsh-desktop-host@${options.release.version}, found `
+      + `${String(hostManifest.name)}@${String(hostManifest.version)}`,
+    )
+  }
+  if (!existsSync(join(options.hostDir, 'lib', 'index.js'))) {
+    throw new Error('desktop development: apps/desktop-host/lib/index.js is missing; run pnpm run build')
+  }
+
+  removeOwnedPath(options.projectDir)
+  createDevelopmentProjectMetadata(options.projectDir, options.release)
+  const destinationModules = join(options.projectDir, 'node_modules')
+  mkdirSync(destinationModules, { recursive: true })
+  mirrorDependencyLinks(options.dependencyDir, destinationModules)
+  const dshLink = join(destinationModules, '@deepseek-ai', 'dsh')
+  removeOwnedPath(dshLink)
+  linkDirectory(options.cliDir, dshLink)
+  const hostLink = join(destinationModules, '@deepseek-ai', 'dsh-desktop-host')
+  removeOwnedPath(hostLink)
+  linkDirectory(options.hostDir, hostLink)
+  return options.projectDir
+}

+ 413 - 0
apps/desktop/scripts/macos-seed-store.ts

@@ -0,0 +1,413 @@
+/** Sign Mach-O content in a pnpm CAS without invalidating the store index. */
+
+import { createHash } from 'node:crypto'
+import {
+  chmodSync,
+  closeSync,
+  copyFileSync,
+  existsSync,
+  mkdirSync,
+  mkdtempSync,
+  openSync,
+  readFileSync,
+  readSync,
+  readdirSync,
+  rmSync,
+  unlinkSync,
+  writeFileSync,
+} from 'node:fs'
+import { availableParallelism, tmpdir } from 'node:os'
+import { basename, dirname, join, relative, sep } from 'node:path'
+import { DatabaseSync } from 'node:sqlite'
+import { Packr } from 'msgpackr'
+import type { MacOSSigningEnvironment } from './desktop-release-environment.mjs'
+import { signMacOSSeedCode, verifyMacOSSeedCode } from './verify-macos-signature.mjs'
+
+const MACH_O_MAGICS = new Set([
+  'cafebabe',
+  'cafebabf',
+  'cefaedfe',
+  'cffaedfe',
+  'feedface',
+  'feedfacf',
+  'bebafeca',
+  'bfbafeca',
+])
+const CAS_PATH_PATTERN = /^([0-9a-f]{2})\/([0-9a-f]{126})(-exec)?$/u
+const MAX_CONCURRENT_CODE_SIGNERS = 4
+const packr = new Packr({ moreTypes: true, useRecords: true })
+
+interface PnpmStoreFileRecord {
+  checkedAt: number
+  digest: string
+  mode: number
+  size: number
+}
+
+interface PnpmSideEffectsRecord {
+  readonly added?: Map<string, PnpmStoreFileRecord>
+}
+
+interface PnpmPackageIndexRecord {
+  readonly algo?: string
+  readonly files?: Map<string, PnpmStoreFileRecord>
+  readonly sideEffects?: Map<string, PnpmSideEffectsRecord>
+}
+
+interface DecodedIndexRow {
+  readonly key: string
+  readonly value: PnpmPackageIndexRecord
+  changed: boolean
+}
+
+interface CasFile {
+  readonly path: string
+  readonly digest: string
+  readonly executable: boolean
+}
+
+interface FileReference {
+  readonly row: DecodedIndexRow
+  readonly record: PnpmStoreFileRecord
+}
+
+interface SigningWork {
+  readonly file: CasFile
+  readonly references: readonly FileReference[]
+  readonly temporaryPath: string
+}
+
+/** Summary of native code rewritten in one pnpm store. */
+export interface MacOSSeedStoreSigningResult {
+  readonly signedFiles: number
+  readonly prunedOrphans: number
+  readonly updatedIndexRows: number
+}
+
+/** A signer used to make one writable Mach-O copy release-valid. */
+export type MacOSSeedCodeSigner = (path: string, identifier: string) => Promise<void>
+
+/** A verifier used to check one Mach-O file after packaging transport. */
+export type MacOSSeedCodeVerifier = (path: string) => void
+
+/** Optional execution controls for seed-store code signing. */
+export interface MacOSSeedStoreSigningOptions {
+  readonly signer?: MacOSSeedCodeSigner
+  readonly concurrency?: number
+}
+
+function isRecord(value: unknown): value is Record<string, unknown> {
+  return typeof value === 'object' && value !== null
+}
+
+function isStoreFileRecord(value: unknown): value is PnpmStoreFileRecord {
+  if (!isRecord(value)) return false
+  return typeof value.checkedAt === 'number'
+    && typeof value.digest === 'string'
+    && /^[0-9a-f]{128}$/u.test(value.digest)
+    && Number.isSafeInteger(value.mode)
+    && Number.isSafeInteger(value.size)
+}
+
+function packageFileMaps(value: unknown, key: string): readonly Map<string, PnpmStoreFileRecord>[] {
+  if (!isRecord(value)) throw new Error(`desktop seed signing: invalid pnpm index record ${key}`)
+  const record = value as PnpmPackageIndexRecord
+  if (record.algo !== undefined && record.algo !== 'sha512') {
+    throw new Error(`desktop seed signing: unsupported pnpm index algorithm in ${key}`)
+  }
+  const maps: Map<string, PnpmStoreFileRecord>[] = []
+  if (record.files !== undefined) {
+    if (!(record.files instanceof Map)) throw new Error(`desktop seed signing: invalid pnpm file map in ${key}`)
+    maps.push(record.files)
+  }
+  if (record.sideEffects !== undefined) {
+    if (!(record.sideEffects instanceof Map)) {
+      throw new Error(`desktop seed signing: invalid pnpm side-effects map in ${key}`)
+    }
+    for (const effect of record.sideEffects.values()) {
+      if (!isRecord(effect)) throw new Error(`desktop seed signing: invalid pnpm side effect in ${key}`)
+      if (effect.added === undefined) continue
+      if (!(effect.added instanceof Map)) {
+        throw new Error(`desktop seed signing: invalid pnpm side-effect file map in ${key}`)
+      }
+      maps.push(effect.added)
+    }
+  }
+  for (const files of maps) {
+    for (const file of files.values()) {
+      if (!isStoreFileRecord(file)) throw new Error(`desktop seed signing: invalid pnpm file record in ${key}`)
+    }
+  }
+  return maps
+}
+
+function isExecutableMode(mode: number): boolean {
+  return (mode & 0o111) !== 0
+}
+
+function referenceKey(digest: string, executable: boolean): string {
+  return `${digest}:${executable ? 'exec' : 'nonexec'}`
+}
+
+function isMachO(path: string): boolean {
+  const descriptor = openSync(path, 'r')
+  try {
+    const header = Buffer.alloc(4)
+    return readSync(descriptor, header, 0, header.length, 0) === header.length
+      && MACH_O_MAGICS.has(header.toString('hex'))
+  } finally {
+    closeSync(descriptor)
+  }
+}
+
+function visitFiles(root: string): readonly string[] {
+  const files: string[] = []
+  const visit = (directory: string): void => {
+    for (const entry of readdirSync(directory, { withFileTypes: true })) {
+      const path = join(directory, entry.name)
+      if (entry.isSymbolicLink()) {
+        throw new Error(`desktop seed signing: pnpm store contains a symbolic link: ${relative(root, path)}`)
+      }
+      if (entry.isDirectory()) visit(path)
+      else if (entry.isFile()) files.push(path)
+      else throw new Error(`desktop seed signing: unsupported pnpm store entry: ${relative(root, path)}`)
+    }
+  }
+  visit(root)
+  return files.sort((left, right) => left.localeCompare(right))
+}
+
+function versionRoots(storeRoot: string): readonly string[] {
+  return readdirSync(storeRoot, { withFileTypes: true })
+    .filter(entry => entry.isDirectory() && /^v\d+$/u.test(entry.name))
+    .map(entry => join(storeRoot, entry.name))
+    .filter(root => existsSync(join(root, 'files')))
+    .sort((left, right) => left.localeCompare(right))
+}
+
+function casFiles(versionRoot: string): readonly CasFile[] {
+  const filesRoot = join(versionRoot, 'files')
+  const result: CasFile[] = []
+  for (const path of visitFiles(filesRoot)) {
+    if (!isMachO(path)) continue
+    const normalized = relative(filesRoot, path).split(sep).join('/')
+    const match = CAS_PATH_PATTERN.exec(normalized)
+    if (match === null) {
+      throw new Error(`desktop seed signing: Mach-O content has an unsupported pnpm CAS path: ${normalized}`)
+    }
+    result.push({
+      path,
+      digest: `${match[1]}${match[2]}`,
+      executable: match[3] !== undefined,
+    })
+  }
+  return result
+}
+
+function readIndexRows(database: DatabaseSync): readonly DecodedIndexRow[] {
+  const rows: DecodedIndexRow[] = []
+  for (const row of database.prepare('SELECT key, data FROM package_index').iterate() as Iterable<{
+    key: string
+    data: Uint8Array
+  }>) {
+    rows.push({ key: row.key, value: packr.unpack(row.data) as PnpmPackageIndexRecord, changed: false })
+  }
+  return rows
+}
+
+function fileReferences(rows: readonly DecodedIndexRow[]): ReadonlyMap<string, readonly FileReference[]> {
+  const references = new Map<string, FileReference[]>()
+  for (const row of rows) {
+    for (const files of packageFileMaps(row.value, row.key)) {
+      for (const record of files.values()) {
+        const key = referenceKey(record.digest, isExecutableMode(record.mode))
+        const values = references.get(key) ?? []
+        values.push({ row, record })
+        references.set(key, values)
+      }
+    }
+  }
+  return references
+}
+
+function writeCasFile(path: string, body: Buffer, mode: number): void {
+  mkdirSync(dirname(path), { recursive: true })
+  try {
+    writeFileSync(path, body, { flag: 'wx', mode })
+  } catch (error) {
+    if (!isRecord(error) || error.code !== 'EEXIST' || !readFileSync(path).equals(body)) throw error
+  }
+  chmodSync(path, mode)
+}
+
+function signedCasPath(versionRoot: string, digest: string, executable: boolean): string {
+  return join(
+    versionRoot,
+    'files',
+    digest.slice(0, 2),
+    `${digest.slice(2)}${executable ? '-exec' : ''}`,
+  )
+}
+
+async function runConcurrent<T>(
+  values: readonly T[],
+  concurrency: number,
+  run: (value: T) => Promise<void>,
+): Promise<void> {
+  let next = 0
+  const failure: { error?: unknown; failed: boolean } = { failed: false }
+  const worker = async (): Promise<void> => {
+    while (!failure.failed) {
+      const index = next
+      if (index >= values.length) return
+      next += 1
+      try {
+        await run(values[index] as T)
+      } catch (error) {
+        if (!failure.failed) {
+          failure.failed = true
+          failure.error = error
+        }
+      }
+    }
+  }
+  const workers = Array.from(
+    { length: Math.min(concurrency, values.length) },
+    async () => worker(),
+  )
+  await Promise.all(workers)
+  if (failure.failed) throw failure.error
+}
+
+async function rewriteVersionStore(
+  versionRoot: string,
+  appId: string,
+  signer: MacOSSeedCodeSigner,
+  concurrency: number,
+): Promise<MacOSSeedStoreSigningResult> {
+  const databasePath = join(versionRoot, 'index.db')
+  if (!existsSync(databasePath)) {
+    throw new Error(`desktop seed signing: pnpm store has no package index: ${databasePath}`)
+  }
+  const database = new DatabaseSync(databasePath)
+  const workRoot = mkdtempSync(join(tmpdir(), 'dsh-desktop-seed-signing-'))
+  const obsoleteFiles = new Set<string>()
+  let prunedOrphans = 0
+  let rows: readonly DecodedIndexRow[] = []
+  try {
+    rows = readIndexRows(database)
+    const references = fileReferences(rows)
+    const signingWork: SigningWork[] = []
+    for (const file of casFiles(versionRoot)) {
+      const body = readFileSync(file.path)
+      const actualDigest = createHash('sha512').update(body).digest('hex')
+      if (actualDigest !== file.digest) {
+        throw new Error(`desktop seed signing: pnpm CAS digest mismatch at ${file.path}`)
+      }
+      const fileReferences = references.get(referenceKey(file.digest, file.executable)) ?? []
+      if (fileReferences.length === 0) {
+        obsoleteFiles.add(file.path)
+        prunedOrphans += 1
+        continue
+      }
+      const temporary = join(workRoot, `${signingWork.length.toString().padStart(4, '0')}-${basename(file.path)}`)
+      copyFileSync(file.path, temporary)
+      chmodSync(temporary, 0o755)
+      signingWork.push({ file, references: fileReferences, temporaryPath: temporary })
+    }
+    await runConcurrent(signingWork, concurrency, async (work) => {
+      await signer(work.temporaryPath, `${appId}.seed.${work.file.digest.slice(0, 32)}`)
+    })
+    for (const work of signingWork) {
+      const signedBody = readFileSync(work.temporaryPath)
+      if (!isMachO(work.temporaryPath)) {
+        throw new Error(`desktop seed signing: signer produced non-Mach-O content for ${work.file.path}`)
+      }
+      const signedDigest = createHash('sha512').update(signedBody).digest('hex')
+      const mode = work.file.executable ? 0o755 : 0o644
+      const destination = signedCasPath(versionRoot, signedDigest, work.file.executable)
+      writeCasFile(destination, signedBody, mode)
+      const checkedAt = Date.now()
+      for (const reference of work.references) {
+        reference.record.checkedAt = checkedAt
+        reference.record.digest = signedDigest
+        reference.record.mode = mode
+        reference.record.size = signedBody.length
+        reference.row.changed = true
+      }
+      if (destination !== work.file.path) obsoleteFiles.add(work.file.path)
+    }
+    const changedRows = rows.filter(row => row.changed)
+    database.exec('BEGIN IMMEDIATE')
+    let committed = false
+    try {
+      const statement = database.prepare('INSERT OR REPLACE INTO package_index (key, data) VALUES (?, ?)')
+      for (const row of changedRows) statement.run(row.key, packr.pack(row.value))
+      database.exec('COMMIT')
+      committed = true
+    } finally {
+      if (!committed) database.exec('ROLLBACK')
+    }
+    for (const path of obsoleteFiles) unlinkSync(path)
+    database.exec('VACUUM')
+    return { signedFiles: signingWork.length, prunedOrphans, updatedIndexRows: changedRows.length }
+  } finally {
+    database.close()
+    rmSync(workRoot, { recursive: true, force: true })
+  }
+}
+
+/**
+ * Replace every Mach-O CAS object with a Developer ID signed object and update pnpm's SHA-512 index.
+ * A signer rejection leaves the original CAS objects and package index unchanged.
+ * @param storeRoot - Loose pnpm store prepared for the packaged seed.
+ * @param appId - Electron application ID used as the signing identifier prefix.
+ * @param expected - Company Developer ID identity and Team ID.
+ * @param options - Optional signer and worker bound used by focused tests.
+ * @returns Counts for release diagnostics after every signer completes and the index transaction commits.
+ */
+export async function signMacOSSeedStore(
+  storeRoot: string,
+  appId: string,
+  expected: MacOSSigningEnvironment,
+  options: MacOSSeedStoreSigningOptions = {},
+): Promise<MacOSSeedStoreSigningResult> {
+  const roots = versionRoots(storeRoot)
+  if (roots.length === 0) throw new Error(`desktop seed signing: no pnpm store versions found in ${storeRoot}`)
+  const concurrency = options.concurrency ?? Math.min(MAX_CONCURRENT_CODE_SIGNERS, availableParallelism())
+  if (!Number.isSafeInteger(concurrency) || concurrency < 1) {
+    throw new Error(`desktop seed signing: concurrency must be a positive integer; received ${String(concurrency)}`)
+  }
+  const signer = options.signer ?? (async (path, identifier) => {
+    await signMacOSSeedCode(path, identifier, expected)
+  })
+  const results: MacOSSeedStoreSigningResult[] = []
+  for (const root of roots) results.push(await rewriteVersionStore(root, appId, signer, concurrency))
+  return results.reduce((total, current) => ({
+    signedFiles: total.signedFiles + current.signedFiles,
+    prunedOrphans: total.prunedOrphans + current.prunedOrphans,
+    updatedIndexRows: total.updatedIndexRows + current.updatedIndexRows,
+  }), { signedFiles: 0, prunedOrphans: 0, updatedIndexRows: 0 })
+}
+
+/**
+ * Verify that every Mach-O CAS object has the expected Developer ID, timestamp, and hardened runtime.
+ * @param storeRoot - Loose or extracted pnpm store.
+ * @param expected - Company Developer ID identity and Team ID.
+ * @param verifier - Injectable signature verifier used by focused tests.
+ * @returns Number of verified Mach-O files.
+ */
+export function verifyMacOSSeedStore(
+  storeRoot: string,
+  expected: MacOSSigningEnvironment,
+  verifier: MacOSSeedCodeVerifier = (path) => { verifyMacOSSeedCode(path, expected) },
+): number {
+  let count = 0
+  for (const root of versionRoots(storeRoot)) {
+    for (const file of casFiles(root)) {
+      verifier(file.path)
+      count += 1
+    }
+  }
+  return count
+}

+ 23 - 0
apps/desktop/scripts/notarize-macos-disk-images.d.mts

@@ -0,0 +1,23 @@
+import type { NotarizeOptions } from '@electron/notarize'
+import type { MacOSSigningEnvironment } from './desktop-release-environment.mjs'
+
+/** Completed electron-builder artifact needed for disk-image notarization. */
+export interface DesktopBuildArtifact {
+  readonly file: string
+}
+
+/**
+ * Submit one generated DMG to Apple, staple its ticket, and verify Gatekeeper acceptance.
+ * @param artifact - Completed electron-builder artifact.
+ * @param env - Packaging environment.
+ * @param expected - Public release identity.
+ * @param submit - Notary submission implementation.
+ * @param verify - Disk-image qualification implementation.
+ */
+export function notarizeMacOSDiskImageArtifact(
+  artifact: DesktopBuildArtifact,
+  env: NodeJS.ProcessEnv,
+  expected: MacOSSigningEnvironment,
+  submit?: (options: NotarizeOptions) => Promise<void>,
+  verify?: (path: string, expected: MacOSSigningEnvironment) => void,
+): Promise<void>

+ 30 - 0
apps/desktop/scripts/notarize-macos-disk-images.mjs

@@ -0,0 +1,30 @@
+/** Notarize and qualify macOS disk images after electron-builder creates them. */
+
+import { notarize } from '@electron/notarize'
+import { rmSync } from 'node:fs'
+import { resolveMacOSNotarizationEnvironment } from './desktop-release-environment.mjs'
+import { verifyMacOSDiskImage } from './verify-macos-signature.mjs'
+
+/**
+ * Submit one generated DMG to Apple, staple its ticket, and verify Gatekeeper acceptance.
+ * @param {{ file: string }} artifact - Completed electron-builder artifact.
+ * @param {NodeJS.ProcessEnv} env - Packaging environment.
+ * @param {{ signingIdentity: string, teamId: string }} expected - Public release identity.
+ * @param {(options: object) => Promise<void>} submit - Notary submission implementation.
+ * @param {(path: string, expected: object) => void} verify - Disk-image qualification implementation.
+ * @returns {Promise<void>}
+ */
+export async function notarizeMacOSDiskImageArtifact(
+  artifact,
+  env,
+  expected,
+  submit = notarize,
+  verify = verifyMacOSDiskImage,
+) {
+  if (!artifact.file.endsWith('.dmg')) return
+  rmSync(`${artifact.file}.blockmap`, { force: true })
+  const credentials = resolveMacOSNotarizationEnvironment(env)
+  await submit({ appPath: artifact.file, ...credentials })
+  verify(artifact.file, expected)
+  process.stdout.write(`desktop macOS notarization: verified disk image ${artifact.file}\n`)
+}

+ 285 - 0
apps/desktop/scripts/package-target.ts

@@ -0,0 +1,285 @@
+/** Build one release target with matching Electron, Node.js, and seed architecture. */
+
+import { spawn } from 'node:child_process'
+import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'
+import { parseArgs } from 'node:util'
+import { join, resolve } from 'node:path'
+import {
+  desktopBuildRecordFilename,
+  resolveDesktopAutoUpdateConfig,
+} from './desktop-auto-update-environment.mjs'
+import { desktopTargetBuildPaths } from './desktop-build-paths.mjs'
+
+const APP_ROOT = resolve(import.meta.dirname, '..')
+const REPOSITORY_ROOT = resolve(APP_ROOT, '..', '..')
+const WINDOWS_SIGNING_ENV_PREFIX = 'DSH_DESKTOP_WINDOWS_'
+const WINDOWS_SIGNING_ENV_NAMES = [
+  'DSH_DESKTOP_WINDOWS_CER_FILE',
+  'DSH_DESKTOP_WINDOWS_KEY_CONTAINER',
+  'DSH_DESKTOP_WINDOWS_SIGNTOOL',
+  'DSH_DESKTOP_WINDOWS_TOKEN_PIN',
+] as const
+const DESKTOP_UPLOAD_CREDENTIAL_ENV_NAMES = new Set([
+  'DOWNLOAD_TEST_COS_SECRET_ID',
+  'DOWNLOAD_TEST_COS_SECRET_KEY',
+  'DOWNLOAD_PROD_COS_SECRET_ID',
+  'DOWNLOAD_PROD_COS_SECRET_KEY',
+])
+
+/** Fixed platform and architecture identifiers exposed by package scripts. */
+export type DesktopPackageTargetName = 'mac-arm64' | 'mac-x64' | 'win-x64'
+
+/** One supported release target and its electron-builder selectors. */
+export interface DesktopPackageTarget {
+  readonly name: DesktopPackageTargetName
+  readonly platform: 'darwin' | 'win32'
+  readonly arch: 'arm64' | 'x64'
+  readonly builderPlatform: '--mac' | '--win'
+  readonly builderArch: '--arm64' | '--x64'
+}
+
+const TARGETS: Record<DesktopPackageTargetName, DesktopPackageTarget> = {
+  'mac-arm64': {
+    name: 'mac-arm64',
+    platform: 'darwin',
+    arch: 'arm64',
+    builderPlatform: '--mac',
+    builderArch: '--arm64',
+  },
+  'mac-x64': {
+    name: 'mac-x64',
+    platform: 'darwin',
+    arch: 'x64',
+    builderPlatform: '--mac',
+    builderArch: '--x64',
+  },
+  'win-x64': {
+    name: 'win-x64',
+    platform: 'win32',
+    arch: 'x64',
+    builderPlatform: '--win',
+    builderArch: '--x64',
+  },
+}
+
+/**
+ * Remove Windows signing configuration from package preparation subprocesses.
+ * @param environment - Packaging command environment.
+ * @returns A copy without Windows signing fields.
+ */
+export function withoutWindowsSigningEnvironment(environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
+  return Object.fromEntries(Object.entries(environment)
+    .filter(([name]) => !name.startsWith(WINDOWS_SIGNING_ENV_PREFIX)))
+}
+
+/**
+ * Remove upload-only COS credentials from every packaging subprocess.
+ * @param environment - Packaging command environment.
+ * @returns A copy without Desktop upload credentials.
+ */
+export function withoutDesktopUploadCredentials(environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
+  return Object.fromEntries(Object.entries(environment)
+    .filter(([name]) => !DESKTOP_UPLOAD_CREDENTIAL_ENV_NAMES.has(name)))
+}
+
+function isTargetName(value: string): value is DesktopPackageTargetName {
+  return Object.hasOwn(TARGETS, value)
+}
+
+function packageVersion(path: string, label: string): string {
+  const manifest = JSON.parse(readFileSync(path, 'utf8')) as { version?: unknown }
+  if (typeof manifest.version !== 'string' || manifest.version === '') {
+    throw new Error(`desktop package: ${label} has no version`)
+  }
+  return manifest.version
+}
+
+function writeReleaseRecord(
+  target: DesktopPackageTarget,
+  environment: NodeJS.ProcessEnv,
+  artifactsRoot: string,
+): void {
+  const desktopVersion = packageVersion(join(APP_ROOT, 'package.json'), 'desktop package')
+  const dshVersion = packageVersion(join(REPOSITORY_ROOT, 'package.json'), 'dsh package')
+  if (desktopVersion !== dshVersion) {
+    throw new Error(`desktop package: desktop version ${desktopVersion} does not match dsh version ${dshVersion}`)
+  }
+  const update = resolveDesktopAutoUpdateConfig(environment, target.platform, target.arch)
+  const recordPath = join(artifactsRoot, desktopBuildRecordFilename(target.name))
+  const temporaryPath = `${recordPath}.tmp`
+  writeFileSync(temporaryPath, `${JSON.stringify({
+    schemaVersion: 1,
+    target: target.name,
+    version: dshVersion,
+    environment: update.environment,
+    publicUrl: update.publicUrl,
+  }, null, 2)}\n`)
+  renameSync(temporaryPath, recordPath)
+}
+
+/**
+ * Resolve a named release target and reject hosts that cannot execute its packaged runtime.
+ * @param name - One of the fixed Desktop release target names.
+ * @param hostPlatform - Build-host Node.js platform.
+ * @param hostArch - Build-host Node.js architecture.
+ * @returns The target selectors shared by runtime preparation and electron-builder.
+ */
+export function resolveDesktopPackageTarget(
+  name: string,
+  hostPlatform: NodeJS.Platform = process.platform,
+  hostArch: string = process.arch,
+): DesktopPackageTarget {
+  if (!isTargetName(name)) {
+    throw new Error(`desktop package: unsupported target ${JSON.stringify(name)}; expected ${Object.keys(TARGETS).join(', ')}`)
+  }
+  const target = TARGETS[name]
+  if (target.platform === 'win32' && (hostPlatform !== 'win32' || hostArch !== 'x64')) {
+    throw new Error('desktop package: win-x64 requires a Windows x64 build host')
+  }
+  if (target.platform === 'darwin' && hostPlatform !== 'darwin') {
+    throw new Error(`desktop package: ${name} requires a macOS build host`)
+  }
+  if (name === 'mac-arm64' && hostArch !== 'arm64') {
+    throw new Error('desktop package: mac-arm64 requires an Apple Silicon build host')
+  }
+  if (name === 'mac-x64' && hostArch !== 'arm64' && hostArch !== 'x64') {
+    throw new Error('desktop package: mac-x64 requires an Intel Mac or Apple Silicon with Rosetta')
+  }
+  return target
+}
+
+interface DesktopPackageInvocation {
+  readonly target: DesktopPackageTarget
+  readonly directory: boolean
+  readonly prepareOnly: boolean
+}
+
+function hostTargetName(platform: NodeJS.Platform, arch: string): DesktopPackageTargetName {
+  const name = `${platform === 'darwin' ? 'mac' : platform === 'win32' ? 'win' : platform}-${arch}`
+  if (!isTargetName(name)) throw new Error(`desktop package: unsupported build host ${platform}-${arch}`)
+  return name
+}
+
+/**
+ * Parse the fixed-target packaging command line.
+ * @param argv - Arguments after the script entry point.
+ * @param hostPlatform - Build-host Node.js platform.
+ * @param hostArch - Build-host Node.js architecture.
+ * @returns The validated target and whether to emit an unpacked directory.
+ */
+export function parseDesktopPackageInvocation(
+  argv: readonly string[],
+  hostPlatform: NodeJS.Platform = process.platform,
+  hostArch: string = process.arch,
+): DesktopPackageInvocation {
+  const { values, positionals } = parseArgs({
+    args: [...argv],
+    allowPositionals: true,
+    options: {
+      dir: { type: 'boolean', default: false },
+      'prepare-only': { type: 'boolean', default: false },
+    },
+  })
+  if (positionals.length > 1) throw new Error('desktop package: expected at most one target')
+  const name = positionals[0] ?? hostTargetName(hostPlatform, hostArch)
+  return {
+    target: resolveDesktopPackageTarget(name, hostPlatform, hostArch),
+    directory: values.dir,
+    prepareOnly: values['prepare-only'],
+  }
+}
+
+/**
+ * Build the electron-builder command arguments for one validated target.
+ * @param target - Supported release target.
+ * @param directory - Whether to stop at an unpacked application directory.
+ * @returns Arguments that keep publishing under the separate validated upload command.
+ */
+export function desktopElectronBuilderArguments(
+  target: DesktopPackageTarget,
+  directory: boolean,
+): readonly string[] {
+  return [
+    'exec',
+    'electron-builder',
+    '--config',
+    'electron-builder.config.mjs',
+    target.builderPlatform,
+    target.builderArch,
+    '--publish',
+    'never',
+    ...(directory ? ['--dir'] : []),
+  ]
+}
+
+function runPnpm(
+  args: readonly string[],
+  env: NodeJS.ProcessEnv = process.env,
+  cwd: string = APP_ROOT,
+): Promise<void> {
+  const pnpmEntry = process.env.npm_execpath
+  if (pnpmEntry === undefined || pnpmEntry === '') {
+    throw new Error('desktop package: invoke this script through a pnpm package command')
+  }
+  return new Promise((resolvePromise, reject) => {
+    const child = spawn(process.execPath, [pnpmEntry, ...args], {
+      cwd,
+      env,
+      stdio: 'inherit',
+    })
+    child.once('error', reject)
+    child.once('close', (code, signal) => {
+      if (code === 0) resolvePromise()
+      else reject(new Error(`desktop package: pnpm ${args.join(' ')} exited with ${String(code ?? signal)}`))
+    })
+  })
+}
+
+async function main(): Promise<void> {
+  const invocation = parseDesktopPackageInvocation(process.argv.slice(2))
+  const { target } = invocation
+  const buildPaths = desktopTargetBuildPaths(target.name)
+  const releaseRecordPath = join(buildPaths.artifacts, desktopBuildRecordFilename(target.name))
+  if (!invocation.prepareOnly) {
+    rmSync(releaseRecordPath, { force: true })
+    rmSync(`${releaseRecordPath}.tmp`, { force: true })
+  }
+  const buildEnv = withoutWindowsSigningEnvironment(withoutDesktopUploadCredentials(process.env))
+  const targetEnv: NodeJS.ProcessEnv = {
+    ...buildEnv,
+    DSH_DESKTOP_TARGET_PLATFORM: target.platform,
+    DSH_DESKTOP_TARGET_ARCH: target.arch,
+  }
+  const electronBuilderEnv = { ...targetEnv }
+  for (const name of WINDOWS_SIGNING_ENV_NAMES) {
+    if (process.env[name] !== undefined) electronBuilderEnv[name] = process.env[name]
+  }
+  await runPnpm(['run', 'build:official'], buildEnv, REPOSITORY_ROOT)
+  await runPnpm(['run', 'release:pack', '--family', 'dsh', '--out', buildPaths.packedDsh], buildEnv, REPOSITORY_ROOT)
+  await runPnpm([
+    '--dir',
+    'apps/desktop-host',
+    'pack',
+    '--pack-destination',
+    buildPaths.packedDsh,
+  ], buildEnv, REPOSITORY_ROOT)
+  await runPnpm(['run', 'release:pack', '--family', 'vendor', '--out', buildPaths.packedVendor], buildEnv, REPOSITORY_ROOT)
+  rmSync(buildPaths.packedLandlock, { recursive: true, force: true })
+  mkdirSync(buildPaths.packedLandlock, { recursive: true })
+  await runPnpm(['--dir', 'native/landlock-run', 'run', 'build:ts'], buildEnv, REPOSITORY_ROOT)
+  await runPnpm([
+    '--dir',
+    'native/landlock-run/packages/entry',
+    'pack',
+    '--pack-destination',
+    buildPaths.packedLandlock,
+  ], buildEnv, REPOSITORY_ROOT)
+  await runPnpm(['run', 'prepare:runtime'], targetEnv)
+  await runPnpm(['run', 'prepare:packages'], targetEnv)
+  await runPnpm(['run', 'prepare:seed'], targetEnv)
+  if (invocation.prepareOnly) return
+  await runPnpm(desktopElectronBuilderArguments(target, invocation.directory), electronBuilderEnv)
+  if (!invocation.directory) writeReleaseRecord(target, electronBuilderEnv, buildPaths.artifacts)
+}
+
+if (process.argv[1] !== undefined && import.meta.filename === resolve(process.argv[1])) await main()

+ 172 - 0
apps/desktop/scripts/prepare-package-set.ts

@@ -0,0 +1,172 @@
+/** Select and copy the local npm tarball closures that supply Desktop dsh and its private Host. */
+
+import { createHash } from 'node:crypto'
+import {
+  constants,
+  copyFileSync,
+  mkdirSync,
+  readFileSync,
+  readdirSync,
+  rmSync,
+  statSync,
+  writeFileSync,
+} from 'node:fs'
+import { basename, join, resolve } from 'node:path'
+import { parseArgs } from 'node:util'
+import {
+  DESKTOP_HOST_PACKAGE,
+  DESKTOP_HOST_RUNTIME_FILES,
+  DESKTOP_PACKAGES_DIR,
+  DESKTOP_PACKAGE_SET_FILE,
+  parseDesktopCorePackageSet,
+  type DesktopCorePackageRecord,
+} from '../src/core-package-set.ts'
+import { capture } from '../../../scripts/release/process.ts'
+import { tarballFiles } from '../../../scripts/release/tarball.ts'
+import { resolveDesktopTargetBuildPaths } from './desktop-build-paths.mjs'
+
+const DSH_PACKAGE = '@deepseek-ai/dsh'
+const ROOT_PACKAGES = [DSH_PACKAGE, DESKTOP_HOST_PACKAGE] as const
+const APP_ROOT = resolve(import.meta.dirname, '..')
+const REPOSITORY_ROOT = resolve(APP_ROOT, '..', '..')
+
+const REQUIRED_DEPENDENCY_SECTIONS = ['dependencies', 'peerDependencies'] as const
+const OPTIONAL_DEPENDENCY_SECTION = 'optionalDependencies'
+
+/** Packed package information needed to form the local Desktop closure. */
+export interface PackedDesktopPackage {
+  readonly tarball: string
+  readonly manifest: Readonly<Record<string, unknown>>
+}
+
+function dependencyNames(manifest: Readonly<Record<string, unknown>>, section: string): string[] {
+  const value = manifest[section]
+  if (value === undefined) return []
+  if (value === null || typeof value !== 'object' || Array.isArray(value)) {
+    throw new Error(`desktop package set: ${String(manifest.name)} has invalid ${section}`)
+  }
+  return Object.keys(value).sort()
+}
+
+/**
+ * Select the complete available first-party dependency closures rooted at dsh and its private Host.
+ * @param available - Packed packages indexed by package name.
+ * @returns Selected packages sorted by name.
+ */
+export function selectDesktopPackageClosure(
+  available: ReadonlyMap<string, PackedDesktopPackage>,
+): PackedDesktopPackage[] {
+  const selected = new Map<string, PackedDesktopPackage>()
+  const visit = (name: string): void => {
+    if (selected.has(name)) return
+    const packed = available.get(name)
+    if (packed === undefined) throw new Error(`desktop package set: packed inputs omit required package ${name}`)
+    selected.set(name, packed)
+    for (const section of REQUIRED_DEPENDENCY_SECTIONS) {
+      for (const dependency of dependencyNames(packed.manifest, section)) {
+        if (available.has(dependency)) visit(dependency)
+        else if (dependency.startsWith('@deepseek-ai/')) {
+          throw new Error(`desktop package set: ${name} requires unpacked internal package ${dependency}`)
+        }
+      }
+    }
+    for (const dependency of dependencyNames(packed.manifest, OPTIONAL_DEPENDENCY_SECTION)) {
+      if (available.has(dependency)) visit(dependency)
+    }
+  }
+  for (const name of ROOT_PACKAGES) {
+    if (!available.has(name)) throw new Error(`desktop package set: packed inputs omit ${name}`)
+    visit(name)
+  }
+  return [...selected.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([, packed]) => packed)
+}
+
+function packedManifest(tarball: string): Record<string, unknown> {
+  const value: unknown = JSON.parse(capture('tar', ['-xOzf', tarball, 'package/package.json']))
+  if (value === null || typeof value !== 'object' || Array.isArray(value)) {
+    throw new Error(`desktop package set: ${tarball} has no package manifest`)
+  }
+  return value as Record<string, unknown>
+}
+
+function packedPackages(inputs: readonly string[]): Map<string, PackedDesktopPackage> {
+  const available = new Map<string, PackedDesktopPackage>()
+  for (const input of inputs) {
+    const tarballs = readdirSync(input).filter(file => file.endsWith('.tgz')).sort()
+    if (tarballs.length === 0) throw new Error(`desktop package set: ${input} contains no tarballs`)
+    for (const file of tarballs) {
+      const tarball = join(input, file)
+      const manifest = packedManifest(tarball)
+      const name = manifest.name
+      if (typeof name !== 'string' || name === '') throw new Error(`desktop package set: ${tarball} has no package name`)
+      if (available.has(name)) throw new Error(`desktop package set: duplicate packed package ${name}`)
+      available.set(name, { tarball, manifest })
+    }
+  }
+  return available
+}
+
+/**
+ * Require every private Host file used before the Desktop profile can pass its health check.
+ * @param files - Tarball paths rooted at `package/`.
+ * @returns Nothing.
+ */
+export function assertDesktopHostPackageFiles(files: readonly string[]): void {
+  const available = new Set(files)
+  const missing = DESKTOP_HOST_RUNTIME_FILES
+    .map(file => `package/${file}`)
+    .filter(file => !available.has(file))
+  if (missing.length > 0) {
+    throw new Error(`desktop package set: ${DESKTOP_HOST_PACKAGE} tarball omits required file(s): ${missing.join(', ')}`)
+  }
+}
+
+/** Prepare a package set from release tarball directories. */
+export function prepareDesktopPackageSet(inputs: readonly string[], output: string): void {
+  const selected = selectDesktopPackageClosure(packedPackages(inputs))
+  const host = selected.find(packed => packed.manifest.name === DESKTOP_HOST_PACKAGE)
+  if (host === undefined) throw new Error(`desktop package set: selected closure omits ${DESKTOP_HOST_PACKAGE}`)
+  assertDesktopHostPackageFiles(tarballFiles(host.tarball))
+  rmSync(output, { recursive: true, force: true })
+  const packageDir = join(output, DESKTOP_PACKAGES_DIR)
+  mkdirSync(packageDir, { recursive: true })
+  const records: DesktopCorePackageRecord[] = selected.map((packed) => {
+    const name = packed.manifest.name
+    const version = packed.manifest.version
+    if (typeof name !== 'string' || typeof version !== 'string') {
+      throw new Error(`desktop package set: ${packed.tarball} has no package identity`)
+    }
+    const file = basename(packed.tarball)
+    const destination = join(packageDir, file)
+    copyFileSync(packed.tarball, destination, constants.COPYFILE_EXCL)
+    const body = readFileSync(destination)
+    return {
+      name,
+      version,
+      file,
+      bytes: statSync(destination).size,
+      integrity: `sha512-${createHash('sha512').update(body).digest('base64')}`,
+    }
+  })
+  const packageSet = parseDesktopCorePackageSet({ schemaVersion: 1, packages: records })
+  writeFileSync(join(output, DESKTOP_PACKAGE_SET_FILE), `${JSON.stringify(packageSet, undefined, 2)}\n`, { mode: 0o600 })
+}
+
+function main(): void {
+  const buildPaths = resolveDesktopTargetBuildPaths()
+  const defaultInputs = [
+    buildPaths.packedDsh,
+    buildPaths.packedVendor,
+    buildPaths.packedLandlock,
+  ]
+  const { values } = parseArgs({
+    options: { from: { type: 'string', multiple: true }, out: { type: 'string' } },
+    allowPositionals: false,
+  })
+  const inputs = (values.from ?? defaultInputs).map(path => resolve(REPOSITORY_ROOT, path))
+  const output = values.out === undefined ? buildPaths.packageSet : resolve(REPOSITORY_ROOT, values.out)
+  prepareDesktopPackageSet(inputs, output)
+  console.log(`desktop package set: prepared ${output}`)
+}
+
+if (import.meta.main) main()

+ 107 - 0
apps/desktop/scripts/prepare-runtime.ts

@@ -0,0 +1,107 @@
+/** Download and verify the upstream Node.js runtime and copy the pinned pnpm CLI. */
+
+import { createHash } from 'node:crypto'
+import { spawnSync } from 'node:child_process'
+import { cpSync, createReadStream, createWriteStream, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
+import { chmod, readFile } from 'node:fs/promises'
+import { createRequire } from 'node:module'
+import { dirname, join } from 'node:path'
+import { pipeline } from 'node:stream/promises'
+import extractZip from 'extract-zip'
+import { extract } from 'tar'
+import { resolveDesktopTargetBuildPaths } from './desktop-build-paths.mjs'
+
+const NODE_VERSION = '24.17.0'
+const BUILD_PATHS = resolveDesktopTargetBuildPaths()
+const RUNTIME_ROOT = BUILD_PATHS.runtime
+const DOWNLOAD_ROOT = BUILD_PATHS.downloads
+
+type RuntimePlatform = 'darwin' | 'linux' | 'win'
+type RuntimeArch = 'arm64' | 'x64'
+
+function target(): { platform: RuntimePlatform; arch: RuntimeArch } {
+  const rawPlatform = process.env.DSH_DESKTOP_TARGET_PLATFORM ?? process.env.npm_config_platform ?? process.platform
+  const rawArch = process.env.DSH_DESKTOP_TARGET_ARCH ?? process.env.npm_config_arch ?? process.arch
+  const platform = rawPlatform === 'win32' ? 'win' : rawPlatform
+  if (platform !== 'darwin' && platform !== 'linux' && platform !== 'win') {
+    throw new Error(`desktop runtime: unsupported platform ${rawPlatform}`)
+  }
+  if (rawArch !== 'arm64' && rawArch !== 'x64') throw new Error(`desktop runtime: unsupported architecture ${rawArch}`)
+  return { platform, arch: rawArch }
+}
+
+async function download(url: string, path: string): Promise<void> {
+  const response = await fetch(url)
+  if (!response.ok) throw new Error(`desktop runtime: ${url} returned HTTP ${String(response.status)}`)
+  writeFileSync(path, new Uint8Array(await response.arrayBuffer()), { mode: 0o600 })
+}
+
+async function prepareNode(platform: RuntimePlatform, arch: RuntimeArch): Promise<void> {
+  const extension = platform === 'win' ? 'zip' : 'tar.gz'
+  const folder = `node-v${NODE_VERSION}-${platform}-${arch}`
+  const archiveName = `${folder}.${extension}`
+  const releaseRoot = `https://nodejs.org/download/release/v${NODE_VERSION}`
+  const archive = join(DOWNLOAD_ROOT, archiveName)
+  const sums = join(DOWNLOAD_ROOT, `node-v${NODE_VERSION}-SHASUMS256.txt`)
+  if (!existsSync(archive)) await download(`${releaseRoot}/${archiveName}`, archive)
+  if (!existsSync(sums)) await download(`${releaseRoot}/SHASUMS256.txt`, sums)
+  const line = (await readFile(sums, 'utf8')).split(/\r?\n/u)
+    .find(candidate => candidate.endsWith(`  ${archiveName}`))
+  if (line === undefined) throw new Error(`desktop runtime: ${archiveName} is absent from Node.js SHASUMS256.txt`)
+  const expected = line.split(/\s+/u)[0]
+  const actual = createHash('sha256').update(await readFile(archive)).digest('hex')
+  if (actual !== expected) throw new Error(`desktop runtime: checksum mismatch for ${archiveName}`)
+
+  const extraction = BUILD_PATHS.nodeExtract
+  rmSync(extraction, { recursive: true, force: true })
+  mkdirSync(extraction, { recursive: true })
+  if (platform === 'win') await extractZip(archive, { dir: extraction })
+  else await extract({ cwd: extraction, file: archive })
+  const source = join(extraction, folder, platform === 'win' ? 'node.exe' : 'bin/node')
+  const destinationRoot = join(RUNTIME_ROOT, 'node')
+  const destination = join(destinationRoot, platform === 'win' ? 'node.exe' : 'node')
+  rmSync(destinationRoot, { recursive: true, force: true })
+  mkdirSync(destinationRoot, { recursive: true })
+  // A fresh write prevents macOS from retaining invalid code-signature vnode state from a tar-extracted Mach-O clone.
+  await pipeline(createReadStream(source), createWriteStream(destination, { flags: 'wx' }))
+  if (platform !== 'win') await chmod(destination, 0o755)
+  const hostPlatform = process.platform === 'win32' ? 'win' : process.platform
+  const hostCanExecute = platform === hostPlatform
+    && (arch === process.arch || (platform === 'darwin' && arch === 'x64' && process.arch === 'arm64'))
+  if (hostCanExecute) {
+    const result = spawnSync(destination, ['--version'], { encoding: 'utf8' })
+    if (result.error !== undefined || result.status !== 0 || result.stdout.trim() !== `v${NODE_VERSION}`) {
+      const detail = result.error?.message ?? result.signal ?? result.stderr.trim()
+      const outcome = detail === '' ? `exit ${String(result.status)}` : detail
+      throw new Error(`desktop runtime: prepared Node.js ${NODE_VERSION} failed executable verification: ${outcome}`)
+    }
+  }
+  rmSync(extraction, { recursive: true, force: true })
+}
+
+function preparePnpm(): string {
+  const require = createRequire(import.meta.url)
+  const manifestPath = require.resolve('pnpm')
+  const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as { version?: unknown }
+  if (typeof manifest.version !== 'string') throw new Error('desktop runtime: pnpm manifest has no version')
+  const packageDir = dirname(manifestPath)
+  const destination = join(RUNTIME_ROOT, 'pnpm')
+  rmSync(destination, { recursive: true, force: true })
+  cpSync(packageDir, destination, { recursive: true })
+  return manifest.version
+}
+
+async function main(): Promise<void> {
+  const { platform, arch } = target()
+  mkdirSync(DOWNLOAD_ROOT, { recursive: true })
+  mkdirSync(RUNTIME_ROOT, { recursive: true })
+  await prepareNode(platform, arch)
+  const pnpmVersion = preparePnpm()
+  writeFileSync(join(RUNTIME_ROOT, 'versions.json'), `${JSON.stringify({
+    schemaVersion: 1,
+    node: NODE_VERSION,
+    pnpm: pnpmVersion,
+  }, undefined, 2)}\n`)
+}
+
+await main()

+ 200 - 0
apps/desktop/scripts/prepare-seed.ts

@@ -0,0 +1,200 @@
+/** Build the release seed through the same embedded pnpm used on first launch. */
+
+import { spawn } from 'node:child_process'
+import { createHash } from 'node:crypto'
+import { copyFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'
+import { tmpdir } from 'node:os'
+import { delimiter, dirname, join, relative, resolve, sep } from 'node:path'
+import { createSeedMetadata } from '../src/project-manager.ts'
+import { DESKTOP_HOST_PROTOCOL_VERSION } from '../src/host-protocol.ts'
+import { parseDesktopRelease, type DesktopRelease } from '../src/release.ts'
+import {
+  DESKTOP_HOST_PACKAGE,
+  DESKTOP_HOST_RUNTIME_FILES,
+  DESKTOP_PACKAGES_DIR,
+  DESKTOP_PACKAGE_SET_FILE,
+  readDesktopCorePackageSet,
+  verifyDesktopCoreLockfile,
+} from '../src/core-package-set.ts'
+import {
+  archivePnpmStore,
+  extractPnpmStoreArchives,
+  removePnpmProjectRegistrations,
+} from '../src/seed-store.ts'
+import {
+  resolveDesktopAppId,
+  resolveMacOSSigningEnvironment,
+} from './desktop-release-environment.mjs'
+import {
+  signMacOSSeedStore,
+  verifyMacOSSeedStore,
+} from './macos-seed-store.ts'
+import { resolveDesktopTargetBuildPaths } from './desktop-build-paths.mjs'
+
+const APP_ROOT = resolve(import.meta.dirname, '..')
+const BUILD_PATHS = resolveDesktopTargetBuildPaths()
+const SEED_OUTPUT_ROOT = BUILD_PATHS.seed
+const SEED_ROOT = mkdtempSync(join(tmpdir(), 'dsh-desktop-seed-'))
+const STORE_ROOT = join(SEED_ROOT, 'store')
+const RUNTIME_ROOT = BUILD_PATHS.runtime
+const PNPM_BUILD_STATE = BUILD_PATHS.seedPnpm
+const PACKAGE_SET_ROOT = BUILD_PATHS.packageSet
+const NODE = join(RUNTIME_ROOT, 'node', process.platform === 'win32' ? 'node.exe' : 'node')
+const PNPM = join(RUNTIME_ROOT, 'pnpm', 'bin', 'pnpm.mjs')
+
+function manifestVersion(path: string, subject: string): string {
+  const manifest = JSON.parse(readFileSync(path, 'utf8')) as { version?: unknown }
+  if (typeof manifest.version !== 'string') throw new Error(`desktop seed: ${subject} has no version`)
+  return manifest.version
+}
+
+function desktopRelease(): DesktopRelease {
+  const version = manifestVersion(join(APP_ROOT, 'package.json'), 'desktop package')
+  const dshVersion = manifestVersion(resolve(APP_ROOT, '..', '..', 'package.json'), 'root dsh package')
+  if (version !== dshVersion) {
+    throw new Error(`desktop seed: Electron ${version} must bind the same version of @deepseek-ai/dsh, found ${dshVersion}`)
+  }
+  const runtime = JSON.parse(readFileSync(join(RUNTIME_ROOT, 'versions.json'), 'utf8')) as Record<string, unknown>
+  return parseDesktopRelease({
+    schemaVersion: 1,
+    version,
+    hostProtocolVersion: DESKTOP_HOST_PROTOCOL_VERSION,
+    nodeVersion: runtime.node,
+    pnpmVersion: runtime.pnpm,
+  })
+}
+
+function runPnpm(args: readonly string[]): Promise<void> {
+  return new Promise((resolvePromise, reject) => {
+    const [command, ...commandArgs] = args
+    if (command === undefined) throw new Error('desktop seed: pnpm command is required')
+    const config = join(PNPM_BUILD_STATE, 'config')
+    const userConfig = join(config, 'npmrc')
+    mkdirSync(config, { recursive: true })
+    writeFileSync(userConfig, '')
+    const child = spawn(NODE, [
+      PNPM,
+      '--config.registry=https://registry.npmjs.org/',
+      `--config.store-dir=${STORE_ROOT}`,
+      '--config.enable-global-virtual-store=false',
+      `--config.userconfig=${userConfig}`,
+      command,
+      ...commandArgs,
+    ], {
+      cwd: SEED_ROOT,
+      env: {
+        ...Object.fromEntries(Object.entries(process.env).filter(([name]) => (
+          !/^DSH_DESKTOP_/u.test(name) && !/^(?:npm|pnpm|corepack)_/iu.test(name)
+        ))),
+        NPM_CONFIG_REGISTRY: 'https://registry.npmjs.org/',
+        NPM_CONFIG_STORE_DIR: STORE_ROOT,
+        NPM_CONFIG_USERCONFIG: userConfig,
+        PATH: `${dirname(NODE)}${delimiter}${process.env.PATH ?? ''}`,
+        XDG_CACHE_HOME: join(PNPM_BUILD_STATE, 'cache'),
+        XDG_CONFIG_HOME: config,
+        XDG_STATE_HOME: join(PNPM_BUILD_STATE, 'state'),
+      },
+      stdio: 'inherit',
+    })
+    child.once('error', reject)
+    child.once('close', (code, signal) => {
+      if (code === 0) resolvePromise()
+      else reject(new Error(`desktop seed: pnpm exited with ${String(code ?? signal)}`))
+    })
+  })
+}
+
+function inventory(root: string): readonly { path: string; bytes: number; sha256: string }[] {
+  const files: string[] = []
+  const visit = (dir: string): void => {
+    for (const entry of readdirSync(dir, { withFileTypes: true })) {
+      const path = join(dir, entry.name)
+      if (entry.isDirectory()) visit(path)
+      else if (entry.isFile()) files.push(path)
+      else throw new Error(`desktop seed: unsupported filesystem entry ${relative(root, path)}`)
+    }
+  }
+  visit(root)
+  return files.sort().map((path) => {
+    const body = readFileSync(path)
+    return {
+      path: relative(root, path).split(sep).join('/'),
+      bytes: statSync(path).size,
+      sha256: createHash('sha256').update(body).digest('hex'),
+    }
+  })
+}
+
+async function verifyOfflineInstallation(release: DesktopRelease): Promise<void> {
+  const installedModules = join(SEED_ROOT, 'node_modules')
+  try {
+    await runPnpm(['install', '--offline', '--frozen-lockfile', '--trust-lockfile'])
+    const hostRoot = join(installedModules, ...DESKTOP_HOST_PACKAGE.split('/'))
+    for (const file of DESKTOP_HOST_RUNTIME_FILES) {
+      if (!existsSync(join(hostRoot, file))) {
+        throw new Error(`desktop seed: local ${DESKTOP_HOST_PACKAGE}@${release.version} does not contain ${file}`)
+      }
+    }
+  } finally {
+    rmSync(installedModules, { recursive: true, force: true })
+  }
+}
+
+async function main(): Promise<void> {
+  rmSync(SEED_OUTPUT_ROOT, { recursive: true, force: true })
+  rmSync(PNPM_BUILD_STATE, { recursive: true, force: true })
+  mkdirSync(STORE_ROOT, { recursive: true })
+  try {
+    const release = desktopRelease()
+    copyFileSync(join(PACKAGE_SET_ROOT, DESKTOP_PACKAGE_SET_FILE), join(SEED_ROOT, DESKTOP_PACKAGE_SET_FILE))
+    cpSync(join(PACKAGE_SET_ROOT, DESKTOP_PACKAGES_DIR), join(SEED_ROOT, DESKTOP_PACKAGES_DIR), { recursive: true })
+    createSeedMetadata(SEED_ROOT, release)
+    await runPnpm(['install', '--lockfile-only'])
+    verifyDesktopCoreLockfile(
+      readFileSync(join(SEED_ROOT, 'pnpm-lock.yaml'), 'utf8'),
+      readDesktopCorePackageSet(SEED_ROOT, release.version),
+    )
+    const installedModules = join(SEED_ROOT, 'node_modules')
+    await runPnpm(['install', '--prod', '--frozen-lockfile', '--trust-lockfile', '--ignore-scripts'])
+    rmSync(installedModules, { recursive: true, force: true })
+    rmSync(PNPM_BUILD_STATE, { recursive: true, force: true })
+    await verifyOfflineInstallation(release)
+    const targetPlatform = process.env.DSH_DESKTOP_TARGET_PLATFORM ?? process.platform
+    let signedMachOFiles: number | undefined
+    let macOSSigning: ReturnType<typeof resolveMacOSSigningEnvironment> | undefined
+    if (targetPlatform === 'darwin') {
+      macOSSigning = resolveMacOSSigningEnvironment(process.env)
+      const signing = await signMacOSSeedStore(
+        STORE_ROOT,
+        resolveDesktopAppId(process.env),
+        macOSSigning,
+      )
+      signedMachOFiles = signing.signedFiles
+      process.stdout.write(
+        `desktop seed: signed ${signing.signedFiles} Mach-O files, updated ${signing.updatedIndexRows} pnpm index records, and pruned ${signing.prunedOrphans} native orphans\n`,
+      )
+      await verifyOfflineInstallation(release)
+    }
+    removePnpmProjectRegistrations(STORE_ROOT)
+    archivePnpmStore(SEED_ROOT, STORE_ROOT)
+    if (macOSSigning !== undefined && signedMachOFiles !== undefined) {
+      const extractedStore = mkdtempSync(join(tmpdir(), 'dsh-desktop-seed-verification-'))
+      try {
+        extractPnpmStoreArchives(SEED_ROOT, extractedStore)
+        const verified = verifyMacOSSeedStore(extractedStore, macOSSigning)
+        if (verified !== signedMachOFiles) {
+          throw new Error(`desktop seed: archived store contains ${verified} signed Mach-O files; expected ${signedMachOFiles}`)
+        }
+      } finally {
+        rmSync(extractedStore, { recursive: true, force: true })
+      }
+    }
+    const records = inventory(SEED_ROOT).filter(entry => entry.path !== 'integrity.json')
+    writeFileSync(join(SEED_ROOT, 'integrity.json'), `${JSON.stringify({ schemaVersion: 2, files: records }, undefined, 2)}\n`)
+    cpSync(SEED_ROOT, SEED_OUTPUT_ROOT, { recursive: true })
+  } finally {
+    rmSync(SEED_ROOT, { recursive: true, force: true })
+  }
+}
+
+await main()

+ 83 - 0
apps/desktop/scripts/upload-target.ts

@@ -0,0 +1,83 @@
+/** Upload one validated Desktop release to its Tencent COS update directory. */
+
+import { createReadStream } from 'node:fs'
+import { stat } from 'node:fs/promises'
+import { resolve } from 'node:path'
+import { parseArgs } from 'node:util'
+import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
+import type { DesktopPackageTargetName } from './package-target.ts'
+import {
+  createDesktopUploadPlan,
+  type DesktopUploadArtifact,
+} from './desktop-upload-plan.ts'
+
+const SUPPORTED_TARGETS = new Set<DesktopPackageTargetName>(['mac-arm64', 'mac-x64', 'win-x64'])
+
+function targetName(value: string): DesktopPackageTargetName {
+  if (!SUPPORTED_TARGETS.has(value as DesktopPackageTargetName)) {
+    throw new Error(`desktop upload: unsupported target ${JSON.stringify(value)}; expected ${[...SUPPORTED_TARGETS].join(', ')}`)
+  }
+  return value as DesktopPackageTargetName
+}
+
+function requiredEnvironmentValue(environment: NodeJS.ProcessEnv, name: string): string {
+  const value = environment[name]?.trim()
+  if (value === undefined || value === '') {
+    throw new Error(`desktop upload: ${name} must be set to a non-empty value`)
+  }
+  return value
+}
+
+async function putArtifact(
+  client: S3Client,
+  bucket: string,
+  artifact: DesktopUploadArtifact,
+): Promise<void> {
+  const details = await stat(artifact.path)
+  const body = createReadStream(artifact.path)
+  try {
+    await client.send(new PutObjectCommand({
+      Bucket: bucket,
+      Key: artifact.key,
+      Body: body,
+      ContentLength: details.size,
+      ContentType: artifact.contentType,
+      CacheControl: artifact.cacheControl,
+    }))
+  }
+  finally {
+    body.destroy()
+  }
+  process.stdout.write(`desktop upload: uploaded ${artifact.key}\n`)
+}
+
+async function main(): Promise<void> {
+  const { positionals } = parseArgs({ args: process.argv.slice(2), allowPositionals: true })
+  const target = positionals[0]
+  if (target === undefined || positionals.length !== 1) {
+    throw new Error('desktop upload: expected exactly one target')
+  }
+  const plan = await createDesktopUploadPlan(targetName(target))
+  const client = new S3Client({
+    region: 'Auto',
+    endpoint: 'https://cos.ap-beijing.myqcloud.com',
+    credentials: {
+      accessKeyId: requiredEnvironmentValue(process.env, plan.secretIdEnvName),
+      secretAccessKey: requiredEnvironmentValue(process.env, plan.secretKeyEnvName),
+    },
+  })
+  process.stdout.write(`desktop upload: ${plan.target} ${plan.version} -> ${plan.publicUrl}\n`)
+  try {
+    for (const artifact of plan.artifacts) await putArtifact(client, plan.bucket, artifact)
+  }
+  finally {
+    client.destroy()
+  }
+}
+
+if (process.argv[1] !== undefined && import.meta.filename === resolve(process.argv[1])) {
+  main().catch((error: unknown) => {
+    process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`)
+    process.exitCode = 1
+  })
+}

+ 73 - 0
apps/desktop/scripts/verify-macos-signature.d.mts

@@ -0,0 +1,73 @@
+import type { MacOSSigningEnvironment } from './desktop-release-environment.mjs'
+
+/**
+ * Reject signature metadata that does not name the company release authority and team.
+ * @param details - Output from `codesign --display --verbose=4`.
+ * @param expected - Public release identity.
+ */
+export function assertMacOSSignatureDetails(details: string, expected: MacOSSigningEnvironment): void
+
+/**
+ * Require the signature properties Apple validates for executable seed content.
+ * @param details - Output from `codesign --display --verbose=4`.
+ * @param expected - Public release identity.
+ */
+export function assertMacOSSeedSignatureDetails(details: string, expected: MacOSSigningEnvironment): void
+
+/**
+ * Sign one Mach-O file embedded in the seed store.
+ * @param path - Writable standalone Mach-O file.
+ * @param identifier - Stable code-signing identifier derived from the release app ID and CAS digest.
+ * @param expected - Public release identity.
+ * @returns Resolves after codesign exits successfully.
+ */
+export function signMacOSSeedCode(
+  path: string,
+  identifier: string,
+  expected: MacOSSigningEnvironment,
+): Promise<void>
+
+/**
+ * Verify one Mach-O file embedded in the seed store.
+ * @param path - Mach-O file to inspect.
+ * @param expected - Public release identity.
+ */
+export function verifyMacOSSeedCode(path: string, expected: MacOSSigningEnvironment): void
+
+/**
+ * Verify the full application signature and its release owner.
+ * @param appPath - Path to the packaged `.app` directory.
+ * @param expected - Public release identity.
+ */
+export function verifyMacOSSignature(appPath: string, expected: MacOSSigningEnvironment): void
+
+/**
+ * Verify the release identity, stapled ticket, and Gatekeeper acceptance of one disk image.
+ * @param diskImagePath - Path to the packaged `.dmg` file.
+ * @param expected - Public release identity.
+ */
+export function verifyMacOSDiskImage(
+  diskImagePath: string,
+  expected: MacOSSigningEnvironment,
+): void
+
+/** Electron-builder fields required to locate a signed macOS application. */
+export interface MacOSAfterSignContext {
+  readonly electronPlatformName: string
+  readonly appOutDir: string
+  readonly packager: {
+    readonly appInfo: {
+      readonly productFilename: string
+    }
+  }
+}
+
+/**
+ * Verify the macOS application produced by electron-builder's signing phase.
+ * @param context - electron-builder hook context.
+ * @param expected - Public release identity.
+ */
+export function verifyMacOSSignatureAfterSign(
+  context: MacOSAfterSignContext,
+  expected: MacOSSigningEnvironment,
+): void

+ 186 - 0
apps/desktop/scripts/verify-macos-signature.mjs

@@ -0,0 +1,186 @@
+/** Sign seed code and verify that packaged macOS artifacts carry the company release identity. */
+
+import { spawn, spawnSync } from 'node:child_process'
+import { resolve } from 'node:path'
+import { resolveMacOSSigningEnvironment } from './desktop-release-environment.mjs'
+
+/**
+ * Reject signature metadata that does not name the company release authority and team.
+ * @param {string} details - Output from `codesign --display --verbose=4`.
+ * @param {{ signingIdentity: string, teamId: string }} expected - Public release identity.
+ * @returns {void}
+ */
+export function assertMacOSSignatureDetails(details, expected) {
+  const fields = new Set(details.split(/\r?\n/u).map(line => line.trim()))
+  const expectedAuthority = `Authority=Developer ID Application: ${expected.signingIdentity}`
+  const expectedTeam = `TeamIdentifier=${expected.teamId}`
+  const missing = [expectedAuthority, expectedTeam].filter(field => !fields.has(field))
+  if (missing.length > 0) {
+    throw new Error(`desktop macOS signing: signature does not match the release identity; missing ${missing.join(', ')}`)
+  }
+}
+
+/**
+ * Require the signature properties Apple validates for executable seed content.
+ * @param {string} details - Output from `codesign --display --verbose=4`.
+ * @param {{ signingIdentity: string, teamId: string }} expected - Public release identity.
+ * @returns {void}
+ */
+export function assertMacOSSeedSignatureDetails(details, expected) {
+  assertMacOSSignatureDetails(details, expected)
+  const fields = details.split(/\r?\n/u).map(line => line.trim())
+  if (!fields.some(line => /^Timestamp=.+/u.test(line))) {
+    throw new Error('desktop macOS signing: seed signature has no secure timestamp')
+  }
+  if (!fields.some(line => /\bflags=0x[0-9a-f]+\(runtime\)(?:\s|$)/iu.test(line))) {
+    throw new Error('desktop macOS signing: seed signature does not enable hardened runtime')
+  }
+}
+
+/**
+ * Execute one Apple release tool and return its diagnostic streams.
+ * @param {string} command - Absolute executable path.
+ * @param {readonly string[]} args - Tool arguments.
+ * @param {string} label - Stable diagnostic name.
+ * @returns {string} Combined stdout and stderr.
+ */
+function runAppleCommand(command, args, label) {
+  const result = spawnSync(command, args, { encoding: 'utf8' })
+  if (result.error !== undefined) {
+    throw new Error(`desktop macOS signing: could not execute ${label}: ${result.error.message}`)
+  }
+  if (result.signal !== null) {
+    throw new Error(`desktop macOS signing: ${label} was terminated by ${result.signal}`)
+  }
+  if (result.status !== 0) {
+    const diagnostic = `${result.stdout}${result.stderr}`.trim()
+    throw new Error(`desktop macOS signing: ${label} exited with ${String(result.status)}${diagnostic === '' ? '' : `: ${diagnostic}`}`)
+  }
+  return `${result.stdout}${result.stderr}`
+}
+
+/**
+ * Execute one Apple release tool without blocking other independent seed signers.
+ * @param {string} command - Absolute executable path.
+ * @param {readonly string[]} args - Tool arguments.
+ * @param {string} label - Stable diagnostic name.
+ * @returns {Promise<string>} Combined stdout and stderr after process exit.
+ */
+function runAppleCommandAsync(command, args, label) {
+  return new Promise((resolvePromise, reject) => {
+    const child = spawn(command, args, { stdio: ['ignore', 'pipe', 'pipe'] })
+    let stdout = ''
+    let stderr = ''
+    let spawnError
+    child.stdout.setEncoding('utf8')
+    child.stderr.setEncoding('utf8')
+    child.stdout.on('data', chunk => { stdout += chunk })
+    child.stderr.on('data', chunk => { stderr += chunk })
+    child.once('error', error => { spawnError = error })
+    child.once('close', (code, signal) => {
+      if (spawnError !== undefined) {
+        reject(new Error(`desktop macOS signing: could not execute ${label}: ${spawnError.message}`))
+        return
+      }
+      if (signal !== null) {
+        reject(new Error(`desktop macOS signing: ${label} was terminated by ${signal}`))
+        return
+      }
+      if (code !== 0) {
+        const diagnostic = `${stdout}${stderr}`.trim()
+        reject(new Error(`desktop macOS signing: ${label} exited with ${String(code)}${diagnostic === '' ? '' : `: ${diagnostic}`}`))
+        return
+      }
+      resolvePromise(`${stdout}${stderr}`)
+    })
+  })
+}
+
+/**
+ * Execute Apple's code-signing tool and return its diagnostic streams.
+ * @param {readonly string[]} args - Arguments passed to `/usr/bin/codesign`.
+ * @returns {string} Combined stdout and stderr.
+ */
+function runCodeSign(args) {
+  return runAppleCommand('/usr/bin/codesign', args, 'codesign')
+}
+
+/**
+ * Sign one Mach-O file embedded in the seed store.
+ * @param {string} path - Writable standalone Mach-O file.
+ * @param {string} identifier - Stable code-signing identifier derived from the release app ID and CAS digest.
+ * @param {{ signingIdentity: string, teamId: string }} expected - Public release identity.
+ * @returns {Promise<void>} Resolves after codesign exits successfully.
+ */
+export async function signMacOSSeedCode(path, identifier, expected) {
+  await runAppleCommandAsync('/usr/bin/codesign', [
+    '--force',
+    '--sign', expected.signingIdentity,
+    '--identifier', identifier,
+    '--timestamp',
+    '--options', 'runtime',
+    path,
+  ], 'codesign')
+}
+
+/**
+ * Verify one Mach-O file embedded in the seed store.
+ * @param {string} path - Mach-O file to inspect.
+ * @param {{ signingIdentity: string, teamId: string }} expected - Public release identity.
+ * @returns {void}
+ */
+export function verifyMacOSSeedCode(path, expected) {
+  runCodeSign(['--verify', '--strict', '--verbose=2', path])
+  const details = runCodeSign(['--display', '--verbose=4', path])
+  assertMacOSSeedSignatureDetails(details, expected)
+}
+
+/**
+ * Verify the full application signature and its release owner.
+ * @param {string} appPath - Path to the packaged `.app` directory.
+ * @param {{ signingIdentity: string, teamId: string }} expected - Public release identity.
+ * @returns {void}
+ */
+export function verifyMacOSSignature(appPath, expected) {
+  runCodeSign(['--verify', '--deep', '--strict', '--verbose=2', appPath])
+  const details = runCodeSign(['--display', '--verbose=4', appPath])
+  assertMacOSSignatureDetails(details, expected)
+}
+
+/**
+ * Verify the release identity, stapled ticket, and Gatekeeper acceptance of one disk image.
+ * @param {string} diskImagePath - Path to the packaged `.dmg` file.
+ * @param {{ signingIdentity: string, teamId: string }} expected - Public release identity.
+ * @returns {void}
+ */
+export function verifyMacOSDiskImage(diskImagePath, expected) {
+  runCodeSign(['--verify', '--strict', '--verbose=2', diskImagePath])
+  const details = runCodeSign(['--display', '--verbose=4', diskImagePath])
+  assertMacOSSignatureDetails(details, expected)
+  runAppleCommand('/usr/bin/xcrun', ['stapler', 'validate', diskImagePath], 'stapler validate')
+  runAppleCommand('/usr/sbin/spctl', ['--assess', '--type', 'install', '--verbose=4', diskImagePath], 'spctl')
+}
+
+/**
+ * Verify the macOS application produced by electron-builder's signing phase.
+ * @param {{ electronPlatformName: string, appOutDir: string, packager: { appInfo: { productFilename: string } } }} context - electron-builder hook context.
+ * @param {{ signingIdentity: string, teamId: string }} expected - Public release identity.
+ * @returns {void}
+ */
+export function verifyMacOSSignatureAfterSign(context, expected) {
+  if (context.electronPlatformName !== 'darwin') return
+  const appPath = resolve(context.appOutDir, `${context.packager.appInfo.productFilename}.app`)
+  verifyMacOSSignature(appPath, expected)
+  process.stdout.write(`desktop macOS signing: verified Developer ID Application: ${expected.signingIdentity} (${expected.teamId})\n`)
+}
+
+if (process.argv[1] !== undefined && import.meta.filename === resolve(process.argv[1])) {
+  const cliArgs = process.argv[2] === '--' ? process.argv.slice(3) : process.argv.slice(2)
+  const appPath = cliArgs[0]
+  if (appPath === undefined || cliArgs.length !== 1) {
+    throw new Error('usage: node scripts/verify-macos-signature.mjs <path-to-app>')
+  }
+  const expected = resolveMacOSSigningEnvironment(process.env)
+  verifyMacOSSignature(resolve(appPath), expected)
+  process.stdout.write(`desktop macOS signing: verified Developer ID Application: ${expected.signingIdentity} (${expected.teamId})\n`)
+}

+ 17 - 0
apps/desktop/scripts/windows-sign.cmd

@@ -0,0 +1,17 @@
+@echo off
+setlocal DisableDelayedExpansion
+set "signTool=%DSH_DESKTOP_WINDOWS_SIGNTOOL%"
+set "certificateFile=%DSH_DESKTOP_WINDOWS_CER_FILE%"
+set "tokenPin=%DSH_DESKTOP_WINDOWS_TOKEN_PIN%"
+set "keyContainer=%DSH_DESKTOP_WINDOWS_KEY_CONTAINER%"
+set "targetFile=%DSH_DESKTOP_WINDOWS_SIGN_TARGET%"
+set "appendSignature="
+if "%DSH_DESKTOP_WINDOWS_SIGN_APPEND%"=="1" set "appendSignature=/as"
+set "DSH_DESKTOP_WINDOWS_SIGNTOOL="
+set "DSH_DESKTOP_WINDOWS_CER_FILE="
+set "DSH_DESKTOP_WINDOWS_TOKEN_PIN="
+set "DSH_DESKTOP_WINDOWS_KEY_CONTAINER="
+set "DSH_DESKTOP_WINDOWS_SIGN_TARGET="
+set "DSH_DESKTOP_WINDOWS_SIGN_APPEND="
+set "signTool=" & set "certificateFile=" & set "tokenPin=" & set "keyContainer=" & set "targetFile=" & set "appendSignature=" & "%signTool%" sign /v /fd sha256 /f "%certificateFile%" /kc "[{{%tokenPin%}}]=%keyContainer%" /csp "eToken Base Cryptographic Provider" %appendSignature% /tr http://timestamp.digicert.com /td sha256 "%targetFile%"
+exit /b %errorlevel%

+ 91 - 0
apps/desktop/scripts/windows-sign.d.mts

@@ -0,0 +1,91 @@
+/**
+ * Build the minimal CMD environment for one Electron artifact.
+ *
+ * @param environment Parent environment.
+ * @param input Validated signing identity and task.
+ * @returns Scrubbed environment plus the fields consumed and cleared by the signing CMD.
+ */
+export function buildWindowsSigningEnvironment(environment: NodeJS.ProcessEnv, input: {
+  certificateFile: string
+  signTool: string
+  path: string
+  isNest: boolean
+  tokenPin: string
+  keyContainer: string
+}): NodeJS.ProcessEnv
+
+/**
+ * Create the electron-builder hook for a hardware-backed Windows code-signing certificate.
+ *
+ * @param options Release signing configuration.
+ * @returns The signing hook.
+ */
+export function createWindowsTokenSigner(options: {
+  certificateFile?: string | undefined
+  signTool?: string | undefined
+  tokenPin?: string | undefined
+  keyContainer?: string | undefined
+  commandInterpreter?: string | undefined
+}): (
+  configuration: {
+    path: string
+    hash: string
+    isNest: boolean
+  },
+) => Promise<void>
+
+/**
+ * Remove inherited credentials before starting a signing-related subprocess.
+ *
+ * @param environment Parent environment.
+ * @returns Environment without credential-shaped names.
+ */
+export function scrubWindowsSigningEnvironment(environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv
+
+/**
+ * Replace a SignTool failure with a diagnostic that cannot retain its command line.
+ *
+ * @param error SignTool process failure.
+ * @param path Artifact that failed signing.
+ * @param secrets Values that must not appear in the diagnostic.
+ * @returns Sanitized signing failure without the original error as its cause.
+ */
+export function createRedactedWindowsSigningError(
+  error: unknown,
+  path: string,
+  secrets: readonly string[],
+): Error
+
+/**
+ * Clear a certificate-table entry that points beyond the end of a generated executable.
+ *
+ * @param path Executable to inspect.
+ * @returns Whether an invalid certificate-table entry was cleared.
+ */
+export function repairDanglingAuthenticodeDirectory(path: string): Promise<boolean>
+
+/**
+ * Sign electron-builder's temporary NSIS executable before enterprise code integrity evaluates it.
+ *
+ * @param options Signing hook and injectable host values.
+ * @returns Nothing.
+ */
+export function installWindowsNsisBootstrapSigner(options: {
+  sign: (configuration: {
+    path: string
+    hash: string
+    isNest: boolean
+  }) => Promise<void>
+  wineVmManager?: {
+    prototype: {
+      exec: (
+        file: string,
+        args: string[],
+        options?: { env?: NodeJS.ProcessEnv },
+        isLogOutIfDebug?: boolean,
+      ) => unknown
+    }
+  }
+  platform?: NodeJS.Platform
+  environment?: NodeJS.ProcessEnv
+}): void

+ 266 - 0
apps/desktop/scripts/windows-sign.mjs

@@ -0,0 +1,266 @@
+import { execFile } from 'node:child_process'
+import { X509Certificate } from 'node:crypto'
+import { readFileSync, realpathSync, statSync } from 'node:fs'
+import { open } from 'node:fs/promises'
+import { dirname, join } from 'node:path'
+import { fileURLToPath } from 'node:url'
+import { promisify } from 'node:util'
+import wineVmModule from 'app-builder-lib/out/vm/WineVm.js'
+
+const execFileAsync = promisify(execFile)
+const { WineVmManager } = wineVmModule
+const CODE_SIGNING_EKU = '1.3.6.1.5.5.7.3.3'
+const NSIS_RUN_AS_INVOKER = 'RunAsInvoker'
+const NSIS_BOOTSTRAP_PATCH = Symbol.for('@deepseek-ai/dsh-desktop/nsis-bootstrap-signing')
+const WINDOWS_SIGN_SCRIPT = 'windows-sign.cmd'
+const WINDOWS_SIGN_SCRIPT_DIRECTORY = dirname(fileURLToPath(import.meta.url))
+const PE_HEADER_READ_SIZE = 4096
+const PE32_MAGIC = 0x10B
+const PE32_PLUS_MAGIC = 0x20B
+const SENSITIVE_ENVIRONMENT_NAME = /(?:KEY|SECRET|TOKEN|PASSWORD)/iu
+const WINDOWS_SIGNING_ENVIRONMENT_PREFIX = 'DSH_DESKTOP_WINDOWS_'
+
+/**
+ * Remove inherited credentials before starting a signing-related subprocess.
+ *
+ * @param {NodeJS.ProcessEnv} environment Parent environment.
+ * @returns {NodeJS.ProcessEnv} Environment without credential-shaped names.
+ */
+export function scrubWindowsSigningEnvironment(environment) {
+  return Object.fromEntries(Object.entries(environment)
+    .filter(([name]) => !SENSITIVE_ENVIRONMENT_NAME.test(name)
+      && !name.startsWith(WINDOWS_SIGNING_ENVIRONMENT_PREFIX)))
+}
+
+function resolveTokenIdentity(input) {
+  const keyContainer = input.keyContainer?.trim()
+  if (!keyContainer) {
+    throw new Error('DSH_DESKTOP_WINDOWS_KEY_CONTAINER must contain the SafeNet private-key container name')
+  }
+  if (/["\r\n]/u.test(keyContainer)) {
+    throw new Error('DSH_DESKTOP_WINDOWS_KEY_CONTAINER cannot contain quotes or line breaks')
+  }
+  const tokenPin = input.tokenPin
+  if (tokenPin === undefined || tokenPin.length === 0) {
+    throw new Error('DSH_DESKTOP_WINDOWS_TOKEN_PIN must contain the SafeNet Token Password')
+  }
+  if (/[\]"\r\n]/u.test(tokenPin)) {
+    throw new Error('DSH_DESKTOP_WINDOWS_TOKEN_PIN cannot contain "]", quotes, or line breaks because the SafeNet key-container syntax uses them as delimiters')
+  }
+  return { keyContainer, tokenPin }
+}
+
+function resolveCertificateFile(value) {
+  const candidate = value?.trim()
+  if (!candidate) {
+    throw new Error('DSH_DESKTOP_WINDOWS_CER_FILE must identify the public X.509 leaf certificate file')
+  }
+  let path
+  let certificate
+  try {
+    path = realpathSync(candidate)
+    certificate = new X509Certificate(readFileSync(path))
+  }
+  catch {
+    throw new Error(`Windows code-signing certificate file is missing or invalid: ${candidate}`)
+  }
+  if (certificate.ca || !certificate.keyUsage?.includes(CODE_SIGNING_EKU)) {
+    throw new Error(`Windows code-signing certificate file must contain a non-CA Code Signing certificate: ${path}`)
+  }
+  return path
+}
+
+function resolveSignTool(value) {
+  const candidate = value?.trim()
+  if (!candidate) {
+    throw new Error('DSH_DESKTOP_WINDOWS_SIGNTOOL must identify the SafeNet-compatible SignTool executable')
+  }
+  let path
+  try {
+    path = realpathSync(candidate)
+    if (!statSync(path).isFile() || !path.toLowerCase().endsWith('.exe')) throw new Error('not an executable file')
+  }
+  catch {
+    throw new Error(`DSH_DESKTOP_WINDOWS_SIGNTOOL is missing or is not an executable file: ${candidate}`)
+  }
+  return path
+}
+
+function redactedSigningOutput(value, secrets) {
+  let output = Buffer.isBuffer(value) ? value.toString('utf8') : typeof value === 'string' ? value : ''
+  for (const secret of secrets) {
+    if (secret !== '') output = output.replaceAll(secret, '<redacted>')
+  }
+  return output
+}
+
+/**
+ * Replace a SignTool failure with a diagnostic that cannot retain its command line.
+ *
+ * @param {unknown} error SignTool process failure.
+ * @param {string} path Artifact that failed signing.
+ * @param {readonly string[]} secrets Values that must not appear in the diagnostic.
+ * @returns {Error} Sanitized signing failure without the original error as its cause.
+ */
+export function createRedactedWindowsSigningError(error, path, secrets) {
+  const record = error !== null && typeof error === 'object' ? error : undefined
+  const code = record !== undefined && 'code' in record
+    && (typeof record.code === 'number' || typeof record.code === 'string')
+    ? ` (exit ${String(record.code)})`
+    : ''
+  const stderr = record !== undefined && 'stderr' in record
+    ? redactedSigningOutput(record.stderr, secrets).trim()
+    : ''
+  return new Error(`Windows release signing failed for ${path}${code}${stderr === '' ? '' : `: ${stderr}`}`)
+}
+
+/**
+ * Build the minimal CMD environment for one Electron artifact.
+ *
+ * @param {NodeJS.ProcessEnv} environment Parent environment.
+ * @param {{ certificateFile: string, signTool: string, path: string, isNest: boolean, tokenPin: string, keyContainer: string }} input Validated signing identity and task.
+ * @returns {NodeJS.ProcessEnv} Scrubbed environment plus fields consumed and cleared by the signing CMD.
+ */
+export function buildWindowsSigningEnvironment(environment, input) {
+  return {
+    ...scrubWindowsSigningEnvironment(environment),
+    DSH_DESKTOP_WINDOWS_SIGNTOOL: input.signTool,
+    DSH_DESKTOP_WINDOWS_CER_FILE: input.certificateFile,
+    DSH_DESKTOP_WINDOWS_TOKEN_PIN: input.tokenPin,
+    DSH_DESKTOP_WINDOWS_KEY_CONTAINER: input.keyContainer,
+    DSH_DESKTOP_WINDOWS_SIGN_TARGET: input.path,
+    DSH_DESKTOP_WINDOWS_SIGN_APPEND: input.isNest ? '1' : '',
+  }
+}
+
+/**
+ * Create the electron-builder hook for a SafeNet-backed Windows code-signing certificate.
+ *
+ * @param {{ certificateFile?: string, signTool?: string, tokenPin?: string, keyContainer?: string, commandInterpreter?: string }} options Release signing configuration.
+ * @returns {(configuration: { path: string, hash: string, isNest: boolean }) => Promise<void>} The signing hook.
+ */
+export function createWindowsTokenSigner(options) {
+  const certificateFile = resolveCertificateFile(options.certificateFile)
+  const signTool = resolveSignTool(options.signTool)
+  const { keyContainer, tokenPin } = resolveTokenIdentity(options)
+  const commandInterpreter = options.commandInterpreter
+    ?? process.env.ComSpec
+    ?? join(process.env.SystemRoot ?? 'C:\\Windows', 'System32', 'cmd.exe')
+  return async (configuration) => {
+    if (configuration.hash !== 'sha256') {
+      throw new Error(`Windows release signing requires SHA-256, received ${configuration.hash}`)
+    }
+    await repairDanglingAuthenticodeDirectory(configuration.path)
+    const secrets = [tokenPin]
+    let result
+    try {
+      result = await execFileAsync(commandInterpreter, [
+        '/d',
+        '/v:off',
+        '/c',
+        WINDOWS_SIGN_SCRIPT,
+      ], {
+        cwd: WINDOWS_SIGN_SCRIPT_DIRECTORY,
+        env: buildWindowsSigningEnvironment(process.env, {
+          certificateFile,
+          signTool,
+          path: configuration.path,
+          isNest: configuration.isNest,
+          tokenPin,
+          keyContainer,
+        }),
+        windowsHide: false,
+      })
+    }
+    catch (error) {
+      throw createRedactedWindowsSigningError(error, configuration.path, secrets)
+    }
+    const stdout = redactedSigningOutput(result.stdout, secrets)
+    const stderr = redactedSigningOutput(result.stderr, secrets)
+    if (stdout !== '') process.stdout.write(stdout)
+    if (stderr !== '') process.stderr.write(stderr)
+  }
+}
+
+/**
+ * Clear a certificate-table entry that points beyond the end of a generated executable.
+ *
+ * @param {string} path Executable to inspect.
+ * @returns {Promise<boolean>} Whether an invalid certificate-table entry was cleared.
+ */
+export async function repairDanglingAuthenticodeDirectory(path) {
+  const file = await open(path, 'r+')
+  try {
+    const { size } = await file.stat()
+    const header = Buffer.alloc(Math.min(PE_HEADER_READ_SIZE, size))
+    await file.read(header, 0, header.length, 0)
+    const directoryOffset = findDanglingAuthenticodeDirectory(header, size)
+    if (directoryOffset === undefined) return false
+    await file.write(Buffer.alloc(8), 0, 8, directoryOffset)
+    return true
+  }
+  finally {
+    await file.close()
+  }
+}
+
+/**
+ * Locate an Authenticode certificate-table entry whose declared bytes are outside the file.
+ *
+ * @param {Buffer} header Initial executable bytes.
+ * @param {number} fileSize Complete file size.
+ * @returns {number | undefined} File offset of the invalid data-directory entry.
+ */
+function findDanglingAuthenticodeDirectory(header, fileSize) {
+  if (header.length < 64 || header.toString('ascii', 0, 2) !== 'MZ') return undefined
+  const peOffset = header.readUInt32LE(60)
+  const optionalHeaderOffset = peOffset + 24
+  if (optionalHeaderOffset + 2 > header.length
+    || header.toString('ascii', peOffset, peOffset + 4) !== 'PE\0\0') return undefined
+  const magic = header.readUInt16LE(optionalHeaderOffset)
+  const dataDirectoryOffset = magic === PE32_MAGIC
+    ? optionalHeaderOffset + 96
+    : magic === PE32_PLUS_MAGIC
+      ? optionalHeaderOffset + 112
+      : undefined
+  if (dataDirectoryOffset === undefined) return undefined
+  const certificateDirectoryOffset = dataDirectoryOffset + (4 * 8)
+  if (certificateDirectoryOffset + 8 > header.length) return undefined
+  const certificateOffset = header.readUInt32LE(certificateDirectoryOffset)
+  const certificateSize = header.readUInt32LE(certificateDirectoryOffset + 4)
+  if (certificateOffset === 0 && certificateSize === 0) return undefined
+  return certificateOffset > 0
+    && certificateSize > 0
+    && certificateOffset + certificateSize <= fileSize
+    ? undefined
+    : certificateDirectoryOffset
+}
+
+/**
+ * Sign electron-builder's temporary NSIS executable before enterprise code integrity evaluates it.
+ *
+ * @param {{ sign: (configuration: { path: string, hash: string, isNest: boolean }) => Promise<void>, wineVmManager?: typeof WineVmManager, platform?: NodeJS.Platform, environment?: NodeJS.ProcessEnv }} options Signing hook and injectable host values.
+ * @returns {void}
+ */
+export function installWindowsNsisBootstrapSigner(options) {
+  if ((options.platform ?? process.platform) !== 'win32') return
+  const prototype = (options.wineVmManager ?? WineVmManager).prototype
+  if (prototype[NSIS_BOOTSTRAP_PATCH] === true) return
+  const originalExec = prototype.exec
+  prototype.exec = async function (file, args, execOptions, isLogOutIfDebug) {
+    const isNsisBootstrap = file.toLowerCase().endsWith('.exe')
+      && execOptions?.env?.__COMPAT_LAYER === NSIS_RUN_AS_INVOKER
+    if (!isNsisBootstrap) {
+      return originalExec.call(this, file, args, execOptions, isLogOutIfDebug)
+    }
+    await options.sign({ path: file, hash: 'sha256', isNest: false })
+    return originalExec.call(this, file, args, {
+      ...execOptions,
+      env: scrubWindowsSigningEnvironment({
+        ...(options.environment ?? process.env),
+        ...execOptions.env,
+      }),
+    }, isLogOutIfDebug)
+  }
+  Object.defineProperty(prototype, NSIS_BOOTSTRAP_PATCH, { value: true })
+}

+ 183 - 0
apps/desktop/src/core-package-set.ts

@@ -0,0 +1,183 @@
+/** Signed local npm package set that supplies the Desktop-owned dsh runtime and private Host. */
+
+import { createHash } from 'node:crypto'
+import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs'
+import { join } from 'node:path'
+
+/** Descriptor copied beside every Desktop profile's local core tarballs. */
+export const DESKTOP_PACKAGE_SET_FILE = 'desktop-packages.json'
+
+/** Profile-relative directory containing immutable core npm tarballs. */
+export const DESKTOP_PACKAGES_DIR = 'desktop-packages'
+
+/** Private package installed beside dsh to boot the Desktop Host process. */
+export const DESKTOP_HOST_PACKAGE = '@deepseek-ai/dsh-desktop-host'
+
+/** Package-relative Desktop Host files required before a profile can boot. */
+export const DESKTOP_HOST_RUNTIME_FILES = [
+  'lib/index.js',
+  'config/desktop.cordis.patch.yml',
+] as const
+
+/** One immutable npm tarball in the Desktop core package set. */
+export interface DesktopCorePackageRecord {
+  readonly name: string
+  readonly version: string
+  readonly file: string
+  readonly bytes: number
+  readonly integrity: string
+}
+
+/** Complete union of the first-party package closures rooted at dsh and its private Desktop Host. */
+export interface DesktopCorePackageSet {
+  readonly schemaVersion: 1
+  readonly packages: readonly DesktopCorePackageRecord[]
+}
+
+const PACKAGE_NAME_PATTERN = /^(?:@[a-z0-9][a-z0-9._~-]*\/[a-z0-9][a-z0-9._~-]*|[a-z0-9][a-z0-9._~-]*)$/u
+const VERSION_PATTERN = /^[0-9A-Za-z][0-9A-Za-z.+_-]*$/u
+const FILE_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*\.tgz$/u
+const INTEGRITY_PATTERN = /^sha512-[A-Za-z0-9+/]+={0,2}$/u
+const DSH_PACKAGE = '@deepseek-ai/dsh'
+const RELEASE_PACKAGES = [DSH_PACKAGE, DESKTOP_HOST_PACKAGE] as const
+
+function isRecord(value: unknown): value is Record<string, unknown> {
+  return typeof value === 'object' && value !== null && !Array.isArray(value)
+}
+
+/**
+ * Validate package-set data read from a release artifact or active profile.
+ * @param value - Parsed descriptor JSON.
+ * @param expectedReleaseVersion - Required dsh and Desktop Host version when validating one release.
+ * @returns The normalized package set in deterministic name order.
+ */
+export function parseDesktopCorePackageSet(
+  value: unknown,
+  expectedReleaseVersion?: string,
+): DesktopCorePackageSet {
+  if (!isRecord(value) || value.schemaVersion !== 1 || !Array.isArray(value.packages)) {
+    throw new Error('desktop package set: invalid descriptor')
+  }
+  const packages = value.packages.map((entry): DesktopCorePackageRecord => {
+    if (!isRecord(entry) || typeof entry.name !== 'string' || !PACKAGE_NAME_PATTERN.test(entry.name)
+      || typeof entry.version !== 'string' || !VERSION_PATTERN.test(entry.version)
+      || typeof entry.file !== 'string' || !FILE_PATTERN.test(entry.file)
+      || typeof entry.bytes !== 'number' || !Number.isSafeInteger(entry.bytes) || entry.bytes < 0
+      || typeof entry.integrity !== 'string' || !INTEGRITY_PATTERN.test(entry.integrity)) {
+      throw new Error('desktop package set: invalid package record')
+    }
+    return {
+      name: entry.name,
+      version: entry.version,
+      file: entry.file,
+      bytes: entry.bytes,
+      integrity: entry.integrity,
+    }
+  })
+  const names = new Set(packages.map(entry => entry.name))
+  const files = new Set(packages.map(entry => entry.file))
+  if (names.size !== packages.length || files.size !== packages.length) {
+    throw new Error('desktop package set: duplicate package name or filename')
+  }
+  const sorted = [...packages].sort((left, right) => left.name.localeCompare(right.name))
+  if (JSON.stringify(sorted) !== JSON.stringify(packages)) {
+    throw new Error('desktop package set: packages must be sorted by name')
+  }
+  for (const name of RELEASE_PACKAGES) {
+    const entry = packages.find(candidate => candidate.name === name)
+    if (entry === undefined) throw new Error(`desktop package set: missing ${name}`)
+    if (expectedReleaseVersion !== undefined && entry.version !== expectedReleaseVersion) {
+      throw new Error(`desktop package set: ${name}@${entry.version} does not match Desktop ${expectedReleaseVersion}`)
+    }
+  }
+  return { schemaVersion: 1, packages }
+}
+
+/** Read and structurally validate one profile's core package descriptor. */
+export function readDesktopCorePackageSet(projectDir: string, expectedReleaseVersion?: string): DesktopCorePackageSet {
+  const path = join(projectDir, DESKTOP_PACKAGE_SET_FILE)
+  let value: unknown
+  try {
+    value = JSON.parse(readFileSync(path, 'utf8'))
+  } catch (error) {
+    throw new Error(`desktop package set: failed to read ${path}: ${String(error)}`)
+  }
+  return parseDesktopCorePackageSet(value, expectedReleaseVersion)
+}
+
+/** Return the project-relative `file:` spec for one local core tarball. */
+export function desktopCorePackageSpec(record: DesktopCorePackageRecord): string {
+  return `file:./${DESKTOP_PACKAGES_DIR}/${record.file}`
+}
+
+/** Return the exact pnpm override map that keeps every core package off registries. */
+export function desktopCorePackageOverrides(packageSet: DesktopCorePackageSet): Record<string, string> {
+  return Object.fromEntries(packageSet.packages.map(record => [record.name, desktopCorePackageSpec(record)]))
+}
+
+/** Return the local direct dependency spec for the dsh package. */
+export function desktopDshPackageSpec(packageSet: DesktopCorePackageSet): string {
+  const record = packageSet.packages.find(entry => entry.name === DSH_PACKAGE)
+  if (record === undefined) throw new Error(`desktop package set: missing ${DSH_PACKAGE}`)
+  return desktopCorePackageSpec(record)
+}
+
+/**
+ * Verify every local tarball and reject extra package files before pnpm executes them.
+ * @param projectDir - Seed or profile directory containing the package set.
+ * @param expectedReleaseVersion - Exact dsh and Desktop Host version bound to Electron.
+ * @returns The verified package set.
+ */
+export function verifyDesktopCorePackageSet(
+  projectDir: string,
+  expectedReleaseVersion: string,
+): DesktopCorePackageSet {
+  const packageSet = readDesktopCorePackageSet(projectDir, expectedReleaseVersion)
+  const packageDir = join(projectDir, DESKTOP_PACKAGES_DIR)
+  const expectedFiles = packageSet.packages.map(entry => entry.file).sort()
+  let actualFiles: string[]
+  try {
+    actualFiles = readdirSync(packageDir).sort()
+  } catch (error) {
+    throw new Error(`desktop package set: failed to read ${packageDir}: ${String(error)}`)
+  }
+  if (JSON.stringify(actualFiles) !== JSON.stringify(expectedFiles)) {
+    throw new Error('desktop package set: package directory does not match its descriptor')
+  }
+  for (const record of packageSet.packages) {
+    const path = join(packageDir, record.file)
+    if (!existsSync(path) || !lstatSync(path).isFile()) {
+      throw new Error(`desktop package set: ${record.file} is not a regular file`)
+    }
+    const body = readFileSync(path)
+    const integrity = `sha512-${createHash('sha512').update(body).digest('base64')}`
+    if (body.byteLength !== record.bytes || integrity !== record.integrity) {
+      throw new Error(`desktop package set: integrity check failed for ${record.file}`)
+    }
+  }
+  return packageSet
+}
+
+function escapeRegExp(value: string): string {
+  return value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&')
+}
+
+/**
+ * Reject a lockfile that resolved any packaged core name through a registry version.
+ * @param lockfile - Generated pnpm lockfile text.
+ * @param packageSet - Verified local core package set.
+ */
+export function verifyDesktopCoreLockfile(
+  lockfile: string,
+  packageSet: DesktopCorePackageSet,
+): void {
+  for (const record of packageSet.packages) {
+    const registryResolution = new RegExp(
+      `^  ['"]?${escapeRegExp(record.name)}@${escapeRegExp(record.version)}(?:\\([^\\r\\n]*\\))?['"]?:`,
+      'mu',
+    )
+    if (registryResolution.test(lockfile)) {
+      throw new Error(`desktop package set: lockfile resolved ${record.name}@${record.version} outside the local package set`)
+    }
+  }
+}

+ 413 - 0
apps/desktop/src/host-process.ts

@@ -0,0 +1,413 @@
+/** Upstream-Node child lifecycle and streaming custom-protocol carrier. */
+
+import { spawn, type ChildProcess } from 'node:child_process'
+import { once } from 'node:events'
+import { join } from 'node:path'
+import { Readable, Writable } from 'node:stream'
+import {
+  DESKTOP_HOST_PROTOCOL_VERSION,
+  DESKTOP_PIPE_CHUNK_BYTES,
+  DESKTOP_REQUEST_PIPE_FD,
+  DESKTOP_RESPONSE_PIPE_FD,
+  DesktopHostResponseDecoder,
+  encodeDesktopRequestCancel,
+  encodeDesktopRequestData,
+  encodeDesktopRequestEnd,
+  encodeDesktopRequestStart,
+  type DesktopHostCommand,
+  type DesktopHostEvent,
+  type DesktopHostResponseFrame,
+} from './host-protocol.ts'
+
+interface PendingResponse {
+  readonly resolve: (response: Response) => void
+  readonly reject: (error: Error) => void
+  responseStarted: boolean
+  uploadOpen: boolean
+  controller?: ReadableStreamDefaultController<Uint8Array>
+  requestReader?: ReadableStreamDefaultReader<Uint8Array>
+  removeAbort?: () => void
+}
+
+function isDesktopHostEvent(message: unknown): message is DesktopHostEvent {
+  if (typeof message !== 'object' || message === null || !('type' in message)) return false
+  const candidate = message as Record<string, unknown>
+  switch (candidate.type) {
+    case 'ready':
+      return candidate.protocolVersion === DESKTOP_HOST_PROTOCOL_VERSION && typeof candidate.dshVersion === 'string'
+    case 'fatal':
+      return typeof candidate.message === 'string'
+    default:
+      return false
+  }
+}
+
+function errorOf(reason: unknown, fallback: string): Error {
+  return reason instanceof Error ? reason : new Error(fallback)
+}
+
+async function exitsWithin(exit: Promise<void>, milliseconds: number): Promise<boolean> {
+  let timer: ReturnType<typeof setTimeout> | undefined
+  const timeout = new Promise<false>((resolve) => {
+    timer = setTimeout(() => { resolve(false) }, milliseconds)
+    timer.unref()
+  })
+  try {
+    return await Promise.race([exit.then(() => true), timeout])
+  } finally {
+    if (timer !== undefined) clearTimeout(timer)
+  }
+}
+
+/** Ready facts reported by one installed dsh child. */
+export interface DesktopHostReady {
+  readonly protocolVersion: typeof DESKTOP_HOST_PROTOCOL_VERSION
+  readonly dshVersion: string
+}
+
+/** One dsh backend running under the bundled upstream Node.js executable. */
+export class DesktopHostProcess {
+  private child: ChildProcess | undefined
+  private requestPipe: Writable | undefined
+  private responsePipe: Readable | undefined
+  private readonly responseDecoder = new DesktopHostResponseDecoder()
+  private requestWriteTail: Promise<void> = Promise.resolve()
+  private nextStreamId = 1
+  private readonly pending = new Map<number, PendingResponse>()
+  private readonly blockedResponses = new Set<number>()
+  private readyResolve!: (ready: DesktopHostReady) => void
+  private readyReject!: (error: Error) => void
+  private readonly readyPromise = new Promise<DesktopHostReady>((resolve, reject) => {
+    this.readyResolve = resolve
+    this.readyReject = reject
+  })
+  private exitPromise: Promise<void> | undefined
+  private stderr = ''
+
+  /**
+   * @param node - absolute bundled upstream Node.js executable.
+   * @param projectDir - active or staged desktop npm project.
+   * @param inspectPort - optional loopback inspector port for workspace development.
+   */
+  constructor(
+    private readonly node: string,
+    private readonly projectDir: string,
+    private readonly inspectPort?: number,
+  ) {}
+
+  /** Start the child once and resolve only after its complete composition is active. */
+  async start(): Promise<DesktopHostReady> {
+    if (this.child !== undefined) return this.readyPromise
+    const entry = join(this.projectDir, 'node_modules', '@deepseek-ai', 'dsh-desktop-host', 'lib', 'index.js')
+    const child = spawn(this.node, [
+      ...(this.inspectPort === undefined ? [] : [`--inspect=127.0.0.1:${String(this.inspectPort)}`]),
+      entry,
+      this.projectDir,
+      ...(this.inspectPort === undefined ? [] : ['--allow-linked-profile']),
+    ], {
+      cwd: this.projectDir,
+      env: Object.fromEntries(Object.entries(process.env).filter(([name]) => (
+        name !== 'NODE_OPTIONS' && !/^DSH_DESKTOP_/u.test(name) && !/^(?:npm|pnpm|corepack)_/iu.test(name)
+      ))),
+      stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe', 'ipc'],
+    })
+    const requestPipe = child.stdio[DESKTOP_REQUEST_PIPE_FD]
+    const responsePipe = child.stdio[DESKTOP_RESPONSE_PIPE_FD]
+    if (!(requestPipe instanceof Writable) || !(responsePipe instanceof Readable)) {
+      child.kill('SIGTERM')
+      throw new Error('dsh desktop host did not expose the required byte pipes and IPC channel')
+    }
+    this.child = child
+    this.requestPipe = requestPipe
+    this.responsePipe = responsePipe
+    child.stderr?.setEncoding('utf8')
+    child.stderr?.on('data', (chunk: string) => { this.stderr += chunk })
+    child.stdout?.pipe(process.stdout)
+    responsePipe.on('data', (chunk: Buffer) => { this.acceptResponseBytes(chunk) })
+    responsePipe.once('end', () => {
+      try {
+        this.responseDecoder.finish()
+        this.fail(new Error('dsh desktop host response pipe ended'))
+      } catch (error) {
+        this.fail(errorOf(error, 'dsh desktop host response pipe failed'))
+      }
+    })
+    requestPipe.once('error', (error) => { this.fail(error) })
+    responsePipe.once('error', (error) => { this.fail(error) })
+    child.on('message', (message: unknown) => {
+      if (!isDesktopHostEvent(message)) {
+        this.fail(new Error('dsh desktop host sent an invalid IPC event'))
+        child.kill('SIGTERM')
+        return
+      }
+      this.handleMessage(message)
+    })
+    child.once('error', (error) => { this.fail(error) })
+    this.exitPromise = new Promise<void>((resolve) => {
+      child.once('exit', (code) => {
+        const suffix = this.stderr.trim() === '' ? '' : `: ${this.stderr.trim()}`
+        if (code !== 0 && code !== null) this.fail(new Error(`dsh desktop host exited with ${String(code)}${suffix}`))
+        else this.fail(new Error(`dsh desktop host stopped${suffix}`))
+        resolve()
+      })
+    })
+    return this.readyPromise
+  }
+
+  /** Forward one `dsh-app://app` request to the child without buffering its body. */
+  async fetch(request: Request): Promise<Response> {
+    await this.start()
+    const child = this.child
+    if (child === undefined || !child.connected || this.requestPipe === undefined) {
+      throw new Error('dsh desktop host is unavailable')
+    }
+    if (this.nextStreamId > 0xffff_ffff) throw new Error('dsh desktop host exhausted its request stream ids')
+    const streamId = this.nextStreamId++
+    const method = request.method.toUpperCase()
+    const hasBody = method !== 'GET' && method !== 'HEAD' && request.body !== null
+    return new Promise<Response>((resolve, reject) => {
+      const pending: PendingResponse = {
+        resolve,
+        reject,
+        responseStarted: false,
+        uploadOpen: hasBody,
+      }
+      const abort = (): void => {
+        if (!this.pending.has(streamId)) return
+        const error = errorOf(request.signal.reason, 'request aborted')
+        pending.uploadOpen = false
+        void pending.requestReader?.cancel(error).catch(() => undefined)
+        this.enqueueRequestFrame(encodeDesktopRequestCancel(streamId)).catch((pipeError: unknown) => {
+          this.fail(errorOf(pipeError, 'dsh desktop request pipe failed'))
+        })
+        if (pending.controller === undefined) pending.reject(error)
+        else pending.controller.error(error)
+        this.finishPending(streamId, false)
+      }
+      if (request.signal.aborted) {
+        reject(errorOf(request.signal.reason, 'request aborted'))
+        return
+      }
+      request.signal.addEventListener('abort', abort, { once: true })
+      pending.removeAbort = () => { request.signal.removeEventListener('abort', abort) }
+      this.pending.set(streamId, pending)
+      this.pumpRequest(streamId, request, hasBody).catch((error: unknown) => {
+        this.failPending(streamId, errorOf(error, 'dsh desktop request upload failed'))
+      })
+    })
+  }
+
+  /** Request graceful teardown, then wait for child exit. */
+  async stop(): Promise<void> {
+    const child = this.child
+    if (child === undefined) return
+    this.blockedResponses.clear()
+    this.responsePipe?.resume()
+    if (child.connected) this.send({ type: 'shutdown' })
+    // Closing the parent-owned write end releases the Host's pending Windows pipe read.
+    this.requestPipe?.destroy()
+    const exited = this.exitPromise ?? Promise.resolve()
+    if (!await exitsWithin(exited, 10_000)) child.kill('SIGTERM')
+    if (!await exitsWithin(exited, 5_000)) {
+      child.kill('SIGKILL')
+      if (!await exitsWithin(exited, 5_000)) {
+        throw new Error('dsh desktop host did not exit after SIGKILL')
+      }
+    }
+    this.child = undefined
+    this.requestPipe = undefined
+    this.responsePipe = undefined
+  }
+
+  private async pumpRequest(streamId: number, request: Request, hasBody: boolean): Promise<void> {
+    await this.enqueueRequestFrame(encodeDesktopRequestStart(streamId, {
+      url: request.url,
+      method: request.method.toUpperCase(),
+      headers: [...request.headers.entries()],
+      hasBody,
+    }))
+    if (!hasBody) return
+    const body = request.body
+    if (body === null) throw new Error('dsh desktop request body disappeared before upload')
+    const reader = body.getReader()
+    const pending = this.pending.get(streamId)
+    if (pending === undefined) {
+      await reader.cancel()
+      return
+    }
+    pending.requestReader = reader
+    try {
+      for (;;) {
+        const next = await reader.read()
+        if (next.done) break
+        for (let offset = 0; offset < next.value.byteLength; offset += DESKTOP_PIPE_CHUNK_BYTES) {
+          if (!this.pending.has(streamId)) return
+          await this.enqueueRequestFrame(encodeDesktopRequestData(
+            streamId,
+            next.value.subarray(offset, offset + DESKTOP_PIPE_CHUNK_BYTES),
+          ))
+        }
+      }
+      const live = this.pending.get(streamId)
+      if (live !== undefined) {
+        await this.enqueueRequestFrame(encodeDesktopRequestEnd(streamId))
+        live.uploadOpen = false
+      }
+    } finally {
+      reader.releaseLock()
+      const live = this.pending.get(streamId)
+      if (live?.requestReader === reader) delete live.requestReader
+    }
+  }
+
+  private enqueueRequestFrame(frame: Buffer): Promise<void> {
+    const write = this.requestWriteTail.then(async () => {
+      const pipe = this.requestPipe
+      if (pipe === undefined || pipe.destroyed) throw new Error('dsh desktop host request pipe is unavailable')
+      if (!pipe.write(frame)) await once(pipe, 'drain')
+    })
+    this.requestWriteTail = write.catch(() => undefined)
+    return write
+  }
+
+  private send(message: DesktopHostCommand): void {
+    const child = this.child
+    if (child === undefined || !child.connected) throw new Error('dsh desktop host IPC is unavailable')
+    child.send(message)
+  }
+
+  private acceptResponseBytes(chunk: Buffer): void {
+    try {
+      for (const frame of this.responseDecoder.push(chunk)) this.handleResponseFrame(frame)
+    } catch (error) {
+      this.fail(errorOf(error, 'dsh desktop host response pipe failed'))
+      this.child?.kill('SIGTERM')
+    }
+  }
+
+  private handleResponseFrame(frame: DesktopHostResponseFrame): void {
+    const pending = this.pending.get(frame.streamId)
+    if (pending === undefined) {
+      if (frame.streamId >= this.nextStreamId) {
+        throw new Error(`dsh desktop host responded for unknown stream ${String(frame.streamId)}`)
+      }
+      return
+    }
+    switch (frame.type) {
+      case 'start': {
+        if (pending.responseStarted) throw new Error(`dsh desktop host started stream ${String(frame.streamId)} twice`)
+        pending.responseStarted = true
+        let body: ReadableStream<Uint8Array> | null = null
+        if (frame.hasBody) {
+          body = new ReadableStream<Uint8Array>({
+            start: (controller) => { pending.controller = controller },
+            pull: () => {
+              this.blockedResponses.delete(frame.streamId)
+              this.resumeResponsePipe()
+            },
+            cancel: (reason) => { this.cancelResponse(frame.streamId, reason) },
+          })
+        }
+        pending.resolve(new Response(body, {
+          status: frame.status,
+          headers: new Headers(frame.headers.map(([name, value]) => [name, value] as [string, string])),
+        }))
+        return
+      }
+      case 'data': {
+        const controller = pending.controller
+        if (!pending.responseStarted || controller === undefined) {
+          throw new Error(`dsh desktop host sent body data before a body start for stream ${String(frame.streamId)}`)
+        }
+        controller.enqueue(frame.data)
+        if ((controller.desiredSize ?? 0) <= 0) {
+          this.blockedResponses.add(frame.streamId)
+          this.responsePipe?.pause()
+        }
+        return
+      }
+      case 'end':
+        if (!pending.responseStarted) {
+          throw new Error(`dsh desktop host ended stream ${String(frame.streamId)} before its response start`)
+        }
+        pending.controller?.close()
+        this.finishPending(frame.streamId, true)
+        return
+      case 'error':
+        this.failPending(frame.streamId, new Error(frame.message))
+        return
+      default:
+        frame satisfies never
+    }
+  }
+
+  private cancelResponse(streamId: number, reason: unknown): void {
+    const pending = this.pending.get(streamId)
+    if (pending === undefined) return
+    pending.uploadOpen = false
+    void pending.requestReader?.cancel(reason).catch(() => undefined)
+    this.enqueueRequestFrame(encodeDesktopRequestCancel(streamId)).catch((error: unknown) => {
+      this.fail(errorOf(error, 'dsh desktop request pipe failed'))
+    })
+    this.finishPending(streamId, false)
+  }
+
+  private failPending(streamId: number, error: Error): void {
+    const pending = this.pending.get(streamId)
+    if (pending === undefined) return
+    pending.uploadOpen = false
+    void pending.requestReader?.cancel(error).catch(() => undefined)
+    if (pending.controller === undefined) pending.reject(error)
+    else pending.controller.error(error)
+    this.enqueueRequestFrame(encodeDesktopRequestCancel(streamId)).catch((pipeError: unknown) => {
+      this.fail(errorOf(pipeError, 'dsh desktop request pipe failed'))
+    })
+    this.finishPending(streamId, false)
+  }
+
+  private finishPending(streamId: number, cancelOpenUpload: boolean): void {
+    const pending = this.pending.get(streamId)
+    if (pending === undefined) return
+    if (cancelOpenUpload && pending.uploadOpen) {
+      pending.uploadOpen = false
+      void pending.requestReader?.cancel().catch(() => undefined)
+      this.enqueueRequestFrame(encodeDesktopRequestCancel(streamId)).catch((error: unknown) => {
+        this.fail(errorOf(error, 'dsh desktop request pipe failed'))
+      })
+    }
+    pending.removeAbort?.()
+    this.pending.delete(streamId)
+    this.blockedResponses.delete(streamId)
+    this.resumeResponsePipe()
+  }
+
+  private resumeResponsePipe(): void {
+    if (this.blockedResponses.size === 0) this.responsePipe?.resume()
+  }
+
+  private handleMessage(message: DesktopHostEvent): void {
+    switch (message.type) {
+      case 'ready':
+        this.readyResolve(message)
+        return
+      case 'fatal':
+        this.fail(new Error(message.message))
+        return
+      default:
+        message satisfies never
+    }
+  }
+
+  private fail(error: Error): void {
+    this.readyReject(error)
+    for (const pending of this.pending.values()) {
+      void pending.requestReader?.cancel(error).catch(() => undefined)
+      if (pending.controller === undefined) pending.reject(error)
+      else pending.controller.error(error)
+      pending.removeAbort?.()
+    }
+    this.pending.clear()
+    this.blockedResponses.clear()
+    this.responsePipe?.resume()
+  }
+}

+ 215 - 0
apps/desktop/src/host-protocol.ts

@@ -0,0 +1,215 @@
+/** Versioned control messages and framed byte transport for the Desktop Host child. */
+
+/** Protocol version implemented by the Electron shell and installed dsh Host. */
+export const DESKTOP_HOST_PROTOCOL_VERSION = 3 as const
+
+/** Child descriptor Electron writes request frames to. */
+export const DESKTOP_REQUEST_PIPE_FD = 3
+
+/** Child descriptor Electron reads response frames from. */
+export const DESKTOP_RESPONSE_PIPE_FD = 4
+
+/** Child descriptor reserved for Node's lifecycle IPC channel. */
+export const DESKTOP_CONTROL_IPC_FD = 5
+
+/** Maximum raw body bytes carried by one data frame. */
+export const DESKTOP_PIPE_CHUNK_BYTES = 64 * 1024
+
+const FRAME_MAGIC = 0x44534833
+const FRAME_HEADER_BYTES = 13
+const MAX_CONTROL_PAYLOAD_BYTES = 1024 * 1024
+
+const REQUEST_FRAME_START = 1
+const REQUEST_FRAME_DATA = 2
+const REQUEST_FRAME_END = 3
+const REQUEST_FRAME_CANCEL = 4
+type RequestFrameType = typeof REQUEST_FRAME_START | typeof REQUEST_FRAME_DATA
+  | typeof REQUEST_FRAME_END | typeof REQUEST_FRAME_CANCEL
+
+const RESPONSE_FRAME_START = 1
+const RESPONSE_FRAME_DATA = 2
+const RESPONSE_FRAME_END = 3
+const RESPONSE_FRAME_ERROR = 4
+
+/** Metadata that precedes one optional request body on the request pipe. */
+export interface DesktopHostRequestStart {
+  readonly url: string
+  readonly method: string
+  readonly headers: readonly [string, string][]
+  readonly hasBody: boolean
+}
+
+/** Commands retained on Node IPC because they do not carry Fetch payload bytes. */
+export type DesktopHostCommand = {
+  readonly type: 'shutdown'
+}
+
+/** Lifecycle events retained on Node IPC. */
+export type DesktopHostEvent = {
+  readonly type: 'ready'
+  readonly protocolVersion: typeof DESKTOP_HOST_PROTOCOL_VERSION
+  readonly dshVersion: string
+} | {
+  readonly type: 'fatal'
+  readonly message: string
+}
+
+/** One decoded response-pipe frame. */
+export type DesktopHostResponseFrame = {
+  readonly type: 'start'
+  readonly streamId: number
+  readonly status: number
+  readonly headers: readonly [string, string][]
+  readonly hasBody: boolean
+} | {
+  readonly type: 'data'
+  readonly streamId: number
+  readonly data: Buffer
+} | {
+  readonly type: 'end'
+  readonly streamId: number
+} | {
+  readonly type: 'error'
+  readonly streamId: number
+  readonly message: string
+}
+
+function isRecord(value: unknown): value is Record<string, unknown> {
+  return typeof value === 'object' && value !== null
+}
+
+function isHeaders(value: unknown): value is readonly [string, string][] {
+  return Array.isArray(value) && value.every(header => Array.isArray(header) && header.length === 2
+    && typeof header[0] === 'string' && typeof header[1] === 'string')
+}
+
+function assertStreamId(streamId: number): void {
+  if (!Number.isInteger(streamId) || streamId < 1 || streamId > 0xffff_ffff) {
+    throw new Error(`dsh desktop: invalid pipe stream id ${String(streamId)}`)
+  }
+}
+
+function encodeFrame(type: RequestFrameType, streamId: number, payload: Buffer): Buffer {
+  assertStreamId(streamId)
+  const limit = type === REQUEST_FRAME_DATA ? DESKTOP_PIPE_CHUNK_BYTES : MAX_CONTROL_PAYLOAD_BYTES
+  if (payload.byteLength > limit) {
+    throw new Error(`dsh desktop: request pipe frame exceeds the ${String(limit)}-byte limit`)
+  }
+  const frame = Buffer.allocUnsafe(FRAME_HEADER_BYTES + payload.byteLength)
+  frame.writeUInt32BE(FRAME_MAGIC, 0)
+  frame.writeUInt8(type, 4)
+  frame.writeUInt32BE(streamId, 5)
+  frame.writeUInt32BE(payload.byteLength, 9)
+  payload.copy(frame, FRAME_HEADER_BYTES)
+  return frame
+}
+
+function encodeJsonFrame(type: RequestFrameType, streamId: number, value: unknown): Buffer {
+  return encodeFrame(type, streamId, Buffer.from(JSON.stringify(value), 'utf8'))
+}
+
+/** Encode the metadata opening one request stream. */
+export function encodeDesktopRequestStart(streamId: number, request: DesktopHostRequestStart): Buffer {
+  return encodeJsonFrame(REQUEST_FRAME_START, streamId, request)
+}
+
+/** Encode one bounded raw request-body chunk. */
+export function encodeDesktopRequestData(streamId: number, data: Uint8Array): Buffer {
+  return encodeFrame(REQUEST_FRAME_DATA, streamId, Buffer.from(data))
+}
+
+/** Encode normal request-body completion. */
+export function encodeDesktopRequestEnd(streamId: number): Buffer {
+  return encodeFrame(REQUEST_FRAME_END, streamId, Buffer.alloc(0))
+}
+
+/** Encode cancellation of one request and its response. */
+export function encodeDesktopRequestCancel(streamId: number): Buffer {
+  return encodeFrame(REQUEST_FRAME_CANCEL, streamId, Buffer.alloc(0))
+}
+
+/** Incrementally decode validated response frames from the Host byte pipe. */
+export class DesktopHostResponseDecoder {
+  private buffer: Buffer = Buffer.alloc(0)
+
+  /**
+   * Append bytes and return every complete response frame.
+   * @param chunk - next bytes read from the Host response pipe.
+   * @returns complete frames in pipe order.
+   */
+  push(chunk: Buffer): DesktopHostResponseFrame[] {
+    this.buffer = this.buffer.byteLength === 0 ? chunk : Buffer.concat([this.buffer, chunk])
+    const frames: DesktopHostResponseFrame[] = []
+    for (;;) {
+      const frame = this.next()
+      if (frame === undefined) return frames
+      frames.push(frame)
+    }
+  }
+
+  /** Reject EOF that splits a frame. */
+  finish(): void {
+    if (this.buffer.byteLength !== 0) throw new Error('dsh desktop: Host response pipe ended inside a frame')
+  }
+
+  private next(): DesktopHostResponseFrame | undefined {
+    if (this.buffer.byteLength < FRAME_HEADER_BYTES) return undefined
+    if (this.buffer.readUInt32BE(0) !== FRAME_MAGIC) throw new Error('dsh desktop: invalid Host response frame marker')
+    const rawType = this.buffer.readUInt8(4)
+    const streamId = this.buffer.readUInt32BE(5)
+    const payloadLength = this.buffer.readUInt32BE(9)
+    assertStreamId(streamId)
+    const limit = rawType === RESPONSE_FRAME_DATA ? DESKTOP_PIPE_CHUNK_BYTES : MAX_CONTROL_PAYLOAD_BYTES
+    if (payloadLength > limit) {
+      throw new Error(`dsh desktop: Host response frame exceeds the ${String(limit)}-byte limit`)
+    }
+    const frameLength = FRAME_HEADER_BYTES + payloadLength
+    if (this.buffer.byteLength < frameLength) return undefined
+    const payload = this.buffer.subarray(FRAME_HEADER_BYTES, frameLength)
+    this.buffer = this.buffer.subarray(frameLength)
+    switch (rawType) {
+      case RESPONSE_FRAME_START:
+        return this.parseStart(streamId, payload)
+      case RESPONSE_FRAME_DATA:
+        return { type: 'data', streamId, data: payload }
+      case RESPONSE_FRAME_END:
+        if (payloadLength !== 0) throw new Error('dsh desktop: Host response end frame carried a payload')
+        return { type: 'end', streamId }
+      case RESPONSE_FRAME_ERROR:
+        return this.parseError(streamId, payload)
+      default:
+        throw new Error(`dsh desktop: unknown Host response frame type ${String(rawType)}`)
+    }
+  }
+
+  private parseStart(streamId: number, payload: Buffer): DesktopHostResponseFrame {
+    const value = this.parseJson(payload, 'start')
+    if (!isRecord(value) || !Number.isInteger(value.status) || (value.status as number) < 100
+      || (value.status as number) > 599 || !isHeaders(value.headers) || typeof value.hasBody !== 'boolean') {
+      throw new Error('dsh desktop: invalid Host response start payload')
+    }
+    return {
+      type: 'start',
+      streamId,
+      status: value.status as number,
+      headers: value.headers,
+      hasBody: value.hasBody,
+    }
+  }
+
+  private parseError(streamId: number, payload: Buffer): DesktopHostResponseFrame {
+    const value = this.parseJson(payload, 'error')
+    if (!isRecord(value) || typeof value.message !== 'string') {
+      throw new Error('dsh desktop: invalid Host response error payload')
+    }
+    return { type: 'error', streamId, message: value.message }
+  }
+
+  private parseJson(payload: Buffer, subject: string): unknown {
+    try {
+      return JSON.parse(payload.toString('utf8')) as unknown
+    } catch (error) {
+      throw new Error(`dsh desktop: Host response ${subject} payload is not JSON: ${error instanceof Error ? error.message : String(error)}`)
+    }
+  }
+}

+ 40 - 0
apps/desktop/src/ipc.ts

@@ -0,0 +1,40 @@
+/** Typed preload operations exposed only by the Electron shell. */
+
+import type { DesktopPluginRecord } from './project-manager.ts'
+import type { DesktopLocale } from './locale.ts'
+
+/** IPC channel names kept private to the desktop application bundle. */
+export const DESKTOP_IPC = {
+  localeGet: 'dsh-desktop:locale-get',
+  pluginsList: 'dsh-desktop:plugins-list',
+  pluginsAdd: 'dsh-desktop:plugins-add',
+  pluginsRemove: 'dsh-desktop:plugins-remove',
+  pluginsUpdate: 'dsh-desktop:plugins-update',
+  updatesCheck: 'dsh-desktop:updates-check',
+  updatesInstall: 'dsh-desktop:updates-install',
+  updatesState: 'dsh-desktop:updates-state',
+} as const
+
+/** Desktop release update state rendered by desktop-owned UI. */
+export interface DesktopUpdateState {
+  readonly phase: 'idle' | 'checking' | 'available' | 'installing' | 'ready' | 'error'
+  readonly version?: string
+  readonly message?: string
+}
+
+/** Narrow bridge exposed through context isolation. */
+export interface DshDesktopApi {
+  readonly protocolVersion: 1
+  locale(): Promise<DesktopLocale>
+  readonly plugins: {
+    list(): Promise<readonly DesktopPluginRecord[]>
+    add(spec: string): Promise<void>
+    remove(name: string): Promise<void>
+    update(name: string, version: string): Promise<void>
+  }
+  readonly updates: {
+    check(): Promise<DesktopUpdateState>
+    install(): Promise<void>
+    subscribe(listener: (state: DesktopUpdateState) => void): () => void
+  }
+}

+ 97 - 0
apps/desktop/src/locale.ts

@@ -0,0 +1,97 @@
+/** Typed English and Chinese copy owned by the Electron shell. */
+
+export const en = {
+  application: 'Application',
+  startupFailed: 'DeepSeek Harness could not start',
+  pluginsMenu: 'Desktop Plugins…',
+  pluginsMenuPackagedOnly: 'Desktop Plugins… (available in packaged applications)',
+  checkUpdatesMenu: 'Check for Updates…',
+  updateCheckFailedTitle: 'Update Check Failed',
+  unknownError: 'Unknown error',
+  updateCheckTitle: 'Check for Updates',
+  updateCurrent: 'You already have the latest version.',
+  updateTitle: 'DeepSeek Harness Update',
+  updateAvailable: 'An update is available',
+  updateDetail: 'DeepSeek Harness {version}\n\nThis release includes its matching dsh version. The application will restart after installation.',
+  installAndRestart: 'Install and Restart',
+  later: 'Later',
+  updateFailedTitle: 'Update Failed',
+  pluginManagerTitle: 'Desktop Plugins',
+  pluginWindowTitle: 'DeepSeek Harness — Desktop Plugins',
+  pluginManagerDescription: 'Plugins are installed only in the Desktop node_modules and are managed by the bundled pnpm.',
+  refresh: 'Refresh',
+  npmPackage: 'npm package',
+  install: 'Install',
+  installed: 'Installed',
+  noPlugins: 'No Desktop plugins are installed.',
+  remove: 'Remove',
+  update: 'Update',
+  targetVersion: 'Enter the target version for {name}',
+  removing: 'Removing {name}…',
+  updating: 'Updating {name}…',
+  installing: 'Installing {spec}…',
+  operationComplete: 'Done. The Desktop backend has restarted.',
+  refreshing: 'Refreshing…',
+  refreshed: 'Plugin list refreshed.',
+  loadingPlugins: 'Reading Desktop plugins…',
+} as const
+
+/** Every Desktop locale supplies the complete English key set. */
+export type DesktopMessages = { readonly [Key in keyof typeof en]: string }
+
+export const zh = {
+  application: '应用',
+  startupFailed: 'DeepSeek Harness 无法启动',
+  pluginsMenu: '桌面插件…',
+  pluginsMenuPackagedOnly: '桌面插件…(打包应用中可用)',
+  checkUpdatesMenu: '检查更新…',
+  updateCheckFailedTitle: '更新检查失败',
+  unknownError: '未知错误',
+  updateCheckTitle: '检查更新',
+  updateCurrent: '当前已是最新版本。',
+  updateTitle: 'DeepSeek Harness 更新',
+  updateAvailable: '发现可用更新',
+  updateDetail: 'DeepSeek Harness {version}\n\n新版本绑定匹配的 dsh,安装后将重新启动。',
+  installAndRestart: '安装并重启',
+  later: '稍后',
+  updateFailedTitle: '更新失败',
+  pluginManagerTitle: '桌面插件',
+  pluginWindowTitle: 'DeepSeek Harness — 桌面插件',
+  pluginManagerDescription: '插件只安装到桌面端自己的 node_modules,并由内置 pnpm 管理。',
+  refresh: '刷新',
+  npmPackage: 'npm 包',
+  install: '安装',
+  installed: '已安装',
+  noPlugins: '还没有安装桌面插件。',
+  remove: '移除',
+  update: '更新',
+  targetVersion: '输入 {name} 的目标版本',
+  removing: '正在移除 {name}…',
+  updating: '正在更新 {name}…',
+  installing: '正在安装 {spec}…',
+  operationComplete: '操作完成,桌面后端已重新启动。',
+  refreshing: '正在刷新…',
+  refreshed: '插件列表已刷新。',
+  loadingPlugins: '正在读取桌面插件…',
+} as const satisfies DesktopMessages
+
+/** Locale payload exposed to the Desktop-owned renderer. */
+export interface DesktopLocale {
+  readonly id: 'en' | 'zh-CN'
+  readonly messages: DesktopMessages
+}
+
+/** Resolve Electron's locale to one shipped Desktop dictionary. */
+export function resolveDesktopLocale(locale: string): DesktopLocale {
+  return locale.toLowerCase().startsWith('zh')
+    ? { id: 'zh-CN', messages: zh }
+    : { id: 'en', messages: en }
+}
+
+/** Replace named placeholders in one locale-owned message. */
+export function formatDesktopMessage(
+  message: string,
+  values: Readonly<Record<string, string>>,
+): string {
+  return message.replaceAll(/\{([^{}]+)\}/gu, (placeholder, key: string) => values[key] ?? placeholder)
+}

+ 397 - 0
apps/desktop/src/main.ts

@@ -0,0 +1,397 @@
+/** Electron shell: desktop project ownership, custom protocol, windows, and lifecycle. */
+
+import { readFile, writeFile } from 'node:fs/promises'
+import { extname, join, normalize, resolve, sep } from 'node:path'
+import { fileURLToPath } from 'node:url'
+import {
+  app,
+  BrowserWindow,
+  dialog,
+  ipcMain,
+  Menu,
+  protocol,
+  type IpcMainInvokeEvent,
+} from 'electron'
+import { resolveDesktopPaths } from './paths.ts'
+import { DesktopProjectManager, type DesktopProjectHooks } from './project-manager.ts'
+import { DesktopHostProcess } from './host-process.ts'
+import { DESKTOP_IPC, type DesktopUpdateState } from './ipc.ts'
+import { formatDesktopMessage, resolveDesktopLocale } from './locale.ts'
+import { claimDesktopSingleInstance } from './single-instance.ts'
+import { DesktopUpdateCoordinator } from './update-coordinator.ts'
+
+const SCHEME = 'dsh-app'
+let focusPrimaryWindow = (): void => {}
+
+function errorOf(reason: unknown, fallback: string): Error {
+  return reason instanceof Error ? reason : new Error(fallback)
+}
+
+protocol.registerSchemesAsPrivileged([{
+  scheme: SCHEME,
+  privileges: {
+    standard: true,
+    secure: true,
+    supportFetchAPI: true,
+    corsEnabled: false,
+    stream: true,
+    codeCache: true,
+  },
+}])
+
+const MIME: Readonly<Record<string, string>> = {
+  '.css': 'text/css; charset=utf-8',
+  '.html': 'text/html; charset=utf-8',
+  '.js': 'text/javascript; charset=utf-8',
+  '.svg': 'image/svg+xml',
+}
+
+interface RuntimeResources {
+  readonly node: string
+  readonly pnpm: string
+  readonly seed: string
+}
+
+function runtimeResources(): RuntimeResources {
+  const development = !app.isPackaged
+  const node = (development ? process.env.DSH_DESKTOP_NODE_BINARY : undefined)
+    ?? join(process.resourcesPath, 'runtime', 'node', process.platform === 'win32' ? 'node.exe' : 'node')
+  const pnpm = (development ? process.env.DSH_DESKTOP_PNPM_ENTRY : undefined)
+    ?? join(process.resourcesPath, 'runtime', 'pnpm', 'bin', 'pnpm.mjs')
+  const seed = (development ? process.env.DSH_DESKTOP_SEED_DIR : undefined) ?? join(process.resourcesPath, 'seed')
+  return { node, pnpm, seed }
+}
+
+function developmentProject(): string | undefined {
+  const configured = process.env.DSH_DESKTOP_DEV_PROJECT_DIR
+  if (configured === undefined || configured === '') return undefined
+  if (app.isPackaged) throw new Error('dsh desktop: development project override is unavailable in packaged applications')
+  return resolve(configured)
+}
+
+function developmentHostInspectPort(enabled: boolean): number | undefined {
+  const configured = process.env.DSH_DESKTOP_HOST_INSPECT_PORT
+  if (!enabled || configured === undefined || configured === '') return undefined
+  const port = Number(configured)
+  if (!Number.isSafeInteger(port) || port < 1 || port > 65_535) {
+    throw new Error('dsh desktop: DSH_DESKTOP_HOST_INSPECT_PORT must be an integer from 1 through 65535')
+  }
+  return port
+}
+
+function createWindow(preload: string): BrowserWindow {
+  const window = new BrowserWindow({
+    width: 1280,
+    height: 840,
+    minWidth: 880,
+    minHeight: 600,
+    show: false,
+    webPreferences: {
+      preload,
+      nodeIntegration: false,
+      contextIsolation: true,
+      sandbox: true,
+      webSecurity: true,
+    },
+  })
+  window.webContents.setWindowOpenHandler(() => ({ action: 'deny' }))
+  window.webContents.on('will-navigate', (event, url) => {
+    if (new URL(url).protocol !== `${SCHEME}:`) event.preventDefault()
+  })
+  return window
+}
+
+function assertDesktopSender(event: IpcMainInvokeEvent, hostnames: readonly string[]): void {
+  const senderFrame = event.senderFrame
+  if (senderFrame === null) throw new Error('dsh desktop: rejected IPC without a sender frame')
+  const url = new URL(senderFrame.url)
+  if (url.protocol !== `${SCHEME}:` || !hostnames.includes(url.hostname)) {
+    throw new Error('dsh desktop: rejected IPC from an unowned renderer')
+  }
+}
+
+async function serveShellAsset(request: Request): Promise<Response> {
+  if (request.method !== 'GET' && request.method !== 'HEAD') return new Response(null, { status: 405 })
+  const root = resolve(app.getAppPath(), 'renderer')
+  const url = new URL(request.url)
+  let pathname: string
+  try {
+    pathname = decodeURIComponent(url.pathname)
+  } catch {
+    return new Response(null, { status: 400 })
+  }
+  const target = resolve(normalize(join(root, pathname)))
+  if (target !== root && !target.startsWith(root + sep)) return new Response(null, { status: 403 })
+  try {
+    const body = request.method === 'HEAD' ? null : await readFile(target)
+    return new Response(body, { headers: { 'content-type': MIME[extname(target)] ?? 'application/octet-stream' } })
+  } catch {
+    return new Response(null, { status: 404 })
+  }
+}
+
+async function main(): Promise<void> {
+  const resources = runtimeResources()
+  const paths = resolveDesktopPaths()
+  const development = developmentProject()
+  const activeProject = development ?? paths.profile
+  const hostInspectPort = developmentHostInspectPort(development !== undefined)
+  const manager = new DesktopProjectManager(paths, resources)
+  if (development === undefined) manager.recover()
+  let host: DesktopHostProcess | undefined
+  let mainWindow: BrowserWindow | undefined
+  let pluginWindow: BrowserWindow | undefined
+  let shellInstallerOwnsQuit = false
+  let updateState: DesktopUpdateState = { phase: 'idle' }
+  const locale = resolveDesktopLocale(app.getLocale())
+  const messages = locale.messages
+  const appPreload = fileURLToPath(new URL('./preload-app.cjs', import.meta.url))
+  const managementPreload = fileURLToPath(new URL('./preload.cjs', import.meta.url))
+
+  const publishUpdate = (state: DesktopUpdateState): DesktopUpdateState => {
+    updateState = state
+    for (const window of BrowserWindow.getAllWindows()) {
+      window.webContents.send(DESKTOP_IPC.updatesState, state)
+    }
+    return state
+  }
+
+  const startHost = async (projectDir = activeProject): Promise<DesktopHostProcess> => {
+    const next = new DesktopHostProcess(resources.node, projectDir, hostInspectPort)
+    await next.start()
+    return next
+  }
+  const hooks: DesktopProjectHooks = {
+    healthCheck: async (projectDir) => {
+      const active = host
+      host = undefined
+      await active?.stop()
+      let healthFailure: unknown
+      let probe: DesktopHostProcess | undefined
+      try {
+        probe = await startHost(projectDir)
+        await probe.stop()
+      } catch (error) {
+        healthFailure = error
+        await probe?.stop().catch(() => undefined)
+      }
+      let restartFailure: unknown
+      if (active !== undefined) {
+        try {
+          host = await startHost()
+        } catch (error) {
+          restartFailure = error
+        }
+      }
+      if (healthFailure !== undefined && restartFailure !== undefined) {
+        throw new AggregateError([
+          errorOf(healthFailure, 'desktop project: staged health check failed'),
+          errorOf(restartFailure, 'desktop project: active backend restart failed'),
+        ], 'desktop project: staged health check and active backend restart failed')
+      }
+      if (healthFailure !== undefined) throw errorOf(healthFailure, 'desktop project: staged health check failed')
+      if (restartFailure !== undefined) throw errorOf(restartFailure, 'desktop project: active backend restart failed')
+    },
+    beforeActivate: async () => {
+      const active = host
+      host = undefined
+      await active?.stop()
+    },
+    afterActivate: async () => {
+      host = await startHost()
+    },
+  }
+
+  if (development === undefined) {
+    await manager.applyRelease(resources.seed, app.getVersion(), {
+      ...hooks,
+      beforeActivate: async () => {},
+      afterActivate: async () => {},
+    })
+  }
+  host = await startHost()
+
+  const updates = new DesktopUpdateCoordinator(
+    publishUpdate,
+    async () => {
+      shellInstallerOwnsQuit = true
+      const active = host
+      host = undefined
+      await active?.stop()
+    },
+  )
+
+  protocol.handle(SCHEME, (request) => {
+    const url = new URL(request.url)
+    if (url.hostname === 'shell') return serveShellAsset(request)
+    if (url.hostname !== 'app') return Promise.resolve(new Response(null, { status: 404 }))
+    const active = host
+    if (active === undefined) return Promise.resolve(new Response('backend unavailable', { status: 503 }))
+    return active.fetch(request)
+  })
+
+  const mutate = async (event: IpcMainInvokeEvent, mutation: Parameters<DesktopProjectManager['mutate']>[0]): Promise<void> => {
+    assertDesktopSender(event, ['shell'])
+    if (development !== undefined) {
+      throw new Error('dsh desktop: plugin package changes require a packaged application')
+    }
+    await manager.mutate(mutation, hooks)
+    if (mainWindow !== undefined && !mainWindow.isDestroyed()) mainWindow.webContents.reload()
+  }
+  ipcMain.handle(DESKTOP_IPC.localeGet, (event) => {
+    assertDesktopSender(event, ['shell'])
+    return locale
+  })
+  ipcMain.handle(DESKTOP_IPC.pluginsList, (event) => {
+    assertDesktopSender(event, ['shell'])
+    if (development !== undefined) return []
+    return manager.listPlugins()
+  })
+  ipcMain.handle(DESKTOP_IPC.pluginsAdd, (event, spec: unknown) => {
+    if (typeof spec !== 'string') throw new Error('dsh desktop: plugin spec must be a string')
+    return mutate(event, { type: 'plugin-add', spec })
+  })
+  ipcMain.handle(DESKTOP_IPC.pluginsRemove, (event, name: unknown) => {
+    if (typeof name !== 'string') throw new Error('dsh desktop: plugin name must be a string')
+    return mutate(event, { type: 'plugin-remove', name })
+  })
+  ipcMain.handle(DESKTOP_IPC.pluginsUpdate, (event, name: unknown, version: unknown) => {
+    if (typeof name !== 'string' || typeof version !== 'string') {
+      throw new Error('dsh desktop: plugin name and version must be strings')
+    }
+    return mutate(event, { type: 'plugin-update', name, version })
+  })
+  ipcMain.handle(DESKTOP_IPC.updatesCheck, async (event) => {
+    assertDesktopSender(event, ['shell'])
+    return updates.check()
+  })
+  ipcMain.handle(DESKTOP_IPC.updatesInstall, async (event) => {
+    assertDesktopSender(event, ['shell'])
+    await updates.install()
+  })
+
+  const checkAndPrompt = async (manual: boolean): Promise<void> => {
+    const state = await updates.check()
+    if (state.phase === 'error') {
+      if (manual) {
+        await dialog.showMessageBox({
+          type: 'error',
+          title: messages.updateCheckFailedTitle,
+          message: state.message ?? messages.unknownError,
+        })
+      }
+      return
+    }
+    if (state.phase !== 'available') {
+      if (manual) {
+        await dialog.showMessageBox({
+          type: 'info',
+          title: messages.updateCheckTitle,
+          message: state.message ?? messages.updateCurrent,
+        })
+      }
+      return
+    }
+    const result = await dialog.showMessageBox({
+      type: 'info',
+      title: messages.updateTitle,
+      message: messages.updateAvailable,
+      detail: formatDesktopMessage(messages.updateDetail, { version: state.version ?? '' }),
+      buttons: [messages.installAndRestart, messages.later],
+      defaultId: 0,
+      cancelId: 1,
+    })
+    if (result.response !== 0) return
+    const installed = await updates.install()
+    if (installed.phase === 'error') {
+      await dialog.showMessageBox({
+        type: 'error',
+        title: messages.updateFailedTitle,
+        message: installed.message ?? messages.unknownError,
+      })
+    }
+  }
+
+  const openPluginWindow = (): void => {
+    if (pluginWindow !== undefined && !pluginWindow.isDestroyed()) {
+      pluginWindow.focus()
+      return
+    }
+    pluginWindow = createWindow(managementPreload)
+    pluginWindow.setSize(900, 620)
+    pluginWindow.setTitle(messages.pluginWindowTitle)
+    pluginWindow.once('ready-to-show', () => { pluginWindow?.show() })
+    pluginWindow.once('closed', () => { pluginWindow = undefined })
+    void pluginWindow.loadURL(`${SCHEME}://shell/plugin-manager.html`)
+  }
+
+  Menu.setApplicationMenu(Menu.buildFromTemplate([{
+    label: process.platform === 'darwin' ? app.name : messages.application,
+    submenu: [
+      {
+        label: development === undefined ? messages.pluginsMenu : messages.pluginsMenuPackagedOnly,
+        accelerator: 'CmdOrCtrl+,',
+        enabled: development === undefined,
+        click: openPluginWindow,
+      },
+      { label: messages.checkUpdatesMenu, click: () => { void checkAndPrompt(true) } },
+      { type: 'separator' },
+      { role: 'quit' },
+    ],
+  }]))
+
+  const createMainWindow = (): BrowserWindow => {
+    const window = createWindow(appPreload)
+    mainWindow = window
+    window.once('ready-to-show', () => { if (!window.isDestroyed()) window.show() })
+    window.on('closed', () => { if (mainWindow === window) mainWindow = undefined })
+    return window
+  }
+  focusPrimaryWindow = () => {
+    const window = mainWindow
+    if (window === undefined || window.isDestroyed()) {
+      const replacement = createMainWindow()
+      void replacement.loadURL(`${SCHEME}://app/index.html`)
+      return
+    }
+    if (window.isMinimized()) window.restore()
+    window.show()
+    window.focus()
+  }
+
+  mainWindow = createMainWindow()
+  await mainWindow.loadURL(`${SCHEME}://app/index.html`)
+  if (development !== undefined && process.env.DSH_DESKTOP_OPEN_DEVTOOLS !== '0') {
+    mainWindow.webContents.openDevTools({ mode: 'detach' })
+  }
+  publishUpdate(updateState)
+  setTimeout(() => { void checkAndPrompt(false) }, 10_000)
+
+  app.on('activate', () => {
+    if (BrowserWindow.getAllWindows().length === 0) focusPrimaryWindow()
+  })
+  app.on('window-all-closed', () => {
+    if (process.platform !== 'darwin') app.quit()
+  })
+  app.on('before-quit', (event) => {
+    if (shellInstallerOwnsQuit) return
+    if (host === undefined) return
+    event.preventDefault()
+    const active = host
+    host = undefined
+    void active.stop().finally(() => { app.quit() })
+  })
+}
+
+const ownsDesktopInstance = claimDesktopSingleInstance(app, () => { focusPrimaryWindow() })
+
+if (ownsDesktopInstance) void app.whenReady().then(main).catch(async (error: unknown) => {
+  const message = error instanceof Error ? error.message : String(error)
+  console.error(error)
+  const diagnosticFile = process.env.DSH_DESKTOP_DIAGNOSTIC_FILE
+  if (diagnosticFile !== undefined) {
+    await writeFile(diagnosticFile, `${error instanceof Error ? error.stack ?? message : message}\n`).catch(() => undefined)
+  }
+  dialog.showErrorBox(resolveDesktopLocale(app.getLocale()).messages.startupFailed, message)
+  app.exit(1)
+})

+ 48 - 0
apps/desktop/src/paths.ts

@@ -0,0 +1,48 @@
+/** Filesystem ownership for the Electron-managed desktop installation. */
+
+import { join } from 'node:path'
+import { resolveDshHome } from '@deepseek-ai/dsh-home-paths'
+
+/** Stable desktop installation paths under the shared Harness home. */
+export interface DesktopPaths {
+  readonly root: string
+  readonly profile: string
+  readonly staging: string
+  readonly rollback: string
+  readonly pending: string
+  readonly lock: string
+  readonly pnpm: {
+    readonly root: string
+    readonly store: string
+    readonly cache: string
+    readonly state: string
+    readonly config: string
+    readonly home: string
+  }
+}
+
+/**
+ * Resolve every Electron-owned path without changing the shared data roots.
+ * @param dshHome - Harness home shared with npm-installed dsh.
+ * @returns immutable desktop path set.
+ */
+export function resolveDesktopPaths(dshHome: string = resolveDshHome()): DesktopPaths {
+  const root = join(dshHome, 'desktop')
+  const pnpm = join(root, 'pnpm')
+  return {
+    root,
+    profile: join(dshHome, 'profiles', 'desktop'),
+    staging: join(root, 'staging'),
+    rollback: join(root, 'rollback', 'profile'),
+    pending: join(root, 'pending.json'),
+    lock: join(root, 'lock'),
+    pnpm: {
+      root: pnpm,
+      store: join(pnpm, 'store'),
+      cache: join(pnpm, 'cache'),
+      state: join(pnpm, 'state'),
+      config: join(pnpm, 'config'),
+      home: join(pnpm, 'home'),
+    },
+  }
+}

+ 5 - 0
apps/desktop/src/preload-app.ts

@@ -0,0 +1,5 @@
+/** Minimal marker that selects the desktop custom-protocol API carrier. */
+
+import { contextBridge } from 'electron'
+
+contextBridge.exposeInMainWorld('dshDesktop', { protocolVersion: 1 })

+ 26 - 0
apps/desktop/src/preload.ts

@@ -0,0 +1,26 @@
+/** Context-isolated renderer bridge for desktop package and update operations. */
+
+import { contextBridge, ipcRenderer } from 'electron'
+import { DESKTOP_IPC, type DshDesktopApi, type DesktopUpdateState } from './ipc.ts'
+
+const api: DshDesktopApi = {
+  protocolVersion: 1,
+  locale: () => ipcRenderer.invoke(DESKTOP_IPC.localeGet) as Promise<ReturnType<DshDesktopApi['locale']> extends Promise<infer T> ? T : never>,
+  plugins: {
+    list: () => ipcRenderer.invoke(DESKTOP_IPC.pluginsList) as Promise<ReturnType<DshDesktopApi['plugins']['list']> extends Promise<infer T> ? T : never>,
+    add: spec => ipcRenderer.invoke(DESKTOP_IPC.pluginsAdd, spec) as Promise<void>,
+    remove: name => ipcRenderer.invoke(DESKTOP_IPC.pluginsRemove, name) as Promise<void>,
+    update: (name, version) => ipcRenderer.invoke(DESKTOP_IPC.pluginsUpdate, name, version) as Promise<void>,
+  },
+  updates: {
+    check: () => ipcRenderer.invoke(DESKTOP_IPC.updatesCheck) as Promise<DesktopUpdateState>,
+    install: () => ipcRenderer.invoke(DESKTOP_IPC.updatesInstall) as Promise<void>,
+    subscribe(listener) {
+      const handle = (_event: Electron.IpcRendererEvent, state: DesktopUpdateState): void => { listener(state) }
+      ipcRenderer.on(DESKTOP_IPC.updatesState, handle)
+      return () => { ipcRenderer.off(DESKTOP_IPC.updatesState, handle) }
+    },
+  },
+}
+
+contextBridge.exposeInMainWorld('dshDesktop', api)

+ 725 - 0
apps/desktop/src/project-manager.ts

@@ -0,0 +1,725 @@
+/** Transactional owner of the reserved desktop profile and its private pnpm state. */
+
+import { spawn } from 'node:child_process'
+import { createHash, randomUUID } from 'node:crypto'
+import {
+  constants,
+  copyFileSync,
+  cpSync,
+  existsSync,
+  fsyncSync,
+  ftruncateSync,
+  lstatSync,
+  mkdirSync,
+  openSync,
+  closeSync,
+  readdirSync,
+  readFileSync,
+  renameSync,
+  rmSync,
+  unlinkSync,
+  writeFileSync,
+  writeSync,
+} from 'node:fs'
+import { basename, delimiter, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path'
+import {
+  DESKTOP_PACKAGES_DIR,
+  DESKTOP_PACKAGE_SET_FILE,
+  DESKTOP_HOST_PACKAGE,
+  desktopCorePackageOverrides,
+  desktopDshPackageSpec,
+  readDesktopCorePackageSet,
+  verifyDesktopCorePackageSet,
+} from './core-package-set.ts'
+import type { DesktopPaths } from './paths.ts'
+import { parseDesktopRelease, type DesktopRelease } from './release.ts'
+import { extractPnpmStoreArchives, mergePnpmStore } from './seed-store.ts'
+
+/** Files the package transaction copies between active and staging projects. */
+const DESKTOP_PROJECT_FILES = [
+  'package.json',
+  'pnpm-lock.yaml',
+  'pnpm-workspace.yaml',
+  'desktop-release.json',
+  DESKTOP_PACKAGE_SET_FILE,
+] as const
+
+/** Desktop plugin record derived from the installed profile. */
+export interface DesktopPluginRecord {
+  readonly name: string
+  readonly version: string
+}
+
+/** Installed desktop project manifest slice. */
+interface DesktopProjectManifest {
+  readonly name: string
+  readonly private: true
+  readonly version: string
+  readonly dependencies: Record<string, string>
+  readonly dsh: {
+    readonly profile: {
+      readonly bundles: string[]
+    }
+  }
+}
+
+/** Journaled activation step used for crash recovery. */
+interface DesktopPendingTransaction {
+  readonly schemaVersion: 1
+  readonly id: string
+  readonly stagingProfile: string
+  readonly step: 'prepared' | 'active-moved' | 'staging-activated'
+}
+
+/** Exact executables the desktop shell bundles. */
+export interface DesktopRuntimeExecutables {
+  readonly node: string
+  readonly pnpm: string
+}
+
+/** Hooks that bind project replacement to backend lifecycle and health. */
+export interface DesktopProjectHooks {
+  /** Prove the staged dependency graph while the active backend is stopped. */
+  healthCheck(projectDir: string): Promise<void>
+  /** Stop the active backend and await process exit before directory moves. */
+  beforeActivate(): Promise<void>
+  /** Start the selected active project after commit or rollback. */
+  afterActivate(): Promise<void>
+}
+
+/** Supported dependency mutation. */
+export type DesktopProjectMutation =
+  | { readonly type: 'plugin-add'; readonly spec: string }
+  | { readonly type: 'plugin-remove'; readonly name: string }
+  | { readonly type: 'plugin-update'; readonly name: string; readonly version: string }
+
+interface DesktopSeedIntegrityRecord {
+  readonly path: string
+  readonly bytes: number
+  readonly sha256: string
+}
+
+const PROJECT_NAME = '@deepseek-ai/dsh-desktop-runtime'
+const DSH_PACKAGE = '@deepseek-ai/dsh'
+const CORE_BUILD_PACKAGE = '@deepseek-ai/dsh-subprocess-local'
+const DESKTOP_PROFILE_BUNDLES = ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'] as const
+const WORKSPACE_SETTINGS = 'nodeLinker: hoisted\nautoInstallPeers: false\nstrictDepBuilds: true\n'
+const PACKAGE_NAME_PATTERN = /^(?:@[a-z0-9][a-z0-9._~-]*\/[a-z0-9][a-z0-9._~-]*|[a-z0-9][a-z0-9._~-]*)$/u
+const VERSION_PATTERN = /^[0-9A-Za-z][0-9A-Za-z.+_-]*$/u
+const MAX_PNPM_DIAGNOSTIC_BYTES = 64 * 1024
+const DESKTOP_REGISTRY = 'https://registry.npmjs.org/'
+
+function errorOf(reason: unknown, fallback: string): Error {
+  return reason instanceof Error ? reason : new Error(fallback)
+}
+
+function writeJson(path: string, value: unknown): void {
+  writeFileSync(path, `${JSON.stringify(value, undefined, 2)}\n`, { mode: 0o600 })
+}
+
+function readJson(path: string): unknown {
+  return JSON.parse(readFileSync(path, 'utf8'))
+}
+
+function workspaceFile(overrides: Readonly<Record<string, string>> = {}): string {
+  const entries = Object.entries(overrides).sort(([left], [right]) => left.localeCompare(right))
+  const overrideSection = entries.length === 0
+    ? ''
+    : `overrides:\n${entries.map(([name, spec]) => `  ${JSON.stringify(name)}: ${JSON.stringify(spec)}`).join('\n')}\n`
+  const coreBuildSpec = overrides[CORE_BUILD_PACKAGE]
+  const coreBuildKey = coreBuildSpec === undefined
+    ? CORE_BUILD_PACKAGE
+    : `${CORE_BUILD_PACKAGE}@${coreBuildSpec.replace('file:./', 'file:')}`
+  return `packages:\n  - .\n\n${overrideSection}${WORKSPACE_SETTINGS}allowBuilds:\n  node-pty: true\n  koffi: true\n  fs-ext: true\n  ${JSON.stringify(coreBuildKey)}: true\n  '@google/genai': false\n  protobufjs: false\n  node-addon-require-builtin: false\n`
+}
+
+function releaseFile(projectDir: string): DesktopRelease {
+  return parseDesktopRelease(readJson(join(projectDir, 'desktop-release.json')))
+}
+
+function isRecord(value: unknown): value is Record<string, unknown> {
+  return typeof value === 'object' && value !== null
+}
+
+function isDescendant(root: string, target: string): boolean {
+  const child = relative(root, target)
+  return child !== '' && child !== '..' && !child.startsWith(`..${sep}`) && !isAbsolute(child)
+}
+
+function assertPackageName(name: string): void {
+  if (!PACKAGE_NAME_PATTERN.test(name)) throw new Error(`desktop project: invalid npm package name ${JSON.stringify(name)}`)
+}
+
+function assertVersion(version: string): void {
+  if (!VERSION_PATTERN.test(version)) throw new Error(`desktop project: invalid exact version ${JSON.stringify(version)}`)
+}
+
+/**
+ * Validate one registry package spec and return its requested package name when explicit.
+ * @param spec - npm registry name with an optional version or tag.
+ * @returns package name, or undefined when the spec's final name is registry-resolved.
+ */
+export function packageNameFromSpec(spec: string): string | undefined {
+  if (spec === '' || spec.startsWith('-') || /[\s\\]/u.test(spec) || spec.includes('://') || spec.startsWith('file:')) {
+    throw new Error(`desktop project: unsupported npm package spec ${JSON.stringify(spec)}`)
+  }
+  if (spec.startsWith('@')) {
+    const slash = spec.indexOf('/')
+    if (slash === -1) throw new Error(`desktop project: invalid scoped package spec ${JSON.stringify(spec)}`)
+    const versionAt = spec.indexOf('@', slash)
+    const name = versionAt === -1 ? spec : spec.slice(0, versionAt)
+    assertPackageName(name)
+    if (versionAt !== -1) assertVersion(spec.slice(versionAt + 1))
+    return name
+  }
+  const versionAt = spec.indexOf('@')
+  const name = versionAt === -1 ? spec : spec.slice(0, versionAt)
+  assertPackageName(name)
+  if (versionAt !== -1) assertVersion(spec.slice(versionAt + 1))
+  return name
+}
+
+function removeOwnedDirectory(path: string): void {
+  if (!existsSync(path)) return
+  const stat = lstatSync(path)
+  if (stat.isSymbolicLink()) {
+    unlinkSync(path)
+    return
+  }
+  if (!stat.isDirectory()) throw new Error(`desktop project: owned directory path is not a directory: ${path}`)
+  rmSync(path, { recursive: true })
+}
+
+function copyMetadata(source: string, target: string): void {
+  mkdirSync(target, { recursive: true, mode: 0o700 })
+  for (const filename of DESKTOP_PROJECT_FILES) {
+    const from = join(source, filename)
+    if (existsSync(from)) copyFileSync(from, join(target, filename), constants.COPYFILE_EXCL)
+  }
+  cpSync(join(source, DESKTOP_PACKAGES_DIR), join(target, DESKTOP_PACKAGES_DIR), {
+    recursive: true,
+    force: false,
+    errorOnExist: true,
+  })
+}
+
+function seedFiles(root: string): readonly DesktopSeedIntegrityRecord[] {
+  const files: DesktopSeedIntegrityRecord[] = []
+  const visit = (directory: string): void => {
+    for (const entry of readdirSync(directory, { withFileTypes: true })) {
+      const path = join(directory, entry.name)
+      const relativePath = path.slice(root.length + 1).split(sep).join('/')
+      if (relativePath === 'integrity.json') continue
+      if (entry.isSymbolicLink()) throw new Error(`desktop seed: symbolic link is not allowed: ${relativePath}`)
+      if (entry.isDirectory()) {
+        visit(path)
+        continue
+      }
+      if (!entry.isFile()) throw new Error(`desktop seed: unsupported file type: ${relativePath}`)
+      const body = readFileSync(path)
+      files.push({
+        path: relativePath,
+        bytes: body.byteLength,
+        sha256: createHash('sha256').update(body).digest('hex'),
+      })
+    }
+  }
+  visit(root)
+  return files.sort((left, right) => left.path.localeCompare(right.path))
+}
+
+/** Verify the packaged offline seed before any content enters writable desktop state. */
+export function verifySeedIntegrity(seedDir: string): void {
+  const integrityPath = join(seedDir, 'integrity.json')
+  const integrity = readJson(integrityPath)
+  if (!isRecord(integrity) || integrity.schemaVersion !== 2 || !Array.isArray(integrity.files)) {
+    throw new Error(`desktop seed: invalid integrity inventory ${integrityPath}`)
+  }
+  const expected: DesktopSeedIntegrityRecord[] = integrity.files.map((record) => {
+    if (!isRecord(record) || typeof record.path !== 'string' || record.path === '' || record.path.startsWith('/')
+      || record.path.split('/').includes('..') || typeof record.bytes !== 'number'
+      || !Number.isSafeInteger(record.bytes) || record.bytes < 0
+      || typeof record.sha256 !== 'string' || !/^[a-f0-9]{64}$/u.test(record.sha256)) {
+      throw new Error(`desktop seed: invalid integrity record in ${integrityPath}`)
+    }
+    return { path: record.path, bytes: record.bytes, sha256: record.sha256 }
+  }).sort((left, right) => left.path.localeCompare(right.path))
+  const actual = seedFiles(seedDir)
+  if (JSON.stringify(actual) !== JSON.stringify(expected)) {
+    throw new Error('desktop seed: integrity verification failed')
+  }
+}
+
+function projectManifest(projectDir: string): DesktopProjectManifest {
+  const path = join(projectDir, 'package.json')
+  const value = readJson(path)
+  const dsh = isRecord(value) && isRecord(value.dsh) ? value.dsh : undefined
+  const profile = isRecord(dsh?.profile) ? dsh.profile : undefined
+  if (!isRecord(value) || value.name !== PROJECT_NAME || value.private !== true
+    || typeof value.version !== 'string' || !isRecord(value.dependencies)
+    || !Array.isArray(profile?.bundles) || !profile.bundles.every(bundle => typeof bundle === 'string')) {
+    throw new Error(`desktop project: invalid desktop profile manifest ${path}`)
+  }
+  const manifest = value as unknown as DesktopProjectManifest
+  const packageSet = readDesktopCorePackageSet(projectDir, releaseFile(projectDir).version)
+  const expectedOverrides = desktopCorePackageOverrides(packageSet)
+  if (manifest.dependencies[DSH_PACKAGE] !== desktopDshPackageSpec(packageSet)
+    || Object.entries(expectedOverrides).some(([name, spec]) => manifest.dependencies[name] !== spec)
+    || readFileSync(join(projectDir, 'pnpm-workspace.yaml'), 'utf8') !== workspaceFile(expectedOverrides)) {
+    throw new Error(`desktop project: core package mapping does not match ${DESKTOP_PACKAGE_SET_FILE}`)
+  }
+  return manifest
+}
+
+function profilePluginNames(projectDir: string): readonly string[] {
+  const bundles = projectManifest(projectDir).dsh.profile.bundles
+  if (!DESKTOP_PROFILE_BUNDLES.every((bundle, index) => bundles[index] === bundle)) {
+    throw new Error('desktop project: profile must begin with the built-in desktop bundle list')
+  }
+  const plugins = bundles.slice(DESKTOP_PROFILE_BUNDLES.length)
+  if (new Set(bundles).size !== bundles.length) {
+    throw new Error('desktop project: profile bundle list contains a duplicate package')
+  }
+  for (const plugin of plugins) assertPackageName(plugin)
+  return plugins
+}
+
+function pluginRecords(projectDir: string): readonly DesktopPluginRecord[] {
+  return profilePluginNames(projectDir).map(name => inspectPlugin(projectDir, name))
+}
+
+function writeProfilePlugins(projectDir: string, plugins: readonly DesktopPluginRecord[]): void {
+  const manifest = projectManifest(projectDir)
+  writeJson(join(projectDir, 'package.json'), {
+    ...manifest,
+    dsh: {
+      ...manifest.dsh,
+      profile: {
+        ...manifest.dsh.profile,
+        bundles: [...DESKTOP_PROFILE_BUNDLES, ...plugins.map(plugin => plugin.name)],
+      },
+    },
+  } satisfies DesktopProjectManifest)
+}
+
+function inspectPlugin(projectDir: string, requestedName: string): DesktopPluginRecord {
+  const manifestPath = join(projectDir, 'node_modules', ...requestedName.split('/'), 'package.json')
+  if (!existsSync(manifestPath)) {
+    throw new Error(`desktop project: installed package ${JSON.stringify(requestedName)} has no manifest`)
+  }
+  const manifest = readJson(manifestPath)
+  if (!isRecord(manifest) || manifest.name !== requestedName || typeof manifest.version !== 'string') {
+    throw new Error(`desktop project: installed package ${JSON.stringify(requestedName)} has inconsistent name or version`)
+  }
+  const dsh = manifest.dsh
+  const bundle = isRecord(dsh) ? dsh.bundle : undefined
+  const patch = isRecord(bundle) ? bundle.patch : undefined
+  if (typeof patch !== 'string' || patch === '') {
+    throw new Error(`desktop project: ${requestedName}@${manifest.version} does not declare dsh.bundle.patch`)
+  }
+  const packageDir = dirname(manifestPath)
+  const patchPath = resolve(packageDir, patch)
+  if ((patchPath !== packageDir && !patchPath.startsWith(packageDir + sep)) || !existsSync(patchPath)) {
+    throw new Error(`desktop project: ${requestedName}@${manifest.version} declares an invalid bundle patch`)
+  }
+  return { name: requestedName, version: manifest.version }
+}
+
+/** Transactional desktop npm project manager. */
+export class DesktopProjectManager {
+  private lockDescriptor: number | undefined
+
+  /**
+   * @param paths - Electron-owned package state and reserved desktop profile paths.
+   * @param runtime - absolute bundled Node.js and pnpm entry paths.
+   */
+  constructor(
+    readonly paths: DesktopPaths,
+    readonly runtime: DesktopRuntimeExecutables,
+  ) {}
+
+  /** Recover an interrupted directory replacement before reading the active project. */
+  recover(): void {
+    if (!existsSync(this.paths.pending)) return
+    const value = readJson(this.paths.pending)
+    if (!isRecord(value) || value.schemaVersion !== 1
+      || typeof value.id !== 'string' || typeof value.stagingProfile !== 'string'
+      || !isDescendant(this.paths.staging, value.stagingProfile)
+      || (value.step !== 'prepared' && value.step !== 'active-moved' && value.step !== 'staging-activated')) {
+      throw new Error(`desktop project: invalid activation journal ${this.paths.pending}`)
+    }
+    const pending: DesktopPendingTransaction = {
+      schemaVersion: 1,
+      id: value.id,
+      stagingProfile: value.stagingProfile,
+      step: value.step,
+    }
+    if (!existsSync(this.paths.profile) && existsSync(this.paths.rollback)) {
+      mkdirSync(dirname(this.paths.profile), { recursive: true })
+      renameSync(this.paths.rollback, this.paths.profile)
+    }
+    removeOwnedDirectory(pending.stagingProfile)
+    unlinkSync(this.paths.pending)
+  }
+
+  /** Read the active desktop plugin inventory. */
+  listPlugins(): readonly DesktopPluginRecord[] {
+    if (!existsSync(this.paths.profile)) return []
+    return pluginRecords(this.paths.profile)
+  }
+
+  /** Read the exact dsh version installed in the active desktop project. */
+  dshVersion(): string {
+    if (!existsSync(this.paths.profile)) throw new Error('desktop project: active profile is not installed')
+    return this.installedPackageVersion(DSH_PACKAGE)
+  }
+
+  private installedPackageVersion(packageName: string): string {
+    const manifestPath = join(this.paths.profile, 'node_modules', ...packageName.split('/'), 'package.json')
+    const manifest = readJson(manifestPath)
+    if (!isRecord(manifest) || typeof manifest.version !== 'string') {
+      throw new Error(`desktop project: installed ${packageName} package has no version`)
+    }
+    assertVersion(manifest.version)
+    return manifest.version
+  }
+
+  /** Read the release version applied to the active desktop project. */
+  releaseVersion(): string {
+    if (!existsSync(this.paths.profile)) throw new Error('desktop project: active profile is not installed')
+    return releaseFile(this.paths.profile).version
+  }
+
+  /** Install or reconcile the active project to the Electron package's exact release. */
+  async applyRelease(seedDir: string, electronVersion: string, hooks: DesktopProjectHooks): Promise<boolean> {
+    return this.withLock(async () => {
+      this.recover()
+      verifySeedIntegrity(seedDir)
+      const target = releaseFile(seedDir)
+      verifyDesktopCorePackageSet(seedDir, target.version)
+      if (target.version !== electronVersion) {
+        throw new Error(`desktop project: seed ${target.version} does not match Electron ${electronVersion}`)
+      }
+      if (existsSync(this.paths.profile) && this.releaseVersion() === target.version
+        && this.dshVersion() === target.version
+        && this.installedPackageVersion(DESKTOP_HOST_PACKAGE) === target.version) {
+        verifyDesktopCorePackageSet(this.paths.profile, target.version)
+        return false
+      }
+      this.mergeSeedPnpmState(seedDir)
+      const stagingProfile = this.newStagingProfile()
+      try {
+        if (existsSync(this.paths.profile)) {
+          const plugins = pluginRecords(this.paths.profile)
+          copyMetadata(seedDir, stagingProfile)
+          await this.runPnpm(stagingProfile, ['install', '--offline', '--frozen-lockfile', '--trust-lockfile'])
+          if (plugins.length > 0) {
+            await this.runPnpm(stagingProfile, [
+              'add',
+              ...plugins.map(plugin => `${plugin.name}@${plugin.version}`),
+              '--save-exact',
+              '--offline',
+            ])
+            writeProfilePlugins(stagingProfile, plugins)
+          }
+        } else {
+          copyMetadata(seedDir, stagingProfile)
+          await this.runPnpm(stagingProfile, ['install', '--offline', '--frozen-lockfile', '--trust-lockfile'])
+        }
+        await hooks.healthCheck(stagingProfile)
+        await this.activate(stagingProfile, hooks)
+        return true
+      } catch (error) {
+        removeOwnedDirectory(stagingProfile)
+        throw error
+      }
+    })
+  }
+
+  /** Apply one exact dependency mutation through a staging project. */
+  async mutate(mutation: DesktopProjectMutation, hooks: DesktopProjectHooks): Promise<void> {
+    await this.withLock(async () => {
+      this.recover()
+      if (!existsSync(this.paths.profile)) throw new Error('desktop project: active profile is not installed')
+      verifyDesktopCorePackageSet(this.paths.profile, this.releaseVersion())
+      const stagingProfile = this.newStagingProfile()
+      try {
+        copyMetadata(this.paths.profile, stagingProfile)
+        await this.applyMutation(stagingProfile, mutation)
+        await hooks.healthCheck(stagingProfile)
+        await this.activate(stagingProfile, hooks)
+      } catch (error) {
+        removeOwnedDirectory(stagingProfile)
+        throw error
+      }
+    })
+  }
+
+  private newStagingProfile(): string {
+    const path = join(this.paths.staging, randomUUID(), 'profile')
+    mkdirSync(path, { recursive: true, mode: 0o700 })
+    return path
+  }
+
+  private async applyMutation(projectDir: string, mutation: DesktopProjectMutation): Promise<void> {
+    switch (mutation.type) {
+      case 'plugin-add': {
+        const requestedName = packageNameFromSpec(mutation.spec)
+        if (requestedName === undefined) throw new Error('desktop project: plugin package name is required')
+        await this.runPnpm(projectDir, ['add', mutation.spec, '--save-exact'])
+        const installed = inspectPlugin(projectDir, requestedName)
+        const current = pluginRecords(projectDir).filter(plugin => plugin.name !== installed.name)
+        writeProfilePlugins(
+          projectDir,
+          [...current, installed].sort((left, right) => left.name.localeCompare(right.name)),
+        )
+        return
+      }
+      case 'plugin-remove': {
+        assertPackageName(mutation.name)
+        if (!profilePluginNames(projectDir).includes(mutation.name)) {
+          throw new Error(`desktop project: plugin ${JSON.stringify(mutation.name)} is not installed`)
+        }
+        const remaining = pluginRecords(projectDir).filter(plugin => plugin.name !== mutation.name)
+        await this.runPnpm(projectDir, ['remove', mutation.name])
+        writeProfilePlugins(projectDir, remaining)
+        return
+      }
+      case 'plugin-update':
+        assertPackageName(mutation.name)
+        assertVersion(mutation.version)
+        if (!profilePluginNames(projectDir).includes(mutation.name)) {
+          throw new Error(`desktop project: plugin ${JSON.stringify(mutation.name)} is not installed`)
+        }
+        await this.runPnpm(projectDir, ['add', `${mutation.name}@${mutation.version}`, '--save-exact'])
+        {
+          const installed = inspectPlugin(projectDir, mutation.name)
+          writeProfilePlugins(
+            projectDir,
+            pluginRecords(projectDir).map(plugin => plugin.name === installed.name ? installed : plugin),
+          )
+        }
+        return
+      default:
+        mutation satisfies never
+    }
+  }
+
+  private mergeSeedPnpmState(seedDir: string): void {
+    const transactionRoot = join(this.paths.staging, randomUUID())
+    const extractedStore = join(transactionRoot, 'store')
+    try {
+      extractPnpmStoreArchives(seedDir, extractedStore)
+      mergePnpmStore(extractedStore, this.paths.pnpm.store)
+    } finally {
+      removeOwnedDirectory(transactionRoot)
+    }
+  }
+
+  private async activate(stagingProfile: string, hooks: DesktopProjectHooks): Promise<void> {
+    const pending: DesktopPendingTransaction = {
+      schemaVersion: 1,
+      id: basename(dirname(stagingProfile)),
+      stagingProfile,
+      step: 'prepared',
+    }
+    writeJson(this.paths.pending, pending)
+    await hooks.beforeActivate()
+    let activeMoved = false
+    try {
+      removeOwnedDirectory(this.paths.rollback)
+      mkdirSync(dirname(this.paths.rollback), { recursive: true, mode: 0o700 })
+      writeJson(this.paths.pending, { ...pending, step: 'active-moved' } satisfies DesktopPendingTransaction)
+      if (existsSync(this.paths.profile)) {
+        renameSync(this.paths.profile, this.paths.rollback)
+        activeMoved = true
+      }
+      mkdirSync(dirname(this.paths.profile), { recursive: true, mode: 0o700 })
+      writeJson(this.paths.pending, { ...pending, step: 'staging-activated' } satisfies DesktopPendingTransaction)
+      renameSync(stagingProfile, this.paths.profile)
+      await hooks.afterActivate()
+      unlinkSync(this.paths.pending)
+    } catch (error) {
+      if (existsSync(this.paths.profile)) removeOwnedDirectory(this.paths.profile)
+      if (activeMoved && existsSync(this.paths.rollback)) renameSync(this.paths.rollback, this.paths.profile)
+      if (existsSync(this.paths.pending)) unlinkSync(this.paths.pending)
+      await hooks.afterActivate().catch(() => undefined)
+      throw error
+    }
+  }
+
+  private async runPnpm(projectDir: string, args: readonly string[]): Promise<void> {
+    const [command, ...commandArgs] = args
+    if (command === undefined) throw new Error('desktop project: pnpm command is required')
+    for (const path of [this.paths.root, this.paths.pnpm.store, this.paths.pnpm.cache,
+      this.paths.pnpm.state, this.paths.pnpm.config, this.paths.pnpm.home]) {
+      mkdirSync(path, { recursive: true, mode: 0o700 })
+    }
+    const npmrc = join(this.paths.pnpm.config, 'npmrc')
+    if (!existsSync(npmrc)) writeFileSync(npmrc, '', { mode: 0o600 })
+    const inherited = Object.fromEntries(Object.entries(process.env).filter(([name]) => (
+      !/^DSH_DESKTOP_/u.test(name) && !/^(?:npm|pnpm|corepack)_/iu.test(name)
+    )))
+    await new Promise<void>((settle, reject) => {
+      const child = spawn(this.runtime.node, [
+        this.runtime.pnpm,
+        `--config.registry=${DESKTOP_REGISTRY}`,
+        `--config.store-dir=${this.paths.pnpm.store}`,
+        '--config.enable-global-virtual-store=false',
+        `--config.userconfig=${npmrc}`,
+        command,
+        ...commandArgs,
+      ], {
+        cwd: projectDir,
+        env: {
+          ...inherited,
+          COREPACK_HOME: this.paths.pnpm.home,
+          NPM_CONFIG_REGISTRY: DESKTOP_REGISTRY,
+          NPM_CONFIG_STORE_DIR: this.paths.pnpm.store,
+          NPM_CONFIG_USERCONFIG: npmrc,
+          PATH: `${dirname(this.runtime.node)}${delimiter}${process.env.PATH ?? ''}`,
+          PNPM_HOME: this.paths.pnpm.home,
+          XDG_CACHE_HOME: this.paths.pnpm.cache,
+          XDG_CONFIG_HOME: this.paths.pnpm.config,
+          XDG_STATE_HOME: this.paths.pnpm.state,
+        },
+        stdio: ['ignore', 'pipe', 'pipe'],
+      })
+      const childPid = child.pid
+      if (childPid === undefined) {
+        child.kill('SIGKILL')
+        reject(new Error('desktop project: pnpm did not report a process id'))
+        return
+      }
+      try {
+        this.writeLockOwner(childPid)
+      } catch (error) {
+        child.kill('SIGKILL')
+        reject(errorOf(error, 'desktop project: failed to assign the package transaction lock to pnpm'))
+        return
+      }
+      let diagnostics = ''
+      let completed = false
+      const appendDiagnostics = (chunk: string): void => {
+        diagnostics = (diagnostics + chunk).slice(-MAX_PNPM_DIAGNOSTIC_BYTES)
+      }
+      child.stdout.setEncoding('utf8')
+      child.stdout.on('data', appendDiagnostics)
+      child.stderr.setEncoding('utf8')
+      child.stderr.on('data', appendDiagnostics)
+      const complete = (settleChild: () => void): void => {
+        if (completed) return
+        completed = true
+        try {
+          this.writeLockOwner(process.pid)
+        } catch (error) {
+          reject(errorOf(error, 'desktop project: failed to return the package transaction lock to Electron'))
+          return
+        }
+        settleChild()
+      }
+      child.once('error', (error) => { complete(() => { reject(error) }) })
+      child.once('close', (code, signal) => {
+        complete(() => {
+          if (code === 0) {
+            settle()
+            return
+          }
+          reject(new Error(
+            `desktop project: pnpm exited with ${String(code ?? signal)}${diagnostics.trim() === '' ? '' : `: ${diagnostics.trim()}`}`,
+          ))
+        })
+      })
+    })
+  }
+
+  private writeLockOwner(pid: number): void {
+    const descriptor = this.lockDescriptor
+    if (descriptor === undefined) throw new Error('desktop project: package transaction lost its lock')
+    const content = Buffer.from(`${String(pid)}\n`)
+    ftruncateSync(descriptor, 0)
+    writeSync(descriptor, content, 0, content.byteLength, 0)
+    fsyncSync(descriptor)
+  }
+
+  private async withLock<T>(operation: () => Promise<T>): Promise<T> {
+    mkdirSync(this.paths.root, { recursive: true, mode: 0o700 })
+    let descriptor: number
+    try {
+      descriptor = openSync(this.paths.lock, 'wx', 0o600)
+    } catch (error) {
+      if ((error as NodeJS.ErrnoException).code === 'EEXIST') {
+        const lock = lstatSync(this.paths.lock)
+        if (lock.isSymbolicLink() || !lock.isFile()) {
+          throw new Error('desktop project: package transaction lock is not a regular file')
+        }
+        const owner = Number.parseInt(readFileSync(this.paths.lock, 'utf8').trim(), 10)
+        let active = !Number.isSafeInteger(owner) || owner <= 0
+        if (!active) {
+          try {
+            process.kill(owner, 0)
+            active = true
+          } catch (signalError) {
+            active = (signalError as NodeJS.ErrnoException).code !== 'ESRCH'
+          }
+        }
+        if (active) throw new Error('desktop project: another package transaction is active')
+        unlinkSync(this.paths.lock)
+        descriptor = openSync(this.paths.lock, 'wx', 0o600)
+      } else {
+        throw error
+      }
+    }
+    try {
+      this.lockDescriptor = descriptor
+      this.writeLockOwner(process.pid)
+      return await operation()
+    } finally {
+      this.lockDescriptor = undefined
+      closeSync(descriptor)
+      unlinkSync(this.paths.lock)
+    }
+  }
+}
+
+/** Create seed metadata for one exact Electron and dsh release. */
+export function createSeedMetadata(seedDir: string, release: DesktopRelease): void {
+  mkdirSync(seedDir, { recursive: true, mode: 0o700 })
+  const packageSet = verifyDesktopCorePackageSet(seedDir, release.version)
+  const manifest: DesktopProjectManifest = {
+    name: PROJECT_NAME,
+    private: true,
+    version: '0.0.0',
+    dependencies: desktopCorePackageOverrides(packageSet),
+    dsh: { profile: { bundles: [...DESKTOP_PROFILE_BUNDLES] } },
+  }
+  writeJson(join(seedDir, 'package.json'), manifest)
+  writeFileSync(
+    join(seedDir, 'pnpm-workspace.yaml'),
+    workspaceFile(desktopCorePackageOverrides(packageSet)),
+    { mode: 0o600 },
+  )
+  writeJson(join(seedDir, 'desktop-release.json'), release)
+}
+
+/**
+ * Create metadata for the unpackaged development project that links the current workspace.
+ * @param projectDir - Disposable development profile directory.
+ * @param release - Release identity shared by the linked CLI package and Electron shell.
+ */
+export function createDevelopmentProjectMetadata(projectDir: string, release: DesktopRelease): void {
+  mkdirSync(projectDir, { recursive: true, mode: 0o700 })
+  const manifest = {
+    name: PROJECT_NAME,
+    private: true,
+    version: '0.0.0',
+    dependencies: {
+      [DSH_PACKAGE]: release.version,
+      [DESKTOP_HOST_PACKAGE]: release.version,
+    },
+    dsh: { profile: { bundles: [...DESKTOP_PROFILE_BUNDLES] } },
+  }
+  writeJson(join(projectDir, 'package.json'), manifest)
+  writeFileSync(join(projectDir, 'pnpm-workspace.yaml'), workspaceFile(), { mode: 0o600 })
+  writeJson(join(projectDir, 'desktop-release.json'), release)
+}

+ 35 - 0
apps/desktop/src/release.ts

@@ -0,0 +1,35 @@
+/** Immutable version identity shared by one Electron shell and its dsh seed. */
+
+import { valid } from 'semver'
+import { DESKTOP_HOST_PROTOCOL_VERSION } from './host-protocol.ts'
+
+/** Release facts embedded in the seed and copied into the active desktop project. */
+export interface DesktopRelease {
+  readonly schemaVersion: 1
+  /** Exact version used by both Electron and `@deepseek-ai/dsh`. */
+  readonly version: string
+  readonly hostProtocolVersion: typeof DESKTOP_HOST_PROTOCOL_VERSION
+  readonly nodeVersion: string
+  readonly pnpmVersion: string
+}
+
+function isRecord(value: unknown): value is Record<string, unknown> {
+  return typeof value === 'object' && value !== null
+}
+
+/** Validate release data read from an installed or packaged filesystem resource. */
+export function parseDesktopRelease(value: unknown): DesktopRelease {
+  if (!isRecord(value) || value.schemaVersion !== 1 || typeof value.version !== 'string'
+    || valid(value.version) === null || value.hostProtocolVersion !== DESKTOP_HOST_PROTOCOL_VERSION
+    || typeof value.nodeVersion !== 'string' || valid(value.nodeVersion) === null
+    || typeof value.pnpmVersion !== 'string' || valid(value.pnpmVersion) === null) {
+    throw new Error('dsh desktop: invalid desktop release metadata')
+  }
+  return {
+    schemaVersion: 1,
+    version: value.version,
+    hostProtocolVersion: DESKTOP_HOST_PROTOCOL_VERSION,
+    nodeVersion: value.nodeVersion,
+    pnpmVersion: value.pnpmVersion,
+  }
+}

+ 271 - 0
apps/desktop/src/seed-store.ts

@@ -0,0 +1,271 @@
+/** Deterministic archive transport for the desktop seed's pnpm store. */
+
+import { createHash } from 'node:crypto'
+import {
+  chmodSync,
+  copyFileSync,
+  cpSync,
+  existsSync,
+  mkdirSync,
+  readdirSync,
+  readFileSync,
+  rmSync,
+  writeFileSync,
+} from 'node:fs'
+import { join, relative, sep } from 'node:path'
+import { DatabaseSync } from 'node:sqlite'
+import { create, extract, list } from 'tar'
+
+/** Directory containing the seed's uncompressed pnpm store archives. */
+export const SEED_STORE_ARCHIVE_DIR = 'store-archives'
+
+/** Manifest describing the deterministic pnpm store archive set. */
+export const SEED_STORE_ARCHIVE_MANIFEST = 'store-archives.json'
+
+const DEFAULT_SHARD_COUNT = 16
+const ARCHIVE_NAME_PATTERN = /^store-[0-9a-f]{2}\.tar$/u
+const STORE_VERSION_PATTERN = /^v\d+$/u
+
+interface SeedStoreArchiveRecord {
+  readonly file: string
+  readonly entries: number
+}
+
+interface SeedStoreArchiveManifest {
+  readonly schemaVersion: 1
+  readonly shardCount: number
+  readonly archives: readonly SeedStoreArchiveRecord[]
+}
+
+function storeFiles(storeRoot: string): readonly string[] {
+  const files: string[] = []
+  const visit = (directory: string): void => {
+    for (const entry of readdirSync(directory, { withFileTypes: true })) {
+      const path = join(directory, entry.name)
+      if (entry.isSymbolicLink()) {
+        throw new Error(`desktop seed: pnpm store contains a symbolic link: ${relative(storeRoot, path)}`)
+      }
+      if (entry.isDirectory()) {
+        visit(path)
+        continue
+      }
+      if (!entry.isFile()) {
+        throw new Error(`desktop seed: pnpm store contains an unsupported file: ${relative(storeRoot, path)}`)
+      }
+      files.push(relative(storeRoot, path).split(sep).join('/'))
+    }
+  }
+  visit(storeRoot)
+  return files.sort((left, right) => left.localeCompare(right))
+}
+
+function shardFor(path: string, shardCount: number): number {
+  return createHash('sha256').update(path).digest().readUInt32BE(0) % shardCount
+}
+
+function readArchiveManifest(seedRoot: string): SeedStoreArchiveManifest {
+  const path = join(seedRoot, SEED_STORE_ARCHIVE_MANIFEST)
+  const value = JSON.parse(readFileSync(path, 'utf8')) as unknown
+  if (typeof value !== 'object' || value === null) {
+    throw new Error(`desktop seed: invalid pnpm store archive manifest ${path}`)
+  }
+  const candidate = value as Record<string, unknown>
+  if (candidate.schemaVersion !== 1 || !Number.isSafeInteger(candidate.shardCount)
+    || (candidate.shardCount as number) < 1 || (candidate.shardCount as number) > 256
+    || !Array.isArray(candidate.archives) || candidate.archives.length === 0) {
+    throw new Error(`desktop seed: invalid pnpm store archive manifest ${path}`)
+  }
+  const names = new Set<string>()
+  const archives = candidate.archives.map((entry): SeedStoreArchiveRecord => {
+    if (typeof entry !== 'object' || entry === null) {
+      throw new Error(`desktop seed: invalid pnpm store archive record in ${path}`)
+    }
+    const record = entry as Record<string, unknown>
+    if (typeof record.file !== 'string' || !ARCHIVE_NAME_PATTERN.test(record.file)
+      || names.has(record.file) || !Number.isSafeInteger(record.entries) || (record.entries as number) < 1) {
+      throw new Error(`desktop seed: invalid pnpm store archive record in ${path}`)
+    }
+    const shard = Number.parseInt(record.file.slice('store-'.length, -'.tar'.length), 16)
+    if (shard >= (candidate.shardCount as number)) {
+      throw new Error(`desktop seed: pnpm store archive shard is outside the manifest range in ${path}`)
+    }
+    names.add(record.file)
+    return { file: record.file, entries: record.entries as number }
+  })
+  return {
+    schemaVersion: 1,
+    shardCount: candidate.shardCount as number,
+    archives,
+  }
+}
+
+function assertArchivePath(path: string): void {
+  if (path === '' || path.startsWith('/') || path.includes('\\') || path.includes('\0')
+    || path.split('/').some(part => part === '' || part === '.' || part === '..')) {
+    throw new Error(`desktop seed: unsafe pnpm store archive path ${JSON.stringify(path)}`)
+  }
+}
+
+/**
+ * Remove pnpm's registrations for projects that populated the seed store.
+ * @param storeRoot - pnpm store directory included in the desktop seed.
+ */
+export function removePnpmProjectRegistrations(storeRoot: string): void {
+  for (const entry of readdirSync(storeRoot, { withFileTypes: true })) {
+    if (!entry.isDirectory() || !/^v\d+$/u.test(entry.name)) continue
+    rmSync(join(storeRoot, entry.name, 'projects'), { recursive: true, force: true })
+  }
+}
+
+function mergeStoreIndex(source: string, destination: string): void {
+  if (!existsSync(destination)) {
+    copyFileSync(source, destination)
+    return
+  }
+  const database = new DatabaseSync(destination)
+  let attached = false
+  try {
+    database.exec('PRAGMA busy_timeout=5000')
+    database.prepare('ATTACH DATABASE ? AS seed').run(source)
+    attached = true
+    database.exec('BEGIN IMMEDIATE')
+    let committed = false
+    try {
+      database.exec('INSERT OR REPLACE INTO package_index (key, data) SELECT key, data FROM seed.package_index')
+      database.exec('COMMIT')
+      committed = true
+    } finally {
+      if (!committed) database.exec('ROLLBACK')
+    }
+  } finally {
+    if (attached) database.exec('DETACH DATABASE seed')
+    database.close()
+  }
+}
+
+/**
+ * Merge a completely extracted seed store into Desktop's persistent pnpm store.
+ * @param source - Verified temporary store extraction.
+ * @param destination - Desktop-owned persistent pnpm store.
+ */
+export function mergePnpmStore(source: string, destination: string): void {
+  mkdirSync(destination, { recursive: true, mode: 0o700 })
+  const indexPaths = readdirSync(source, { withFileTypes: true })
+    .filter(entry => entry.isDirectory() && STORE_VERSION_PATTERN.test(entry.name)
+      && existsSync(join(source, entry.name, 'index.db')))
+    .map(entry => `${entry.name}/index.db`)
+  const indexes = new Set(indexPaths)
+  cpSync(source, destination, {
+    recursive: true,
+    force: true,
+    filter: path => !indexes.has(relative(source, path).split(sep).join('/')),
+  })
+  for (const path of indexPaths) {
+    mergeStoreIndex(join(source, ...path.split('/')), join(destination, ...path.split('/')))
+  }
+}
+
+/**
+ * Replace a prepared loose pnpm store with deterministic uncompressed archive shards.
+ * @param seedRoot - seed directory that owns the archive output.
+ * @param storeRoot - populated pnpm store to archive and remove after success.
+ * @param shardCount - stable shard count used to limit update churn.
+ */
+export function archivePnpmStore(
+  seedRoot: string,
+  storeRoot: string,
+  shardCount = DEFAULT_SHARD_COUNT,
+): void {
+  if (!Number.isSafeInteger(shardCount) || shardCount < 1 || shardCount > 256) {
+    throw new Error(`desktop seed: invalid pnpm store shard count ${shardCount}`)
+  }
+  const archiveRoot = join(seedRoot, SEED_STORE_ARCHIVE_DIR)
+  const manifestPath = join(seedRoot, SEED_STORE_ARCHIVE_MANIFEST)
+  rmSync(archiveRoot, { recursive: true, force: true })
+  rmSync(manifestPath, { force: true })
+  mkdirSync(archiveRoot, { recursive: true })
+  const shards = Array.from({ length: shardCount }, (): string[] => [])
+  for (const path of storeFiles(storeRoot)) (shards[shardFor(path, shardCount)] as string[]).push(path)
+  const archives: SeedStoreArchiveRecord[] = []
+  for (const [index, paths] of shards.entries()) {
+    if (paths.length === 0) continue
+    const file = `store-${index.toString(16).padStart(2, '0')}.tar`
+    create({
+      cwd: storeRoot,
+      file: join(archiveRoot, file),
+      noDirRecurse: true,
+      noMtime: true,
+      portable: true,
+      sync: true,
+    }, paths)
+    chmodSync(join(archiveRoot, file), 0o644)
+    archives.push({ file, entries: paths.length })
+  }
+  if (archives.length === 0) throw new Error('desktop seed: pnpm store is empty')
+  writeFileSync(manifestPath, `${JSON.stringify({ schemaVersion: 1, shardCount, archives }, undefined, 2)}\n`)
+  rmSync(storeRoot, { recursive: true })
+}
+
+/**
+ * Validate and extract a packaged pnpm store archive set into an empty directory.
+ * @param seedRoot - verified packaged seed directory.
+ * @param destination - empty Desktop-owned temporary extraction directory.
+ */
+export function extractPnpmStoreArchives(seedRoot: string, destination: string): void {
+  const manifest = readArchiveManifest(seedRoot)
+  const archiveRoot = join(seedRoot, SEED_STORE_ARCHIVE_DIR)
+  const actualFiles = readdirSync(archiveRoot, { withFileTypes: true }).map((entry) => {
+    if (!entry.isFile() || entry.isSymbolicLink()) {
+      throw new Error(`desktop seed: invalid pnpm store archive entry ${entry.name}`)
+    }
+    return entry.name
+  }).sort()
+  const expectedFiles = manifest.archives.map(archive => archive.file).sort()
+  if (JSON.stringify(actualFiles) !== JSON.stringify(expectedFiles)) {
+    throw new Error('desktop seed: pnpm store archive set does not match its manifest')
+  }
+  if (existsSync(destination) && readdirSync(destination).length !== 0) {
+    throw new Error(`desktop seed: pnpm store extraction directory is not empty: ${destination}`)
+  }
+  mkdirSync(destination, { recursive: true, mode: 0o700 })
+  const paths = new Set<string>()
+  for (const archive of manifest.archives) {
+    const archivePath = join(archiveRoot, archive.file)
+    const archiveShard = Number.parseInt(archive.file.slice('store-'.length, -'.tar'.length), 16)
+    let entries = 0
+    list({
+      file: archivePath,
+      onReadEntry: (entry) => {
+        if (entry.type !== 'File' && entry.type !== 'OldFile') {
+          throw new Error(`desktop seed: unsupported pnpm store archive entry type ${entry.type}`)
+        }
+        assertArchivePath(entry.path)
+        if (shardFor(entry.path, manifest.shardCount) !== archiveShard) {
+          throw new Error(`desktop seed: pnpm store path is assigned to the wrong archive shard: ${entry.path}`)
+        }
+        if (paths.has(entry.path)) {
+          throw new Error(`desktop seed: duplicate pnpm store archive path ${entry.path}`)
+        }
+        paths.add(entry.path)
+        entries += 1
+      },
+      strict: true,
+      sync: true,
+    })
+    if (entries !== archive.entries) {
+      throw new Error(`desktop seed: pnpm store archive ${archive.file} has an unexpected entry count`)
+    }
+  }
+  for (const archive of manifest.archives) {
+    extract({
+      chmod: true,
+      cwd: destination,
+      file: join(archiveRoot, archive.file),
+      noMtime: true,
+      preservePaths: false,
+      processUmask: 0,
+      strict: true,
+      sync: true,
+    })
+  }
+}

+ 26 - 0
apps/desktop/src/single-instance.ts

@@ -0,0 +1,26 @@
+/** Electron single-instance ownership before any Desktop profile lifecycle begins. */
+
+/** Minimal Electron application operations needed for instance ownership. */
+export interface DesktopSingleInstanceApplication {
+  requestSingleInstanceLock(): boolean
+  quit(): void
+  on(event: 'second-instance', listener: () => void): unknown
+}
+
+/**
+ * Claim the process-lifetime Desktop lock and route later launches to the owner.
+ * @param application - Electron application singleton.
+ * @param focusOwner - focus or recreate the primary window after a later launch.
+ * @returns true only in the process that may access the Desktop profile.
+ */
+export function claimDesktopSingleInstance(
+  application: DesktopSingleInstanceApplication,
+  focusOwner: () => void,
+): boolean {
+  if (!application.requestSingleInstanceLock()) {
+    application.quit()
+    return false
+  }
+  application.on('second-instance', focusOwner)
+  return true
+}

+ 95 - 0
apps/desktop/src/update-coordinator.ts

@@ -0,0 +1,95 @@
+/** One Electron release stream for the version-bound shell and dsh seed. */
+
+import { existsSync } from 'node:fs'
+import { join } from 'node:path'
+import { app } from 'electron'
+import electronUpdater, { type AppUpdater } from 'electron-updater'
+import type { DesktopUpdateState } from './ipc.ts'
+const { autoUpdater } = electronUpdater
+
+/** Checks, downloads, and installs one complete Desktop release. */
+export class DesktopUpdateCoordinator {
+  private availableVersion: string | undefined
+  private checkOperation: Promise<DesktopUpdateState> | undefined
+  private installOperation: Promise<DesktopUpdateState> | undefined
+
+  /**
+   * @param publish - state sink for every desktop window.
+   * @param beforeRestart - stop application-owned processes before replacement.
+   * @param updater - Electron artifact updater; replaceable for tests.
+   * @param enabled - whether this packaged process carries updater configuration.
+   */
+  constructor(
+    private readonly publish: (state: DesktopUpdateState) => DesktopUpdateState,
+    private readonly beforeRestart: () => Promise<void> = async () => {},
+    private readonly updater: AppUpdater = autoUpdater,
+    private readonly enabled: () => boolean = () => (
+      app.isPackaged && existsSync(join(process.resourcesPath, 'app-update.yml'))
+    ),
+  ) {
+    this.updater.autoDownload = false
+    this.updater.autoInstallOnAppQuit = false
+  }
+
+  /** Check the configured Desktop release stream and retain an available version. */
+  async check(): Promise<DesktopUpdateState> {
+    if (this.installOperation !== undefined) return this.installOperation
+    if (this.checkOperation !== undefined) return this.checkOperation
+    this.checkOperation = this.doCheck().finally(() => { this.checkOperation = undefined })
+    return this.checkOperation
+  }
+
+  /** Wait for an in-flight check, then download and install its retained release. */
+  async install(): Promise<DesktopUpdateState> {
+    if (this.installOperation !== undefined) return this.installOperation
+    this.installOperation = (async () => {
+      await this.checkOperation
+      return this.doInstall()
+    })().finally(() => { this.installOperation = undefined })
+    return this.installOperation
+  }
+
+  private async doCheck(): Promise<DesktopUpdateState> {
+    this.publish({ phase: 'checking' })
+    try {
+      if (!this.enabled()) {
+        this.availableVersion = undefined
+        return this.publish({ phase: 'idle' })
+      }
+      const result = await this.updater.checkForUpdates()
+      const version = result?.isUpdateAvailable === true ? result.updateInfo.version : undefined
+      this.availableVersion = version
+      return version === undefined
+        ? this.publish({ phase: 'idle' })
+        : this.publish({ phase: 'available', version })
+    } catch (error) {
+      this.availableVersion = undefined
+      return this.publish({
+        phase: 'error',
+        message: error instanceof Error ? error.message : String(error),
+      })
+    }
+  }
+
+  private async doInstall(): Promise<DesktopUpdateState> {
+    const version = this.availableVersion
+    if (version === undefined) {
+      throw new Error('desktop update: no verified update is available')
+    }
+    this.publish({ phase: 'installing', version })
+    try {
+      await this.updater.downloadUpdate()
+      this.availableVersion = undefined
+      const ready = this.publish({ phase: 'ready', version })
+      await this.beforeRestart()
+      this.updater.quitAndInstall(false, true)
+      return ready
+    } catch (error) {
+      return this.publish({
+        phase: 'error',
+        version,
+        message: error instanceof Error ? error.message : String(error),
+      })
+    }
+  }
+}

+ 103 - 0
apps/desktop/tests/core-package-set.spec.ts

@@ -0,0 +1,103 @@
+import { createHash } from 'node:crypto'
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
+import { tmpdir } from 'node:os'
+import { join } from 'node:path'
+import { afterEach, describe, expect, it } from 'vitest'
+import {
+  DESKTOP_PACKAGES_DIR,
+  DESKTOP_PACKAGE_SET_FILE,
+  desktopCorePackageOverrides,
+  desktopDshPackageSpec,
+  parseDesktopCorePackageSet,
+  verifyDesktopCoreLockfile,
+  verifyDesktopCorePackageSet,
+  type DesktopCorePackageRecord,
+} from '../src/core-package-set.ts'
+
+const roots: string[] = []
+
+function record(name: string, file: string, body: Buffer, version = '1.2.3'): DesktopCorePackageRecord {
+  return {
+    name,
+    version,
+    file,
+    bytes: body.byteLength,
+    integrity: `sha512-${createHash('sha512').update(body).digest('base64')}`,
+  }
+}
+
+function packageSetProject(): {
+  root: string
+  dsh: DesktopCorePackageRecord
+  base: DesktopCorePackageRecord
+  host: DesktopCorePackageRecord
+} {
+  const root = mkdtempSync(join(tmpdir(), 'dsh-desktop-package-set-'))
+  roots.push(root)
+  const packageDir = join(root, DESKTOP_PACKAGES_DIR)
+  mkdirSync(packageDir)
+  const dshBody = Buffer.from('dsh')
+  const baseBody = Buffer.from('base')
+  const hostBody = Buffer.from('host')
+  const dsh = record('@deepseek-ai/dsh', 'dsh.tgz', dshBody)
+  const base = record('@deepseek-ai/dsh-base', 'dsh-base.tgz', baseBody)
+  const host = record('@deepseek-ai/dsh-desktop-host', 'dsh-desktop-host.tgz', hostBody)
+  writeFileSync(join(packageDir, dsh.file), dshBody)
+  writeFileSync(join(packageDir, base.file), baseBody)
+  writeFileSync(join(packageDir, host.file), hostBody)
+  writeFileSync(join(root, DESKTOP_PACKAGE_SET_FILE), `${JSON.stringify({
+    schemaVersion: 1,
+    packages: [dsh, base, host],
+  })}\n`)
+  return { root, dsh, base, host }
+}
+
+afterEach(() => {
+  for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true })
+})
+
+describe('desktop core package set', () => {
+  it('pins the direct dsh dependency and every internal package to local tarballs', () => {
+    const { root } = packageSetProject()
+    const packageSet = verifyDesktopCorePackageSet(root, '1.2.3')
+    expect(desktopDshPackageSpec(packageSet)).toBe('file:./desktop-packages/dsh.tgz')
+    expect(desktopCorePackageOverrides(packageSet)).toEqual({
+      '@deepseek-ai/dsh': 'file:./desktop-packages/dsh.tgz',
+      '@deepseek-ai/dsh-base': 'file:./desktop-packages/dsh-base.tgz',
+      '@deepseek-ai/dsh-desktop-host': 'file:./desktop-packages/dsh-desktop-host.tgz',
+    })
+  })
+
+  it('rejects version drift, descriptor disorder, corruption, and extra files', () => {
+    const { root, dsh, base, host } = packageSetProject()
+    expect(() => verifyDesktopCorePackageSet(root, '2.0.0')).toThrow(/does not match Desktop/u)
+    expect(() => parseDesktopCorePackageSet({
+      schemaVersion: 1,
+      packages: [dsh, base, { ...host, version: '2.0.0' }],
+    }, '1.2.3')).toThrow(/dsh-desktop-host@2\.0\.0 does not match Desktop 1\.2\.3/u)
+    expect(() => parseDesktopCorePackageSet({ schemaVersion: 1, packages: [base, dsh, host] }))
+      .toThrow(/sorted by name/u)
+    writeFileSync(join(root, DESKTOP_PACKAGES_DIR, dsh.file), 'changed')
+    expect(() => verifyDesktopCorePackageSet(root, '1.2.3')).toThrow(/integrity check failed/u)
+    writeFileSync(join(root, DESKTOP_PACKAGES_DIR, 'extra.tgz'), '')
+    expect(() => verifyDesktopCorePackageSet(root, '1.2.3')).toThrow(/does not match its descriptor/u)
+  })
+
+  it('rejects registry resolutions for names supplied by the local package set', () => {
+    const dsh = record('@deepseek-ai/dsh', 'dsh.tgz', Buffer.from('dsh'))
+    const host = record('@deepseek-ai/dsh-desktop-host', 'host.tgz', Buffer.from('host'))
+    const packageSet = parseDesktopCorePackageSet({ schemaVersion: 1, packages: [dsh, host] })
+    expect(() => {
+      verifyDesktopCoreLockfile(
+        "packages:\n  '@deepseek-ai/dsh@file:desktop-packages/dsh.tgz':\n    resolution: {}\n",
+        packageSet,
+      )
+    }).not.toThrow()
+    expect(() => {
+      verifyDesktopCoreLockfile(
+        "packages:\n  '@deepseek-ai/dsh@1.2.3':\n    resolution: {integrity: sha512-registry}\n",
+        packageSet,
+      )
+    }).toThrow(/outside the local package set/u)
+  })
+})

+ 89 - 0
apps/desktop/tests/desktop-auto-update-environment.spec.ts

@@ -0,0 +1,89 @@
+import { describe, expect, it } from 'vitest'
+import {
+  desktopBuildRecordFilename,
+  desktopUpdateMetadataFilename,
+  resolveDesktopAutoUpdateConfig,
+  resolveDesktopAutoUpdateEnvironment,
+  resolveDesktopAutoUpdateTarget,
+  resolveDesktopUploadConfig,
+} from '../scripts/desktop-auto-update-environment.mjs'
+
+describe('desktop auto-update environment', () => {
+  it('defaults packages and uploads to the test deployment', () => {
+    expect(resolveDesktopAutoUpdateEnvironment({})).toBe('test')
+    expect(resolveDesktopAutoUpdateConfig({
+      DOWNLOAD_TEST_ORIGIN: 'https://desktop-updates.example.com/',
+    }, 'darwin', 'arm64')).toEqual({
+      environment: 'test',
+      target: 'mac-arm64',
+      origin: 'https://desktop-updates.example.com',
+      publicUrl: 'https://desktop-updates.example.com/_/harness/desktop/stable/mac-arm64/',
+      keyPrefix: '_/harness/desktop/stable/mac-arm64',
+    })
+    expect(resolveDesktopUploadConfig({
+      DOWNLOAD_TEST_ORIGIN: 'https://desktop-updates.example.com/',
+      DOWNLOAD_TEST_COS_BUCKET: 'test-download-bucket',
+    }, 'darwin', 'arm64')).toMatchObject({
+      bucket: 'test-download-bucket',
+      secretIdEnvName: 'DOWNLOAD_TEST_COS_SECRET_ID',
+      secretKeyEnvName: 'DOWNLOAD_TEST_COS_SECRET_KEY',
+    })
+  })
+
+  it('selects the production URL for packages and bucket for uploads', () => {
+    expect(resolveDesktopAutoUpdateConfig({
+      DSH_DESKTOP_AUTO_UPDATE_ENV: 'production',
+    }, 'win32', 'x64')).toMatchObject({
+      environment: 'production',
+      target: 'win-x64',
+      publicUrl: 'https://download.deepseek.com/_/harness/desktop/stable/win-x64/',
+    })
+    expect(resolveDesktopUploadConfig({
+      DSH_DESKTOP_AUTO_UPDATE_ENV: 'production',
+      DOWNLOAD_PROD_COS_BUCKET: 'production-download-bucket',
+    }, 'win32', 'x64')).toMatchObject({
+      bucket: 'production-download-bucket',
+      secretIdEnvName: 'DOWNLOAD_PROD_COS_SECRET_ID',
+      secretKeyEnvName: 'DOWNLOAD_PROD_COS_SECRET_KEY',
+    })
+  })
+
+  it('requires the selected deployment origin for packages and bucket only for uploads', () => {
+    expect(() => resolveDesktopAutoUpdateConfig({}, 'darwin', 'arm64'))
+      .toThrow(/DOWNLOAD_TEST_ORIGIN/u)
+    expect(resolveDesktopAutoUpdateConfig({
+      DOWNLOAD_TEST_ORIGIN: 'https://desktop-updates.example.com',
+    }, 'darwin', 'arm64').publicUrl).toContain('/mac-arm64/')
+    expect(() => resolveDesktopUploadConfig({
+      DOWNLOAD_TEST_ORIGIN: 'https://desktop-updates.example.com',
+    }, 'darwin', 'arm64')).toThrow(/DOWNLOAD_TEST_COS_BUCKET/u)
+    expect(() => resolveDesktopUploadConfig({
+      DSH_DESKTOP_AUTO_UPDATE_ENV: 'production',
+    }, 'win32', 'x64')).toThrow(/DOWNLOAD_PROD_COS_BUCKET/u)
+  })
+
+  it('rejects a test download URL that is not an HTTPS origin', () => {
+    expect(() => resolveDesktopAutoUpdateConfig({
+      DOWNLOAD_TEST_ORIGIN: 'https://desktop-updates.example.com/releases',
+    }, 'darwin', 'arm64')).toThrow(/HTTPS origin without a path/u)
+    expect(() => resolveDesktopAutoUpdateConfig({
+      DOWNLOAD_TEST_ORIGIN: 'http://desktop-updates.example.com',
+    }, 'darwin', 'arm64')).toThrow(/HTTPS origin/u)
+  })
+
+  it('rejects unknown deployments and targets', () => {
+    expect(() => resolveDesktopAutoUpdateEnvironment({
+      DSH_DESKTOP_AUTO_UPDATE_ENV: 'staging',
+    })).toThrow(/test.*production/u)
+    expect(() => resolveDesktopAutoUpdateTarget('linux', 'x64')).toThrow(/unsupported target/u)
+    expect(() => desktopBuildRecordFilename('linux-x64' as 'mac-arm64')).toThrow(/unsupported target/u)
+  })
+
+  it('matches electron-builder channel metadata names to the Desktop version', () => {
+    expect(desktopUpdateMetadataFilename('1.2.3', 'darwin')).toBe('latest-mac.yml')
+    expect(desktopUpdateMetadataFilename('1.2.3-alpha.4', 'darwin')).toBe('alpha-mac.yml')
+    expect(desktopUpdateMetadataFilename('1.2.3-beta.2', 'win32')).toBe('beta.yml')
+    expect(() => desktopUpdateMetadataFilename('not-semver', 'darwin')).toThrow(/invalid Desktop version/u)
+    expect(() => desktopUpdateMetadataFilename('1.2.3', 'linux')).toThrow(/unsupported metadata platform/u)
+  })
+})

+ 50 - 0
apps/desktop/tests/desktop-build-paths.spec.ts

@@ -0,0 +1,50 @@
+import { join, sep } from 'node:path'
+import { describe, expect, it } from 'vitest'
+import {
+  desktopTargetBuildPaths,
+  resolveDesktopBuildTarget,
+} from '../scripts/desktop-build-paths.mjs'
+
+describe('desktop build paths', () => {
+  it('isolates every mutable build directory by complete target', () => {
+    const arm64 = desktopTargetBuildPaths('mac-arm64')
+    const x64 = desktopTargetBuildPaths('mac-x64')
+    const windows = desktopTargetBuildPaths('win-x64')
+    const mutableKeys = [
+      'root',
+      'artifacts',
+      'runtime',
+      'packageSet',
+      'seed',
+      'seedPnpm',
+      'nodeExtract',
+      'packedDsh',
+      'packedVendor',
+      'packedLandlock',
+    ] as const
+
+    for (const key of mutableKeys) {
+      expect(new Set([arm64[key], x64[key], windows[key]]).size).toBe(3)
+    }
+    expect(arm64.artifacts).toContain(join('targets', 'mac-arm64', 'artifacts'))
+    expect(x64.seed).toContain(join('targets', 'mac-x64', 'seed'))
+    expect(windows.runtime).toContain(join('targets', 'win-x64', 'runtime'))
+  })
+
+  it('shares only the immutable upstream download cache', () => {
+    const arm64 = desktopTargetBuildPaths('mac-arm64')
+    const x64 = desktopTargetBuildPaths('mac-x64')
+    expect(arm64.downloads).toBe(x64.downloads)
+    expect(arm64.downloads).not.toContain(`${sep}targets${sep}`)
+  })
+
+  it('resolves environment overrides and rejects unsupported targets', () => {
+    expect(resolveDesktopBuildTarget({
+      DSH_DESKTOP_TARGET_PLATFORM: 'darwin',
+      DSH_DESKTOP_TARGET_ARCH: 'x64',
+    }, 'darwin', 'arm64')).toBe('mac-x64')
+    expect(resolveDesktopBuildTarget({}, 'win32', 'x64')).toBe('win-x64')
+    expect(() => resolveDesktopBuildTarget({}, 'linux', 'x64')).toThrow(/unsupported target/u)
+    expect(() => desktopTargetBuildPaths('linux-x64' as 'mac-x64')).toThrow(/unsupported target/u)
+  })
+})

+ 195 - 0
apps/desktop/tests/desktop-upload-plan.spec.ts

@@ -0,0 +1,195 @@
+import { createHash } from 'node:crypto'
+import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'
+import { tmpdir } from 'node:os'
+import { join } from 'node:path'
+import { afterEach, describe, expect, it } from 'vitest'
+import { createDesktopUploadPlan } from '../scripts/desktop-upload-plan.ts'
+import { desktopUpdateMetadataFilename } from '../scripts/desktop-auto-update-environment.mjs'
+import type { DesktopPackageTargetName } from '../scripts/package-target.ts'
+
+const temporaryDirectories: string[] = []
+const TEST_ORIGIN = 'https://desktop-updates.example.com'
+const TEST_BUCKET = 'test-download-bucket'
+const PRODUCTION_BUCKET = 'production-download-bucket'
+
+interface Fixture {
+  readonly repositoryRoot: string
+  readonly appRoot: string
+  readonly artifactsRoot: string
+  readonly environment: NodeJS.ProcessEnv
+}
+
+function digest(contents: string): string {
+  return createHash('sha512').update(contents).digest('base64')
+}
+
+async function fixture(
+  target: DesktopPackageTargetName,
+  version = '1.2.3',
+  environment: 'test' | 'production' = 'test',
+): Promise<Fixture> {
+  const root = await mkdtemp(join(tmpdir(), 'dsh-desktop-upload-'))
+  temporaryDirectories.push(root)
+  const repositoryRoot = join(root, 'repository')
+  const appRoot = join(repositoryRoot, 'apps', 'desktop')
+  const artifactsRoot = join(appRoot, '.desktop-build', 'artifacts')
+  await mkdir(artifactsRoot, { recursive: true })
+  await writeFile(join(repositoryRoot, 'package.json'), `${JSON.stringify({ version })}\n`)
+  await writeFile(join(appRoot, 'package.json'), `${JSON.stringify({ version })}\n`)
+
+  const [os, arch] = target.split('-') as ['mac' | 'win', 'arm64' | 'x64']
+  const base = `deepseek-harness-${version}-${os}-${arch}`
+  const origin = environment === 'test'
+    ? TEST_ORIGIN
+    : 'https://download.deepseek.com'
+  await writeFile(join(artifactsRoot, `${target}-release.json`), `${JSON.stringify({
+    schemaVersion: 1,
+    target,
+    version,
+    environment,
+    publicUrl: `${origin}/_/harness/desktop/stable/${target}/`,
+  })}\n`)
+
+  if (os === 'mac') {
+    const zip = 'signed macOS ZIP fixture'
+    await writeFile(join(artifactsRoot, `${base}.zip`), zip)
+    await writeFile(join(artifactsRoot, `${base}.zip.blockmap`), 'blockmap')
+    await writeFile(join(artifactsRoot, `${base}.dmg`), 'notarized DMG fixture')
+    await writeFile(join(artifactsRoot, desktopUpdateMetadataFilename(version, 'darwin')), `${JSON.stringify({
+      version,
+      files: [{ url: `${base}.zip`, size: Buffer.byteLength(zip), sha512: digest(zip) }],
+    })}\n`)
+  }
+  else {
+    const executable = 'signed NSIS executable fixture'
+    await writeFile(join(artifactsRoot, `${base}.exe`), executable)
+    await writeFile(join(artifactsRoot, desktopUpdateMetadataFilename(version, 'win32')), `${JSON.stringify({
+      version,
+      files: [{
+        url: `${base}.exe`,
+        size: Buffer.byteLength(executable),
+        sha512: digest(executable),
+        blockMapSize: 128,
+      }],
+    })}\n`)
+  }
+  return {
+    repositoryRoot,
+    appRoot,
+    artifactsRoot,
+    environment: environment === 'test'
+      ? {
+        DSH_DESKTOP_AUTO_UPDATE_ENV: 'test',
+        DOWNLOAD_TEST_ORIGIN: TEST_ORIGIN,
+        DOWNLOAD_TEST_COS_BUCKET: TEST_BUCKET,
+      }
+      : {
+        DSH_DESKTOP_AUTO_UPDATE_ENV: 'production',
+        DOWNLOAD_PROD_COS_BUCKET: PRODUCTION_BUCKET,
+      },
+  }
+}
+
+afterEach(async () => {
+  await Promise.all(temporaryDirectories.splice(0).map(async path => rm(path, {
+    recursive: true,
+    force: true,
+  })))
+})
+
+describe('desktop upload plan', () => {
+  it('validates macOS artifacts and puts channel metadata last', async () => {
+    const paths = await fixture('mac-arm64')
+    const plan = await createDesktopUploadPlan('mac-arm64', paths)
+    expect(plan).toMatchObject({
+      environment: 'test',
+      version: '1.2.3',
+      publicUrl: 'https://desktop-updates.example.com/_/harness/desktop/stable/mac-arm64/',
+      bucket: TEST_BUCKET,
+    })
+    expect(plan.artifacts.map(artifact => artifact.filename)).toEqual([
+      'deepseek-harness-1.2.3-mac-arm64.dmg',
+      'deepseek-harness-1.2.3-mac-arm64.zip',
+      'deepseek-harness-1.2.3-mac-arm64.zip.blockmap',
+      'latest-mac.yml',
+    ])
+    expect(plan.artifacts.at(-1)).toMatchObject({
+      channelMetadata: true,
+      cacheControl: 'no-cache',
+    })
+  })
+
+  it('uploads the prerelease channel metadata emitted by electron-builder', async () => {
+    const paths = await fixture('mac-arm64', '1.2.3-alpha.4')
+    const plan = await createDesktopUploadPlan('mac-arm64', paths)
+    expect(plan.artifacts.map(artifact => artifact.filename)).toEqual([
+      'deepseek-harness-1.2.3-alpha.4-mac-arm64.dmg',
+      'deepseek-harness-1.2.3-alpha.4-mac-arm64.zip',
+      'deepseek-harness-1.2.3-alpha.4-mac-arm64.zip.blockmap',
+      'alpha-mac.yml',
+    ])
+  })
+
+  it('validates the Windows installer with its embedded blockmap and production destination', async () => {
+    const paths = await fixture('win-x64', '2.0.0', 'production')
+    const plan = await createDesktopUploadPlan('win-x64', paths)
+    expect(plan.artifacts.map(artifact => artifact.filename)).toEqual([
+      'deepseek-harness-2.0.0-win-x64.exe',
+      'latest.yml',
+    ])
+    expect(plan).toMatchObject({
+      publicUrl: 'https://download.deepseek.com/_/harness/desktop/stable/win-x64/',
+      bucket: PRODUCTION_BUCKET,
+    })
+  })
+
+  it('rejects Windows metadata without an embedded blockmap size', async () => {
+    const paths = await fixture('win-x64')
+    const executable = 'signed NSIS executable fixture'
+    await writeFile(join(paths.artifactsRoot, 'latest.yml'), `${JSON.stringify({
+      version: '1.2.3',
+      files: [{
+        url: 'deepseek-harness-1.2.3-win-x64.exe',
+        size: Buffer.byteLength(executable),
+        sha512: digest(executable),
+      }],
+    })}\n`)
+    await expect(createDesktopUploadPlan('win-x64', paths)).rejects.toThrow(/blockMapSize/u)
+  })
+
+  it('rejects a completed build from another dsh version or deployment', async () => {
+    const paths = await fixture('mac-x64')
+    await writeFile(join(paths.repositoryRoot, 'package.json'), '{"version":"1.2.4"}\n')
+    await writeFile(join(paths.appRoot, 'package.json'), '{"version":"1.2.4"}\n')
+    await expect(createDesktopUploadPlan('mac-x64', paths)).rejects.toThrow(/completion record.*1\.2\.4/u)
+
+    const productionPaths = await fixture('mac-x64', '1.2.3', 'production')
+    await expect(createDesktopUploadPlan('mac-x64', {
+      ...productionPaths,
+      environment: {
+        DSH_DESKTOP_AUTO_UPDATE_ENV: 'test',
+        DOWNLOAD_TEST_ORIGIN: TEST_ORIGIN,
+        DOWNLOAD_TEST_COS_BUCKET: TEST_BUCKET,
+      },
+    })).rejects.toThrow(/completion record.*test/u)
+  })
+
+  it('rejects stale architecture metadata and modified updater bytes', async () => {
+    const paths = await fixture('mac-arm64')
+    const metadataPath = join(paths.artifactsRoot, 'latest-mac.yml')
+    const zipPath = join(paths.artifactsRoot, 'deepseek-harness-1.2.3-mac-arm64.zip')
+    await writeFile(zipPath, 'modified')
+    await expect(createDesktopUploadPlan('mac-arm64', paths)).rejects.toThrow(/size.*metadata/u)
+
+    const x64 = 'wrong architecture'
+    await writeFile(metadataPath, `${JSON.stringify({
+      version: '1.2.3',
+      files: [{
+        url: 'deepseek-harness-1.2.3-mac-x64.zip',
+        size: Buffer.byteLength(x64),
+        sha512: digest(x64),
+      }],
+    })}\n`)
+    await expect(createDesktopUploadPlan('mac-arm64', paths)).rejects.toThrow(/mac-arm64\.zip/u)
+  })
+})

+ 89 - 0
apps/desktop/tests/development-project.spec.ts

@@ -0,0 +1,89 @@
+import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs'
+import { tmpdir } from 'node:os'
+import { join } from 'node:path'
+import { afterEach, describe, expect, it } from 'vitest'
+import { prepareDevelopmentProject } from '../scripts/development-project.ts'
+import { DESKTOP_HOST_PROTOCOL_VERSION } from '../src/host-protocol.ts'
+import type { DesktopRelease } from '../src/release.ts'
+
+const roots: string[] = []
+
+function temporaryRoot(): string {
+  const root = mkdtempSync(join(tmpdir(), 'dsh-desktop-development-test-'))
+  roots.push(root)
+  return root
+}
+
+function release(version = '1.2.3'): DesktopRelease {
+  return {
+    schemaVersion: 1,
+    version,
+    hostProtocolVersion: DESKTOP_HOST_PROTOCOL_VERSION,
+    nodeVersion: '24.17.0',
+    pnpmVersion: '11.7.0',
+  }
+}
+
+afterEach(() => {
+  for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true })
+})
+
+describe('desktop development project', () => {
+  it('projects the built dsh and Desktop Host applications with their dependency graph', () => {
+    const root = temporaryRoot()
+    const cli = join(root, 'apps', 'cli')
+    const host = join(root, 'apps', 'desktop-host')
+    const dependencies = join(root, 'workspace-dependencies')
+    mkdirSync(join(cli, 'lib'), { recursive: true })
+    mkdirSync(join(host, 'lib'), { recursive: true })
+    mkdirSync(join(dependencies, '@scope'), { recursive: true })
+    mkdirSync(join(dependencies, '@deepseek-ai', 'dsh'), { recursive: true })
+    writeFileSync(join(cli, 'package.json'), '{"name":"@deepseek-ai/dsh","version":"1.2.3"}\n')
+    writeFileSync(join(host, 'package.json'), '{"name":"@deepseek-ai/dsh-desktop-host","version":"1.2.3"}\n')
+    writeFileSync(join(host, 'lib', 'index.js'), '')
+    writeFileSync(join(dependencies, '@deepseek-ai', 'dsh', 'package.json'), '{}\n')
+    mkdirSync(join(dependencies, 'plain-dependency'))
+    writeFileSync(join(dependencies, 'plain-dependency', 'package.json'), '{}\n')
+    mkdirSync(join(dependencies, '@scope', 'dependency'))
+    writeFileSync(join(dependencies, '@scope', 'dependency', 'package.json'), '{}\n')
+
+    const project = prepareDevelopmentProject({
+      projectDir: join(root, 'development'),
+      cliDir: cli,
+      hostDir: host,
+      dependencyDir: dependencies,
+      release: release(),
+    })
+    expect(realpathSync(join(project, 'node_modules', '@deepseek-ai', 'dsh'))).toBe(realpathSync(cli))
+    expect(realpathSync(join(project, 'node_modules', '@deepseek-ai', 'dsh-desktop-host'))).toBe(realpathSync(host))
+    expect(realpathSync(join(project, 'node_modules', 'plain-dependency')))
+      .toBe(realpathSync(join(dependencies, 'plain-dependency')))
+    expect(realpathSync(join(project, 'node_modules', '@scope', 'dependency')))
+      .toBe(realpathSync(join(dependencies, '@scope', 'dependency')))
+    const manifest = JSON.parse(readFileSync(join(project, 'package.json'), 'utf8')) as {
+      dependencies: Record<string, string>
+    }
+    expect(manifest.dependencies['@deepseek-ai/dsh']).toBe('1.2.3')
+    expect(manifest.dependencies['@deepseek-ai/dsh-desktop-host']).toBe('1.2.3')
+  })
+
+  it('rejects a CLI package from another release', () => {
+    const root = temporaryRoot()
+    const cli = join(root, 'apps', 'cli')
+    const host = join(root, 'apps', 'desktop-host')
+    const dependencies = join(root, 'workspace-dependencies')
+    mkdirSync(join(cli, 'lib'), { recursive: true })
+    mkdirSync(join(host, 'lib'), { recursive: true })
+    mkdirSync(dependencies, { recursive: true })
+    writeFileSync(join(cli, 'package.json'), '{"name":"@deepseek-ai/dsh","version":"2.0.0"}\n')
+    writeFileSync(join(host, 'package.json'), '{"name":"@deepseek-ai/dsh-desktop-host","version":"1.2.3"}\n')
+    writeFileSync(join(host, 'lib', 'index.js'), '')
+    expect(() => prepareDevelopmentProject({
+      projectDir: join(root, 'development'),
+      cliDir: cli,
+      hostDir: host,
+      dependencyDir: dependencies,
+      release: release(),
+    })).toThrow(/must be @deepseek-ai\/dsh@1\.2\.3/u)
+  })
+})

+ 216 - 0
apps/desktop/tests/host-process.spec.ts

@@ -0,0 +1,216 @@
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
+import { tmpdir } from 'node:os'
+import { join } from 'node:path'
+import { afterEach, describe, expect, it } from 'vitest'
+import { DesktopHostProcess } from '../src/host-process.ts'
+
+const roots: string[] = []
+
+const HOST_WIRE = `
+import { closeSync, createReadStream, createWriteStream } from 'node:fs'
+const requestPipe = createReadStream('', { fd: 3, autoClose: false })
+const responsePipe = createWriteStream('', { fd: 4, autoClose: false })
+const MAGIC = 0x44534833
+const HEADER = 13
+function responseFrame(type, streamId, payload = Buffer.alloc(0)) {
+  const frame = Buffer.allocUnsafe(HEADER + payload.length)
+  frame.writeUInt32BE(MAGIC, 0)
+  frame.writeUInt8(type, 4)
+  frame.writeUInt32BE(streamId, 5)
+  frame.writeUInt32BE(payload.length, 9)
+  payload.copy(frame, HEADER)
+  return frame
+}
+function responseStart(streamId, options = {}) {
+  const value = { status: options.status ?? 200, headers: options.headers ?? [], hasBody: options.hasBody ?? true }
+  responsePipe.write(responseFrame(1, streamId, Buffer.from(JSON.stringify(value))))
+}
+function responseData(streamId, data) {
+  responsePipe.write(responseFrame(2, streamId, Buffer.from(data)))
+}
+function responseEnd(streamId) { responsePipe.write(responseFrame(3, streamId)) }
+function responseError(streamId, message) {
+  responsePipe.write(responseFrame(4, streamId, Buffer.from(JSON.stringify({ message }))))
+}
+let requestBuffer = Buffer.alloc(0)
+requestPipe.on('data', chunk => {
+  requestBuffer = requestBuffer.length === 0 ? chunk : Buffer.concat([requestBuffer, chunk])
+  while (requestBuffer.length >= HEADER) {
+    if (requestBuffer.readUInt32BE(0) !== MAGIC) throw new Error('invalid request marker')
+    const type = requestBuffer.readUInt8(4)
+    const streamId = requestBuffer.readUInt32BE(5)
+    const length = requestBuffer.readUInt32BE(9)
+    if (requestBuffer.length < HEADER + length) return
+    const payload = requestBuffer.subarray(HEADER, HEADER + length)
+    requestBuffer = requestBuffer.subarray(HEADER + length)
+    onRequestFrame({ type, streamId, payload })
+  }
+})
+process.on('message', message => {
+  if (message.type === 'shutdown') {
+    requestPipe.destroy()
+    closeSync(3)
+    responsePipe.end(() => {
+      responsePipe.destroy()
+      closeSync(4)
+      process.disconnect()
+      process.exitCode = 0
+    })
+  }
+})
+`
+
+function projectWithHost(source: string): string {
+  const project = mkdtempSync(join(tmpdir(), 'dsh-desktop-host-test-'))
+  roots.push(project)
+  const packageRoot = join(project, 'node_modules', '@deepseek-ai', 'dsh-desktop-host')
+  mkdirSync(join(packageRoot, 'lib'), { recursive: true })
+  writeFileSync(join(packageRoot, 'package.json'), '{"name":"@deepseek-ai/dsh-desktop-host","type":"module"}\n')
+  writeFileSync(join(packageRoot, 'lib', 'index.js'), `${HOST_WIRE}\n${source}`)
+  return project
+}
+
+afterEach(() => {
+  for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true })
+})
+
+describe('desktop host process', () => {
+  it('carries raw request and response bytes and shuts the child down cleanly', async () => {
+    const project = projectWithHost(`
+const bodies = new Map()
+process.send({ type: 'ready', protocolVersion: 3, dshVersion: process.env.NODE_OPTIONS ?? 'clean' })
+function onRequestFrame(frame) {
+  if (frame.type === 1) {
+    const request = JSON.parse(frame.payload)
+    bodies.set(frame.streamId, Buffer.alloc(0))
+    if (!request.hasBody) answer(frame.streamId)
+  } else if (frame.type === 2) {
+    bodies.set(frame.streamId, Buffer.concat([bodies.get(frame.streamId), frame.payload]))
+  } else if (frame.type === 3) {
+    answer(frame.streamId)
+  }
+}
+function answer(streamId) {
+  responseStart(streamId, { headers: [['content-type', 'text/plain']] })
+  responseData(streamId, Buffer.concat([Buffer.from('desktop:'), bodies.get(streamId)]))
+  responseEnd(streamId)
+}
+`)
+    const previous = process.env.NODE_OPTIONS
+    process.env.NODE_OPTIONS = '--require /path/that-must-not-reach-the-child'
+    const host = new DesktopHostProcess(process.execPath, project)
+    try {
+      await expect(host.start()).resolves.toMatchObject({ dshVersion: 'clean' })
+      const response = await host.fetch(new Request('dsh-app://app/example', { method: 'POST', body: 'request' }))
+      expect(response.status).toBe(200)
+      await expect(response.text()).resolves.toBe('desktop:request')
+      await expect(host.stop()).resolves.toBeUndefined()
+    } finally {
+      if (previous === undefined) delete process.env.NODE_OPTIONS
+      else process.env.NODE_OPTIONS = previous
+      await host.stop().catch(() => undefined)
+    }
+  })
+
+  it('streams a large binary response in bounded raw frames', async () => {
+    const size = 2 * 1024 * 1024
+    const project = projectWithHost(`
+process.send({ type: 'ready', protocolVersion: 3, dshVersion: 'large-response' })
+function onRequestFrame(frame) {
+  if (frame.type !== 1) return
+  responseStart(frame.streamId)
+  const bytes = Buffer.alloc(${String(64 * 1024)}, 97)
+  for (let offset = 0; offset < ${String(size)}; offset += bytes.length) responseData(frame.streamId, bytes)
+  responseEnd(frame.streamId)
+}
+`)
+    const host = new DesktopHostProcess(process.execPath, project)
+    try {
+      const response = await host.fetch(new Request('dsh-app://app/large'))
+      const body = new Uint8Array(await response.arrayBuffer())
+      expect(body).toHaveLength(size)
+      expect(body[0]).toBe(97)
+      expect(body.at(-1)).toBe(97)
+    } finally {
+      await host.stop().catch(() => undefined)
+    }
+  })
+
+  it('stops an unfinished upload when the Host completes its response early', async () => {
+    const project = projectWithHost(`
+process.send({ type: 'ready', protocolVersion: 3, dshVersion: 'early-response' })
+function onRequestFrame(frame) {
+  if (frame.type !== 2) return
+  responseStart(frame.streamId)
+  responseData(frame.streamId, 'accepted')
+  responseEnd(frame.streamId)
+}
+`)
+    let canceled = false
+    const body = new ReadableStream<Uint8Array>({
+      start(controller) { controller.enqueue(Buffer.from('first')) },
+      cancel() { canceled = true },
+    })
+    const host = new DesktopHostProcess(process.execPath, project)
+    try {
+      const request = new Request('dsh-app://app/early', {
+        method: 'POST',
+        body,
+        duplex: 'half',
+      } as RequestInit & { duplex: 'half' })
+      const response = await host.fetch(request)
+      await expect(response.text()).resolves.toBe('accepted')
+      await expect.poll(() => canceled).toBe(true)
+    } finally {
+      await host.stop().catch(() => undefined)
+    }
+  })
+
+  it('ignores a response end that arrives after the renderer cancels its stream', async () => {
+    const project = projectWithHost(`
+process.send({ type: 'ready', protocolVersion: 3, dshVersion: 'cancel-race' })
+const urls = new Map()
+function onRequestFrame(frame) {
+  if (frame.type === 1) {
+    const request = JSON.parse(frame.payload)
+    urls.set(frame.streamId, request.url)
+    responseStart(frame.streamId)
+    if (request.url.endsWith('/after')) {
+      responseData(frame.streamId, 'alive')
+      responseEnd(frame.streamId)
+    }
+  } else if (frame.type === 4 && urls.get(frame.streamId).endsWith('/cancel')) {
+    responseEnd(frame.streamId)
+  }
+}
+`)
+    const host = new DesktopHostProcess(process.execPath, project)
+    try {
+      const canceled = await host.fetch(new Request('dsh-app://app/cancel'))
+      await canceled.body?.cancel()
+      await new Promise(resolve => setTimeout(resolve, 25))
+      const after = await host.fetch(new Request('dsh-app://app/after'))
+      await expect(after.text()).resolves.toBe('alive')
+    } finally {
+      await host.stop().catch(() => undefined)
+    }
+  })
+
+  it('rejects invalid response framing and a clean exit before readiness', async () => {
+    const invalid = new DesktopHostProcess(process.execPath, projectWithHost(`
+process.send({ type: 'ready', protocolVersion: 3, dshVersion: 'invalid-frame' })
+function onRequestFrame(frame) {
+  if (frame.type === 1) responsePipe.write(Buffer.alloc(13))
+}
+`))
+    await invalid.start()
+    await expect(invalid.fetch(new Request('dsh-app://app/invalid'))).rejects.toThrow(/invalid Host response frame marker/u)
+    await invalid.stop().catch(() => undefined)
+
+    const earlyExit = new DesktopHostProcess(process.execPath, projectWithHost(`
+function onRequestFrame() {}
+process.exit(0)
+`))
+    await expect(earlyExit.start()).rejects.toThrow(/response pipe ended/u)
+  })
+})

+ 98 - 0
apps/desktop/tests/host-protocol.spec.ts

@@ -0,0 +1,98 @@
+import { describe, expect, it } from 'vitest'
+import {
+  DesktopHostRequestDecoder,
+  encodeDesktopResponseData,
+  encodeDesktopResponseEnd,
+  encodeDesktopResponseError,
+  encodeDesktopResponseStart,
+} from '../../desktop-host/src/wire.ts'
+import {
+  DesktopHostResponseDecoder,
+  encodeDesktopRequestCancel,
+  encodeDesktopRequestData,
+  encodeDesktopRequestEnd,
+  encodeDesktopRequestStart,
+} from '../src/host-protocol.ts'
+
+function decodeInPieces<T>(bytes: Buffer, push: (chunk: Buffer) => readonly T[]): T[] {
+  const values: T[] = []
+  for (let offset = 0; offset < bytes.byteLength; offset += 7) {
+    values.push(...push(bytes.subarray(offset, offset + 7)))
+  }
+  return values
+}
+
+describe('desktop Host pipe protocol', () => {
+  it('keeps Electron request frames compatible with the installed Host decoder', () => {
+    const decoder = new DesktopHostRequestDecoder()
+    const bytes = Buffer.concat([
+      encodeDesktopRequestStart(7, {
+        url: 'dsh-app://app/api/session',
+        method: 'POST',
+        headers: [['content-type', 'application/json']],
+        hasBody: true,
+      }),
+      encodeDesktopRequestData(7, Buffer.from('{"ok":true}')),
+      encodeDesktopRequestEnd(7),
+      encodeDesktopRequestCancel(7),
+    ])
+
+    expect(decodeInPieces(bytes, chunk => decoder.push(chunk))).toEqual([
+      {
+        type: 'start',
+        streamId: 7,
+        url: 'dsh-app://app/api/session',
+        method: 'POST',
+        headers: [['content-type', 'application/json']],
+        hasBody: true,
+      },
+      { type: 'data', streamId: 7, data: Buffer.from('{"ok":true}') },
+      { type: 'end', streamId: 7 },
+      { type: 'cancel', streamId: 7 },
+    ])
+    expect(() => { decoder.finish() }).not.toThrow()
+  })
+
+  it('keeps Host response frames compatible with the Electron decoder', () => {
+    const decoder = new DesktopHostResponseDecoder()
+    const bytes = Buffer.concat([
+      encodeDesktopResponseStart(9, {
+        status: 201,
+        headers: [['content-type', 'application/octet-stream']],
+        hasBody: true,
+      }),
+      encodeDesktopResponseData(9, Buffer.from([0, 1, 2, 255])),
+      encodeDesktopResponseEnd(9),
+      encodeDesktopResponseError(10, 'failed'),
+    ])
+
+    expect(decodeInPieces(bytes, chunk => decoder.push(chunk))).toEqual([
+      {
+        type: 'start',
+        streamId: 9,
+        status: 201,
+        headers: [['content-type', 'application/octet-stream']],
+        hasBody: true,
+      },
+      { type: 'data', streamId: 9, data: Buffer.from([0, 1, 2, 255]) },
+      { type: 'end', streamId: 9 },
+      { type: 'error', streamId: 10, message: 'failed' },
+    ])
+    expect(() => { decoder.finish() }).not.toThrow()
+  })
+
+  it('rejects a corrupt marker and truncated EOF on both directions', () => {
+    const request = new DesktopHostRequestDecoder()
+    const response = new DesktopHostResponseDecoder()
+    expect(() => request.push(Buffer.alloc(13))).toThrow(/request frame marker/u)
+    expect(() => response.push(Buffer.alloc(13))).toThrow(/response frame marker/u)
+
+    const partialRequest = new DesktopHostRequestDecoder()
+    partialRequest.push(encodeDesktopRequestEnd(1).subarray(0, 5))
+    expect(() => { partialRequest.finish() }).toThrow(/ended inside a frame/u)
+
+    const partialResponse = new DesktopHostResponseDecoder()
+    partialResponse.push(encodeDesktopResponseEnd(1).subarray(0, 5))
+    expect(() => { partialResponse.finish() }).toThrow(/ended inside a frame/u)
+  })
+})

+ 23 - 0
apps/desktop/tests/locale.spec.ts

@@ -0,0 +1,23 @@
+import { readFileSync } from 'node:fs'
+import { describe, expect, it } from 'vitest'
+import { en, formatDesktopMessage, resolveDesktopLocale, zh } from '../src/locale.ts'
+
+describe('desktop locale dictionaries', () => {
+  it('ships the same key set in English and Chinese', () => {
+    expect(Object.keys(zh)).toEqual(Object.keys(en))
+    expect(resolveDesktopLocale('zh-Hans-CN')).toEqual({ id: 'zh-CN', messages: zh })
+    expect(resolveDesktopLocale('en-US')).toEqual({ id: 'en', messages: en })
+    expect(resolveDesktopLocale('fr-FR')).toEqual({ id: 'en', messages: en })
+  })
+
+  it('formats named values without consuming unknown placeholders', () => {
+    expect(formatDesktopMessage('{name}@{version} {missing}', { name: 'plugin', version: '1.2.3' }))
+      .toBe('plugin@1.2.3 {missing}')
+  })
+
+  it('keeps visible plugin-manager HTML copy in the locale dictionaries', () => {
+    const html = readFileSync(new URL('../renderer/plugin-manager.html', import.meta.url), 'utf8')
+    const staticText = [...html.matchAll(/>([^<]*\p{L}[^<]*)</gu)].map(match => match[1]?.trim())
+    expect(staticText).toEqual([])
+  })
+})

+ 245 - 0
apps/desktop/tests/macos-seed-store.spec.ts

@@ -0,0 +1,245 @@
+import { createHash } from 'node:crypto'
+import {
+  appendFileSync,
+  existsSync,
+  mkdirSync,
+  mkdtempSync,
+  readFileSync,
+  rmSync,
+  writeFileSync,
+} from 'node:fs'
+import { tmpdir } from 'node:os'
+import { dirname, join } from 'node:path'
+import { DatabaseSync } from 'node:sqlite'
+import { Packr } from 'msgpackr'
+import { afterEach, describe, expect, it } from 'vitest'
+import {
+  signMacOSSeedStore,
+  verifyMacOSSeedStore,
+} from '../scripts/macos-seed-store.ts'
+
+const temporaryRoots: string[] = []
+const packr = new Packr({ moreTypes: true, useRecords: true })
+const SIGNING_ENVIRONMENT = {
+  signingIdentity: 'Example Company (TEAMID1234)',
+  teamId: 'TEAMID1234',
+}
+
+function temporaryRoot(): string {
+  const root = mkdtempSync(join(tmpdir(), 'dsh-desktop-seed-signing-test-'))
+  temporaryRoots.push(root)
+  return root
+}
+
+function casPath(store: string, body: Buffer, executable = false): { digest: string; path: string } {
+  const digest = createHash('sha512').update(body).digest('hex')
+  return {
+    digest,
+    path: join(store, 'v11', 'files', digest.slice(0, 2), `${digest.slice(2)}${executable ? '-exec' : ''}`),
+  }
+}
+
+function createStoreFile(path: string, body: Buffer): void {
+  mkdirSync(dirname(path), { recursive: true })
+  writeFileSync(path, body)
+}
+
+afterEach(() => {
+  for (const root of temporaryRoots.splice(0)) rmSync(root, { recursive: true, force: true })
+})
+
+describe('desktop macOS seed store signing', () => {
+  it('rehashes signed Mach-O content, rewrites every package reference, and prunes native orphans', async () => {
+    const store = temporaryRoot()
+    const native = Buffer.concat([Buffer.from('cffaedfe', 'hex'), Buffer.from('native-code')])
+    const nativeCas = casPath(store, native)
+    createStoreFile(nativeCas.path, native)
+    const orphan = Buffer.concat([Buffer.from('cafebabe', 'hex'), Buffer.from('orphan')])
+    const orphanCas = casPath(store, orphan)
+    createStoreFile(orphanCas.path, orphan)
+    const plain = Buffer.from('plain package content')
+    const plainCas = casPath(store, plain)
+    createStoreFile(plainCas.path, plain)
+
+    const database = new DatabaseSync(join(store, 'v11', 'index.db'))
+    database.exec('CREATE TABLE package_index (key TEXT PRIMARY KEY, data BLOB NOT NULL) WITHOUT ROWID')
+    const insert = database.prepare('INSERT INTO package_index (key, data) VALUES (?, ?)')
+    for (const key of ['package-a', 'package-b']) {
+      insert.run(key, packr.pack({
+        algo: 'sha512',
+        files: new Map([
+          ['native.node', { checkedAt: 1, digest: nativeCas.digest, mode: 0o644, size: native.length }],
+          ['index.js', { checkedAt: 1, digest: plainCas.digest, mode: 0o644, size: plain.length }],
+        ]),
+        sideEffects: key === 'package-b'
+          ? new Map([['build', { added: new Map([
+            ['built/native.node', { checkedAt: 1, digest: nativeCas.digest, mode: 0o644, size: native.length }],
+          ]) }]])
+          : undefined,
+      }))
+    }
+    database.close()
+
+    const result = await signMacOSSeedStore(
+      store,
+      'com.example.desktop',
+      SIGNING_ENVIRONMENT,
+      {
+        signer: async (path, identifier) => {
+          expect(identifier).toBe(`com.example.desktop.seed.${nativeCas.digest.slice(0, 32)}`)
+          appendFileSync(path, 'signed')
+        },
+      },
+    )
+
+    expect(result).toEqual({ signedFiles: 1, prunedOrphans: 1, updatedIndexRows: 2 })
+    expect(existsSync(nativeCas.path)).toBe(false)
+    expect(existsSync(orphanCas.path)).toBe(false)
+    expect(readFileSync(plainCas.path)).toEqual(plain)
+
+    const updated = new DatabaseSync(join(store, 'v11', 'index.db'), { readOnly: true })
+    const digests = [...updated.prepare('SELECT data FROM package_index').iterate() as Iterable<{ data: Uint8Array }>]
+      .flatMap((row) => {
+        const record = packr.unpack(row.data) as {
+          files: Map<string, { digest: string }>
+          sideEffects?: Map<string, { added: Map<string, { digest: string }> }>
+        }
+        return [
+          record.files.get('native.node')?.digest,
+          ...[...(record.sideEffects?.values() ?? [])].map(effect => effect.added.get('built/native.node')?.digest),
+        ].filter((digest): digest is string => digest !== undefined)
+      })
+    updated.close()
+    expect(new Set(digests).size).toBe(1)
+    expect(digests).toHaveLength(3)
+    expect(digests[0]).not.toBe(nativeCas.digest)
+
+    const verified: string[] = []
+    expect(verifyMacOSSeedStore(store, SIGNING_ENVIRONMENT, (path) => { verified.push(path) })).toBe(1)
+    expect(verified).toHaveLength(1)
+  })
+
+  it('bounds concurrent signing while allowing independent Mach-O files to overlap', async () => {
+    const store = temporaryRoot()
+    const files = new Map<string, { checkedAt: number; digest: string; mode: number; size: number }>()
+    for (let index = 0; index < 6; index += 1) {
+      const body = Buffer.concat([Buffer.from('feedfacf', 'hex'), Buffer.from(`native-${index}`)])
+      const nativeCas = casPath(store, body)
+      createStoreFile(nativeCas.path, body)
+      files.set(`native-${index}.node`, {
+        checkedAt: 1,
+        digest: nativeCas.digest,
+        mode: 0o644,
+        size: body.length,
+      })
+    }
+    const database = new DatabaseSync(join(store, 'v11', 'index.db'))
+    database.exec('CREATE TABLE package_index (key TEXT PRIMARY KEY, data BLOB NOT NULL) WITHOUT ROWID')
+    database.prepare('INSERT INTO package_index (key, data) VALUES (?, ?)')
+      .run('package', packr.pack({ algo: 'sha512', files }))
+    database.close()
+
+    let started = 0
+    let active = 0
+    let maximumActive = 0
+    let releaseSigning = (): void => {}
+    const signingReleased = new Promise<void>((resolve) => { releaseSigning = resolve })
+    let markFirstWaveReady = (): void => {}
+    const firstWaveReady = new Promise<void>((resolve) => { markFirstWaveReady = resolve })
+    const signing = signMacOSSeedStore(store, 'com.example.desktop', SIGNING_ENVIRONMENT, {
+      concurrency: 4,
+      signer: async () => {
+        started += 1
+        active += 1
+        maximumActive = Math.max(maximumActive, active)
+        if (started === 4) markFirstWaveReady()
+        try {
+          await signingReleased
+        } finally {
+          active -= 1
+        }
+      },
+    })
+
+    await firstWaveReady
+    expect({ started, active, maximumActive }).toEqual({ started: 4, active: 4, maximumActive: 4 })
+    releaseSigning()
+    await expect(signing).resolves.toMatchObject({ signedFiles: 6, updatedIndexRows: 1 })
+    expect({ started, active, maximumActive }).toEqual({ started: 6, active: 0, maximumActive: 4 })
+  })
+
+  it('awaits active signers and preserves the store when one signer fails', async () => {
+    const store = temporaryRoot()
+    const originals: { digest: string; path: string }[] = []
+    const files = new Map<string, { checkedAt: number; digest: string; mode: number; size: number }>()
+    for (let index = 0; index < 2; index += 1) {
+      const body = Buffer.concat([Buffer.from('feedfacf', 'hex'), Buffer.from(`native-${index}`)])
+      const nativeCas = casPath(store, body)
+      originals.push(nativeCas)
+      createStoreFile(nativeCas.path, body)
+      files.set(`native-${index}.node`, {
+        checkedAt: 1,
+        digest: nativeCas.digest,
+        mode: 0o644,
+        size: body.length,
+      })
+    }
+    const databasePath = join(store, 'v11', 'index.db')
+    const database = new DatabaseSync(databasePath)
+    database.exec('CREATE TABLE package_index (key TEXT PRIMARY KEY, data BLOB NOT NULL) WITHOUT ROWID')
+    database.prepare('INSERT INTO package_index (key, data) VALUES (?, ?)')
+      .run('package', packr.pack({ algo: 'sha512', files }))
+    database.close()
+    const originalIndex = readFileSync(databasePath)
+
+    let started = 0
+    let settled = 0
+    let releaseSigning = (): void => {}
+    const signingReleased = new Promise<void>((resolve) => { releaseSigning = resolve })
+    let markBothReady = (): void => {}
+    const bothReady = new Promise<void>((resolve) => { markBothReady = resolve })
+    const signing = signMacOSSeedStore(store, 'com.example.desktop', SIGNING_ENVIRONMENT, {
+      concurrency: 2,
+      signer: async () => {
+        started += 1
+        const call = started
+        if (started === 2) markBothReady()
+        try {
+          await signingReleased
+          if (call === 1) throw new Error('signing failed')
+        } finally {
+          settled += 1
+        }
+      },
+    })
+
+    await bothReady
+    releaseSigning()
+    await expect(signing).rejects.toThrow(/signing failed/u)
+    expect({ started, settled }).toEqual({ started: 2, settled: 2 })
+    expect(readFileSync(databasePath)).toEqual(originalIndex)
+    for (const original of originals) expect(existsSync(original.path)).toBe(true)
+  })
+
+  it('rejects an invalid signing worker bound before starting a signer', async () => {
+    const store = temporaryRoot()
+    mkdirSync(join(store, 'v11', 'files'), { recursive: true })
+    await expect(signMacOSSeedStore(store, 'com.example.desktop', SIGNING_ENVIRONMENT, {
+      concurrency: 0,
+      signer: async () => {},
+    })).rejects.toThrow(/positive integer/u)
+  })
+
+  it('propagates a signature-verification failure', () => {
+    const store = temporaryRoot()
+    const native = Buffer.concat([Buffer.from('feedfacf', 'hex'), Buffer.from('native-code')])
+    const nativeCas = casPath(store, native)
+    createStoreFile(nativeCas.path, native)
+
+    expect(() => {
+      verifyMacOSSeedStore(store, SIGNING_ENVIRONMENT, () => {
+        throw new Error('invalid signature')
+      })
+    }).toThrow(/invalid signature/u)
+  })
+})

+ 171 - 0
apps/desktop/tests/macos-signature.spec.ts

@@ -0,0 +1,171 @@
+import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
+import type { NotarizeOptions } from '@electron/notarize'
+import {
+  resolveDesktopAppId,
+  resolveMacOSNotarizationEnvironment,
+  resolveMacOSSigningEnvironment,
+} from '../scripts/desktop-release-environment.mjs'
+import { notarizeMacOSDiskImageArtifact } from '../scripts/notarize-macos-disk-images.mjs'
+import {
+  assertMacOSSeedSignatureDetails,
+  assertMacOSSignatureDetails,
+} from '../scripts/verify-macos-signature.mjs'
+
+const RELEASE_ENVIRONMENT = {
+  DSH_DESKTOP_APP_ID: 'com.example.desktop',
+  DSH_DESKTOP_TARGET_PLATFORM: 'darwin',
+  DSH_DESKTOP_TARGET_ARCH: 'arm64',
+  DSH_DESKTOP_MACOS_SIGNING_IDENTITY: 'Example Company (TEAMID1234)',
+  DSH_DESKTOP_MACOS_TEAM_ID: 'TEAMID1234',
+  APPLE_API_KEY: '/private/credentials/AuthKey_TEST123456.p8',
+  APPLE_API_KEY_ID: 'TEST123456',
+  APPLE_API_ISSUER: '11111111-2222-3333-4444-555555555555',
+  DOWNLOAD_TEST_ORIGIN: 'https://desktop-updates.example.com',
+}
+
+function portablePath(value: string): string {
+  return value.replaceAll('\\', '/')
+}
+
+describe('desktop macOS release signature', () => {
+  beforeAll(() => {
+    for (const [name, value] of Object.entries(RELEASE_ENVIRONMENT)) vi.stubEnv(name, value)
+  })
+
+  afterAll(() => {
+    vi.unstubAllEnvs()
+  })
+
+  it('loads release identifiers from the environment and requires code signing', async () => {
+    const { createElectronBuilderConfig } = await import('../electron-builder.config.mjs')
+    const config = createElectronBuilderConfig(RELEASE_ENVIRONMENT, 'darwin', 'arm64')
+    expect(portablePath(config.directories.output)).toContain('/.desktop-build/targets/mac-arm64/artifacts')
+    expect(config.extraResources).toHaveLength(2)
+    expect(config.extraResources[0]?.to).toBe('runtime')
+    expect(config.extraResources[1]?.to).toBe('seed')
+    expect(portablePath(config.extraResources[0]?.from ?? '')).toContain('/.desktop-build/targets/mac-arm64/runtime')
+    expect(portablePath(config.extraResources[1]?.from ?? '')).toContain('/.desktop-build/targets/mac-arm64/seed')
+    expect(config).toMatchObject({
+      appId: RELEASE_ENVIRONMENT.DSH_DESKTOP_APP_ID,
+      mac: {
+        identity: RELEASE_ENVIRONMENT.DSH_DESKTOP_MACOS_SIGNING_IDENTITY,
+        forceCodeSigning: true,
+        notarize: true,
+      },
+      dmg: {
+        sign: true,
+        writeUpdateInfo: false,
+      },
+      publish: [{
+        provider: 'generic',
+        url: 'https://desktop-updates.example.com/_/harness/desktop/stable/mac-arm64/',
+      }],
+    })
+    expect(typeof config.artifactBuildCompleted).toBe('function')
+  })
+
+  it('validates Windows signing without requiring macOS identifiers for a Windows target', async () => {
+    const { createElectronBuilderConfig } = await import('../electron-builder.config.mjs')
+    expect(() => createElectronBuilderConfig({
+      DSH_DESKTOP_APP_ID: RELEASE_ENVIRONMENT.DSH_DESKTOP_APP_ID,
+      DSH_DESKTOP_TARGET_PLATFORM: 'win32',
+    }, 'win32')).toThrow(/DSH_DESKTOP_WINDOWS_CER_FILE/u)
+  })
+
+  it('accepts the configured authority and team', () => {
+    const expected = resolveMacOSSigningEnvironment(RELEASE_ENVIRONMENT)
+    expect(() => {
+      assertMacOSSignatureDetails([
+        `Authority=Developer ID Application: ${expected.signingIdentity}`,
+        `TeamIdentifier=${expected.teamId}`,
+      ].join('\n'), expected)
+    }).not.toThrow()
+  })
+
+  it('requires a secure timestamp and hardened runtime for seed code', () => {
+    const expected = resolveMacOSSigningEnvironment(RELEASE_ENVIRONMENT)
+    const details = [
+      `Authority=Developer ID Application: ${expected.signingIdentity}`,
+      `TeamIdentifier=${expected.teamId}`,
+      'Timestamp=31 Aug 2026 at 20:00:00',
+      'CodeDirectory v=20500 size=773 flags=0x10000(runtime) hashes=13+7 location=embedded',
+    ].join('\n')
+    expect(() => { assertMacOSSeedSignatureDetails(details, expected) }).not.toThrow()
+    expect(() => {
+      assertMacOSSeedSignatureDetails(details.replace(/^Timestamp=.*\n/um, ''), expected)
+    }).toThrow(/secure timestamp/u)
+    expect(() => {
+      assertMacOSSeedSignatureDetails(details.replace('flags=0x10000(runtime)', 'flags=0x0(none)'), expected)
+    }).toThrow(/hardened runtime/u)
+  })
+
+  it('rejects another developer identity', () => {
+    const expected = resolveMacOSSigningEnvironment(RELEASE_ENVIRONMENT)
+    expect(() => {
+      assertMacOSSignatureDetails([
+        'Authority=Developer ID Application: Other Company (OTHERID123)',
+        'TeamIdentifier=OTHERID123',
+      ].join('\n'), expected)
+    }).toThrow(/release identity/u)
+  })
+
+  it('rejects an unexpected team even when the authority is present', () => {
+    const expected = resolveMacOSSigningEnvironment(RELEASE_ENVIRONMENT)
+    expect(() => {
+      assertMacOSSignatureDetails([
+        `Authority=Developer ID Application: ${expected.signingIdentity}`,
+        'TeamIdentifier=OTHERID123',
+      ].join('\n'), expected)
+    }).toThrow(`TeamIdentifier=${expected.teamId}`)
+  })
+
+  it('rejects missing and malformed release identifiers', () => {
+    expect(() => resolveDesktopAppId({})).toThrow(/DSH_DESKTOP_APP_ID/u)
+    expect(() => resolveDesktopAppId({ DSH_DESKTOP_APP_ID: 'not-a-bundle-id' })).toThrow(/reverse-DNS/u)
+    expect(() => resolveMacOSSigningEnvironment({})).toThrow(/DSH_DESKTOP_MACOS_SIGNING_IDENTITY/u)
+    expect(() => resolveMacOSSigningEnvironment({
+      DSH_DESKTOP_MACOS_SIGNING_IDENTITY: 'Developer ID Application: Example Company (TEAMID1234)',
+      DSH_DESKTOP_MACOS_TEAM_ID: 'TEAMID1234',
+    })).toThrow(/must omit/u)
+    expect(() => resolveMacOSSigningEnvironment({
+      DSH_DESKTOP_MACOS_SIGNING_IDENTITY: 'Example Company (TEAMID1234)',
+      DSH_DESKTOP_MACOS_TEAM_ID: 'short',
+    })).toThrow(/10 uppercase/u)
+  })
+
+  it('requires one complete notarization credential strategy', () => {
+    expect(resolveMacOSNotarizationEnvironment(RELEASE_ENVIRONMENT)).toEqual({
+      appleApiKey: RELEASE_ENVIRONMENT.APPLE_API_KEY,
+      appleApiKeyId: RELEASE_ENVIRONMENT.APPLE_API_KEY_ID,
+      appleApiIssuer: RELEASE_ENVIRONMENT.APPLE_API_ISSUER,
+    })
+    expect(resolveMacOSNotarizationEnvironment({
+      APPLE_KEYCHAIN_PROFILE: 'dsh-notary',
+    })).toEqual({ keychainProfile: 'dsh-notary' })
+    expect(() => resolveMacOSNotarizationEnvironment({})).toThrow(/macOS packaging requires/u)
+    expect(() => resolveMacOSNotarizationEnvironment({ APPLE_API_KEY: '/tmp/key.p8' })).toThrow(/APPLE_API_KEY_ID/u)
+  })
+
+  it('notarizes and qualifies a DMG before electron-builder publishes it', async () => {
+    const submitted: string[] = []
+    const submit = vi.fn(async (options: NotarizeOptions) => { submitted.push(options.appPath) })
+    const verified: string[] = []
+    const verify = vi.fn((path: string) => { verified.push(path) })
+    await notarizeMacOSDiskImageArtifact(
+      { file: '/tmp/release.dmg' },
+      RELEASE_ENVIRONMENT,
+      resolveMacOSSigningEnvironment(RELEASE_ENVIRONMENT),
+      submit,
+      verify,
+    )
+    await notarizeMacOSDiskImageArtifact(
+      { file: '/tmp/release.zip' },
+      RELEASE_ENVIRONMENT,
+      resolveMacOSSigningEnvironment(RELEASE_ENVIRONMENT),
+      submit,
+      verify,
+    )
+    expect(submitted).toEqual(['/tmp/release.dmg'])
+    expect(verified).toEqual(['/tmp/release.dmg'])
+  })
+})

+ 86 - 0
apps/desktop/tests/package-target.spec.ts

@@ -0,0 +1,86 @@
+import { describe, expect, it } from 'vitest'
+import {
+  desktopElectronBuilderArguments,
+  parseDesktopPackageInvocation,
+  resolveDesktopPackageTarget,
+  withoutDesktopUploadCredentials,
+  withoutWindowsSigningEnvironment,
+} from '../scripts/package-target.ts'
+
+describe('desktop package target', () => {
+  it('selects matching runtime and electron-builder architectures', () => {
+    expect(resolveDesktopPackageTarget('mac-arm64', 'darwin', 'arm64')).toMatchObject({
+      platform: 'darwin', arch: 'arm64', builderPlatform: '--mac', builderArch: '--arm64',
+    })
+    expect(resolveDesktopPackageTarget('mac-x64', 'darwin', 'x64')).toMatchObject({
+      platform: 'darwin', arch: 'x64', builderPlatform: '--mac', builderArch: '--x64',
+    })
+    expect(resolveDesktopPackageTarget('win-x64', 'win32', 'x64')).toMatchObject({
+      platform: 'win32', arch: 'x64', builderPlatform: '--win', builderArch: '--x64',
+    })
+  })
+
+  it('allows an Apple Silicon host to build the Intel target through Rosetta', () => {
+    expect(resolveDesktopPackageTarget('mac-x64', 'darwin', 'arm64').arch).toBe('x64')
+  })
+
+  it('rejects unsupported targets and hosts before building', () => {
+    expect(() => resolveDesktopPackageTarget('linux-x64', 'linux', 'x64')).toThrow(/unsupported target/u)
+    expect(() => resolveDesktopPackageTarget('win-x64', 'darwin', 'arm64')).toThrow(/Windows x64/u)
+    expect(() => resolveDesktopPackageTarget('mac-arm64', 'darwin', 'x64')).toThrow(/Apple Silicon/u)
+    expect(() => resolveDesktopPackageTarget('mac-arm64', 'linux', 'arm64')).toThrow(/macOS/u)
+    expect(() => resolveDesktopPackageTarget('mac-x64', 'darwin', 'ppc64')).toThrow(/Rosetta/u)
+  })
+
+  it('parses installer and unpacked-directory invocations', () => {
+    expect(parseDesktopPackageInvocation(['mac-arm64'], 'darwin', 'arm64').directory).toBe(false)
+    expect(parseDesktopPackageInvocation(['mac-arm64', '--dir'], 'darwin', 'arm64').directory).toBe(true)
+    expect(parseDesktopPackageInvocation([], 'darwin', 'arm64').target.name).toBe('mac-arm64')
+    expect(parseDesktopPackageInvocation(['--prepare-only'], 'darwin', 'arm64').prepareOnly).toBe(true)
+    expect(() => parseDesktopPackageInvocation(['mac-arm64', 'mac-x64'], 'darwin', 'arm64'))
+      .toThrow(/at most one target/u)
+  })
+
+  it('keeps electron-builder publishing disabled for the separate validated upload', () => {
+    const target = resolveDesktopPackageTarget('mac-arm64', 'darwin', 'arm64')
+    expect(desktopElectronBuilderArguments(target, false)).toEqual([
+      'exec',
+      'electron-builder',
+      '--config',
+      'electron-builder.config.mjs',
+      '--mac',
+      '--arm64',
+      '--publish',
+      'never',
+    ])
+    expect(desktopElectronBuilderArguments(target, true)).toContain('--dir')
+  })
+
+  it('keeps Windows signing fields out of build and seed preparation subprocesses', () => {
+    expect(withoutWindowsSigningEnvironment({
+      DSH_DESKTOP_WINDOWS_CER_FILE: 'C:\\release\\server.cer',
+      DSH_DESKTOP_WINDOWS_TOKEN_PIN: 'token-secret',
+      DSH_DESKTOP_WINDOWS_KEY_CONTAINER: 'container',
+      DSH_DESKTOP_WINDOWS_SIGNTOOL: 'C:\\tools\\signtool.exe',
+      DSH_DESKTOP_AUTO_UPDATE_ENV: 'production',
+    })).toEqual({ DSH_DESKTOP_AUTO_UPDATE_ENV: 'production' })
+  })
+
+  it('keeps COS credentials out of every packaging subprocess', () => {
+    expect(withoutDesktopUploadCredentials({
+      DOWNLOAD_TEST_ORIGIN: 'https://desktop-updates.example.com',
+      DOWNLOAD_TEST_COS_BUCKET: 'test-download-bucket',
+      DOWNLOAD_TEST_COS_SECRET_ID: 'test-id',
+      DOWNLOAD_TEST_COS_SECRET_KEY: 'test-key',
+      DOWNLOAD_PROD_COS_BUCKET: 'production-download-bucket',
+      DOWNLOAD_PROD_COS_SECRET_ID: 'production-id',
+      DOWNLOAD_PROD_COS_SECRET_KEY: 'production-key',
+      DSH_DESKTOP_AUTO_UPDATE_ENV: 'production',
+    })).toEqual({
+      DOWNLOAD_TEST_ORIGIN: 'https://desktop-updates.example.com',
+      DOWNLOAD_TEST_COS_BUCKET: 'test-download-bucket',
+      DOWNLOAD_PROD_COS_BUCKET: 'production-download-bucket',
+      DSH_DESKTOP_AUTO_UPDATE_ENV: 'production',
+    })
+  })
+})

+ 79 - 0
apps/desktop/tests/prepare-package-set.spec.ts

@@ -0,0 +1,79 @@
+import { afterEach, describe, expect, it, vi } from 'vitest'
+import {
+  assertDesktopHostPackageFiles,
+  selectDesktopPackageClosure,
+  type PackedDesktopPackage,
+} from '../scripts/prepare-package-set.ts'
+
+function packed(name: string, manifest: Record<string, unknown> = {}): PackedDesktopPackage {
+  return { tarball: `${name}.tgz`, manifest: { name, version: '1.0.0', ...manifest } }
+}
+
+describe('desktop package-set selection', () => {
+  afterEach(() => {
+    vi.unstubAllEnvs()
+  })
+
+  it('does not select a packaging target when imported as a library', async () => {
+    vi.stubEnv('DSH_DESKTOP_TARGET_PLATFORM', 'linux')
+    vi.stubEnv('DSH_DESKTOP_TARGET_ARCH', 'x64')
+    vi.resetModules()
+    await expect(import('../scripts/prepare-package-set.ts')).resolves.toHaveProperty('prepareDesktopPackageSet')
+  })
+
+  it('includes only the available internal production closure', () => {
+    const available = new Map<string, PackedDesktopPackage>([
+      ['@deepseek-ai/dsh', packed('@deepseek-ai/dsh', {
+        dependencies: { '@deepseek-ai/dsh-base': '^1.0.0', external: '^2.0.0' },
+        optionalDependencies: { '@deepseek-ai/platform-package': '1.0.0', '@deepseek-ai/missing-platform': '1.0.0' },
+      })],
+      ['@deepseek-ai/dsh-desktop-host', packed('@deepseek-ai/dsh-desktop-host', {
+        dependencies: { '@deepseek-ai/dsh': '^1.0.0' },
+      })],
+      ['@deepseek-ai/dsh-base', packed('@deepseek-ai/dsh-base', {
+        peerDependencies: { '@deepseek-ai/cordis': '^1.0.0' },
+      })],
+      ['@deepseek-ai/cordis', packed('@deepseek-ai/cordis')],
+      ['@deepseek-ai/platform-package', packed('@deepseek-ai/platform-package')],
+      ['@deepseek-ai/unused', packed('@deepseek-ai/unused')],
+    ])
+    expect(selectDesktopPackageClosure(available).map(entry => entry.manifest.name)).toEqual([
+      '@deepseek-ai/cordis',
+      '@deepseek-ai/dsh',
+      '@deepseek-ai/dsh-base',
+      '@deepseek-ai/dsh-desktop-host',
+      '@deepseek-ai/platform-package',
+    ])
+  })
+
+  it('rejects a required internal package absent from the packed release inputs', () => {
+    const available = new Map<string, PackedDesktopPackage>([
+      ['@deepseek-ai/dsh', packed('@deepseek-ai/dsh', {
+        dependencies: { '@deepseek-ai/dsh-base': '^1.0.0' },
+      })],
+      ['@deepseek-ai/dsh-desktop-host', packed('@deepseek-ai/dsh-desktop-host', {
+        dependencies: { '@deepseek-ai/dsh': '^1.0.0' },
+      })],
+    ])
+    expect(() => selectDesktopPackageClosure(available)).toThrow(/unpacked internal package/u)
+    expect(() => selectDesktopPackageClosure(new Map([
+      ['@deepseek-ai/dsh', packed('@deepseek-ai/dsh')],
+    ]))).toThrow(/omit @deepseek-ai\/dsh-desktop-host/u)
+  })
+
+  it('requires the Desktop Host entry and its packaged overlay', () => {
+    const files = [
+      'package/lib/index.js',
+      'package/config/desktop.cordis.patch.yml',
+    ]
+    expect(() => {
+      assertDesktopHostPackageFiles(files)
+    }).not.toThrow()
+    expect(() => {
+      assertDesktopHostPackageFiles(files.slice(0, 1))
+    }).toThrow(/desktop\.cordis\.patch\.yml/u)
+    expect(() => {
+      assertDesktopHostPackageFiles(files.slice(1))
+    }).toThrow(/lib\/index\.js/u)
+  })
+})

+ 392 - 0
apps/desktop/tests/project-manager.spec.ts

@@ -0,0 +1,392 @@
+import { createHash } from 'node:crypto'
+import { existsSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs'
+import { tmpdir } from 'node:os'
+import { dirname, join, relative, sep } from 'node:path'
+import { pathToFileURL } from 'node:url'
+import { afterEach, describe, expect, it } from 'vitest'
+import { resolveDesktopPaths } from '../src/paths.ts'
+import {
+  createSeedMetadata,
+  DesktopProjectManager,
+  packageNameFromSpec,
+  verifySeedIntegrity,
+  type DesktopProjectHooks,
+} from '../src/project-manager.ts'
+import { DESKTOP_HOST_PROTOCOL_VERSION } from '../src/host-protocol.ts'
+import { DESKTOP_PACKAGES_DIR, DESKTOP_PACKAGE_SET_FILE } from '../src/core-package-set.ts'
+import type { DesktopRelease } from '../src/release.ts'
+import { archivePnpmStore } from '../src/seed-store.ts'
+
+const roots: string[] = []
+
+function temporaryRoot(): string {
+  const root = mkdtempSync(join(tmpdir(), 'dsh-desktop-test-'))
+  roots.push(root)
+  return root
+}
+
+function writeIntegrity(seed: string): void {
+  const paths: string[] = []
+  const visit = (directory: string): void => {
+    for (const entry of readdirSync(directory, { withFileTypes: true })) {
+      const path = join(directory, entry.name)
+      if (entry.isDirectory()) visit(path)
+      else if (entry.name !== 'integrity.json') paths.push(path)
+    }
+  }
+  visit(seed)
+  const files = paths.sort().map((path) => {
+    const body = readFileSync(path)
+    return {
+      path: relative(seed, path).split(sep).join('/'),
+      bytes: statSync(path).size,
+      sha256: createHash('sha256').update(body).digest('hex'),
+    }
+  })
+  writeFileSync(join(seed, 'integrity.json'), `${JSON.stringify({ schemaVersion: 2, files })}\n`)
+}
+
+function archiveStore(seed: string): void {
+  const store = join(seed, 'store')
+  mkdirSync(store, { recursive: true })
+  if (readdirSync(store).length === 0) writeFileSync(join(store, 'test-entry'), 'content')
+  archivePnpmStore(seed, store)
+}
+
+function writeCorePackageSet(seed: string, version: string): void {
+  const packages = [
+    { name: '@deepseek-ai/dsh', file: `deepseek-ai-dsh-${version}.tgz`, body: Buffer.from(`dsh-${version}`) },
+    {
+      name: '@deepseek-ai/dsh-desktop-host',
+      file: `deepseek-ai-dsh-desktop-host-${version}.tgz`,
+      body: Buffer.from(`desktop-host-${version}`),
+    },
+  ]
+  mkdirSync(join(seed, DESKTOP_PACKAGES_DIR), { recursive: true })
+  for (const entry of packages) writeFileSync(join(seed, DESKTOP_PACKAGES_DIR, entry.file), entry.body)
+  writeFileSync(join(seed, DESKTOP_PACKAGE_SET_FILE), `${JSON.stringify({
+    schemaVersion: 1,
+    packages: packages.map(({ name, file, body }) => ({
+      name,
+      version,
+      file,
+      bytes: body.byteLength,
+      integrity: `sha512-${createHash('sha512').update(body).digest('base64')}`,
+    })),
+  })}\n`)
+}
+
+function createTestSeedMetadata(seed: string, desktopRelease: DesktopRelease): void {
+  writeCorePackageSet(seed, desktopRelease.version)
+  createSeedMetadata(seed, desktopRelease)
+}
+
+function writeFakePnpm(root: string): string {
+  const path = join(root, 'pnpm.mjs')
+  writeFileSync(path, String.raw`
+import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
+import { dirname, join } from 'node:path'
+const args = process.argv.slice(2)
+const project = process.cwd()
+const command = args.find(value => value === 'install' || value === 'add' || value === 'remove')
+const manifestPath = join(project, 'package.json')
+const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
+const packageName = spec => spec.startsWith('@')
+  ? spec.slice(0, spec.indexOf('@', spec.indexOf('/') + 1) === -1 ? undefined : spec.indexOf('@', spec.indexOf('/') + 1))
+  : spec.split('@')[0]
+const packageVersion = spec => {
+  const index = spec.startsWith('@') ? spec.indexOf('@', spec.indexOf('/') + 1) : spec.indexOf('@')
+  return index === -1 ? '1.0.0' : spec.slice(index + 1)
+}
+
+if (command === 'add') {
+  const spec = args[args.indexOf('add') + 1]
+  manifest.dependencies[packageName(spec)] = packageVersion(spec)
+}
+if (command === 'remove') delete manifest.dependencies[args[args.indexOf('remove') + 1]]
+writeFileSync(manifestPath, JSON.stringify(manifest))
+rmSync(join(project, 'node_modules'), { recursive: true, force: true })
+for (const [name, version] of Object.entries(manifest.dependencies)) {
+  const packageRoot = join(project, 'node_modules', ...name.split('/'))
+  mkdirSync(packageRoot, { recursive: true })
+  const core = name === '@deepseek-ai/dsh' || name === '@deepseek-ai/dsh-desktop-host'
+  const plugin = !core
+  const installedVersion = plugin
+    ? version
+    : JSON.parse(readFileSync(join(project, 'desktop-release.json'), 'utf8')).version
+  writeFileSync(join(packageRoot, 'package.json'), JSON.stringify({
+    name, version: installedVersion,
+    ...(plugin ? { dsh: { bundle: { patch: './bundle.yml' } } } : {}),
+  }))
+  if (plugin) writeFileSync(join(packageRoot, 'bundle.yml'), '[]\n')
+  else if (name === '@deepseek-ai/dsh-desktop-host') {
+    mkdirSync(join(packageRoot, 'lib'), { recursive: true })
+    writeFileSync(join(packageRoot, 'lib', 'index.js'), '')
+  }
+}
+writeFileSync(join(project, 'pnpm-lock.yaml'), 'lockfileVersion: 9\n')
+if (process.env.TEST_PNPM_LOG) writeFileSync(process.env.TEST_PNPM_LOG, JSON.stringify({ args, env: process.env }))
+`)
+  return path
+}
+
+function writeBlockingFakePnpm(root: string, ready: string, release: string): string {
+  const path = join(root, 'blocking-pnpm.mjs')
+  const delegate = writeFakePnpm(root)
+  writeFileSync(path, `
+import { existsSync, writeFileSync } from 'node:fs'
+import { setTimeout as sleep } from 'node:timers/promises'
+writeFileSync(${JSON.stringify(ready)}, String(process.pid))
+while (!existsSync(${JSON.stringify(release)})) await sleep(10)
+await import(${JSON.stringify(pathToFileURL(delegate).href)})
+`)
+  return path
+}
+
+function hooks(overrides: Partial<DesktopProjectHooks> = {}): DesktopProjectHooks {
+  return {
+    healthCheck: async () => {},
+    beforeActivate: async () => {},
+    afterActivate: async () => {},
+    ...overrides,
+  }
+}
+
+function release(version = '1.0.0'): DesktopRelease {
+  return {
+    schemaVersion: 1,
+    version,
+    hostProtocolVersion: DESKTOP_HOST_PROTOCOL_VERSION,
+    nodeVersion: '24.17.0',
+    pnpmVersion: '11.7.0',
+  }
+}
+
+afterEach(() => {
+  for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true })
+})
+
+describe('desktop package policy', () => {
+  it('accepts registry package specs but rejects alternate sources and flags', () => {
+    expect(packageNameFromSpec('@scope/plugin@1.2.3')).toBe('@scope/plugin')
+    expect(packageNameFromSpec('plugin@next')).toBe('plugin')
+    expect(() => packageNameFromSpec('file:../plugin')).toThrow(/unsupported npm package spec/u)
+    expect(() => packageNameFromSpec('--registry=evil')).toThrow(/unsupported npm package spec/u)
+    expect(() => packageNameFromSpec('https://example.test/plugin.tgz')).toThrow(/unsupported npm package spec/u)
+  })
+
+  it('rejects any seed content changed after release inventory generation', () => {
+    const seed = join(temporaryRoot(), 'seed')
+    createTestSeedMetadata(seed, release())
+    writeFileSync(join(seed, 'pnpm-lock.yaml'), 'lockfileVersion: 9\n')
+    writeIntegrity(seed)
+    expect(() => { verifySeedIntegrity(seed) }).not.toThrow()
+    writeFileSync(join(seed, 'package.json'), '{}\n')
+    expect(() => { verifySeedIntegrity(seed) }).toThrow(/integrity verification failed/u)
+  })
+})
+
+describe('desktop project transactions', () => {
+  it('installs the offline seed and reconciles a mismatched private Host', async () => {
+    const root = temporaryRoot()
+    const seed = join(root, 'seed')
+    const log = join(root, 'pnpm-log.json')
+    createTestSeedMetadata(seed, release())
+    writeFileSync(join(seed, 'pnpm-lock.yaml'), 'lockfileVersion: 9\n')
+    mkdirSync(join(seed, 'store'), { recursive: true })
+    writeFileSync(join(seed, 'store', 'seed-entry'), 'content')
+    archiveStore(seed)
+    writeIntegrity(seed)
+    const paths = resolveDesktopPaths(join(root, '.dsh'))
+    const manager = new DesktopProjectManager(paths, { node: process.execPath, pnpm: writeFakePnpm(root) })
+    const previousLog = process.env.TEST_PNPM_LOG
+    const previousRegistry = process.env.npm_config_registry
+    process.env.TEST_PNPM_LOG = log
+    process.env.npm_config_registry = 'https://user-registry.invalid'
+    try {
+      await expect(manager.applyRelease(seed, '2.0.0', hooks())).rejects.toThrow(/does not match Electron/u)
+      await manager.applyRelease(seed, '1.0.0', hooks())
+      writeFileSync(
+        join(paths.profile, 'node_modules', '@deepseek-ai', 'dsh-desktop-host', 'package.json'),
+        '{"name":"@deepseek-ai/dsh-desktop-host","version":"0.9.0"}\n',
+      )
+      await expect(manager.applyRelease(seed, '1.0.0', hooks())).resolves.toBe(true)
+    } finally {
+      if (previousLog === undefined) delete process.env.TEST_PNPM_LOG
+      else process.env.TEST_PNPM_LOG = previousLog
+      if (previousRegistry === undefined) delete process.env.npm_config_registry
+      else process.env.npm_config_registry = previousRegistry
+    }
+    expect(manager.dshVersion()).toBe('1.0.0')
+    expect(manager.releaseVersion()).toBe('1.0.0')
+    expect(paths.profile).toBe(join(root, '.dsh', 'profiles', 'desktop'))
+    expect(existsSync(join(paths.profile, 'node_modules', '@deepseek-ai', 'dsh'))).toBe(true)
+    const installedHost = JSON.parse(readFileSync(
+      join(paths.profile, 'node_modules', '@deepseek-ai', 'dsh-desktop-host', 'package.json'),
+      'utf8',
+    )) as { version: string }
+    expect(installedHost.version).toBe('1.0.0')
+    expect(existsSync(join(paths.profile, 'desktop-plugins.json'))).toBe(false)
+    expect(readFileSync(join(paths.pnpm.store, 'seed-entry'), 'utf8')).toBe('content')
+    const invocation = JSON.parse(readFileSync(log, 'utf8')) as { args: string[]; env: Record<string, string> }
+    expect(invocation.args).toContain('--offline')
+    expect(invocation.args).toContain('--trust-lockfile')
+    expect(invocation.args).toContain(`--config.store-dir=${paths.pnpm.store}`)
+    expect(invocation.args).toContain('--config.enable-global-virtual-store=false')
+    expect(invocation.args).toContain('--config.registry=https://registry.npmjs.org/')
+    expect(invocation.env.NPM_CONFIG_REGISTRY).toBe('https://registry.npmjs.org/')
+    expect(invocation.env.NPM_CONFIG_STORE_DIR).toBe(paths.pnpm.store)
+    expect(invocation.env.NPM_CONFIG_USERCONFIG).toBe(join(paths.pnpm.config, 'npmrc'))
+    expect(invocation.env.npm_config_registry).toBeUndefined()
+  })
+
+  it('restores the active project when the replacement backend cannot start', async () => {
+    const root = temporaryRoot()
+    const seed = join(root, 'seed')
+    createTestSeedMetadata(seed, release())
+    writeFileSync(join(seed, 'pnpm-lock.yaml'), 'lockfileVersion: 9\n')
+    archiveStore(seed)
+    writeIntegrity(seed)
+    const paths = resolveDesktopPaths(join(root, '.dsh'))
+    const manager = new DesktopProjectManager(paths, { node: process.execPath, pnpm: writeFakePnpm(root) })
+    await manager.applyRelease(seed, '1.0.0', hooks())
+    let starts = 0
+    await expect(manager.mutate({ type: 'plugin-add', spec: '@scope/plugin@2.0.0' }, hooks({
+      afterActivate: async () => {
+        starts += 1
+        if (starts === 1) throw new Error('backend rejected staged graph')
+      },
+    }))).rejects.toThrow(/backend rejected staged graph/u)
+    expect(manager.listPlugins()).toEqual([])
+    expect(manager.dshVersion()).toBe('1.0.0')
+    expect(starts).toBe(2)
+  })
+
+  it('restores rollback when the active move completed before its journal update', async () => {
+    const root = temporaryRoot()
+    const seed = join(root, 'seed')
+    createTestSeedMetadata(seed, release())
+    writeFileSync(join(seed, 'pnpm-lock.yaml'), 'lockfileVersion: 9\n')
+    archiveStore(seed)
+    writeIntegrity(seed)
+    const paths = resolveDesktopPaths(join(root, '.dsh'))
+    const manager = new DesktopProjectManager(paths, { node: process.execPath, pnpm: writeFakePnpm(root) })
+    await manager.applyRelease(seed, '1.0.0', hooks())
+    await manager.mutate({ type: 'plugin-add', spec: '@scope/plugin@2.0.0' }, hooks())
+    const stagingProfile = join(paths.staging, 'interrupted', 'profile')
+    mkdirSync(stagingProfile, { recursive: true })
+    writeFileSync(join(stagingProfile, 'marker'), 'staging')
+    rmSync(paths.rollback, { recursive: true, force: true })
+    mkdirSync(dirname(paths.rollback), { recursive: true })
+    renameSync(paths.profile, paths.rollback)
+    writeFileSync(paths.pending, `${JSON.stringify({
+      schemaVersion: 1,
+      id: 'interrupted',
+      stagingProfile,
+      step: 'prepared',
+    })}\n`)
+
+    manager.recover()
+
+    expect(manager.listPlugins()).toEqual([{ name: '@scope/plugin', version: '2.0.0' }])
+    expect(existsSync(stagingProfile)).toBe(false)
+    expect(existsSync(paths.pending)).toBe(false)
+  })
+
+  it('records the live pnpm worker as transaction owner until it exits', async () => {
+    const root = temporaryRoot()
+    const seed = join(root, 'seed')
+    const ready = join(root, 'pnpm-ready')
+    const releaseWorker = join(root, 'pnpm-release')
+    createTestSeedMetadata(seed, release())
+    writeFileSync(join(seed, 'pnpm-lock.yaml'), 'lockfileVersion: 9\n')
+    archiveStore(seed)
+    writeIntegrity(seed)
+    const paths = resolveDesktopPaths(join(root, '.dsh'))
+    const runtime = { node: process.execPath, pnpm: writeBlockingFakePnpm(root, ready, releaseWorker) }
+    const manager = new DesktopProjectManager(paths, runtime)
+    const installing = manager.applyRelease(seed, '1.0.0', hooks())
+    await expect.poll(() => existsSync(ready)).toBe(true)
+    const workerPid = Number.parseInt(readFileSync(ready, 'utf8'), 10)
+    expect(readFileSync(paths.lock, 'utf8')).toBe(`${String(workerPid)}\n`)
+    const competing = new DesktopProjectManager(paths, runtime)
+    await expect(competing.applyRelease(seed, '1.0.0', hooks())).rejects.toThrow(/another package transaction is active/u)
+    writeFileSync(releaseWorker, 'continue')
+    await expect(installing).resolves.toBe(true)
+    expect(existsSync(paths.lock)).toBe(false)
+  })
+
+  it('keeps core packages local while installing plugins from the desktop registry', async () => {
+    const root = temporaryRoot()
+    const seed = join(root, 'seed')
+    const log = join(root, 'pnpm-log.json')
+    createTestSeedMetadata(seed, release())
+    writeFileSync(join(seed, 'pnpm-lock.yaml'), 'lockfileVersion: 9\n')
+    archiveStore(seed)
+    writeIntegrity(seed)
+    const paths = resolveDesktopPaths(join(root, '.dsh'))
+    const manager = new DesktopProjectManager(paths, { node: process.execPath, pnpm: writeFakePnpm(root) })
+    await manager.applyRelease(seed, '1.0.0', hooks())
+    const previousLog = process.env.TEST_PNPM_LOG
+    process.env.TEST_PNPM_LOG = log
+    try {
+      await manager.mutate({ type: 'plugin-add', spec: '@scope/plugin@2.0.0' }, hooks())
+    } finally {
+      if (previousLog === undefined) delete process.env.TEST_PNPM_LOG
+      else process.env.TEST_PNPM_LOG = previousLog
+    }
+
+    const manifest = JSON.parse(readFileSync(join(paths.profile, 'package.json'), 'utf8')) as {
+      dependencies: Record<string, string>
+    }
+    const coreSpec = manifest.dependencies['@deepseek-ai/dsh']
+    expect(coreSpec).toMatch(/^file:\.\/desktop-packages\//u)
+    expect(readFileSync(join(paths.profile, 'pnpm-workspace.yaml'), 'utf8'))
+      .toContain(`${JSON.stringify('@deepseek-ai/dsh')}: ${JSON.stringify(coreSpec)}`)
+    expect(manifest.dependencies['@scope/plugin']).toBe('2.0.0')
+    const invocation = JSON.parse(readFileSync(log, 'utf8')) as { args: string[]; env: Record<string, string> }
+    expect(invocation.args).toContain('add')
+    expect(invocation.args).toContain('@scope/plugin@2.0.0')
+    expect(invocation.args).toContain('--config.registry=https://registry.npmjs.org/')
+    expect(invocation.env.NPM_CONFIG_REGISTRY).toBe('https://registry.npmjs.org/')
+  })
+
+  it('reconciles dsh to the packaged release without removing desktop plugins', async () => {
+    const root = temporaryRoot()
+    const paths = resolveDesktopPaths(join(root, '.dsh'))
+    const manager = new DesktopProjectManager(paths, { node: process.execPath, pnpm: writeFakePnpm(root) })
+    const firstSeed = join(root, 'seed-1')
+    createTestSeedMetadata(firstSeed, release('1.0.0'))
+    writeFileSync(join(firstSeed, 'pnpm-lock.yaml'), 'lockfileVersion: 9\n')
+    mkdirSync(join(firstSeed, 'store'), { recursive: true })
+    writeFileSync(join(firstSeed, 'store', 'release-1'), 'one')
+    archiveStore(firstSeed)
+    writeIntegrity(firstSeed)
+    await manager.applyRelease(firstSeed, '1.0.0', hooks())
+    await manager.mutate({ type: 'plugin-add', spec: '@scope/plugin@2.0.0' }, hooks())
+
+    const nextSeed = join(root, 'seed-2')
+    createTestSeedMetadata(nextSeed, release('1.1.0'))
+    writeFileSync(join(nextSeed, 'pnpm-lock.yaml'), 'lockfileVersion: 9\n')
+    mkdirSync(join(nextSeed, 'store'), { recursive: true })
+    writeFileSync(join(nextSeed, 'store', 'release-2'), 'two')
+    archiveStore(nextSeed)
+    writeIntegrity(nextSeed)
+
+    await expect(manager.applyRelease(nextSeed, '1.1.0', hooks())).resolves.toBe(true)
+    expect(manager.releaseVersion()).toBe('1.1.0')
+    expect(manager.dshVersion()).toBe('1.1.0')
+    expect(manager.listPlugins()).toEqual([{ name: '@scope/plugin', version: '2.0.0' }])
+    const profile = JSON.parse(readFileSync(join(paths.profile, 'package.json'), 'utf8')) as {
+      dsh: { profile: { bundles: string[] } }
+    }
+    expect(profile.dsh.profile.bundles).toEqual([
+      '@deepseek-ai/dsh-base',
+      '@deepseek-ai/dsh-web-app',
+      '@scope/plugin',
+    ])
+    expect(readFileSync(join(paths.pnpm.store, 'release-1'), 'utf8')).toBe('one')
+    expect(readFileSync(join(paths.pnpm.store, 'release-2'), 'utf8')).toBe('two')
+    await expect(manager.applyRelease(nextSeed, '1.1.0', hooks())).resolves.toBe(false)
+  })
+})

+ 167 - 0
apps/desktop/tests/seed-store.spec.ts

@@ -0,0 +1,167 @@
+import {
+  chmodSync,
+  existsSync,
+  mkdirSync,
+  mkdtempSync,
+  readFileSync,
+  readdirSync,
+  rmSync,
+  statSync,
+  utimesSync,
+  writeFileSync,
+} from 'node:fs'
+import { tmpdir } from 'node:os'
+import { join } from 'node:path'
+import { DatabaseSync } from 'node:sqlite'
+import { afterEach, describe, expect, it } from 'vitest'
+import {
+  archivePnpmStore,
+  extractPnpmStoreArchives,
+  mergePnpmStore,
+  removePnpmProjectRegistrations,
+  SEED_STORE_ARCHIVE_DIR,
+  SEED_STORE_ARCHIVE_MANIFEST,
+} from '../src/seed-store.ts'
+
+const temporaryRoots: string[] = []
+
+function temporaryRoot(): string {
+  const root = mkdtempSync(join(tmpdir(), 'dsh-desktop-store-'))
+  temporaryRoots.push(root)
+  return root
+}
+
+function archiveBytes(seed: string): readonly { path: string; body: Buffer }[] {
+  return [SEED_STORE_ARCHIVE_MANIFEST, ...readdirSync(join(seed, SEED_STORE_ARCHIVE_DIR))]
+    .map(path => ({
+      path,
+      body: readFileSync(path === SEED_STORE_ARCHIVE_MANIFEST
+        ? join(seed, path)
+        : join(seed, SEED_STORE_ARCHIVE_DIR, path)),
+    }))
+}
+
+afterEach(() => {
+  for (const root of temporaryRoots.splice(0)) rmSync(root, { recursive: true, force: true })
+})
+
+describe('desktop seed store cleanup', () => {
+  it('removes project registrations without removing package data', () => {
+    const storeRoot = temporaryRoot()
+    mkdirSync(join(storeRoot, 'v11', 'projects', 'temporary-project'), { recursive: true })
+    mkdirSync(join(storeRoot, 'v12', 'projects'), { recursive: true })
+    mkdirSync(join(storeRoot, 'metadata', 'projects'), { recursive: true })
+    writeFileSync(join(storeRoot, 'v11', 'package-data'), 'package')
+
+    removePnpmProjectRegistrations(storeRoot)
+
+    expect(existsSync(join(storeRoot, 'v11', 'projects'))).toBe(false)
+    expect(existsSync(join(storeRoot, 'v12', 'projects'))).toBe(false)
+    expect(existsSync(join(storeRoot, 'v11', 'package-data'))).toBe(true)
+    expect(existsSync(join(storeRoot, 'metadata', 'projects'))).toBe(true)
+  })
+})
+
+describe('desktop seed store merge', () => {
+  it('preserves installed package records while the verified seed replaces matching records and files', () => {
+    const root = temporaryRoot()
+    const source = join(root, 'source')
+    const destination = join(root, 'destination')
+    for (const store of [source, destination]) {
+      mkdirSync(join(store, 'v11', 'files'), { recursive: true })
+      const database = new DatabaseSync(join(store, 'v11', 'index.db'))
+      database.exec('CREATE TABLE package_index (key TEXT PRIMARY KEY, data BLOB NOT NULL) WITHOUT ROWID')
+      const insert = database.prepare('INSERT INTO package_index (key, data) VALUES (?, ?)')
+      if (store === source) {
+        insert.run('seed-only', Buffer.from('seed'))
+        insert.run('shared', Buffer.from('new'))
+      } else {
+        insert.run('plugin-only', Buffer.from('plugin'))
+        insert.run('shared', Buffer.from('old'))
+      }
+      database.close()
+    }
+    writeFileSync(join(source, 'v11', 'files', 'shared'), 'new')
+    writeFileSync(join(destination, 'v11', 'files', 'shared'), 'old')
+    writeFileSync(join(destination, 'v11', 'files', 'plugin'), 'plugin')
+
+    mergePnpmStore(source, destination)
+
+    const database = new DatabaseSync(join(destination, 'v11', 'index.db'), { readOnly: true })
+    const records = database.prepare('SELECT key, data FROM package_index ORDER BY key').all() as {
+      key: string
+      data: Uint8Array
+    }[]
+    database.close()
+    expect(records.map(record => [record.key, Buffer.from(record.data).toString()])).toEqual([
+      ['plugin-only', 'plugin'],
+      ['seed-only', 'seed'],
+      ['shared', 'new'],
+    ])
+    expect(readFileSync(join(destination, 'v11', 'files', 'shared'), 'utf8')).toBe('new')
+    expect(readFileSync(join(destination, 'v11', 'files', 'plugin'), 'utf8')).toBe('plugin')
+  })
+})
+
+describe('desktop seed store archives', () => {
+  it('extracts package bytes and executable modes without retaining loose seed files', () => {
+    const root = temporaryRoot()
+    const seed = join(root, 'seed')
+    const store = join(seed, 'store')
+    const executable = join(store, 'v10', 'files', 'native-addon')
+    mkdirSync(join(store, 'v10', 'files'), { recursive: true })
+    writeFileSync(executable, 'native')
+    chmodSync(executable, 0o755)
+    writeFileSync(join(store, 'v10', 'files', 'package-data'), 'package')
+
+    archivePnpmStore(seed, store)
+    const destination = join(root, 'extracted')
+    extractPnpmStoreArchives(seed, destination)
+
+    expect(existsSync(store)).toBe(false)
+    expect(readFileSync(join(destination, 'v10', 'files', 'package-data'), 'utf8')).toBe('package')
+    if (process.platform !== 'win32') {
+      expect(statSync(join(destination, 'v10', 'files', 'native-addon')).mode & 0o111).toBe(0o111)
+    }
+  })
+
+  it('produces identical shards for identical paths, bytes, and modes', () => {
+    const root = temporaryRoot()
+    const seeds = [join(root, 'first'), join(root, 'second')]
+    for (const [index, seed] of seeds.entries()) {
+      const store = join(seed, 'store')
+      mkdirSync(join(store, 'nested'), { recursive: true })
+      const paths = index === 0 ? ['alpha', 'nested/beta'] : ['nested/beta', 'alpha']
+      for (const path of paths) {
+        const target = join(store, path)
+        writeFileSync(target, path)
+        utimesSync(target, new Date(index * 10_000), new Date(index * 20_000))
+      }
+      archivePnpmStore(seed, store)
+    }
+
+    const first = archiveBytes(seeds[0] as string)
+    const second = archiveBytes(seeds[1] as string)
+    expect(second.map(entry => entry.path)).toEqual(first.map(entry => entry.path))
+    expect(second.map(entry => entry.body)).toEqual(first.map(entry => entry.body))
+  })
+
+  it('rejects an archive whose entry count differs from the manifest', () => {
+    const root = temporaryRoot()
+    const seed = join(root, 'seed')
+    const store = join(seed, 'store')
+    mkdirSync(store, { recursive: true })
+    writeFileSync(join(store, 'package-data'), 'package')
+    archivePnpmStore(seed, store)
+    const manifestPath = join(seed, SEED_STORE_ARCHIVE_MANIFEST)
+    const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as {
+      archives: { entries: number }[]
+    }
+    const archive = manifest.archives[0]
+    if (archive === undefined) throw new Error('test seed has no archive')
+    archive.entries += 1
+    writeFileSync(manifestPath, JSON.stringify(manifest))
+
+    expect(() => { extractPnpmStoreArchives(seed, join(root, 'extracted')) }).toThrow(/unexpected entry count/u)
+  })
+})

+ 32 - 0
apps/desktop/tests/single-instance.spec.ts

@@ -0,0 +1,32 @@
+import { describe, expect, it, vi } from 'vitest'
+import { claimDesktopSingleInstance, type DesktopSingleInstanceApplication } from '../src/single-instance.ts'
+
+describe('desktop single-instance ownership', () => {
+  it('quits a second process without registering lifecycle work', () => {
+    const quit = vi.fn()
+    const on = vi.fn()
+    const application = {
+      requestSingleInstanceLock: () => false,
+      quit,
+      on,
+    } satisfies DesktopSingleInstanceApplication
+
+    expect(claimDesktopSingleInstance(application, vi.fn())).toBe(false)
+    expect(quit).toHaveBeenCalledOnce()
+    expect(on).not.toHaveBeenCalled()
+  })
+
+  it('routes a later launch to the primary process', () => {
+    let secondInstance: (() => void) | undefined
+    const focus = vi.fn()
+    const application = {
+      requestSingleInstanceLock: () => true,
+      quit: vi.fn(),
+      on: vi.fn((_event: 'second-instance', listener: () => void) => { secondInstance = listener }),
+    } satisfies DesktopSingleInstanceApplication
+
+    expect(claimDesktopSingleInstance(application, focus)).toBe(true)
+    secondInstance?.()
+    expect(focus).toHaveBeenCalledOnce()
+  })
+})

+ 102 - 0
apps/desktop/tests/update-coordinator.spec.ts

@@ -0,0 +1,102 @@
+import { describe, expect, it, vi } from 'vitest'
+import type { AppUpdater } from 'electron-updater'
+import { DESKTOP_HOST_PROTOCOL_VERSION } from '../src/host-protocol.ts'
+import { parseDesktopRelease } from '../src/release.ts'
+import type { DesktopUpdateState } from '../src/ipc.ts'
+
+vi.mock('electron', () => ({ app: { isPackaged: false } }))
+vi.mock('electron-updater', () => ({
+  default: { autoUpdater: { autoDownload: true, autoInstallOnAppQuit: true } },
+}))
+
+const { DesktopUpdateCoordinator } = await import('../src/update-coordinator.ts')
+
+describe('desktop release metadata', () => {
+  it('accepts one exact release identity for Electron and dsh', () => {
+    expect(parseDesktopRelease({
+      schemaVersion: 1,
+      version: '1.2.3',
+      hostProtocolVersion: DESKTOP_HOST_PROTOCOL_VERSION,
+      nodeVersion: '24.17.0',
+      pnpmVersion: '11.7.0',
+    })).toEqual({
+      schemaVersion: 1,
+      version: '1.2.3',
+      hostProtocolVersion: DESKTOP_HOST_PROTOCOL_VERSION,
+      nodeVersion: '24.17.0',
+      pnpmVersion: '11.7.0',
+    })
+  })
+
+  it('rejects invalid versions and unsupported host protocols', () => {
+    const base = {
+      schemaVersion: 1,
+      version: '1.2.3',
+      hostProtocolVersion: DESKTOP_HOST_PROTOCOL_VERSION,
+      nodeVersion: '24.17.0',
+      pnpmVersion: '11.7.0',
+    }
+    expect(() => parseDesktopRelease({ ...base, version: 'latest' })).toThrow(/invalid desktop release metadata/u)
+    expect(() => parseDesktopRelease({ ...base, hostProtocolVersion: 999 })).toThrow(/invalid desktop release metadata/u)
+  })
+})
+
+describe('desktop update coordinator', () => {
+  it('installs one Electron release and restarts after download', async () => {
+    const states: DesktopUpdateState[] = []
+    const downloadUpdate = vi.fn(async () => [])
+    const quitAndInstall = vi.fn()
+    const beforeRestart = vi.fn(async () => {})
+    const updater = {
+      autoDownload: true,
+      autoInstallOnAppQuit: true,
+      checkForUpdates: vi.fn(async () => ({
+        isUpdateAvailable: true,
+        updateInfo: { version: '1.1.0' },
+      })),
+      downloadUpdate,
+      quitAndInstall,
+    } as unknown as AppUpdater
+    const coordinator = new DesktopUpdateCoordinator(
+      (state) => {
+        states.push(state)
+        return state
+      },
+      beforeRestart,
+      updater,
+      () => true,
+    )
+
+    await expect(coordinator.check()).resolves.toEqual({ phase: 'available', version: '1.1.0' })
+    await expect(coordinator.install()).resolves.toEqual({ phase: 'ready', version: '1.1.0' })
+    expect(downloadUpdate).toHaveBeenCalledOnce()
+    expect(beforeRestart).toHaveBeenCalledOnce()
+    expect(quitAndInstall).toHaveBeenCalledWith(false, true)
+    expect(states.map(state => state.phase)).toEqual(['checking', 'available', 'installing', 'ready'])
+  })
+
+  it('queues install behind an in-flight check instead of returning the check result', async () => {
+    const checked = Promise.withResolvers<{
+      isUpdateAvailable: true
+      updateInfo: { version: string }
+    }>()
+    const downloadUpdate = vi.fn(async () => [])
+    const updater = {
+      autoDownload: true,
+      autoInstallOnAppQuit: true,
+      checkForUpdates: vi.fn(() => checked.promise),
+      downloadUpdate,
+      quitAndInstall: vi.fn(),
+    } as unknown as AppUpdater
+    const coordinator = new DesktopUpdateCoordinator(state => state, async () => {}, updater, () => true)
+
+    const checking = coordinator.check()
+    const installing = coordinator.install()
+    expect(downloadUpdate).not.toHaveBeenCalled()
+    checked.resolve({ isUpdateAvailable: true, updateInfo: { version: '1.2.0' } })
+
+    await expect(checking).resolves.toEqual({ phase: 'available', version: '1.2.0' })
+    await expect(installing).resolves.toEqual({ phase: 'ready', version: '1.2.0' })
+    expect(downloadUpdate).toHaveBeenCalledOnce()
+  })
+})

+ 226 - 0
apps/desktop/tests/windows-sign.spec.ts

@@ -0,0 +1,226 @@
+import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
+import { tmpdir } from 'node:os'
+import { join, resolve } from 'node:path'
+import { describe, expect, it, vi } from 'vitest'
+import {
+  buildWindowsSigningEnvironment,
+  createRedactedWindowsSigningError,
+  createWindowsTokenSigner,
+  installWindowsNsisBootstrapSigner,
+  repairDanglingAuthenticodeDirectory,
+  scrubWindowsSigningEnvironment,
+} from '../scripts/windows-sign.mjs'
+
+vi.mock('node:crypto', () => ({
+  X509Certificate: class {
+    readonly ca = false
+    readonly keyUsage = ['1.3.6.1.5.5.7.3.3']
+
+    constructor(contents: Buffer) {
+      if (contents.toString('utf8') !== 'code-signing-certificate-fixture') {
+        throw new Error('invalid test certificate')
+      }
+    }
+  },
+}))
+
+const CERTIFICATE_FILE = 'C:\\release\\server.cer'
+const SIGN_SCRIPT = resolve(import.meta.dirname, '../scripts/windows-sign.cmd')
+
+describe('Windows token signing', () => {
+  it('passes only the validated BAT fields to the signing command interpreter', () => {
+    expect(buildWindowsSigningEnvironment({
+      SystemRoot: 'C:\\Windows',
+      DSH_DESKTOP_WINDOWS_TOKEN_PIN: 'inherited-token-secret',
+      DEEPSEEK_API_KEY: 'api-secret',
+      BUILD_PASSWORD: 'build-secret',
+    }, {
+      certificateFile: CERTIFICATE_FILE,
+      signTool: 'C:\\tools\\signtool.exe',
+      path: 'C:\\release\\DeepSeek Harness.exe',
+      isNest: false,
+      tokenPin: 'token-secret!',
+      keyContainer: 'te-container',
+    })).toEqual({
+      SystemRoot: 'C:\\Windows',
+      DSH_DESKTOP_WINDOWS_SIGNTOOL: 'C:\\tools\\signtool.exe',
+      DSH_DESKTOP_WINDOWS_CER_FILE: CERTIFICATE_FILE,
+      DSH_DESKTOP_WINDOWS_TOKEN_PIN: 'token-secret!',
+      DSH_DESKTOP_WINDOWS_KEY_CONTAINER: 'te-container',
+      DSH_DESKTOP_WINDOWS_SIGN_TARGET: 'C:\\release\\DeepSeek Harness.exe',
+      DSH_DESKTOP_WINDOWS_SIGN_APPEND: '',
+    })
+  })
+
+  it('requests an appended signature only for an electron-builder nested task', () => {
+    expect(buildWindowsSigningEnvironment({}, {
+      certificateFile: CERTIFICATE_FILE,
+      signTool: 'C:\\tools\\signtool.exe',
+      path: 'C:\\release\\setup.exe',
+      isNest: true,
+      tokenPin: 'token-secret!',
+      keyContainer: 'te-container',
+    }).DSH_DESKTOP_WINDOWS_SIGN_APPEND).toBe('1')
+  })
+
+  it('keeps the verified SafeNet command in an ASCII CRLF CMD file', async () => {
+    const contents = await readFile(SIGN_SCRIPT)
+    const text = contents.toString('ascii')
+    expect(contents.every(byte => byte <= 0x7F)).toBe(true)
+    expect(text).toContain('\r\n')
+    expect(text.replaceAll('\r\n', '')).not.toContain('\n')
+    expect(text).toContain('setlocal DisableDelayedExpansion\r\n')
+    expect(text).toContain('set "DSH_DESKTOP_WINDOWS_CER_FILE="\r\n')
+    expect(text).toContain('set "DSH_DESKTOP_WINDOWS_TOKEN_PIN="\r\n')
+    expect(text).toContain('"%signTool%" sign /v /fd sha256 /f "%certificateFile%" /kc "[{{%tokenPin%}}]=%keyContainer%" /csp "eToken Base Cryptographic Provider" %appendSignature% /tr http://timestamp.digicert.com /td sha256 "%targetFile%"\r\n')
+  })
+
+  it('rejects incomplete signing identities and non-SHA-256 signing tasks', async () => {
+    const directory = await mkdtemp(join(tmpdir(), 'dsh-windows-sign-tool-'))
+    const certificateFile = join(directory, 'server.cer')
+    const signTool = join(directory, 'signtool.exe')
+    await writeFile(certificateFile, 'code-signing-certificate-fixture')
+    await writeFile(signTool, 'fixture')
+    expect(() => createWindowsTokenSigner({
+      certificateFile: undefined,
+      signTool,
+      tokenPin: 'token-secret!',
+      keyContainer: 'te-container',
+    })).toThrow(/DSH_DESKTOP_WINDOWS_CER_FILE/u)
+    expect(() => createWindowsTokenSigner({
+      certificateFile,
+      signTool: undefined,
+      tokenPin: 'token-secret!',
+      keyContainer: 'te-container',
+    })).toThrow(/DSH_DESKTOP_WINDOWS_SIGNTOOL/u)
+    const signer = createWindowsTokenSigner({
+      certificateFile,
+      signTool,
+      tokenPin: 'token-secret!',
+      keyContainer: 'te-container',
+    })
+    try {
+      expect(() => createWindowsTokenSigner({
+        certificateFile,
+        signTool,
+        tokenPin: 'token-secret!',
+      })).toThrow(/DSH_DESKTOP_WINDOWS_KEY_CONTAINER/u)
+      expect(() => createWindowsTokenSigner({
+        certificateFile,
+        signTool,
+        tokenPin: '',
+        keyContainer: 'te-container',
+      })).toThrow(/DSH_DESKTOP_WINDOWS_TOKEN_PIN/u)
+      expect(() => createWindowsTokenSigner({
+        certificateFile,
+        signTool,
+        tokenPin: 'token]secret',
+        keyContainer: 'te-container',
+      })).toThrow(/cannot contain/u)
+      await expect(signer({
+        path: 'C:\\release\\setup.exe',
+        hash: 'sha1',
+        isNest: false,
+      })).rejects.toThrow(/requires SHA-256/u)
+    }
+    finally {
+      await rm(directory, { recursive: true })
+    }
+  })
+
+  it('removes inherited credentials and redacts SignTool process failures', () => {
+    expect(scrubWindowsSigningEnvironment({
+      SystemRoot: 'C:\\Windows',
+      DSH_DESKTOP_WINDOWS_CER_FILE: 'C:\\release\\server.cer',
+      DSH_DESKTOP_WINDOWS_SIGNTOOL: 'C:\\tools\\signtool.exe',
+      DSH_DESKTOP_WINDOWS_TOKEN_PIN: 'token-secret',
+      DEEPSEEK_API_KEY: 'api-secret',
+      BUILD_PASSWORD: 'build-secret',
+    })).toEqual({ SystemRoot: 'C:\\Windows' })
+
+    const processError = Object.assign(new Error('failed'), {
+      code: 1,
+      cmd: 'signtool /kc [{{token-secret}}]=te-container',
+      stderr: 'provider rejected token-secret',
+    })
+    const failure = createRedactedWindowsSigningError(
+      processError,
+      'C:\\release\\setup.exe',
+      ['token-secret'],
+    )
+    expect(failure.message).toBe('Windows release signing failed for C:\\release\\setup.exe (exit 1): provider rejected <redacted>')
+    expect(failure.message).not.toContain('token-secret')
+    expect(failure).not.toHaveProperty('cause')
+    expect(failure).not.toHaveProperty('cmd')
+  })
+
+  it('signs the temporary NSIS executable before enterprise policy evaluates it', async () => {
+    const events: string[] = []
+    let receivedEnvironment: NodeJS.ProcessEnv | undefined
+    class FakeWineVmManager {
+      async exec(
+        file: string,
+        _args: string[],
+        options?: { env?: NodeJS.ProcessEnv },
+      ): Promise<string> {
+        events.push(`exec:${file}`)
+        receivedEnvironment = options?.env
+        return 'executed'
+      }
+    }
+    installWindowsNsisBootstrapSigner({
+      sign: async (configuration) => {
+        events.push(`sign:${configuration.path}:${configuration.hash}:${String(configuration.isNest)}`)
+      },
+      wineVmManager: FakeWineVmManager,
+      platform: 'win32',
+      environment: {
+        SystemRoot: 'C:\\Windows',
+        DSH_DESKTOP_WINDOWS_TOKEN_PIN: 'token-secret',
+      },
+    })
+
+    const result = await new FakeWineVmManager().exec('C:\\release\\setup.exe', [], {
+      env: {
+        __COMPAT_LAYER: 'RunAsInvoker',
+        BUILD_PASSWORD: 'build-secret',
+      },
+    })
+
+    expect(result).toBe('executed')
+    expect(events).toEqual([
+      'sign:C:\\release\\setup.exe:sha256:false',
+      'exec:C:\\release\\setup.exe',
+    ])
+    expect(receivedEnvironment).toEqual({
+      SystemRoot: 'C:\\Windows',
+      __COMPAT_LAYER: 'RunAsInvoker',
+    })
+  })
+
+  it('clears a certificate table inherited beyond the generated uninstaller', async () => {
+    const directory = await mkdtemp(join(tmpdir(), 'dsh-windows-sign-'))
+    const path = join(directory, 'uninstaller.exe')
+    const executable = Buffer.alloc(512)
+    const peOffset = 216
+    const optionalHeaderOffset = peOffset + 24
+    const certificateDirectoryOffset = optionalHeaderOffset + 96 + (4 * 8)
+    executable.write('MZ', 0, 'ascii')
+    executable.writeUInt32LE(peOffset, 60)
+    executable.write('PE\0\0', peOffset, 'ascii')
+    executable.writeUInt16LE(0x10B, optionalHeaderOffset)
+    executable.writeUInt32LE(600, certificateDirectoryOffset)
+    executable.writeUInt32LE(100, certificateDirectoryOffset + 4)
+    await writeFile(path, executable)
+    try {
+      await expect(repairDanglingAuthenticodeDirectory(path)).resolves.toBe(true)
+      const repaired = await readFile(path)
+      expect(repaired.readUInt32LE(certificateDirectoryOffset)).toBe(0)
+      expect(repaired.readUInt32LE(certificateDirectoryOffset + 4)).toBe(0)
+      await expect(repairDanglingAuthenticodeDirectory(path)).resolves.toBe(false)
+    }
+    finally {
+      await rm(directory, { recursive: true })
+    }
+  })
+})

+ 11 - 0
apps/desktop/tsconfig.json

@@ -0,0 +1,11 @@
+{
+  "extends": "../../tsconfig.base.json",
+  "compilerOptions": {
+    "rootDir": "src",
+    "outDir": "lib/types"
+  },
+  "include": ["src"],
+  "references": [
+    { "path": "../../packages/util/home-paths" }
+  ]
+}

+ 30 - 0
apps/desktop/tsdown.config.ts

@@ -0,0 +1,30 @@
+import { defineConfig } from 'tsdown'
+
+export default defineConfig([
+  {
+    entry: ['lib/types/main.js'],
+    outDir: 'lib',
+    format: ['esm'],
+    platform: 'node',
+    target: 'es2024',
+    fixedExtension: false,
+    dts: false,
+    clean: false,
+    deps: { neverBundle: ['electron'] },
+  },
+  {
+    // Sandboxed Electron preloads run as CommonJS even though the application package is ESM.
+    entry: {
+      preload: 'lib/types/preload.js',
+      'preload-app': 'lib/types/preload-app.js',
+    },
+    outDir: 'lib',
+    format: ['cjs'],
+    platform: 'node',
+    target: 'es2024',
+    fixedExtension: false,
+    dts: false,
+    clean: false,
+    deps: { neverBundle: ['electron'] },
+  },
+])

+ 2 - 2
docs/architecture.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write docs/architecture.md
-architecture.md: fcb9c1e59b60dc059ab66b64ac26acd3c9157c96
-architecture.zh.md: d7a0a3833ebf9397837967065249d7fe1650d707
+architecture.md: a77dfd06c61cbb12d7008133a7e8d515a3449a02
+architecture.zh.md: 83fa958609500cc85af9c0d1f7e339104f6c18c7

+ 6 - 0
docs/architecture.md

@@ -46,6 +46,12 @@ Vendored CLIs, build-only and test-only executables, direct in-process plugin mo
 
 The Python SDK follows the same application architecture. Its runtime wheel packages the normal `dsh` CLI as `deepseek-harness-sdk-runtime-<platform>-<arch>`, and the client launches `dsh --profile sdk` with an explicit Harness home by default. The minimal example selects the shipped `sdk-minimal` profile. Python exposes profile selection and ordered patch files rather than a complete Cordis tree; persistent external plugins are installed through `dsh plugin`. The removed private direct-config carrier has no compatibility bin or fallback parser.
 
+## Desktop application
+
+The [Electron desktop application](../apps/desktop/README.md) owns the reserved `$DSH_HOME/profiles/desktop` npm project. Each signed Electron release binds one exact dsh version and carries a first-party offline seed; startup installs that version into the writable profile with the bundled pnpm, while retaining exact desktop-plugin versions from the previous profile. CLI profiles share supported product data under `$DSH_HOME`, but never executable packages, plugin activation, lockfiles, or `node_modules` with Desktop.
+
+Electron starts the private Desktop Host package under its bundled upstream Node.js process; that package loads the installed dsh backend and matching client graph from the reserved profile. Unary RPC, Remote streams, and version-matched client assets cross versioned framed byte pipes with Node IPC reserved for lifecycle control, then reach the renderer through the secure `dsh-app://` protocol; the desktop composition opens no Web server or loopback port. Only shell-owned UI can run plugin transactions through the bundled pnpm and its private `$DSH_HOME/desktop/pnpm/store`.
+
 ## Core packages
 
 Here are some core packages that contribute to the Cordis tree.

+ 6 - 0
docs/architecture.zh.md

@@ -46,6 +46,12 @@ Vendored CLI、仅用于构建和测试的可执行文件、进程内直接挂
 
 Python SDK 遵循相同的应用架构。其运行时 wheel 把普通 `dsh` CLI 打包为 `deepseek-harness-sdk-runtime-<platform>-<arch>`,客户端默认以显式 Harness home 启动 `dsh --profile sdk`。极简示例选择随附的 `sdk-minimal` profile。Python 暴露 profile 选择与有序 patch 文件,而不是完整 Cordis 树;持久外部插件通过 `dsh plugin` 安装。已删除的私有直读配置载体没有兼容 bin 或回退 parser。
 
+## 桌面应用
+
+[Electron 桌面应用](../apps/desktop/README.zh.md)持有保留的 `$DSH_HOME/profiles/desktop` npm 项目。每个签名 Electron 发行版绑定一个确切 dsh 版本并携带第一方离线 seed;启动时通过内置 pnpm 把该版本安装进可写 profile,同时保留旧 profile 中桌面插件的确切版本。CLI profile 与 Desktop 共享 `$DSH_HOME` 下受支持的产品数据,但绝不共享可执行包、插件激活、lockfile 或 `node_modules`。
+
+Electron 通过内置的上游 Node.js 进程启动私有 Desktop Host 包;该包从保留 profile 加载已安装的 dsh 后端与匹配的客户端图。一元 RPC、Remote stream 与版本匹配的客户端资源经带版本的分帧字节管道传输,Node IPC 只保留生命周期控制,再通过安全的 `dsh-app://` 协议到达渲染进程;因此桌面组合不会开放 Web server 或 loopback 端口。只有壳自有 UI 能通过内置 pnpm 及其私有 `$DSH_HOME/desktop/pnpm/store` 执行插件事务。
+
 ## 核心包
 
 以下是向 Cordis 树贡献内容的部分核心包。

+ 2 - 2
docs/config-catalog.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write docs/config-catalog.md
-config-catalog.md: 90ec60c30c5ef2ef5fcf75e18ab3d2b51f9cac60
-config-catalog.zh.md: e5c3134561393afe3267808ee04a4cf46399a0f9
+config-catalog.md: 32644b4782cc424835f9e303fff5f30ce95cd14f
+config-catalog.zh.md: 24dc4a3cb7acc1b793d25e5ac3cafc1b5e86e14a

+ 2 - 2
docs/config-catalog.md

@@ -315,7 +315,7 @@ Source: [`packages/shell/bash-sandbox/src/index.ts:36`](../packages/shell/bash-s
 
 ## `@deepseek-ai/dsh-client-connection`
 
-Requires: `webServer` · `credentials`
+Requires: `credentials`
 
 ```ts config-catalog
 /** Browser authentication, request limits, and connection recovery configuration. */
@@ -3397,7 +3397,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co
 - `@deepseek-ai/dsh-authorization` — requires `credentials` ([`packages/credentials/authorization/src/index.ts`](../packages/credentials/authorization/src/index.ts))
 - `@deepseek-ai/dsh-client-file-upload` — requires `agents` · `attachments` · `commands` · `connection` ([`packages/client/file-upload/src/index.ts`](../packages/client/file-upload/src/index.ts))
 - `@deepseek-ai/dsh-client-locale` ([`packages/client/locale/src/index.ts`](../packages/client/locale/src/index.ts))
-- `@deepseek-ai/dsh-client-modules` — requires `webServer` · `loader` ([`packages/client/modules/src/index.ts`](../packages/client/modules/src/index.ts))
+- `@deepseek-ai/dsh-client-modules` — requires `loader` ([`packages/client/modules/src/index.ts`](../packages/client/modules/src/index.ts))
 - `@deepseek-ai/dsh-client-ui-agent-preset` ([`packages/client/ui-agent-preset/src/index.ts`](../packages/client/ui-agent-preset/src/index.ts))
 - `@deepseek-ai/dsh-client-ui-approval` ([`packages/client/ui-approval/src/index.ts`](../packages/client/ui-approval/src/index.ts))
 - `@deepseek-ai/dsh-client-ui-attachment` ([`packages/client/ui-attachment/src/index.ts`](../packages/client/ui-attachment/src/index.ts))

+ 2 - 2
docs/config-catalog.zh.md

@@ -317,7 +317,7 @@ export type Config = LocalConfig
 
 ## `@deepseek-ai/dsh-client-connection`
 
-需要: `webServer` · `credentials`
+需要:`credentials`
 
 ```ts config-catalog
 /** Browser authentication, request limits, and connection recovery configuration. */
@@ -3399,7 +3399,7 @@ export interface Config {
 - `@deepseek-ai/dsh-authorization` — 需要 `credentials`([`packages/credentials/authorization/src/index.ts`](../packages/credentials/authorization/src/index.ts))
 - `@deepseek-ai/dsh-client-file-upload` — 需要 `agents` · `attachments` · `commands` · `connection`([`packages/client/file-upload/src/index.ts`](../packages/client/file-upload/src/index.ts))
 - `@deepseek-ai/dsh-client-locale`([`packages/client/locale/src/index.ts`](../packages/client/locale/src/index.ts))
-- `@deepseek-ai/dsh-client-modules` — 需要 `webServer` · `loader`([`packages/client/modules/src/index.ts`](../packages/client/modules/src/index.ts))
+- `@deepseek-ai/dsh-client-modules` — 需要 `loader`([`packages/client/modules/src/index.ts`](../packages/client/modules/src/index.ts))
 - `@deepseek-ai/dsh-client-ui-agent-preset`([`packages/client/ui-agent-preset/src/index.ts`](../packages/client/ui-agent-preset/src/index.ts))
 - `@deepseek-ai/dsh-client-ui-approval`([`packages/client/ui-approval/src/index.ts`](../packages/client/ui-approval/src/index.ts))
 - `@deepseek-ai/dsh-client-ui-attachment`([`packages/client/ui-attachment/src/index.ts`](../packages/client/ui-attachment/src/index.ts))

+ 2 - 2
docs/subsystems/client-modules.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write docs/subsystems/client-modules.md
-client-modules.md: f58cb6592a009292ffc4f87a207fe4e103fd2365
-client-modules.zh.md: 18c72dfba6f851776100a9b6200e7222d580db7c
+client-modules.md: 12925fa8ddb7c7193af76cf1bed5894f5d7d75e0
+client-modules.zh.md: 513391af8369a5ba4d9a439a14f7fb5eb1f45d27

+ 9 - 0
docs/subsystems/client-modules.md

@@ -130,6 +130,15 @@ graph(): WebBootGraph
  */
 clientPath(id: string): string | undefined
 
+/**
+ * Serve an advertised revisioned bundle or source map without a Web server.
+ * Unknown URLs return 404, unsupported methods return 405, and `HEAD`
+ * returns the same immutable headers without a body.
+ * @param request - shell-carrier request for a `/plugins` resource.
+ * @returns the exact response also exposed by the optional Web route.
+ */
+fetchBundle(request: Request): Response
+
 /**
  * Filesystem baseline captured before an entry's current bytes were read.
  * HMR compares it with the live files when installing a watch, so a write

+ 9 - 0
docs/subsystems/client-modules.zh.md

@@ -130,6 +130,15 @@ graph(): WebBootGraph
  */
 clientPath(id: string): string | undefined
 
+/**
+ * Serve an advertised revisioned bundle or source map without a Web server.
+ * Unknown URLs return 404, unsupported methods return 405, and `HEAD`
+ * returns the same immutable headers without a body.
+ * @param request - shell-carrier request for a `/plugins` resource.
+ * @returns the exact response also exposed by the optional Web route.
+ */
+fetchBundle(request: Request): Response
+
 /**
  * Filesystem baseline captured before an entry's current bytes were read.
  * HMR compares it with the live files when installing a watch, so a write

+ 16 - 1
package.json

@@ -20,10 +20,25 @@
     "build": "tsx scripts/build.ts",
     "build:bench": "npm run build:lib && tsdown --config benchmarks/tsdown.config.ts",
     "build:official": "tsx scripts/build.ts --profile official",
-    "build:lib": "npm run build:lib:host && npm run build:lib:client",
+    "build:lib": "pnpm run build:lib:host && pnpm run build:lib:client",
     "build:lib:host": "node --max-old-space-size=4096 ./node_modules/typescript/bin/tsc -b tsconfig.host.json && tsdown --env.DSH_BUILD_FACE host",
     "build:lib:client": "tsc -b tsconfig.client.json && tsdown --env.DSH_BUILD_FACE client",
     "build:web": "pnpm --filter @deepseek-ai/dsh-web-frontend run build",
+    "build:desktop": "pnpm --filter @deepseek-ai/dsh-desktop run build",
+    "dev:desktop": "pnpm --filter @deepseek-ai/dsh-desktop run dev",
+    "start:desktop": "pnpm --filter @deepseek-ai/dsh-desktop run start",
+    "prepare:desktop": "pnpm --filter @deepseek-ai/dsh-desktop run prepare:package",
+    "package:desktop": "pnpm --filter @deepseek-ai/dsh-desktop run package",
+    "package:desktop:dir": "pnpm --filter @deepseek-ai/dsh-desktop run package:dir",
+    "package:desktop:mac:arm64": "pnpm --filter @deepseek-ai/dsh-desktop run package:mac:arm64",
+    "package:desktop:mac:arm64:dir": "pnpm --filter @deepseek-ai/dsh-desktop run package:mac:arm64:dir",
+    "package:desktop:mac:x64": "pnpm --filter @deepseek-ai/dsh-desktop run package:mac:x64",
+    "package:desktop:mac:x64:dir": "pnpm --filter @deepseek-ai/dsh-desktop run package:mac:x64:dir",
+    "package:desktop:win:x64": "pnpm --filter @deepseek-ai/dsh-desktop run package:win:x64",
+    "package:desktop:win:x64:dir": "pnpm --filter @deepseek-ai/dsh-desktop run package:win:x64:dir",
+    "upload:mac:arm64": "pnpm --filter @deepseek-ai/dsh-desktop run upload:mac:arm64",
+    "upload:mac:x64": "pnpm --filter @deepseek-ai/dsh-desktop run upload:mac:x64",
+    "upload:win:x64": "pnpm --filter @deepseek-ai/dsh-desktop run upload:win:x64",
     "clean": "tsx scripts/clean.ts",
     "change-scope": "tsx scripts/change-scope.ts",
     "typecheck": "npm run build:lib:host && npm run typecheck:contracts-ready",

+ 2 - 2
packages/boot/app-boot/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/boot/app-boot/README.md
-README.md: e6afceaebf7fc94f5a6d708639de128cb9005d72
-README.zh.md: a98be1fd5d0624f971af5d0ce5a81fdc0cafab54
+README.md: 086424009c9f7157f687a00cbb50cdc80a210e11
+README.zh.md: 4d0e45dd6be961d22d638134541c80501edec505

+ 1 - 1
packages/boot/app-boot/README.md

@@ -47,7 +47,7 @@ With that entry point, success looks like a running app with every plugin active
 
 Import profile and bundle declaration types from [`@deepseek-ai/dsh-package-manifest`](../../util/package-manifest/README.md). App-boot owns profile loading, JSON validation, and resolved runtime data.
 
-A profile is how one dsh installation ships different app surfaces: `web`, `headless`, `acp`, `sdk`, and `sdk-minimal` start distinct compositions from the same launcher. A profile lives at `$DSH_HOME/profiles/<name>` and combines installable bundles, its own `cordis.patch.yml`, and `patchReload: live | startup`; omitted reload policy keeps the historical `live` default for custom profiles. The shipped `web` template uses live reload, while the other shipped templates apply patches only at startup. `sdk-minimal` names only its standalone bundle; the other templates retain base-plus-mode stacks. `dsh plugin` creates custom profiles, and a missing bundle or one without a patch declaration fails startup loudly.
+A profile is how one dsh installation ships different app surfaces: `web`, `headless`, `acp`, `sdk`, and `sdk-minimal` start distinct compositions from the same launcher. A profile lives at `$DSH_HOME/profiles/<name>` and combines installable bundles, its own `cordis.patch.yml`, and `patchReload: live | startup`; omitted reload policy keeps the historical `live` default for custom profiles. The shipped `web` template uses live reload, while the other shipped templates apply patches only at startup. `sdk-minimal` names only its standalone bundle; the other templates retain base-plus-mode stacks. `dsh plugin` creates custom profiles, and a missing bundle or one without a patch declaration fails startup loudly. Application-owned npm projects, such as Electron's reserved Desktop profile, use `loadProfileDirectory` to load an already initialized directory without exposing it through CLI profile lookup.
 
 Your machine-local preferences also live in the Harness home:
 

+ 1 - 1
packages/boot/app-boot/README.zh.md

@@ -47,7 +47,7 @@ const ctx = await boot('dsh', resolveConfigPath(argv[2], process.env.DSH_SNAPSHO
 
 Profile 与 bundle 的声明类型从 [`@deepseek-ai/dsh-package-manifest`](../../util/package-manifest/README.zh.md) 导入。App-boot 负责 profile 加载、JSON 校验和解析后的运行时数据。
 
-profile 是同一套 dsh 安装提供不同应用界面的方式:`web`、`headless`、`acp`、`sdk` 与 `sdk-minimal` 从同一 launcher 启动不同组合。profile 位于 `$DSH_HOME/profiles/<name>`,由可安装 bundle、自身 `cordis.patch.yml` 与 `patchReload: live | startup` 组成;自定义 profile 省略 reload 策略时保留历史 `live` 默认值。随产品交付的 `web` 模板实时重载,其他随附模板只在启动时应用 patch。`sdk-minimal` 只列出自身的独立 bundle,其他模板保留 base 加模式 bundle 的栈。`dsh plugin` 创建自定义 profile;缺失 bundle 或未声明 patch 的 bundle 会让启动明确失败。
+profile 是同一套 dsh 安装提供不同应用界面的方式:`web`、`headless`、`acp`、`sdk` 与 `sdk-minimal` 从同一 launcher 启动不同组合。profile 位于 `$DSH_HOME/profiles/<name>`,由可安装 bundle、自身 `cordis.patch.yml` 与 `patchReload: live | startup` 组成;自定义 profile 省略 reload 策略时保留历史 `live` 默认值。随产品交付的 `web` 模板实时重载,其他随附模板只在启动时应用 patch。`sdk-minimal` 只列出自身的独立 bundle,其他模板保留 base 加模式 bundle 的栈。`dsh plugin` 创建自定义 profile;缺失 bundle 或未声明 patch 的 bundle 会让启动明确失败。由应用持有的 npm 项目(例如 Electron 保留的 Desktop profile)通过 `loadProfileDirectory` 加载已经初始化的目录,而不会将它暴露给 CLI profile 查找。
 
 你的机器本地偏好同样位于 harness home 中:
 

+ 1 - 0
packages/boot/app-boot/src/index.ts

@@ -34,6 +34,7 @@ export {
   healProfilesModuleFallback,
   initProfile,
   loadProfile,
+  loadProfileDirectory,
   PROFILE_PATCH_FILENAME,
   PROFILE_TEMPLATES,
   PROFILES_DIR,

+ 44 - 25
packages/boot/app-boot/src/profile.ts

@@ -761,6 +761,48 @@ export function resolveBundleDir(
   )
 }
 
+/**
+ * Load an already initialized profile directory without resolving it through
+ * the shared Harness home. This is used by application-owned profiles whose
+ * package project and lifecycle belong to that application.
+ * @param binName - the diagnostic prefix on thrown errors.
+ * @param dir - absolute profile package directory.
+ * @param installAnchor - absolute path of the owning dsh app's package.json.
+ * @param options - `userLayer: false` skips reading `cordis.patch.yml`.
+ * @returns the resolved bundle layers and optional user patch layer.
+ */
+export function loadProfileDirectory(
+  binName: string,
+  dir: string,
+  installAnchor: string,
+  options: { userLayer?: boolean } = {},
+): Profile {
+  const manifest = readProfileManifest(binName, dir)
+  const bundles = manifest.dsh?.profile?.bundles ?? []
+  const rawPatchReload: unknown = manifest.dsh?.profile?.patchReload
+  if (rawPatchReload !== undefined && rawPatchReload !== 'live' && rawPatchReload !== 'startup') {
+    throw new Error(
+      `${binName}: profile manifest ${join(dir, 'package.json')} dsh.profile.patchReload must be "live" or "startup"`,
+    )
+  }
+  const patchReload = rawPatchReload ?? DEFAULT_PROFILE_PATCH_RELOAD
+  const layers = bundles.map((packageName): ProfileLayer => {
+    const packageDir = resolveBundleDir(binName, packageName, installAnchor, dir)
+    const bundleManifest = JSON.parse(readFileSync(join(packageDir, 'package.json'), 'utf8')) as ProfileManifest
+    const declared = bundleManifest.dsh?.bundle?.patch
+    if (declared === undefined) {
+      throw new Error(`${binName}: profile bundle ${JSON.stringify(packageName)} declares no dsh.bundle in its package.json`)
+    }
+    const patchPath = join(packageDir, declared)
+    return { packageName, packageDir, patchPath, patches: loadOverlayPatches(binName, patchPath) }
+  })
+  const patchPath = join(dir, PROFILE_PATCH_FILENAME)
+  const patches = options.userLayer !== false && existsSync(patchPath)
+    ? loadOverlayPatches(binName, patchPath)
+    : []
+  return { name: basename(dir), dir, layers, patchPath, patches, patchReload }
+}
+
 /**
  * Load a profile: resolve every `dsh.profile.bundles` entry to its patch
  * layer and parse the profile's own patch file. A listed bundle without a
@@ -789,31 +831,8 @@ export function loadProfile(
     }
     initProfile(dir, template.bundles, template.patchReload)
   }
-  const manifest = normalizeShippedProfile(name, dir, readProfileManifest(binName, dir))
-  // A hand-written profile manifest may omit the dsh section entirely.
-  const bundles = manifest.dsh?.profile?.bundles ?? []
-  const rawPatchReload: unknown = manifest.dsh?.profile?.patchReload
-  if (rawPatchReload !== undefined && rawPatchReload !== 'live' && rawPatchReload !== 'startup') {
-    throw new Error(
-      `${binName}: profile manifest ${join(dir, 'package.json')} dsh.profile.patchReload must be "live" or "startup"`,
-    )
-  }
-  const patchReload = rawPatchReload ?? DEFAULT_PROFILE_PATCH_RELOAD
-  const layers = bundles.map((packageName): ProfileLayer => {
-    const packageDir = resolveBundleDir(binName, packageName, installAnchor, dir)
-    const bundleManifest = JSON.parse(readFileSync(join(packageDir, 'package.json'), 'utf8')) as ProfileManifest
-    const declared = bundleManifest.dsh?.bundle?.patch
-    if (declared === undefined) {
-      throw new Error(`${binName}: profile bundle ${JSON.stringify(packageName)} declares no dsh.bundle in its package.json`)
-    }
-    const patchPath = join(packageDir, declared)
-    return { packageName, packageDir, patchPath, patches: loadOverlayPatches(binName, patchPath) }
-  })
-  const patchPath = join(dir, PROFILE_PATCH_FILENAME)
-  const patches = options.userLayer !== false && existsSync(patchPath)
-    ? loadOverlayPatches(binName, patchPath)
-    : []
-  return { name, dir, layers, patchPath, patches, patchReload }
+  normalizeShippedProfile(name, dir, readProfileManifest(binName, dir))
+  return loadProfileDirectory(binName, dir, installAnchor, options)
 }
 
 /**

+ 11 - 0
packages/boot/app-boot/tests/profile.spec.ts

@@ -17,6 +17,7 @@ import {
   healProfilesModuleFallback,
   initProfile,
   loadProfile,
+  loadProfileDirectory,
   PROFILE_PATCH_FILENAME,
   PROFILE_TEMPLATES,
   readProfileManifest,
@@ -163,6 +164,16 @@ describe('resolveBundleDir', () => {
 })
 
 describe('loadProfile', () => {
+  it('loads an explicitly owned profile directory outside CLI discovery', () => {
+    const anchor = stageInstallation({ 'bundle-a': { patch: '[]\n' } })
+    const dir = join(tmp(), 'managed', 'desktop')
+    initProfile(dir, ['bundle-a'])
+    const profile = loadProfileDirectory('managed app', dir, anchor)
+    expect(profile.dir).toBe(dir)
+    expect(profile.name).toBe('desktop')
+    expect(profile.layers.map(layer => layer.packageName)).toEqual(['bundle-a'])
+  })
+
   it('resolves each dsh.profile.bundles entry to its patch layer in order, plus the user layer', () => {
     const anchor = stageInstallation({
       'bundle-a': { patch: '- insert:\n    - id: a\n      name: pkg-a\n' },

+ 2 - 2
packages/client/connection/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/client/connection/README.md
-README.md: 4273c523039ddbc7b0644250800425c555c4c067
-README.zh.md: b416659b38a9f63d25842f7926a19eb78842e505
+README.md: 865c894403d2177e15085d0616289cf8950b555d
+README.zh.md: 3d299a4be7b3490298554982eed6d99fbba9718c

+ 1 - 1
packages/client/connection/README.md

@@ -25,7 +25,7 @@ The package carries browser-to-Host Remote calls, exact Fetch responses, and con
 <a id="use-this-package"></a>
 ## Use this package
 
-The browser uses HTTP POST for Remote unary calls. API Gateway owns the `/api/remote.mux` WebSocket and its logical streams; in-process compositions provide equivalent Remote streams through `connection.rpc.open` without opening a WebSocket. The Host half owns the sole `/api` route, Fetch bridge, browser authentication, Host/Origin checks, and exact `GET`/`HEAD`/`POST` route registry. Each exact route declares buffered or streaming request-body handling before the bridge reads any bytes. Typert Gateway claims generated Remote endpoints, feature packages register non-JSON responses such as Session-log downloads and raw file uploads, and unclaimed requests return 404. Loopback hostname classification remains package-internal to the browser-facing Client state. Browser raw-body transfer is provided by [`dsh-client-file-upload`](../file-upload/README.md).
+The browser uses HTTP POST for Remote unary calls. API Gateway owns the `/api/remote.mux` WebSocket and its logical streams; shell-owned compositions provide equivalent Remote streams through `connection.rpc.open` without opening a WebSocket. The Host half always provides the carrier-neutral RPC and exact `GET`/`HEAD`/`POST` route registries. When a Web carrier is present it also owns the sole `/api` route, Fetch bridge, browser authentication, and Host/Origin checks; a shell-owned carrier dispatches the shared Fetch handler directly. Each exact route declares buffered or streaming request-body handling before the bridge reads any bytes. Typert Gateway claims generated Remote endpoints, feature packages register non-JSON responses such as Session-log downloads and raw file uploads, and unclaimed requests return 404. Loopback hostname classification remains package-internal to the browser-facing Client state. Browser raw-body transfer is provided by [`dsh-client-file-upload`](../file-upload/README.md).
 
 -----
 

+ 1 - 1
packages/client/connection/README.zh.md

@@ -25,7 +25,7 @@ kind: "package-reference"
 <a id="use-this-package"></a>
 ## 使用本包
 
-浏览器通过 HTTP POST 执行 Remote 一元调用。API Gateway 自己拥有 `/api/remote.mux` WebSocket 及其逻辑流。进程内组合通过 `connection.rpc.open` 提供等价的 Remote 流,不打开 WebSocket。Host half 拥有唯一 `/api` route、Fetch bridge、浏览器认证、Host/Origin 校验与精确 `GET`/`HEAD`/`POST` 路由注册表。每条精确路由会在 bridge 读取任何字节前声明缓冲或流式请求体处理方式。Typert Gateway 认领生成的 Remote endpoint,功能包注册 Session 日志下载、原始文件上传等非 JSON 响应,未认领的请求返回 404。Loopback hostname 判定只供浏览器侧当前页面状态使用,留在包内。浏览器原始请求体传输由 [`dsh-client-file-upload`](../file-upload/README.zh.md) 提供。
+浏览器通过 HTTP POST 执行 Remote 一元调用;API Gateway 自己拥有 `/api/remote.mux` WebSocket 及其逻辑流。由 shell 持有的组合通过 `connection.rpc.open` 提供等价的 Remote 流,不打开 WebSocket。Host half 始终提供与载体无关的 RPC 注册表和精确 `GET`/`HEAD`/`POST` 路由注册表。存在 Web 载体时,它还持有唯一 `/api` route、Fetch bridge、浏览器认证与 Host/Origin 校验;由 shell 持有的载体则直接分派共享 Fetch handler。每条精确路由会在 bridge 读取任何字节前声明缓冲或流式请求体处理方式。Typert Gateway 认领生成的 Remote endpoint,功能包注册 Session 日志下载、原始文件上传等非 JSON 响应,未认领的请求返回 404。Loopback hostname 判定只供浏览器侧当前页面状态使用,留在包内。浏览器原始请求体传输由 [`dsh-client-file-upload`](../file-upload/README.zh.md) 提供。
 
 -----
 

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff