1
0
Эх сурвалжийг харах

Merge pull request #3899 from deepseek-harness/worktree/manifest-plugin-metadata

feat(manifest): separate package metadata from DSH declarations
Yichen Jiang 3 долоо хоног өмнө
parent
commit
6469b522c1
24 өөрчлөгдсөн 222 нэмэгдсэн , 116 устгасан
  1. 2 2
      .agents/notes/implemented/architecture/2026-09-05-package-manifest-types.i18n.yaml
  2. 4 2
      .agents/notes/implemented/architecture/2026-09-05-package-manifest-types.md
  3. 4 2
      .agents/notes/implemented/architecture/2026-09-05-package-manifest-types.zh.md
  4. 6 0
      .agents/notes/implemented/architecture/2026-09-10-public-package-manifest.i18n.yaml
  5. 35 0
      .agents/notes/implemented/architecture/2026-09-10-public-package-manifest.md
  6. 35 0
      .agents/notes/implemented/architecture/2026-09-10-public-package-manifest.zh.md
  7. 0 1
      package.json
  8. 2 2
      packages/boot/app-boot/README.i18n.yaml
  9. 1 1
      packages/boot/app-boot/README.md
  10. 1 1
      packages/boot/app-boot/README.zh.md
  11. 4 9
      packages/boot/app-boot/src/profile.ts
  12. 2 2
      packages/experimental/webworker-packer/README.i18n.yaml
  13. 1 1
      packages/experimental/webworker-packer/README.md
  14. 1 1
      packages/experimental/webworker-packer/README.zh.md
  15. 1 2
      packages/experimental/webworker-packer/package.json
  16. 11 2
      packages/experimental/webworker-packer/src/repository.ts
  17. 0 3
      packages/experimental/webworker-packer/tsconfig.json
  18. 2 2
      packages/util/package-manifest/README.i18n.yaml
  19. 25 9
      packages/util/package-manifest/README.md
  20. 25 9
      packages/util/package-manifest/README.zh.md
  21. 2 3
      packages/util/package-manifest/src/index.ts
  22. 36 53
      packages/util/package-manifest/src/types.ts
  23. 0 6
      pnpm-lock.yaml
  24. 22 3
      scripts/gen-session-format-catalog.ts

+ 2 - 2
.agents/notes/implemented/architecture/2026-09-05-package-manifest-types.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-09-05-package-manifest-types.md
-2026-09-05-package-manifest-types.md: 94317a9317ba12059e726612840e4a001be2a892
-2026-09-05-package-manifest-types.zh.md: 2c40facd2591921123b5eae71b80c516f3b1f697
+2026-09-05-package-manifest-types.md: dc018ba019028b942a17cd016c8670f2405c3bc7
+2026-09-05-package-manifest-types.zh.md: 8fd323bfe6058a6a062e3834a8630c0181a203fc

+ 4 - 2
.agents/notes/implemented/architecture/2026-09-05-package-manifest-types.md

@@ -10,11 +10,11 @@ External packages need Harness manifest types without depending on boot or clien
 
 ## Decision
 
-[`@deepseek-ai/dsh-package-manifest`](../../../../packages/util/package-manifest/README.md) owns `DshManifest` and its member declarations in one type-only file. The package belongs to the existing utility group and exports no runtime values. Author declarations and launcher-generated module fallback metadata are explicitly distinguished.
+[`@deepseek-ai/dsh-package-manifest`](../../../../packages/util/package-manifest/README.md) owns `DshManifest` and its member declarations in one type-only file. The package belongs to the existing utility group and exports no runtime values. The [public package metadata decision](2026-09-10-public-package-manifest.md) owns the public field set and the separation from internal tool metadata.
 
 Readers import the shared declarations directly. Boot retains profile loading, raw JSON checks, defaults, and resolved runtime data. Client modules retain their normalized boot graph. The image packer resolves declared paths into directories. The Session catalog generator derives a read-only validated entry with a resolved import path; raw inputs and discovery rules remain local.
 
-App-boot declares a production dependency because its published declarations reference the shared types. Client modules, the private packer, and root scripts use development dependencies because their published APIs do not expose these types. Every package consumer has a TypeScript project reference. External authors import from the utility package; app-boot provides no compatibility re-exports.
+App-boot declares a production dependency because its published declarations reference the shared types. Client modules use a development dependency because their published APIs do not expose these types. Internal image-packer and Session catalog declarations stay with their readers. Every package consumer has a TypeScript project reference. External authors import from the utility package; app-boot provides no compatibility re-exports.
 
 ## Alternatives considered
 
@@ -29,3 +29,5 @@ App-boot declares a production dependency because its published declarations ref
 Authors gain one public import path at the cost of a published package and explicit dependency edges. Existing app-boot manifest type imports must use the new package. The [profile composition design](2026-08-05-profile-plugin-bundles.md) continues to own runtime semantics; type extraction does not change configuration acceptance or model-visible behavior.
 
 Compiler and packaged NodeNext consumer checks cover public imports. Existing profile, client, image configuration, and Session catalog tests cover reader behavior; documentation checks cover the utility classification and generated package catalogs. Optional declaration fields still require deliberate consumer updates when added.
+
+Manifest format and host compatibility declarations have no enforcement in current installers or loaders. The type-only package supplies neither a SemVer parser nor an installation policy; its README records that limitation so an author declaration is not mistaken for a compatibility check.

