07akioni 1 tydzień temu
rodzic
commit
194bad298a

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.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 .agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.md
-2026-08-25-electron-desktop-packaging-and-updates.md: 789fadb670fbf37f5d8cc90c24b1e5efec6b6723
-2026-08-25-electron-desktop-packaging-and-updates.zh.md: 83a2ac99b84659f7c2365ca5bf7438716c8e55fa
+2026-08-25-electron-desktop-packaging-and-updates.md: 7b01ba80e31667d851642e57cbb025dfdce7b463
+2026-08-25-electron-desktop-packaging-and-updates.zh.md: a0db068b13f64ce5ef343d77a07e621304ae4d35

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.md

@@ -68,7 +68,7 @@ The renderer uses `nodeIntegration: false`, `contextIsolation: true`, and `sandb
 
 The installer never mutates the active profile in place. It copies profile metadata into a transaction staging directory, applies an exact dependency change with the bundled pnpm, performs a full health check, stops the backend, moves the active profile to `rollback/profile`, moves staging into `.dsh/profiles/desktop`, and restarts. `pending.json` journals the filesystem moves so startup can complete or reverse an interrupted replacement.
 
-The packaged seed is an offline installation kit, not an executable dsh tree. It contains the release identity, initial desktop-project manifest, a descriptor and immutable tarballs for the first-party package closure rooted at dsh, lockfile, integrity inventory, and required store subset. The release build requires the Electron package and root dsh package to have the same version, creates final npm tarballs from the official source build, selects the reachable dsh and vendored packages plus the Landlock entry, and verifies the dsh tarball's `lib/desktop-host.js` entry. These tarballs remain the official `pnpm pack` results governed by each package's `files` manifest; Desktop does not remove published declarations or otherwise create a second package-content policy. The manifest lists every selected package as a local direct dependency, automatic peer installation is disabled, and the workspace file overrides every selected first-party name to its local tarball. The build rejects any lockfile that resolves one of those names by registry version. Bundled pnpm fetches external production dependencies from npm, performs an offline installation, checks the Host entry, and removes `node_modules` before final store preparation.
+The packaged seed is an offline installation kit, not an executable dsh tree. It contains the release identity, initial desktop-project manifest, a descriptor and immutable tarballs for the first-party package closure rooted at dsh, lockfile, integrity inventory, and required store subset. The release build requires the Electron package and root dsh package to have the same version, creates final npm tarballs from the official source build, selects the reachable dsh and vendored packages plus the Landlock entry, and verifies the dsh tarball's `lib/desktop-host.js` entry and `config/desktop.cordis.patch.yml` overlay. The overlay is the only CLI configuration file published specifically for Desktop; example configurations remain outside the tarball. These tarballs remain the official `pnpm pack` results governed by each package's `files` manifest; Desktop does not remove published declarations or otherwise create a second package-content policy. The manifest lists every selected package as a local direct dependency, automatic peer installation is disabled, and the workspace file overrides every selected first-party name to its local tarball. The build rejects any lockfile that resolves one of those names by registry version. Bundled pnpm fetches external production dependencies from npm, performs an offline installation, checks both Desktop Host files, and removes `node_modules` before final store preparation. Requiring both files before copying the package set and after offline installation prevents a release whose Host entry loads but cannot compose its required overlay from reaching application signing.
 
 The seed stores pnpm content in 16 deterministic uncompressed tar shards selected by normalized store path. Apple notarization inspects Mach-O code inside those archives, so macOS seed preparation signs every Mach-O content-addressed object with the release Developer ID, a secure timestamp, and hardened runtime before sharding. Signing changes the bytes: preparation writes each object at its new SHA-512 path and transactionally rewrites every base and side-effects file reference in pnpm's MessagePack SQLite index. A second offline installation proves that pnpm resolves the rewritten store; preparation then shards it, extracts the final archives, and repeats signature verification. Package paths and non-native bytes remain unchanged, and the seed retains bundled architecture variants because removing files would create a Desktop-specific package file set. Seed integrity covers the shard manifest and every archive before extraction. Startup validates archive paths, entry types, uniqueness, and counts, extracts every shard into a unique Desktop-owned staging directory, and only then merges the complete extraction into `.dsh/desktop/pnpm/store`. An interrupted merge may leave valid immutable cache content, but profile installation and activation still require pnpm integrity and the complete health check.
 

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.zh.md

@@ -68,7 +68,7 @@ Electron 拥有保留 profile `.dsh/profiles/desktop`。其中精确的 `@deepse
 
 安装器绝不原地修改活跃 profile。它把 profile 元数据复制到事务暂存目录,使用内置 pnpm 应用精确依赖变更,执行完整健康检查,停止后端,把活跃 profile 移到 `rollback/profile`,把暂存 profile 移到 `.dsh/profiles/desktop`,然后重启。`pending.json` 记录文件系统移动,使启动过程可以完成或反转中断的替换。
 
-打包种子是离线安装包,而不是可执行 dsh 目录。它包含发布身份、初始桌面项目 manifest、以 dsh 为根的第一方包闭包描述文件及不可变 tarball、lockfile、完整性清单和所需 store 子集。发布构建要求 Electron 包与根 dsh 包使用相同版本,从正式源码构建生成最终 npm tarball,选择可达的 dsh 与 vendored 包以及 Landlock 入口,并验证 dsh tarball 中的 `lib/desktop-host.js`。这些 tarball 保持为由各包 `files` manifest 决定内容的正式 `pnpm pack` 结果;Desktop 不删除已发布的声明文件,也不建立第二套包内容策略。manifest 把每个选中的包列为本地直接依赖,关闭对等依赖自动安装,workspace 文件再把每个选中的第一方包 override 到对应本地 tarball。构建会拒绝任何通过 registry 版本解析这些包名的 lockfile。内置 pnpm 从 npm 拉取外部生产依赖,执行离线安装,检查 Host 入口,并在最终准备 store 前删除 `node_modules`。
+打包种子是离线安装包,而不是可执行 dsh 目录。它包含发布身份、初始桌面项目 manifest、以 dsh 为根的第一方包闭包描述文件及不可变 tarball、lockfile、完整性清单和所需 store 子集。发布构建要求 Electron 包与根 dsh 包使用相同版本,从正式源码构建生成最终 npm tarball,选择可达的 dsh 与 vendored 包以及 Landlock 入口,并验证 dsh tarball 中的 `lib/desktop-host.js` 入口与 `config/desktop.cordis.patch.yml` overlay。该 overlay 是唯一为了 Desktop 而发布的 CLI 配置文件;示例配置仍留在 tarball 之外。这些 tarball 保持为由各包 `files` manifest 决定内容的正式 `pnpm pack` 结果;Desktop 不删除已发布的声明文件,也不建立第二套包内容策略。manifest 把每个选中的包列为本地直接依赖,关闭对等依赖自动安装,workspace 文件再把每个选中的第一方包 override 到对应本地 tarball。构建会拒绝任何通过 registry 版本解析这些包名的 lockfile。内置 pnpm 从 npm 拉取外部生产依赖,执行离线安装,检查两个 Desktop Host 文件,并在最终准备 store 前删除 `node_modules`。在复制 package set 前与离线安装后都要求两个文件,可防止 Host 入口本身能够加载、却无法组合所需 overlay 的发布进入应用签名阶段。
 
 种子根据规范化 store 路径,把 pnpm 内容放入 16 个确定性的未压缩 tar 分片。Apple 公证会检查这些归档内的 Mach-O 代码,因此 macOS seed 在分片前会用发布 Developer ID、安全时间戳与 hardened runtime 签署每个内容寻址 Mach-O 对象。签名会改变字节:准备过程把每个对象写到新的 SHA-512 路径,并以事务方式重写 pnpm MessagePack SQLite 索引内全部基础文件和 side-effects 文件引用。第二次离线安装证明 pnpm 可以解析重写后的 store;准备过程随后完成分片、解包最终归档并再次验证签名。包路径和非原生字节保持不变;种子保留包内附带的架构变体,因为删除文件会创建 Desktop 专属的包文件集。种子完整性覆盖分片 manifest 和解包前的每个归档。启动时验证归档路径、条目类型、唯一性和数量,把所有分片解包到唯一且由 Desktop 拥有的 staging 目录,然后才把完整结果合并进 `.dsh/desktop/pnpm/store`。中断的合并可能留下有效的不可变缓存内容,但 profile 安装与激活仍必须通过 pnpm 完整性与完整健康检查。
 

+ 1 - 1
apps/cli/package.json