+ 4 - 2
.agents/notes/implemented/architecture/2026-09-05-package-manifest-types.zh.md

@@ -10,11 +10,11 @@ Status: implemented
 
 ## 决策
 
-[`@deepseek-ai/dsh-package-manifest`](../../../../packages/util/package-manifest/README.zh.md) 在一个纯类型文件中拥有 `DshManifest` 及其成员声明。本包属于现有工具库分组,不导出运行时值。作者声明与启动器生成的模块后备元数据有明确区分。
+[`@deepseek-ai/dsh-package-manifest`](../../../../packages/util/package-manifest/README.zh.md) 在一个纯类型文件中拥有 `DshManifest` 及其成员声明。本包属于现有工具库分组,不导出运行时值。[公共包元数据决策](2026-09-10-public-package-manifest.zh.md) 拥有公共字段范围及其与内部工具元数据的划分。
 
 各读取方直接导入共享声明。启动器保留 profile 加载、原始 JSON 检查、默认值和解析后的运行时数据。客户端模块保留归一化的启动图。镜像打包器将声明路径解析为目录。Session 目录生成器派生带有已解析导入路径的只读校验结果;原始输入和发现规则仍由本地负责。
 
-App-boot 声明生产依赖,因为其发布的声明文件引用共享类型。客户端模块、私有打包器和根脚本使用开发依赖,因为其发布 API 不暴露这些类型。每个包消费方都有 TypeScript 项目引用。外部作者从工具包导入;app-boot 不提供兼容性再导出。
+App-boot 声明生产依赖,因为其发布的声明文件引用共享类型。客户端模块使用开发依赖,因为其发布 API 不暴露这些类型。内部镜像打包器和 Session 目录声明保留在各自读取方。每个包消费方都有 TypeScript 项目引用。外部作者从工具包导入;app-boot 不提供兼容性再导出。
 
 ## 考虑过的替代方案
 
@@ -29,3 +29,5 @@ App-boot 声明生产依赖,因为其发布的声明文件引用共享类型
 作者获得统一的公共导入路径,代价是一个发布包和明确的依赖边。已有的 app-boot manifest 类型导入需要改用新包。[Profile 组合设计](2026-08-05-profile-plugin-bundles.zh.md) 继续负责运行时语义;类型提取不改变配置接受范围或模型可见行为。
 
 编译器与打包后的 NodeNext 消费方检查覆盖公共导入。已有 profile、客户端、镜像配置和 Session 目录测试覆盖读取行为;文档检查覆盖工具库分类与生成的包目录。新增可选声明字段时,仍需主动更新消费方。
+
+当前安装器和加载器不强制检查 manifest 格式或宿主兼容性声明。纯类型包既不提供 SemVer 解析器,也不提供安装策略;其 README 记录此限制,避免将作者声明误认为兼容性检查。

+ 6 - 0
.agents/notes/implemented/architecture/2026-09-10-public-package-manifest.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-09-10-public-package-manifest.md
+2026-09-10-public-package-manifest.md: 0e6c1650a6b04e98aeba1aa6ef45df78e3f2cb44
+2026-09-10-public-package-manifest.zh.md: 9b7d15f479ca1aab7bea655b2dee43625728a442

+ 35 - 0
.agents/notes/implemented/architecture/2026-09-10-public-package-manifest.md

@@ -0,0 +1,35 @@
+# Agent Note: Public package manifest fields
+
+Status: implemented
+
+English | [中文](2026-09-10-public-package-manifest.zh.md)
+
+## Problem
+
+Plugin authors need npm identity, runtime requirements, and DSH declarations from one public import. Internal image-packaging, Session catalog, and generated proxy metadata do not define extension points for community plugins. Exposing those fields together makes internal mechanisms appear available to external authors.
+
+## Decision
+
+[`DshPackageManifest`](../../../../packages/util/package-manifest/src/types.ts) describes the package.json fields DSH uses, with required `name` and `version`. Its optional `dsh` member uses `DshManifest` for public composition and author metadata. The type is a selected npm field set, not a complete package.json schema. App-boot adapts it with `Partial` for local profiles, which need no published identity.
+
+Runtime requirements live at top-level `engines`: `dsh`, `node`, and `npm` are optional version strings, and other engine names are allowed. `dsh.manifestVersion` identifies declaration format `1`. Format and DSH compatibility declarations are not enforced by current installers or loaders.
+
+The image packer owns `configTrees`, the workspace catalog generator owns Session migration declarations, and app-boot owns generated module-fallback metadata. Their existing on-disk keys remain readable by those internal tools, but the public manifest types do not expose them. This scope refines the [shared declaration ownership decision](2026-09-05-package-manifest-types.md), whose package placement and dependency rules remain active.
+
+Each consumer owns JSON parsing, field validation, default resolution, and adaptation to runtime data. Interfaces do not validate parsed JSON. A helper belongs in the shared package only when multiple consumers need the same validation or normalization; getters that repeat property access add no shared policy.
+
+## Alternatives considered
+
+**Keep internal metadata in the public declaration.** A workspace-only migration catalog and an experimental image packer cannot offer public plugin behavior merely because their metadata is discoverable.
+
+**Put DSH compatibility under `dsh.engines`.** [VS Code](https://code.visualstudio.com/api/references/extension-manifest) places its host requirement in top-level `engines.vscode`. Top-level `engines.dsh` gives authors one location for runtime requirements; DSH still owns enforcement of its custom key.
+
+**Use peer dependencies as the sole host requirement.** Peer dependencies constrain installed npm packages, including the CLI package `@deepseek-ai/dsh`. They do not identify the currently running DSH process when plugins live in a separate profile project.
+
+**Parse every domain through one mandatory parser.** Existing readers consume different subsets and own different errors and defaults. Combining them would make a client reader validate unrelated profile declarations. The public types remain independent of filesystem access and parsing policy.
+
+## Consequences
+
+External authors gain a complete package-level declaration and a smaller DSH author API. Consumers of removed internal types must use their owning implementations. The packer and repository catalog no longer depend on the public declaration package; app-boot retains a production dependency because its published profile type references it.
+
+Compiler and built NodeNext import checks verify required package identity, partial profiles, top-level engine declarations, and the absence of internal fields from the public API. Existing profile, packer, and Session catalog tests retain coverage of their accepted files and malformed declarations. No Session format, plugin loading rule, or model-visible behavior changes.

+ 35 - 0
.agents/notes/implemented/architecture/2026-09-10-public-package-manifest.zh.md

@@ -0,0 +1,35 @@
+# Agent Note: 公共 package manifest 字段
+
+Status: implemented
+
+[English](2026-09-10-public-package-manifest.md) | 中文
+
+## 问题
+
+插件作者需要从统一的公共导入路径获取 npm 身份、运行时要求和 DSH 声明。内部镜像打包、Session 目录和生成的代理元数据不定义社区插件扩展点。将这些字段一起暴露,会让外部作者误以为内部机制也可供使用。
+
+## 决策
+
+[`DshPackageManifest`](../../../../packages/util/package-manifest/src/types.ts) 描述 DSH 使用的 package.json 字段,其中 `name` 和 `version` 必填。其可选的 `dsh` 成员使用 `DshManifest` 描述公共组合与作者元数据。该类型只选取所需 npm 字段,不是完整的 package.json schema(模式)。App-boot 通过 `Partial` 适配无需发布身份的本地 profile。
+
+运行时要求位于顶层 `engines`:`dsh`、`node` 和 `npm` 均为可选版本字符串,也允许其他 engine 名称。`dsh.manifestVersion` 标识声明格式 `1`。当前安装器和加载器不强制检查格式与 DSH 兼容性声明。
+
+镜像打包器拥有 `configTrees`,工作区目录生成器拥有 Session 迁移声明,app-boot 拥有生成的模块后备元数据。这些内部工具仍可读取既有磁盘字段,但公共 manifest 类型不暴露这些字段。此范围细化了[共享声明归属决策](2026-09-05-package-manifest-types.zh.md),后者的包位置与依赖规则仍然有效。
+
+各消费方负责 JSON 解析、字段校验、默认值解析和运行时数据适配。接口不会校验已解析的 JSON。只有多个消费方需要相同校验或归一化时,helper 才属于共享包;重复属性访问的 getter 不提供共享策略。
+
+## 考虑过的替代方案
+
+**将内部元数据保留在公共声明中。** 仅限工作区的迁移目录和实验性镜像打包器,不会因为其元数据可被发现就提供公共插件行为。
+
+**将 DSH 兼容性放在 `dsh.engines` 下。** [VS Code](https://code.visualstudio.com/api/references/extension-manifest) 将宿主要求放在顶层 `engines.vscode`。顶层 `engines.dsh` 让作者在同一位置声明运行时要求;自定义键的检查仍由 DSH 负责。
+
+**仅用 peer dependency 声明宿主要求。** Peer dependency 约束已安装的 npm 包,包括 CLI 包 `@deepseek-ai/dsh`。插件位于独立 profile 项目时,它们无法标识当前运行的 DSH 进程。
+
+**通过统一的强制解析器解析所有领域。** 现有读取方消费不同字段子集,并各自拥有错误与默认值。合并它们会让客户端读取方校验无关的 profile 声明。公共类型保持独立于文件系统访问和解析策略。
+
+## 后果
+
+外部作者获得完整的包级声明和更小的 DSH 作者 API。已移除内部类型的消费方必须使用各自负责的实现。打包器与仓库目录不再依赖公共声明包;app-boot 保留生产依赖,因为其发布的 profile 类型引用该包。
+
+编译器和构建后的 NodeNext 导入检查验证包身份必填、部分 profile、顶层 engine 声明,以及公共 API 不含内部字段。现有 profile、打包器和 Session 目录测试继续覆盖其接受的文件与畸形声明。Session 格式、插件加载规则和模型可见行为均不改变。

+ 0 - 1
package.json

@@ -184,7 +184,6 @@
   },
   "devDependencies": {
     "@deepseek-ai/dsh-agent": "workspace:^",
-    "@deepseek-ai/dsh-package-manifest": "workspace:^",
     "@deepseek-ai/dsh-tool-session-query": "workspace:^",
     "@deepseek-ai/dsh-web-fetch-http": "workspace:^",
     "@stylistic/eslint-plugin": "^5.10.0",

+ 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: df386b64089f962960b09538381e9d4b4905feb0
-README.zh.md: 2698aa248e97db0aac53fd4a2e8bea1adaa3179a
+README.md: a525440f20879314bd8d12b647830f2c541f59b3
+README.zh.md: 90aa0beda52c2968c93eab13bae11640e81e0664

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

@@ -45,7 +45,7 @@ With that entry point, success looks like a running app with every plugin active
 <a id="profiles"></a>
 ### Profiles
 
-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.
+Import profile and bundle declaration types from [`@deepseek-ai/dsh-package-manifest`](../../util/package-manifest/README.md). App-boot adapts `DshPackageManifest` to `ProfileManifest` with optional package identity because local profiles need no published version. 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 --profile <name> --from-default-profile <template>` creates a custom profile at a new non-shipped name from one shipped template, while `dsh plugin` initializes a base-backed profile and manages its installed bundles. 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.
 

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

@@ -45,7 +45,7 @@ const ctx = await boot('dsh', resolveConfigPath(argv[2], process.env.DSH_SNAPSHO
 <a id="profiles"></a>
 ### Profile
 
-Profile 与组合包的声明类型从 [`@deepseek-ai/dsh-package-manifest`](../../util/package-manifest/README.zh.md) 导入。App-boot 负责 profile 加载、JSON 校验和解析后的运行时数据。
+Profile 与组合包的声明类型从 [`@deepseek-ai/dsh-package-manifest`](../../util/package-manifest/README.zh.md) 导入。App-boot 将 `DshPackageManifest` 适配为包身份可选的 `ProfileManifest`,因为本地 profile 无需发布版本。App-boot 负责 profile 加载、JSON 校验和解析后的运行时数据。
 
 profile 是同一套 dsh 安装提供不同应用界面的方式:`web`、`headless`、`acp`、`sdk` 与 `sdk-minimal` 从同一 launcher 启动不同组合。profile 位于 `$DSH_HOME/profiles/<name>`,由可安装组合包、自身 `cordis.patch.yml` 与 `patchReload: live | startup` 组成;自定义 profile 省略 reload 策略时保留历史 `live` 默认值。随产品交付的 `web` 模板实时重载,其他随附模板只在启动时应用 patch。`sdk-minimal` 只列出自身的独立组合包,其他模板保留 base 加模式的组合包栈。`dsh --profile <name> --from-default-profile <template>` 从一个随附模板,在新的非内置名称处创建自定义 profile;`dsh plugin` 则初始化以 base 为基础的 profile,并管理其中安装的组合包。缺失组合包或未声明 patch 的组合包会让启动明确失败。由应用持有的 npm 项目(例如 Electron 保留的 Desktop profile)通过 `loadProfileDirectory` 加载已经初始化的目录,而不会将它暴露给 CLI profile 查找。
 

+ 4 - 9
packages/boot/app-boot/src/profile.ts

@@ -34,7 +34,7 @@ import { withFileLock } from '@deepseek-ai/dsh-atomic-write'
 import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader'
 import { applyEntryPatches, type PatchOptions } from '@deepseek-ai/cordis-plugin-include'
 import { resolveDshHome } from '@deepseek-ai/dsh-home-paths'
-import type { DshManifest, DshModuleFallbackManifest, ProfilePatchReload } from '@deepseek-ai/dsh-package-manifest'
+import type { DshPackageManifest, ProfilePatchReload } from '@deepseek-ai/dsh-package-manifest'
 import { resolve as resolvePackage, type Package as ResolvePackageManifest } from 'resolve.exports'
 import { loadOverlayPatches } from './index.ts'
 
@@ -55,13 +55,8 @@ export interface ProfileTemplate {
   patchReload: ProfilePatchReload
 }
 
-/** The slice of package.json both profiles and bundles use. */
-export interface ProfileManifest {
-  name?: string
-  dependencies?: Record<string, string>
-  peerDependencies?: Record<string, string>
-  dsh?: DshManifest
-}
+/** Package metadata accepted by the profile reader; local profiles need no published identity. */
+export type ProfileManifest = Partial<DshPackageManifest>
 
 /** One resolved bundle layer of a profile. */
 export interface ProfileLayer {
@@ -308,7 +303,7 @@ interface ModuleProxyManifest {
   private: true
   type: 'module'
   exports: Record<string, string>
-  dsh: { moduleFallback: DshModuleFallbackManifest }
+  dsh: { moduleFallback: { targets: Record<string, string> } }
 }
 
 interface ModuleProxyRecord {

+ 2 - 2
packages/experimental/webworker-packer/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/experimental/webworker-packer/README.md
-README.md: 4cf8b569ef288905ea555e4206553b5aeada1708
-README.zh.md: 54db885cc103f471c8d87c89db597bed6147ce79
+README.md: f3863d89712ccb3cb522e4b4908b43602af5c997
+README.zh.md: f3ab2c5e3dee64c76d58436f939c483664b61c46

+ 1 - 1
packages/experimental/webworker-packer/README.md

@@ -23,7 +23,7 @@ The VFS image packer: turns one composed profile into the gzip-compressed base t
 <a id="use-this-package"></a>
 ## Use this package
 
-The [`DshConfigTreeDeclaration`](../../util/package-manifest/README.md) type describes each `dsh.configTrees` entry; this packer validates it and resolves its source directory.
+The packer owns the internal `dsh.configTrees` declaration, its validation, and source directory resolution in [`src/repository.ts`](src/repository.ts). This field is not part of the public plugin manifest API.
 
 The pack is a three-layer standard stack:
 

+ 1 - 1
packages/experimental/webworker-packer/README.zh.md

@@ -23,7 +23,7 @@ VFS 镜像打包器:把一份合成 profile 变成浏览器 worker 挂载为
 <a id="use-this-package"></a>
 ## 使用本包
 
-[`DshConfigTreeDeclaration`](../../util/package-manifest/README.zh.md) 描述每个 `dsh.configTrees` 条目;本打包器负责校验并解析其源目录。
+打包器在 [`src/repository.ts`](src/repository.ts) 中拥有内部 `dsh.configTrees` 声明、校验和源目录解析。该字段不属于公共插件 manifest API。
 
 打包是三层标准栈:
 

+ 1 - 2
packages/experimental/webworker-packer/package.json

@@ -40,8 +40,7 @@
   "devDependencies": {
     "@deepseek-ai/cordis": "workspace:^",
     "@types/js-yaml": "^4.0.9",
-    "@types/picomatch": "^3.0.2",
-    "@deepseek-ai/dsh-package-manifest": "workspace:^"
+    "@types/picomatch": "^3.0.2"
   },
   "peerDependencies": {
     "@deepseek-ai/cordis": "workspace:^"

+ 11 - 2
packages/experimental/webworker-packer/src/repository.ts

@@ -12,7 +12,6 @@ import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync } from 'node
 import { tmpdir } from 'node:os'
 import { join, relative } from 'node:path'
 import { DSH_HOME_ENV } from '@deepseek-ai/dsh-home-paths'
-import type { DshConfigTreeDeclaration } from '@deepseek-ai/dsh-package-manifest'
 import type { ConfigTree, ImageTree, PackResult } from './pack.ts'
 
 /**
@@ -32,6 +31,16 @@ const CLI_ENTRY = `${CLI_PACKAGE}/src/bin.ts`
 /** Repository-owned deterministic filesystem content offered by the preview. */
 const PREVIEW_EXAMPLE_ROOT = 'packages/experimental/webworker-runtime/tests/fixtures/vfs-example'
 
+/** Config directory metadata owned by the CLI image packer, not the public plugin manifest. */
+interface ConfigTreeDeclaration {
+  /** Non-empty destination path in the image; mount values must be unique. */
+  mount: string
+  /** Non-empty source directory path relative to the declaring package root. */
+  path: string
+  /** Include the directory's YAML plugin rows in the package roster; absent means false. */
+  scanRoster?: boolean
+}
+
 /** One built-in Preview source and the trees packed into its overlay. */
 export interface PreviewFixture {
   /** URL/query-safe identifier. */
@@ -119,7 +128,7 @@ export function configTrees(repoRoot: string): ConfigTree[] {
   }
   const mounts = new Set<string>()
   return declared.map((entry, index) => {
-    const tree = entry as Partial<DshConfigTreeDeclaration> | null
+    const tree = entry as Partial<ConfigTreeDeclaration> | null
     const at = `${CLI_PACKAGE} dsh.configTrees[${String(index)}]`
     if (tree === null || typeof tree !== 'object'
       || typeof tree.mount !== 'string' || tree.mount === ''

+ 0 - 3
packages/experimental/webworker-packer/tsconfig.json

@@ -19,9 +19,6 @@
     },
     {
       "path": "../../util/home-paths"
-    },
-    {
-      "path": "../../util/package-manifest"
     }
   ]
 }

+ 2 - 2
packages/util/package-manifest/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/util/package-manifest/README.md
-README.md: 971df84d7db3205eb8be75ba895046d4151780d2
-README.zh.md: 8989cbe9bc29bbf8112d5465eac9806c211fa88d
+README.md: 36e25a7bee7ce6cc31133e0c815c0dc607c58de7
+README.zh.md: d46ae346c2a654a0bfbfb4e7e7b2ba317f85a762

+ 25 - 9
packages/util/package-manifest/README.md

@@ -1,5 +1,5 @@
 ---
-description: "Shared TypeScript declarations for package.json.dsh metadata, usable by boot, client, build, and external packages."
+description: "Shared TypeScript declarations for package identity, runtime requirements, and DSH plugin metadata."
 kind: "package-library"
 ---
 
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
 
 ## Summary
 
-Use `DshManifest` to type a package's Harness metadata, or a member type such as `DshClientManifest` for one declaration. Boot, client, build, and external packages import the same types; each reader owns JSON validation and default resolution.
+Use `DshPackageManifest` for package metadata, `DshManifest` for the public fields under `dsh`, and member types such as `DshClientManifest` for one domain. Each reader owns JSON parsing, validation, and default resolution.
 
 ## Table of Contents
 
@@ -28,16 +28,31 @@ Use `DshManifest` to type a package's Harness metadata, or a member type such as
 Import from the package root. Use a development dependency when only checking your own source; use a production dependency if your published declarations reference these types.
 
 ```ts
-import type { DshClientManifest, DshManifest } from '@deepseek-ai/dsh-package-manifest'
+import type { DshClientManifest, DshPackageManifest } from '@deepseek-ai/dsh-package-manifest'
 
 const client: DshClientManifest = { platform: 'web' }
-const dsh: DshManifest = {
-  bundle: { patch: './cordis.patch.yml' },
-  client,
+const manifest: DshPackageManifest = {
+  name: 'example-dsh-plugin',
+  version: '1.0.0',
+  engines: { node: '>=24', dsh: '0.1.5-alpha.1' },
+  dsh: {
+    manifestVersion: 1,
+    bundle: { patch: './cordis.patch.yml' },
+    client,
+  },
 }
 ```
 
-`DshManifest` describes `bundle`, `profile`, `client`, `configTrees`, `sessionFormatMigration`, and `moduleFallback`, not the surrounding npm manifest. `moduleFallback` is launcher-generated metadata and is not an author configuration entry. TypeScript checks this object and erases `import type` during compilation; JSON files cannot import types, and this example does not write a `package.json`. See [`src/types.ts`](src/types.ts) for the declarations.
+`DshPackageManifest` describes the package.json fields used by DSH, with required `name` and `version`; it is not an exhaustive npm schema. Local profile readers use `Partial<DshPackageManifest>` because profiles need no published version. `DshManifest` describes only public author fields under `dsh`. TypeScript checks the example and erases `import type`; these interfaces do not parse JSON or write a file.
+
+The following metadata fields are optional. Omitting them leaves the format version or compatible host versions undeclared; readers do not infer defaults.
+
+| Field | Meaning |
+|---|---|
+| `dsh.manifestVersion` | Manifest format identifier; the declared format is `1`, independent of the npm package version and Session format version. |
+| `engines.dsh` | Author-declared compatible DSH versions as a SemVer range, including exact prerelease versions. This field sits beside `engines.node` and `engines.npm`; an engines object may omit `dsh`. |
+
+Public composition declarations are defined in [`src/types.ts`](src/types.ts). Internal `configTrees`, `sessionFormatMigration`, and generated `moduleFallback` metadata remain owned by their image-packer, catalog, and launcher readers; the public types do not expose them.
 
 -----
 
@@ -57,7 +72,7 @@ The package root only re-exports declarations from [`src/types.ts`](src/types.ts
 ## Further Exploration
 
 - [Profile launcher](../../boot/app-boot/README.md#profiles) — manifest loading and composition.
-- [Declaration ownership](../../../.agents/notes/implemented/architecture/2026-09-05-package-manifest-types.md) — scope and dependency rationale.
+- [Public package metadata](../../../.agents/notes/implemented/architecture/2026-09-10-public-package-manifest.md) — field placement and reader ownership.
 
 <a id="model-experience"></a>
 ## Model Experience
@@ -72,7 +87,8 @@ Type declarations add no model input, so provider cache reuse is unaffected.
 
 <a id="known-limitations-and-deferred-work"></a>
 
-- **Static typing only.** These declarations do not validate JSON, check file existence, or supply defaults. `configTrees` serves the experimental image packer, and `sessionFormatMigration` is discovered only for workspace migration packages; declaring them does not register external plugin behavior.
+- **Static typing only.** Consumers read and validate the JSON fields they use, then adapt the shared declarations to their runtime data. The package supplies no parser, getter helpers, file checks, or defaults.
+- **Compatibility is declarative.** Current installers and loaders do not enforce `dsh.manifestVersion` or `engines.dsh`; declaring a range does not reject incompatible hosts or validate SemVer syntax.
 
 <a id="dev-note"></a>
 ### Dev Note

+ 25 - 9
packages/util/package-manifest/README.zh.md

@@ -1,5 +1,5 @@
 ---
-description: "供启动器、客户端、构建工具和外部包共同使用的 package.json.dsh 元数据 TypeScript 声明。"
+description: "包身份、运行时要求和 DSH 插件元数据的共享 TypeScript 声明。"
 kind: "package-library"
 ---
 
@@ -9,7 +9,7 @@ kind: "package-library"
 
 ## 概述
 
-使用 `DshManifest` 为包的 Harness 元数据添加类型,也可用 `DshClientManifest` 等成员类型描述单项声明。启动器、客户端、构建工具和外部包导入同一组类型;各读取方负责 JSON 校验和默认值解析。
+使用 `DshPackageManifest` 描述包元数据、`DshManifest` 描述 `dsh` 下的公共字段,以及 `DshClientManifest` 等成员类型描述单个领域。各读取方负责 JSON 解析、校验和默认值解析。
 
 ## 目录
 
@@ -28,16 +28,31 @@ kind: "package-library"
 从包根导入类型。仅检查自己的源码时使用开发依赖;若发布的声明文件引用这些类型,则使用生产依赖。
 
 ```ts
-import type { DshClientManifest, DshManifest } from '@deepseek-ai/dsh-package-manifest'
+import type { DshClientManifest, DshPackageManifest } from '@deepseek-ai/dsh-package-manifest'
 
 const client: DshClientManifest = { platform: 'web' }
-const dsh: DshManifest = {
-  bundle: { patch: './cordis.patch.yml' },
-  client,
+const manifest: DshPackageManifest = {
+  name: 'example-dsh-plugin',
+  version: '1.0.0',
+  engines: { node: '>=24', dsh: '0.1.5-alpha.1' },
+  dsh: {
+    manifestVersion: 1,
+    bundle: { patch: './cordis.patch.yml' },
+    client,
+  },
 }
 ```
 
-`DshManifest` 描述 `bundle`、`profile`、`client`、`configTrees`、`sessionFormatMigration` 和 `moduleFallback`,不包含外层 npm manifest。`moduleFallback` 是启动器生成的元数据,不是作者配置项。TypeScript 检查该对象,并在编译时删除 `import type`;JSON 文件不能导入类型,此示例也不会写入 `package.json`。声明见 [`src/types.ts`](src/types.ts)。
+`DshPackageManifest` 描述 DSH 使用的 package.json 字段,其中 `name` 和 `version` 必填;它不是完整的 npm schema(模式)。本地 profile 读取方使用 `Partial<DshPackageManifest>`,因为 profile 无需发布版本。`DshManifest` 仅描述 `dsh` 下的公共作者字段。TypeScript 检查示例并删除 `import type`;这些接口不解析 JSON,也不写入文件。
+
+以下元数据字段均可选。省略时,格式版本或兼容的宿主版本保持未声明状态;读取方不推断默认值。
+
+| 字段 | 含义 |
+|---|---|
+| `dsh.manifestVersion` | manifest(元数据清单)格式标识;声明的格式为 `1`,独立于 npm 包版本和 Session 格式版本。 |
+| `engines.dsh` | 作者声明的兼容 DSH 版本,使用 SemVer 范围,也可填写精确的预发布版本。此字段与 `engines.node`、`engines.npm` 并列;engines 对象可省略 `dsh`。 |
+
+公共组合声明定义在 [`src/types.ts`](src/types.ts) 中。内部 `configTrees`、`sessionFormatMigration` 和生成的 `moduleFallback` 元数据分别由镜像打包器、目录生成器和启动器读取方拥有;公共类型不暴露这些字段。
 
 -----
 
@@ -57,7 +72,7 @@ const dsh: DshManifest = {
 ## 进一步探索
 
 - [Profile 启动器](../../boot/app-boot/README.zh.md#profiles)——manifest 加载与组合。
-- [声明归属](../../../.agents/notes/implemented/architecture/2026-09-05-package-manifest-types.zh.md)——范围与依赖依据。
+- [公共包元数据](../../../.agents/notes/implemented/architecture/2026-09-10-public-package-manifest.zh.md)——字段位置与读取方归属。
 
 <a id="model-experience"></a>
 ## 模型体验
@@ -72,7 +87,8 @@ const dsh: DshManifest = {
 
 <a id="known-limitations-and-deferred-work"></a>
 
-- **仅提供静态类型。** 这些声明不校验 JSON、不检查文件存在性,也不提供默认值。`configTrees` 服务于实验性镜像打包器,`sessionFormatMigration` 仅从工作区迁移包中发现;声明它们不会注册外部插件行为。
+- **仅提供静态类型。** 消费方读取并校验所需的 JSON 字段,再将共享声明适配为运行时数据。本包不提供解析器、getter helper、文件检查或默认值。
+- **兼容性仅作声明。** 当前安装器和加载器不强制检查 `dsh.manifestVersion` 或 `engines.dsh`;声明范围不会拒绝不兼容的宿主,也不会校验 SemVer 语法。
 
 <a id="dev-note"></a>
 ### 开发备注

+ 2 - 3
packages/util/package-manifest/src/index.ts

@@ -6,10 +6,9 @@
 export type {
   DshBundleManifest,
   DshClientManifest,
-  DshConfigTreeDeclaration,
+  DshEnginesManifest,
   DshManifest,
-  DshModuleFallbackManifest,
+  DshPackageManifest,
   DshProfileManifest,
-  DshSessionFormatMigrationManifest,
   ProfilePatchReload,
 } from './types.ts'

+ 36 - 53
packages/util/package-manifest/src/types.ts

@@ -1,26 +1,51 @@
 /**
- * Shared declarations for `package.json.dsh`.
+ * Shared declarations for the package.json fields used by DSH plugin authors.
  * Each reader owns JSON validation and resolved defaults.
  * @module @deepseek-ai/dsh-package-manifest/types
  */
 
-/** The `dsh` property of an npm manifest; a package may declare several roles. */
+/** Package identity and metadata; local profile readers may accept a partial declaration. */
+export interface DshPackageManifest {
+  /** Published npm package name. */
+  name: string
+  /** Published npm package version. */
+  version: string
+  /** Package summary for discovery and display. */
+  description?: string
+  /** Prevent npm publication, for example for local profile projects. */
+  private?: boolean
+  /** Packages installed alongside this package. */
+  dependencies?: Record<string, string>
+  /** Compatible versions of packages supplied by the consuming project. */
+  peerDependencies?: Record<string, string>
+  /** Runtime requirements; DSH compatibility is declarative until a reader enforces it. */
+  engines?: DshEnginesManifest
+  /** DSH-specific author declarations. */
+  dsh?: DshManifest
+}
+
+/** Public author fields under `package.json.dsh`; a package may declare several roles. */
 export interface DshManifest {
+  /** Manifest format version, independent of the npm package and Session format versions. */
+  manifestVersion?: 1
   /** Bundle metadata consumed by the profile launcher. */
   bundle?: DshBundleManifest
   /** Profile metadata consumed by the profile launcher. */
   profile?: DshProfileManifest
   /** Client module loading and build metadata. */
   client?: DshClientManifest
-  /** Config directories consumed by the experimental deployment-image packer. */
-  configTrees?: DshConfigTreeDeclaration[]
-  /** Adjacent Session migration metadata consumed by the workspace catalog generator. */
-  sessionFormatMigration?: DshSessionFormatMigrationManifest
-  /**
-   * Launcher-generated module proxy metadata, not an author configuration entry.
-   * @internal
-   */
-  moduleFallback?: DshModuleFallbackManifest
+}
+
+/** Runtime version requirements under `package.json.engines`. */
+export interface DshEnginesManifest {
+  /** Compatible DSH versions as a SemVer range, including an exact version. */
+  dsh?: string
+  /** Compatible Node.js versions. */
+  node?: string
+  /** Compatible npm versions. */
+  npm?: string
+  /** Requirements for additional runtimes or package managers. */
+  [engine: string]: string | undefined
 }
 
 /** The configuration layer exported by a bundle package. */
@@ -55,45 +80,3 @@ export interface DshClientManifest {
    */
   external?: string[]
 }
-
-/** One config directory read from the CLI package by the experimental image packer. */
-export interface DshConfigTreeDeclaration {
-  /** Non-empty destination path in the image; mount values must be unique. */
-  mount: string
-  /** Non-empty source directory path relative to the declaring package root. */
-  path: string
-  /** Include the directory's YAML plugin rows in the package roster; absent means false. */
-  scanRoster?: boolean
-}
-
-/**
- * Adjacent Session migration metadata declared on disk. The catalog generator
- * discovers only packages/session/session-format-vN-to-vN+1, not external plugins.
- */
-export interface DshSessionFormatMigrationManifest {
-  /** Non-negative safe integer source version; negative zero is rejected. */
-  from: number
-  /** Non-negative safe integer target version, exactly from + 1. */
-  to: number
-  /** Non-empty package export path, such as `.` or `./migration`. */
-  export: string
-  /** Non-empty named export of the migration implementation. */
-  migration: string
-  /** Non-empty named export of the source version codec. */
-  sourceCodec: string
-  /** Non-empty named export of the target version codec. */
-  targetCodec: string
-  /** Non-empty named export of the target header validator. */
-  targetHeaderValidator: string
-  /** Non-empty named export of the target version restorer. */
-  targetRestorer: string
-}
-
-/**
- * Metadata generated and read by the launcher's module fallback proxies.
- * @internal
- */
-export interface DshModuleFallbackManifest {
-  /** Package export subpaths mapped to resolved target file URLs. */
-  targets: Record<string, string>
-}

+ 0 - 6
pnpm-lock.yaml

@@ -19,9 +19,6 @@ importers:
       '@deepseek-ai/dsh-agent':
         specifier: workspace:^
         version: link:packages/core/agent
-      '@deepseek-ai/dsh-package-manifest':
-        specifier: workspace:^
-        version: link:packages/util/package-manifest
       '@deepseek-ai/dsh-tool-session-query':
         specifier: workspace:^
         version: link:packages/session-query/tool-session-query
@@ -5641,9 +5638,6 @@ importers:
       '@deepseek-ai/cordis':
         specifier: workspace:^
         version: link:../../../vendor/cordis
-      '@deepseek-ai/dsh-package-manifest':
-        specifier: workspace:^
-        version: link:../../util/package-manifest
       '@types/js-yaml':
         specifier: ^4.0.9
         version: 4.0.9

+ 22 - 3
scripts/gen-session-format-catalog.ts

@@ -3,13 +3,32 @@
 import { globSync, readFileSync, writeFileSync } from 'node:fs'
 import { resolve } from 'node:path'
 import { pathToFileURL } from 'node:url'
-import type { DshSessionFormatMigrationManifest } from '@deepseek-ai/dsh-package-manifest'
 
 const root = resolve(import.meta.dirname, '..')
 const OUT = 'packages/session/session-format-catalog/src/generated.ts'
 
+/** Internal adjacent migration metadata discovered only in workspace migration packages. */
+interface SessionFormatMigrationDeclaration {
+  /** Non-negative safe integer source version; negative zero is rejected. */
+  from: number
+  /** Non-negative safe integer target version, exactly from + 1. */
+  to: number
+  /** Non-empty package export path, such as `.` or `./migration`. */
+  export: string
+  /** Non-empty named export of the migration implementation. */
+  migration: string
+  /** Non-empty named export of the source version codec. */
+  sourceCodec: string
+  /** Non-empty named export of the target version codec. */
+  targetCodec: string
+  /** Non-empty named export of the target header validator. */
+  targetHeaderValidator: string
+  /** Non-empty named export of the target version restorer. */
+  targetRestorer: string
+}
+
 /** Validated adjacent migration metadata with its resolved package import path. */
-export interface SessionFormatMigrationManifest extends Readonly<Omit<DshSessionFormatMigrationManifest, 'export'>> {
+export interface SessionFormatMigrationManifest extends Readonly<Omit<SessionFormatMigrationDeclaration, 'export'>> {
   readonly packageName: string
   readonly importPath: string
 }
@@ -72,7 +91,7 @@ export function collectSessionFormatMigrations(
     if (metadata === undefined) {
       throw new Error(`gen-session-format-catalog: ${rel} lacks dsh.sessionFormatMigration`)
     }
-    const allowed: ReadonlySet<string> = new Set<keyof DshSessionFormatMigrationManifest>([
+    const allowed: ReadonlySet<string> = new Set<keyof SessionFormatMigrationDeclaration>([
       'from', 'to', 'export', 'migration', 'sourceCodec', 'targetCodec',
       'targetHeaderValidator', 'targetRestorer',
     ])