@@ -23,7 +23,7 @@
   },
   "files": [
     "lib/*.js",
-    "config"
+    "config/desktop.cordis.patch.yml"
   ],
   "dsh": {
     "configTrees": [

+ 2 - 2
apps/desktop/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/desktop/README.md
-README.md: 1b5be9b33101e1a9b5c00f8e4927b2dacac230f2
-README.zh.md: 04be419851d2b86457906cb4fbd4249b446a8516
+README.md: d3c796a2b23caa789c3e511a8d8af046ca8b784a
+README.zh.md: 130173b8656225141e40463c3de958b031d7283b

Plik diff jest za duży
+ 0 - 0
apps/desktop/README.md


Plik diff jest za duży
+ 0 - 1
apps/desktop/README.zh.md


+ 18 - 3
apps/desktop/scripts/prepare-package-set.ts

@@ -14,6 +14,7 @@ import {
 import { basename, join, resolve } from 'node:path'
 import { parseArgs } from 'node:util'
 import {
+  DESKTOP_DSH_RUNTIME_FILES,
   DESKTOP_PACKAGES_DIR,
   DESKTOP_PACKAGE_SET_FILE,
   parseDesktopCorePackageSet,
@@ -106,13 +107,27 @@ function packedPackages(inputs: readonly string[]): Map<string, PackedDesktopPac
   return available
 }
 
+/**
+ * Require every dsh file used before the Desktop profile can pass its health check.
+ * @param files - Tarball paths rooted at `package/`.
+ * @returns Nothing.
+ */
+export function assertDesktopDshPackageFiles(files: readonly string[]): void {
+  const available = new Set(files)
+  const missing = DESKTOP_DSH_RUNTIME_FILES
+    .map(file => `package/${file}`)
+    .filter(file => !available.has(file))
+  if (missing.length > 0) {
+    throw new Error(`desktop package set: ${DSH_PACKAGE} tarball omits required file(s): ${missing.join(', ')}`)
+  }
+}
+
 /** Prepare `.desktop-build/package-set` from release tarball directories. */
 export function prepareDesktopPackageSet(inputs: readonly string[], output = OUTPUT_ROOT): void {
   const selected = selectDesktopPackageClosure(packedPackages(inputs))
   const dsh = selected.find(packed => packed.manifest.name === DSH_PACKAGE)
-  if (dsh === undefined || !tarballFiles(dsh.tarball).includes('package/lib/desktop-host.js')) {
-    throw new Error(`desktop package set: ${DSH_PACKAGE} tarball does not contain lib/desktop-host.js`)
-  }
+  if (dsh === undefined) throw new Error(`desktop package set: selected closure omits ${DSH_PACKAGE}`)
+  assertDesktopDshPackageFiles(tarballFiles(dsh.tarball))
   rmSync(output, { recursive: true, force: true })
   const packageDir = join(output, DESKTOP_PACKAGES_DIR)
   mkdirSync(packageDir, { recursive: true })

+ 6 - 5
apps/desktop/scripts/prepare-seed.ts

@@ -9,6 +9,7 @@ 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_DSH_RUNTIME_FILES,
   DESKTOP_PACKAGES_DIR,
   DESKTOP_PACKAGE_SET_FILE,
   readDesktopCorePackageSet,
@@ -125,11 +126,11 @@ async function verifyOfflineInstallation(release: DesktopRelease): Promise<void>
   const installedModules = join(SEED_ROOT, 'node_modules')
   try {
     await runPnpm(['install', '--offline', '--frozen-lockfile', '--trust-lockfile'])
-    const desktopHost = join(installedModules, '@deepseek-ai', 'dsh', 'lib', 'desktop-host.js')
-    if (!existsSync(desktopHost)) {
-      throw new Error(
-        `desktop seed: local @deepseek-ai/dsh@${release.version} does not contain lib/desktop-host.js`,
-      )
+    const dshRoot = join(installedModules, '@deepseek-ai', 'dsh')
+    for (const file of DESKTOP_DSH_RUNTIME_FILES) {
+      if (!existsSync(join(dshRoot, file))) {
+        throw new Error(`desktop seed: local @deepseek-ai/dsh@${release.version} does not contain ${file}`)
+      }
     }
   } finally {
     rmSync(installedModules, { recursive: true, force: true })

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

@@ -10,6 +10,12 @@ export const DESKTOP_PACKAGE_SET_FILE = 'desktop-packages.json'
 /** Profile-relative directory containing immutable core npm tarballs. */
 export const DESKTOP_PACKAGES_DIR = 'desktop-packages'
 
+/** Package-relative dsh files required to boot the packaged Desktop Host. */
+export const DESKTOP_DSH_RUNTIME_FILES = [
+  'lib/desktop-host.js',
+  'config/desktop.cordis.patch.yml',
+] as const
+
 /** One immutable npm tarball in the Desktop core package set. */
 export interface DesktopCorePackageRecord {
   readonly name: string

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

@@ -1,5 +1,6 @@
 import { describe, expect, it } from 'vitest'
 import {
+  assertDesktopDshPackageFiles,
   selectDesktopPackageClosure,
   type PackedDesktopPackage,
 } from '../scripts/prepare-package-set.ts'
@@ -38,4 +39,20 @@ describe('desktop package-set selection', () => {
     ])
     expect(() => selectDesktopPackageClosure(available)).toThrow(/unpacked internal package/u)
   })
+
+  it('requires the Desktop Host entry and its packaged overlay', () => {
+    const files = [
+      'package/lib/desktop-host.js',
+      'package/config/desktop.cordis.patch.yml',
+    ]
+    expect(() => {
+      assertDesktopDshPackageFiles(files)
+    }).not.toThrow()
+    expect(() => {
+      assertDesktopDshPackageFiles(files.slice(0, 1))
+    }).toThrow(/desktop\.cordis\.patch\.yml/u)
+    expect(() => {
+      assertDesktopDshPackageFiles(files.slice(1))
+    }).toThrow(/desktop-host\.js/u)
+  })
 })

+ 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: 95931bfcc105299e8f22ee5e1dbfd330216aacde
-config-catalog.zh.md: 413c8348b61c653fb10dfffe73f6327c9b6d8c3b
+config-catalog.md: 5d6ec49b298e34a7678d69b2260db6e563b51cf3
+config-catalog.zh.md: 030186b7760af1ea41b49b521c1e10f8ad825b2e

+ 1 - 1
docs/config-catalog.md

@@ -317,7 +317,7 @@ Source: [`packages/shell/bash-sandbox/src/index.ts:35`](../packages/shell/bash-s
 
 ## `@deepseek-ai/dsh-client-connection`
 
-Requires: `webServer` · `credentials`
+Requires: `credentials`
 
 ```ts config-catalog
 /** Plugin config: the deployment's non-loopback serving authorities. */

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

@@ -319,7 +319,7 @@ export type Config = LocalConfig
 
 ## `@deepseek-ai/dsh-client-connection`
 
-需要:`webServer` · `credentials`
+需要:`credentials`
 
 ```ts config-catalog
 /** Plugin config: the deployment's non-loopback serving authorities. */

+ 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

+ 6 - 0
packages/extensions/tool-cordis/src/api-catalog.ts

@@ -565,6 +565,12 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
         parameters: [{ name: 'id', description: 'entry id (package name).' }],
         returns: 'the path, or undefined for an unknown id.',
       },
+      {
+        signature: 'fetchBundle(request: Request): Response',
+        description: '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.',
+        parameters: [{ name: 'request', description: 'shell-carrier request for a `/plugins` resource.' }],
+        returns: 'the exact response also exposed by the optional Web route.',
+      },
       {
         signature: 'artifactBaseline(id: string): ClientArtifactBaseline | undefined',
         description: '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 between startup composition and watch installation cannot disappear into the watcher\'s initial state.',

+ 1 - 1
scripts/check-workspace-constraints.ts

@@ -58,7 +58,7 @@ const releaseMemberDirectory = /^(?:packages\/(?!experimental\/)[^/]+\/[^/]+|app
 const desktopApplicationDirectory = 'apps/desktop'
 const localArtifactDirs = new Set(['node_modules'])
 const appPackageFiles: Readonly<Record<string, readonly string[]>> = {
-  '@deepseek-ai/dsh': ['lib/*.js'],
+  '@deepseek-ai/dsh': ['lib/*.js', 'config/desktop.cordis.patch.yml'],
   // Sourcemaps stay out by payload policy; the worker-preview surface
   // (dist/preview.html and dist/preview/) backs private experimental
   // packages and is not published.

+ 2 - 0
scripts/gen-cordis-catalog.ts

@@ -646,6 +646,8 @@ export const FOUNDATION_TYPE_NAMES: ReadonlySet<string> = new Set([
   'Promise',
   'Record',
   'Readonly',
+  'Request',
+  'Response',
   'Uint8Array',
 ])
 

+ 0 - 1
tsconfig.base.json

@@ -159,7 +159,6 @@
       "@deepseek-ai/dsh-host-plugin-inventory/types": ["./packages/host/plugin-inventory/src/types.ts"],
       "@deepseek-ai/dsh-client-ui-slots": ["./packages/client/ui-slots/src"],
       "@deepseek-ai/dsh-client-ui-attachment": ["./packages/client/ui-attachment/src"],
-      "@deepseek-ai/dsh-client-ui-directory-picker-native": ["./packages/client/ui-directory-picker-native/src"],
       "@deepseek-ai/dsh-client-ui-primitives": ["./packages/client/ui-primitives/src"],
       "@deepseek-ai/dsh-client-store": ["./packages/client/store/src/index.ts"],
       "@deepseek-ai/dsh-client-store/invariant": ["./packages/client/store/src/invariant.ts"],

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików