diff --git a/docs/capability-seams.i18n.yaml b/docs/capability-seams.i18n.yaml index c5cc60a014..d6c4832447 100644 --- a/docs/capability-seams.i18n.yaml +++ b/docs/capability-seams.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/capability-seams.md -capability-seams.md: 85aee35af0e3de4d7cdbb715bc60c832c023d5ac -capability-seams.zh.md: d3664b649f36b1788c6633497c95d5731ad4a401 +capability-seams.md: c102167aa76b9ba613b1b434cb0aa58765d26106 +capability-seams.zh.md: 7c4eab8d5a2d890bdf4513bb9acdc81f55414642 diff --git a/docs/capability-seams.zh.md b/docs/capability-seams.zh.md index d3664b649f..7c4eab8d5a 100644 --- a/docs/capability-seams.zh.md +++ b/docs/capability-seams.zh.md @@ -439,7 +439,7 @@ flowchart LR | `ctx.spillStore` | `seam` | [`spill`](../packages/spill/spill) | [`spill-local`](../packages/spill/spill-local) | [`spill-policy`](../packages/spill/spill-policy) | - | 后端保存过大的工具文本,并返回面向模型的定位信息和取回提示;spill-policy 是 tools/post-execute 消费方,负责决定何时 spill。 | | `ctx.directoryPicker` | `seam` | `directory-picker` | `directory-picker-native`、`directory-picker-browse` | `apiproxy` | - | 带判别标记的交互能力:原生后端在 Host 显示设备上打开一个操作系统选择器,浏览后端为应用内浏览器提供列表与创建原语;双端后端通过其浏览器侧填充 ui-workspace 目录流程的 slot(不通过协议发布)。 | | `ctx.httpServer` | `core` | `webserver` | - | `connection`、`modules`、`hmr` | - | 普通的 node:http 载体:具名路由注册表、索引转换 tap,以及静态 dist 回退;Web 传输插件注册自己的路由。 | -| `ctx.clientModuleHost` | `core` | `modules` | - | `hmr` | - | 通过增量 dshClient 扫描组合 __DSH_BOOT__ 入口图,提供插件组合包,并通知重建/图变更订阅方。 | +| `ctx.clientModuleHost` | `core` | `modules` | - | `hmr` | - | 通过增量 `dsh.client` 扫描组合 __DSH_BOOT__ 入口图,提供插件组合包,并通知重建/图变更订阅方。 | | `ctx.workflows` | `seam` | [`workflow`](../packages/workflow/workflow) | [`workflow-workerthread`](../packages/workflow/workflow-workerthread) | [`tool-workflow`](../packages/workflow/tool-workflow)、[`tool-ralph`](../packages/workflow/tool-ralph) | - | 每个上下文使用一个引擎,与 bash 相同,且没有具名提供方注册表;通用工作流与固定 Ralph 消费方启动运行,其中的 agent() 调用通过 ctx.subagents 扇出。 | 维护模式:混合模式。服务从 Cordis 声明中发现;接口、实现和消费方角色在 `scripts/gen-doc-graphs.ts` 中分类,并设有完整性守卫。 diff --git a/docs/subsystems/README.i18n.yaml b/docs/subsystems/README.i18n.yaml index 445d2b5a45..9e578f7886 100644 --- a/docs/subsystems/README.i18n.yaml +++ b/docs/subsystems/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 docs/subsystems/README.md -README.md: 7d66cfcf66ffb0bed9430892308934c1f10982f4 -README.zh.md: 90e2b28b15870387539500568bb85b525db63ef6 +README.md: fddbf460c8e9e7c6f9ed1d3375bdabe65947661f +README.zh.md: febc5a97426fef5ec4b2b80d9677957369994a3b diff --git a/docs/subsystems/README.md b/docs/subsystems/README.md index 7d66cfcf66..fddbf460c8 100644 --- a/docs/subsystems/README.md +++ b/docs/subsystems/README.md @@ -45,7 +45,7 @@ One page per subsystem of the DeepSeek Harness: what it is, the data structures | [http-server.md](http-server.md) | the HTTP carrier: `WebRouteKind`/`WebRoute`, match order, the claimable fallback seat, index taps | | [storage.md](storage.md) | the storage subsystem: the backend contract (`StorageBackend`), `StorageForms`, `DomainSpec`/`Domain`, `domain/changed` | | [workspace.md](workspace.md) | the workspace registry: `Workspace`/`WorkspaceId`, registration and resolution, the session `cwd` relationship | -| [client-modules.md](client-modules.md) | the web plugin table: `dshClient` declarations, `WebBootGraph` wire composition, the bundle route and index tap | +| [client-modules.md](client-modules.md) | the web plugin table: `dsh.client` declarations, `WebBootGraph` wire composition, the bundle route and index tap | | [session-projection.md](session-projection.md) | the projection seam: `SessionProjectionMap`, the pure `ProjectionDefinition` unit, `ProjectionSnapshot`'s consistent cut, the change feed | | [telemetry.md](telemetry.md) | the outbound session-reporting capability seam: `TelemetryRecord`/`TelemetrySeverity`, the `TelemetryBackend` contract, and the `telemetry/record` redact waterfall | diff --git a/docs/subsystems/README.zh.md b/docs/subsystems/README.zh.md index 90e2b28b15..febc5a9742 100644 --- a/docs/subsystems/README.zh.md +++ b/docs/subsystems/README.zh.md @@ -45,7 +45,7 @@ | [http-server.md](http-server.md) | HTTP 载体:`WebRouteKind`/`WebRoute`、匹配顺序、可认领的回退席位、index 转换 | | [storage.md](storage.md) | 存储子系统:后端约定(`StorageBackend`)、`StorageForms`、`DomainSpec`/`Domain`、`domain/changed` | | [workspace.md](workspace.md) | 工作区注册表:`Workspace`/`WorkspaceId`、注册与解析、与会话 `cwd` 的关系 | -| [client-modules.md](client-modules.md) | Web 插件表:`dshClient` 声明、`WebBootGraph` 线上组合、bundle 路由与 index 转换 | +| [client-modules.md](client-modules.md) | Web 插件表:`dsh.client` 声明、`WebBootGraph` 线上组合、bundle 路由与 index 转换 | | [session-projection.md](session-projection.md) | 投影 seam:`SessionProjectionMap`、纯函数 `ProjectionDefinition` 单元、`ProjectionSnapshot` 的一致切面、变更馈送 | | [telemetry.md](telemetry.md) | 对外会话上报能力 seam:`TelemetryRecord`/`TelemetrySeverity`、`TelemetryBackend` 约定和 `telemetry/record` 脱敏 waterfall | diff --git a/docs/subsystems/client-modules.i18n.yaml b/docs/subsystems/client-modules.i18n.yaml index 8c320d18e6..71441e73fe 100644 --- a/docs/subsystems/client-modules.i18n.yaml +++ b/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: 808fa62b2e5a4351b1dc98bbe4f3d2af63c9dd2b -client-modules.zh.md: dcab60bea7c54770cc5e6032afb39d6dd189d037 +client-modules.md: 14fa5b2ca94c109a8c6bbe2ce09bde73a4448769 +client-modules.zh.md: 7c4df1689dc8df27f7e394763e3bde4320d5c632 diff --git a/docs/subsystems/client-modules.md b/docs/subsystems/client-modules.md index 808fa62b2e..14fa5b2ca9 100644 --- a/docs/subsystems/client-modules.md +++ b/docs/subsystems/client-modules.md @@ -2,7 +2,7 @@ English | [中文](client-modules.zh.md) -The web plugin table: the Node half of the client module system in [dsh-client-modules](../../packages/client/modules), provided as `ctx.clientModuleHost` (`ClientModuleHostService`). It scans the host Loader's entries for `dshClient` packages, composes the `window.__DSH_BOOT__` entry graph, serves each bundle at `/plugins//client.js`, and taps the index render to inject the boot manifest — the four faces of one service. It is an optional capability of the web GUI stack, not part of the agent-loop spine, and it is a consumer of [dsh-host-webserver](../../packages/host/webserver): the carrier described in [http-server.md](http-server.md) supplies the prefix route and index tap this service registers. The same package's browser half (`ctx.modules`, the lazy-CJS module table that fetches and materializes these bundles) is kernel machinery documented in the [package README](../../packages/client/modules/README.md), not here. +The web plugin table: the Node half of the client module system in [dsh-client-modules](../../packages/client/modules), provided as `ctx.clientModuleHost` (`ClientModuleHostService`). It scans the host Loader's entries for packages declaring `dsh.client`, composes the `window.__DSH_BOOT__` entry graph, serves each bundle at `/plugins//client.js`, and taps the index render to inject the boot manifest — the four faces of one service. It is an optional capability of the web GUI stack, not part of the agent-loop spine, and it is a consumer of [dsh-host-webserver](../../packages/host/webserver): the carrier described in [http-server.md](http-server.md) supplies the prefix route and index tap this service registers. The same package's browser half (`ctx.modules`, the lazy-CJS module table that fetches and materializes these bundles) is kernel machinery documented in the [package README](../../packages/client/modules/README.md), not here. Source: [`packages/client/modules/src/client/manifest.ts`](../../packages/client/modules/src/client/manifest.ts) @@ -15,7 +15,7 @@ The graph is the wire single source between the Node and browser halves: the hos * One composed client entry pushed by the host (a graph row). Wire * single source: the host node half (package root) produces this same shape. * `immediately` marks stage-one prefetch; `inject` is informational graph - * metadata (the authoritative edges live in each package's dshClient + * metadata (the authoritative edges live in each package's `dsh.client` * declaration and reach fibers through entry creation). */ interface WebBootEntry { @@ -46,7 +46,7 @@ Each row's `rev` is the bundle's content hash and rides the URL as a cache-busti ## The scan -A package joins the table by declaring `dshClient` (`platform: 'web'`, optional `inject` edges, optional `immediately`) in its package.json and exporting its built bundle at `exports["./client"]`. Package resolution anchors at the config tree's `ctx.baseUrl` — the cordis.yml directory, whose package declares every composed plugin as a dependency — and construction throws when that anchor is unset. +A package joins the table by declaring `dsh.client` (`platform: 'web'`, optional `inject` edges, optional `immediately`) in its package.json and exporting its built bundle at `exports["./client"]`. Package resolution anchors at the config tree's `ctx.baseUrl` — the cordis.yml directory, whose package declares every composed plugin as a dependency — and construction throws when that anchor is unset. Scanning is incremental per package; there is no full-rescan code path. Every cordis `internal/plugin` emission (fiber construction or disposal) marks the fiber's entry name dirty, and a microtask flush reconciles each dirty name against the live loader entries. The activation pass seeds the same dirty set with all current entries and flushes synchronously, so first scan and steady state share one implementation — with opposite failure postures. At activation, a malformed declaration or missing bundle among the already-loaded entries aggregates into one loud `AggregateError` listing every broken package: the fiber FAILS and the boot's fail-loud sweep reports it. In steady state, a broken package logs a warning and must not poison the others. @@ -74,7 +74,7 @@ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnp ### `ctx.clientModuleHost` — `ClientModuleHostService` -The web plugin table service: incremental dshClient scan + wire composition + bundle route + index tap. Construction runs the activation scan synchronously — a malformed declaration or missing bundle among the already-loaded entries aggregates into one loud throw (FAILED fiber; the boot activation audit reports it). +The web plugin table service: incremental `dsh.client` scan + wire composition + bundle route + index tap. Construction runs the activation scan synchronously — a malformed declaration or missing bundle among the already-loaded entries aggregates into one loud throw (FAILED fiber; the boot activation audit reports it). ```ts cordis-catalog /** diff --git a/docs/subsystems/client-modules.zh.md b/docs/subsystems/client-modules.zh.md index dcab60bea7..7c4df1689d 100644 --- a/docs/subsystems/client-modules.zh.md +++ b/docs/subsystems/client-modules.zh.md @@ -2,7 +2,7 @@ [English](client-modules.md) | 中文 -Web 插件表:[dsh-client-modules](../../packages/client/modules) 中 client 模块系统的 Node 半,以 `ctx.clientModuleHost`(`ClientModuleHostService`)形式提供。它扫描宿主 Loader 的 entry 找出 `dshClient` 包(package),组合出 `window.__DSH_BOOT__` entry 图,在 `/plugins//client.js` 提供各个 bundle,并经 index 转换(index tap)注入启动 manifest(元数据清单)——这是同一个服务的四个面。它是 Web GUI 栈的一项可选能力,不属于 agent loop(智能体循环)主干,并且是 [dsh-host-webserver](../../packages/host/webserver) 的消费方:[http-server.md](http-server.md) 所述的载体提供本服务注册的前缀路由与 index 转换。同一个包的浏览器半(`ctx.modules`,即拉取并物化这些 bundle 的 lazy CJS 模块表)属于内核机件,记录在[包 README](../../packages/client/modules/README.md)中,不在本页。 +Web 插件表:[dsh-client-modules](../../packages/client/modules) 中 client 模块系统的 Node 半,以 `ctx.clientModuleHost`(`ClientModuleHostService`)形式提供。它扫描宿主 Loader 的 entry,找出声明了 `dsh.client` 的包(package),组合出 `window.__DSH_BOOT__` entry 图,在 `/plugins//client.js` 提供各个 bundle,并经 index 转换(index tap)注入启动 manifest(元数据清单)——这是同一个服务的四个面。它是 Web GUI 栈的一项可选能力,不属于 agent loop(智能体循环)主干,并且是 [dsh-host-webserver](../../packages/host/webserver) 的消费方:[http-server.md](http-server.md) 所述的载体提供本服务注册的前缀路由与 index 转换。同一个包的浏览器半(`ctx.modules`,即拉取并物化这些 bundle 的 lazy CJS 模块表)属于内核机件,记录在[包 README](../../packages/client/modules/README.md)中,不在本页。 源码:[`packages/client/modules/src/client/manifest.ts`](../../packages/client/modules/src/client/manifest.ts) @@ -15,7 +15,7 @@ Web 插件表:[dsh-client-modules](../../packages/client/modules) 中 client * One composed client entry pushed by the host (a graph row). Wire * single source: the host node half (package root) produces this same shape. * `immediately` marks stage-one prefetch; `inject` is informational graph - * metadata (the authoritative edges live in each package's dshClient + * metadata (the authoritative edges live in each package's `dsh.client` * declaration and reach fibers through entry creation). */ interface WebBootEntry { @@ -46,7 +46,7 @@ interface WebBootGraph { ## 扫描 -包加入这张表的方式,是在自己的 package.json 中声明 `dshClient`(`platform: 'web'`、可选的 `inject` 边、可选的 `immediately`),并在 `exports["./client"]` 导出构建好的 bundle。包解析锚定在配置树的 `ctx.baseUrl`——即 cordis.yml 所在目录,该目录的包把每个被组合的插件声明为依赖——这一锚点未设置时,构造即抛错。 +包加入这张表的方式,是在自己的 package.json 中声明 `dsh.client`(`platform: 'web'`、可选的 `inject` 边、可选的 `immediately`),并在 `exports["./client"]` 导出构建好的 bundle。包解析锚定在配置树的 `ctx.baseUrl`——即 cordis.yml 所在目录,该目录的包把每个被组合的插件声明为依赖——这一锚点未设置时,构造即抛错。 扫描是单包增量的;不存在全量重扫代码路径。fiber 构造或 dispose(资源释放)时的每次 cordis `internal/plugin` 发射都把该 fiber 的 entry 名标脏,一次微任务 flush 把每个脏名与实时 loader entry 对账。激活趟以全部当前 entry 灌入同一个脏集合并同步 flush,因此初扫与稳态共享一条实现——但失败姿态相反。激活时,已加载 entry 中的畸形声明或缺失 bundle 会聚合为一个大声的 `AggregateError`,列出每个损坏的包:该 fiber 进入 FAILED,由启动的大声失败 sweep 上报。稳态下,损坏的包只记录一条警告,且不得殃及其他包。 @@ -74,7 +74,7 @@ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnp ### `ctx.clientModuleHost` — `ClientModuleHostService` -The web plugin table service: incremental dshClient scan + wire composition + bundle route + index tap. Construction runs the activation scan synchronously — a malformed declaration or missing bundle among the already-loaded entries aggregates into one loud throw (FAILED fiber; the boot activation audit reports it). +The web plugin table service: incremental `dsh.client` scan + wire composition + bundle route + index tap. Construction runs the activation scan synchronously — a malformed declaration or missing bundle among the already-loaded entries aggregates into one loud throw (FAILED fiber; the boot activation audit reports it). ```ts cordis-catalog /** diff --git a/packages/api/gateway/package.json b/packages/api/gateway/package.json index fa351d84bf..0b76b607cd 100644 --- a/packages/api/gateway/package.json +++ b/packages/api/gateway/package.json @@ -26,13 +26,15 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-typert-registry", - "@deepseek-ai/dsh-client-connection" - ], - "platform": "web", - "immediately": true + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-typert-registry", + "@deepseek-ai/dsh-client-connection" + ], + "platform": "web", + "immediately": true + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/api/remotes/package.json b/packages/api/remotes/package.json index 0a1e3ec71d..3112a1e7c6 100644 --- a/packages/api/remotes/package.json +++ b/packages/api/remotes/package.json @@ -22,12 +22,14 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-api-gateway" - ], - "platform": "web", - "immediately": true + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-api-gateway" + ], + "platform": "web", + "immediately": true + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/client/connection/package.json b/packages/client/connection/package.json index baafbd4456..1433c59655 100644 --- a/packages/client/connection/package.json +++ b/packages/client/connection/package.json @@ -22,10 +22,12 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [], - "platform": "web", - "immediately": true + "dsh": { + "client": { + "inject": [], + "platform": "web", + "immediately": true + } }, "license": "BSD-3-Clause", "dependencies": { diff --git a/packages/client/hmr/package.json b/packages/client/hmr/package.json index 7f86a68b10..04e94c24bd 100644 --- a/packages/client/hmr/package.json +++ b/packages/client/hmr/package.json @@ -22,10 +22,12 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [], - "platform": "web", - "immediately": true + "dsh": { + "client": { + "inject": [], + "platform": "web", + "immediately": true + } }, "license": "BSD-3-Clause", "dependencies": { diff --git a/packages/client/locale/package.json b/packages/client/locale/package.json index cfadff76b8..0814c86b8d 100644 --- a/packages/client/locale/package.json +++ b/packages/client/locale/package.json @@ -22,13 +22,15 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-connection", - "@deepseek-ai/dsh-client-runtime" - ], - "platform": "web", - "immediately": true + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-connection", + "@deepseek-ai/dsh-client-runtime" + ], + "platform": "web", + "immediately": true + } }, "license": "BSD-3-Clause", "peerDependencies": { diff --git a/packages/client/modules/package.json b/packages/client/modules/package.json index 2283853a7d..906ef783ed 100644 --- a/packages/client/modules/package.json +++ b/packages/client/modules/package.json @@ -1,6 +1,6 @@ { "name": "@deepseek-ai/dsh-client-modules", - "description": "Client module system, dual-face: node half composes the __DSH_BOOT__ entry graph (incremental dshClient scan, bundle route, index tap, webPlugins service); browser half is the lazy-CJS module table the vendored cordis Loader consumes as its internal seam", + "description": "Client module system, dual-face: node half composes the __DSH_BOOT__ entry graph (incremental dsh.client scan, bundle route, index tap, webPlugins service); browser half is the lazy-CJS module table the vendored cordis Loader consumes as its internal seam", "version": "0.0.1", "private": true, "type": "module", @@ -22,10 +22,12 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "platform": "web", - "inject": [], - "immediately": true + "dsh": { + "client": { + "platform": "web", + "inject": [], + "immediately": true + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/client/modules/src/index.ts b/packages/client/modules/src/index.ts index d53be39e0d..a0f932fa84 100644 --- a/packages/client/modules/src/index.ts +++ b/packages/client/modules/src/index.ts @@ -1,6 +1,6 @@ /** - * Node half of the client module system (dshClient dual-face package): scans - * the host Loader's entries for `dshClient` packages, composes the + * Node half of the client module system (`dsh.client` dual-face package): scans + * the host Loader's entries for packages declaring `dsh.client`, composes the * `window.__DSH_BOOT__` entry graph (wire single source: {@link WebBootEntry} * in `./client/manifest.ts`), serves `/plugins//client.js` and its source * map, taps the index render to inject the boot manifest, and provides the @@ -43,7 +43,7 @@ declare module 'cordis' { } } -/** package.json `dshClient` declaration fields, validated one by one after reading the file. */ +/** package.json `dsh.client` declaration fields, validated one by one after reading the file. */ interface DshClientDeclaration { inject?: string[] platform: string @@ -51,7 +51,7 @@ interface DshClientDeclaration { immediately?: boolean } -/** Resolved package metadata for one dshClient package (cached per name, never expires). */ +/** Resolved package metadata for one `dsh.client` package (cached per name, never expires). */ interface PkgMeta { clientPath: string inject?: string[] @@ -105,21 +105,21 @@ interface WebPluginRecord { clientPath: string } -/** Narrow an unknown parsed JSON value to the dshClient declaration, throwing on malformed fields. */ +/** Narrow an unknown parsed JSON value to the `dsh.client` declaration, throwing on malformed fields. */ function parseDshClient(pkgName: string, value: unknown): DshClientDeclaration | undefined { if (value === undefined) return undefined if (typeof value !== 'object' || value === null) { - throw new Error(`client-modules: ${pkgName} has a non-object dshClient declaration`) + throw new Error(`client-modules: ${pkgName} has a non-object dsh.client declaration`) } const decl = value as Record if (typeof decl.platform !== 'string') { - throw new Error(`client-modules: ${pkgName} dshClient.platform must be a string`) + throw new Error(`client-modules: ${pkgName} dsh.client.platform must be a string`) } if (decl.inject !== undefined && (!Array.isArray(decl.inject) || decl.inject.some(i => typeof i !== 'string'))) { - throw new Error(`client-modules: ${pkgName} dshClient.inject must be a string array`) + throw new Error(`client-modules: ${pkgName} dsh.client.inject must be a string array`) } if (decl.immediately !== undefined && typeof decl.immediately !== 'boolean') { - throw new Error(`client-modules: ${pkgName} dshClient.immediately must be a boolean`) + throw new Error(`client-modules: ${pkgName} dsh.client.immediately must be a boolean`) } return { platform: decl.platform, @@ -175,7 +175,7 @@ export function injectBootManifest(html: string, graph: WebBootGraph): string { } /** - * The web plugin table service: incremental dshClient scan + wire composition + * The web plugin table service: incremental `dsh.client` scan + wire composition * + bundle route + index tap. Construction runs the activation scan * synchronously — a malformed declaration or missing bundle among the * already-loaded entries aggregates into one loud throw (FAILED fiber; the @@ -186,7 +186,7 @@ export class ClientModuleHostService extends Service { private readonly table = new Map() // Negative verdicts (unresolvable specifier — builtins like cordis:include, - // subpath rows — or a package without a web dshClient declaration) are + // subpath rows — or a package without a web `dsh.client` declaration) are // cached as null and never expire: plugin-set changes take effect on restart. private readonly pkgMeta = new Map() private readonly rebuildListeners = new Set<(id: string, rev: string) => void>() @@ -342,14 +342,18 @@ export class ClientModuleHostService extends Service { return null } const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as Record - const decl = parseDshClient(pkgName, pkg.dshClient) + const dsh = pkg.dsh + const decl = parseDshClient( + pkgName, + dsh !== null && typeof dsh === 'object' ? (dsh as Record).client : undefined, + ) if (decl === undefined || decl.platform !== 'web') { this.pkgMeta.set(pkgName, null) return null } const clientRel = clientExportOf(pkgName, pkg.exports) if (clientRel === undefined) { - throw new Error(`client-modules: ${pkgName} declares dshClient but exports no "./client" bundle`) + throw new Error(`client-modules: ${pkgName} declares dsh.client but exports no "./client" bundle`) } const meta: PkgMeta = { clientPath: join(dirname(pkgPath), clientRel), diff --git a/packages/client/modules/tests/node-half.spec.ts b/packages/client/modules/tests/node-half.spec.ts index f95ff952c9..c2c865fd12 100644 --- a/packages/client/modules/tests/node-half.spec.ts +++ b/packages/client/modules/tests/node-half.spec.ts @@ -17,8 +17,11 @@ afterEach(() => { root = undefined }) -/** Create a resolvable dshClient package whose client export points at the returned path. */ -function writePackage(packageName: string): string { +/** Create a resolvable package whose client export points at the returned path. */ +function writePackage( + packageName: string, + metadata: Record = { dsh: { client: { platform: 'web' } } }, +): string { root ??= realpathSync(mkdtempSync(join(tmpdir(), 'dsh-client-modules-'))) const pkgRoot = join(root, 'node_modules', ...packageName.split('/')) const clientPath = join(pkgRoot, 'lib', 'client.js') @@ -29,7 +32,7 @@ function writePackage(packageName: string): string { './client': './lib/client.js', './package.json': './package.json', }, - dshClient: { platform: 'web' }, + ...metadata, })) return clientPath } @@ -66,6 +69,20 @@ function construct(packageNames: string[]): ClientModuleHostService { } describe('client bundle activation', () => { + it('allows sibling dsh roles', () => { + const currentName = '@fixture/current-client-field' + const clientPath = writePackage(currentName, { + dsh: { + bundle: { patch: './cordis.patch.yml' }, + client: { platform: 'web' }, + profile: { bundles: [] }, + }, + }) + mkdirSync(dirname(clientPath), { recursive: true }) + writeFileSync(clientPath, 'module.exports = {}\n') + expect(construct([currentName]).graph().entries.map(entry => entry.id)).toEqual([currentName]) + }) + it('groups missing bundles under one source-build instruction with a package/path list', () => { const firstName = '@fixture/missing-first' const secondName = '@fixture/missing-second' diff --git a/packages/client/runtime/package.json b/packages/client/runtime/package.json index bb24c62a32..9f922ed956 100644 --- a/packages/client/runtime/package.json +++ b/packages/client/runtime/package.json @@ -22,13 +22,15 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-connection", - "@deepseek-ai/dsh-typert-registry" - ], - "platform": "web", - "immediately": true + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-connection", + "@deepseek-ai/dsh-typert-registry" + ], + "platform": "web", + "immediately": true + } }, "license": "BSD-3-Clause", "dependencies": { diff --git a/packages/client/ui-agent-preset/package.json b/packages/client/ui-agent-preset/package.json index 6b42c14ec2..2f682d3de5 100644 --- a/packages/client/ui-agent-preset/package.json +++ b/packages/client/ui-agent-preset/package.json @@ -22,15 +22,17 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-connection", - "@deepseek-ai/dsh-client-locale", - "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-client-ui-conversation", - "@deepseek-ai/dsh-client-ui-settings" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-connection", + "@deepseek-ai/dsh-client-locale", + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-ui-conversation", + "@deepseek-ai/dsh-client-ui-settings" + ], + "platform": "web" + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/client/ui-agent-preset/src/index.ts b/packages/client/ui-agent-preset/src/index.ts index c145962f1d..61f59ec50d 100644 --- a/packages/client/ui-agent-preset/src/index.ts +++ b/packages/client/ui-agent-preset/src/index.ts @@ -2,7 +2,7 @@ * Agent-preset surface plugin, node half. The empty apply exists so the plugin * appears in the host cordis.yml / Loader; the browser half ships the * General-settings row through exports["./client"], discovered from the - * package.json dshClient declaration. + * package.json dsh.client declaration. */ /** Host plugin body — no host-side behavior for this surface plugin. */ diff --git a/packages/client/ui-command/package.json b/packages/client/ui-command/package.json index 30f23bcbf2..df3c88a678 100644 --- a/packages/client/ui-command/package.json +++ b/packages/client/ui-command/package.json @@ -22,14 +22,16 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-client-locale", - "@deepseek-ai/dsh-client-ui-slash", - "@deepseek-ai/dsh-client-ui-conversation" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-locale", + "@deepseek-ai/dsh-client-ui-slash", + "@deepseek-ai/dsh-client-ui-conversation" + ], + "platform": "web" + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/client/ui-conversation/package.json b/packages/client/ui-conversation/package.json index 51aef9470b..5dda7ef637 100644 --- a/packages/client/ui-conversation/package.json +++ b/packages/client/ui-conversation/package.json @@ -22,14 +22,16 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-connection", - "@deepseek-ai/dsh-client-locale", - "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-client-ui-layout" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-connection", + "@deepseek-ai/dsh-client-locale", + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-ui-layout" + ], + "platform": "web" + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/client/ui-deliverables/package.json b/packages/client/ui-deliverables/package.json index 0b5318499b..07e8a1bc28 100644 --- a/packages/client/ui-deliverables/package.json +++ b/packages/client/ui-deliverables/package.json @@ -22,13 +22,15 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-locale", - "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-client-ui-conversation" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-locale", + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-ui-conversation" + ], + "platform": "web" + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/client/ui-goal/package.json b/packages/client/ui-goal/package.json index 63410707a0..9e8b6865f9 100644 --- a/packages/client/ui-goal/package.json +++ b/packages/client/ui-goal/package.json @@ -22,14 +22,16 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-api-remotes", - "@deepseek-ai/dsh-client-locale", - "@deepseek-ai/dsh-client-ui-conversation" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-api-remotes", + "@deepseek-ai/dsh-client-locale", + "@deepseek-ai/dsh-client-ui-conversation" + ], + "platform": "web" + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/client/ui-layout/package.json b/packages/client/ui-layout/package.json index 1a6e2785b6..ba41bc9dcb 100644 --- a/packages/client/ui-layout/package.json +++ b/packages/client/ui-layout/package.json @@ -22,12 +22,14 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-client-ui-theme" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-ui-theme" + ], + "platform": "web" + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/client/ui-model/package.json b/packages/client/ui-model/package.json index 6be5dcbc43..6b6ed328fc 100644 --- a/packages/client/ui-model/package.json +++ b/packages/client/ui-model/package.json @@ -22,13 +22,15 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-locale", - "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-client-ui-command" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-locale", + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-ui-command" + ], + "platform": "web" + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/client/ui-models/package.json b/packages/client/ui-models/package.json index abe8dbc0f5..dc02a09aa6 100644 --- a/packages/client/ui-models/package.json +++ b/packages/client/ui-models/package.json @@ -22,13 +22,15 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-client-ui-settings", - "@deepseek-ai/dsh-client-locale" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-ui-settings", + "@deepseek-ai/dsh-client-locale" + ], + "platform": "web" + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/client/ui-permission/package.json b/packages/client/ui-permission/package.json index 55d896dbec..4c90851cf7 100644 --- a/packages/client/ui-permission/package.json +++ b/packages/client/ui-permission/package.json @@ -22,14 +22,16 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-connection", - "@deepseek-ai/dsh-client-locale", - "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-client-ui-command" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-connection", + "@deepseek-ai/dsh-client-locale", + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-ui-command" + ], + "platform": "web" + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/client/ui-plan/package.json b/packages/client/ui-plan/package.json index 2ded06429c..c9aa3fd0f1 100644 --- a/packages/client/ui-plan/package.json +++ b/packages/client/ui-plan/package.json @@ -22,13 +22,15 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-connection", - "@deepseek-ai/dsh-client-locale", - "@deepseek-ai/dsh-client-ui-conversation" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-connection", + "@deepseek-ai/dsh-client-locale", + "@deepseek-ai/dsh-client-ui-conversation" + ], + "platform": "web" + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/client/ui-question/package.json b/packages/client/ui-question/package.json index 7e129c9731..874a634f7c 100644 --- a/packages/client/ui-question/package.json +++ b/packages/client/ui-question/package.json @@ -22,12 +22,14 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-locale", - "@deepseek-ai/dsh-client-ui-conversation" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-locale", + "@deepseek-ai/dsh-client-ui-conversation" + ], + "platform": "web" + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/client/ui-settings-general/package.json b/packages/client/ui-settings-general/package.json index a3e8973280..95166e6d66 100644 --- a/packages/client/ui-settings-general/package.json +++ b/packages/client/ui-settings-general/package.json @@ -22,14 +22,16 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-client-ui-settings", - "@deepseek-ai/dsh-client-locale", - "@deepseek-ai/dsh-client-connection" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-ui-settings", + "@deepseek-ai/dsh-client-locale", + "@deepseek-ai/dsh-client-connection" + ], + "platform": "web" + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/client/ui-settings/package.json b/packages/client/ui-settings/package.json index 6fa2fdc8cb..9383ca0205 100644 --- a/packages/client/ui-settings/package.json +++ b/packages/client/ui-settings/package.json @@ -22,12 +22,14 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-client-ui-sidebar" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-ui-sidebar" + ], + "platform": "web" + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/client/ui-sidebar/package.json b/packages/client/ui-sidebar/package.json index dfbeefbe44..d40bcabcdb 100644 --- a/packages/client/ui-sidebar/package.json +++ b/packages/client/ui-sidebar/package.json @@ -22,13 +22,15 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-client-ui-layout", - "@deepseek-ai/dsh-client-locale" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-ui-layout", + "@deepseek-ai/dsh-client-locale" + ], + "platform": "web" + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/client/ui-skill/package.json b/packages/client/ui-skill/package.json index 420b1c4322..68a2dc2667 100644 --- a/packages/client/ui-skill/package.json +++ b/packages/client/ui-skill/package.json @@ -22,14 +22,16 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-client-locale", - "@deepseek-ai/dsh-client-ui-tool", - "@deepseek-ai/dsh-client-ui-slash" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-locale", + "@deepseek-ai/dsh-client-ui-tool", + "@deepseek-ai/dsh-client-ui-slash" + ], + "platform": "web" + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/client/ui-slash/package.json b/packages/client/ui-slash/package.json index 7562a536e3..01e48dcdf6 100644 --- a/packages/client/ui-slash/package.json +++ b/packages/client/ui-slash/package.json @@ -22,12 +22,14 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-client-locale" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-locale" + ], + "platform": "web" + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/client/ui-subagent/package.json b/packages/client/ui-subagent/package.json index 026b00196b..43a9c35a42 100644 --- a/packages/client/ui-subagent/package.json +++ b/packages/client/ui-subagent/package.json @@ -22,15 +22,17 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-locale", - "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-client-ui-conversation", - "@deepseek-ai/dsh-client-ui-primitives", - "@deepseek-ai/dsh-client-ui-slash" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-locale", + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-ui-conversation", + "@deepseek-ai/dsh-client-ui-primitives", + "@deepseek-ai/dsh-client-ui-slash" + ], + "platform": "web" + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/client/ui-theme/package.json b/packages/client/ui-theme/package.json index a4f9a78d37..dbc65c11e9 100644 --- a/packages/client/ui-theme/package.json +++ b/packages/client/ui-theme/package.json @@ -23,14 +23,16 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-connection", - "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-client-locale" - ], - "platform": "web", - "immediately": true + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-connection", + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-locale" + ], + "platform": "web", + "immediately": true + } }, "license": "BSD-3-Clause", "peerDependencies": { diff --git a/packages/client/ui-tool/package.json b/packages/client/ui-tool/package.json index 965a084cd2..c40b09abdb 100644 --- a/packages/client/ui-tool/package.json +++ b/packages/client/ui-tool/package.json @@ -22,13 +22,15 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-client-locale", - "@deepseek-ai/dsh-client-ui-conversation" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-locale", + "@deepseek-ai/dsh-client-ui-conversation" + ], + "platform": "web" + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/client/ui-trajectory/package.json b/packages/client/ui-trajectory/package.json index f56a9825ce..9ed4b550c1 100644 --- a/packages/client/ui-trajectory/package.json +++ b/packages/client/ui-trajectory/package.json @@ -22,12 +22,14 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-client-ui-conversation" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-ui-conversation" + ], + "platform": "web" + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/client/ui-workspace/package.json b/packages/client/ui-workspace/package.json index 71148df7b6..197369cd48 100644 --- a/packages/client/ui-workspace/package.json +++ b/packages/client/ui-workspace/package.json @@ -22,14 +22,16 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-locale", - "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-client-ui-conversation", - "@deepseek-ai/dsh-client-ui-sidebar" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-locale", + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-ui-conversation", + "@deepseek-ai/dsh-client-ui-sidebar" + ], + "platform": "web" + } }, "scripts": { "bundle": "tsdown", diff --git a/packages/host/directory-picker-browse/package.json b/packages/host/directory-picker-browse/package.json index 58f984159f..85256ef7dc 100644 --- a/packages/host/directory-picker-browse/package.json +++ b/packages/host/directory-picker-browse/package.json @@ -56,12 +56,14 @@ "cordis": "^4.0.0-rc.7", "react": "^18.2.0" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-client-ui-workspace", - "@deepseek-ai/dsh-client-locale" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-ui-workspace", + "@deepseek-ai/dsh-client-locale" + ], + "platform": "web" + } } } diff --git a/packages/host/directory-picker-native/package.json b/packages/host/directory-picker-native/package.json index 2c75ba0a5e..c8e4e4003b 100644 --- a/packages/host/directory-picker-native/package.json +++ b/packages/host/directory-picker-native/package.json @@ -57,11 +57,13 @@ "react": "^18.2.0", "tsx": "^4.19.2" }, - "dshClient": { - "inject": [ - "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-client-ui-workspace" - ], - "platform": "web" + "dsh": { + "client": { + "inject": [ + "@deepseek-ai/dsh-client-runtime", + "@deepseek-ai/dsh-client-ui-workspace" + ], + "platform": "web" + } } } diff --git a/packages/self-modification/tool-cordis/src/api-catalog.ts b/packages/self-modification/tool-cordis/src/api-catalog.ts index 69fac20609..01631bb22c 100644 --- a/packages/self-modification/tool-cordis/src/api-catalog.ts +++ b/packages/self-modification/tool-cordis/src/api-catalog.ts @@ -280,7 +280,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [ }, { key: 'clientModuleHost', - summary: 'The web plugin table service: incremental dshClient scan + wire composition + bundle route + index tap.', + summary: 'The web plugin table service: incremental `dsh.client` scan + wire composition + bundle route + index tap.', methods: [ { signature: 'graph(): WebBootGraph', diff --git a/packages/typert/generator/README.i18n.yaml b/packages/typert/generator/README.i18n.yaml index 55fa47ebc9..2bbbb27f46 100644 --- a/packages/typert/generator/README.i18n.yaml +++ b/packages/typert/generator/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/typert/generator/README.md -README.md: 38030c2b7e07c70ab79001086640b6581943dbd9 -README.zh.md: 0cf0a365785775a9461e9744d29a0da482e54d14 +README.md: ac2610620af14a30143e95921273a8c254b50a17 +README.zh.md: a1fa777493578533c93db6cd3354b89c8757d837 diff --git a/packages/typert/generator/README.md b/packages/typert/generator/README.md index 38030c2b7e..ac2610620a 100644 --- a/packages/typert/generator/README.md +++ b/packages/typert/generator/README.md @@ -4,7 +4,7 @@ English | [中文](README.zh.md) TypeScript project analyzer and model-driven Typert generator. It converts the developer-authored source type tree into compiler-independent `FaceModel` and `TypeGraph` data before any artifact is rendered. Static analysis can consume that model without Cordis; emitters never receive TypeScript AST or checker objects. -The analyzer can use independent `ts.Program` instances seeded from `tsconfig.host.json` or `tsconfig.client.json`. Direct project references establish compiler-face membership, while package subpaths establish TypeRT runtime-face contributions: an ordinary single-project `dshClient` package may contribute both Host and Client runtime models, and only a split project explicitly referenced through `tsconfig.host.json` or `tsconfig.client.json` is restricted to that corresponding face. `package.json#exports` establishes every cross-package public boundary, and source imports or re-exports are the only allowed cross-face edges. Types owned by NPM dependencies, including global declarations from `@types` packages, remain `external` references instead of being expanded. +The analyzer can use independent `ts.Program` instances seeded from `tsconfig.host.json` or `tsconfig.client.json`. Direct project references establish compiler-face membership, while package subpaths establish TypeRT runtime-face contributions: an ordinary single-project package declaring `dsh.client` may contribute both Host and Client runtime models, and only a split project explicitly referenced through `tsconfig.host.json` or `tsconfig.client.json` is restricted to that corresponding face. `package.json#exports` establishes every cross-package public boundary, and source imports or re-exports are the only allowed cross-face edges. Types owned by NPM dependencies, including global declarations from `@types` packages, remain `external` references instead of being expanded. ## Analysis Model diff --git a/packages/typert/generator/README.zh.md b/packages/typert/generator/README.zh.md index 0cf0a36578..a1fa777493 100644 --- a/packages/typert/generator/README.zh.md +++ b/packages/typert/generator/README.zh.md @@ -4,7 +4,7 @@ TypeScript 项目分析器和模型驱动的 Typert 生成器。在生成任何产物之前,它会先将开发者编写的源类型树转换为独立于编译器的 `FaceModel` 和 `TypeGraph` 数据。静态分析无需 Cordis 即可消费该模型;各产物生成组件均不会接收 TypeScript 抽象语法树(AST)或类型检查器对象。 -分析器可以分别使用由 `tsconfig.host.json` 或 `tsconfig.client.json` 初始化的独立 `ts.Program`。直接项目引用确定编译器 face 的成员归属,而包子路径确定 TypeRT 运行时 face 的贡献:带 `dshClient` 的普通单项目包可以同时贡献 Host 与 Client 运行时模型;只有通过 `tsconfig.host.json` 或 `tsconfig.client.json` 显式引用的拆分项目,才会被限制在相应 face。`package.json#exports` 确定所有跨包公开边界,跨 face 的边只能来自源码导入或重新导出。NPM 依赖拥有的类型(包括 `@types` 包中的全局声明)继续以 `external` 引用表示,不会被展开。 +分析器可以分别使用由 `tsconfig.host.json` 或 `tsconfig.client.json` 初始化的独立 `ts.Program`。直接项目引用确定编译器 face 的成员归属,而包子路径确定 TypeRT 运行时 face 的贡献:声明 `dsh.client` 的普通单项目包可以同时贡献 Host 与 Client 运行时模型;只有通过 `tsconfig.host.json` 或 `tsconfig.client.json` 显式引用的拆分项目,才会被限制在相应 face。`package.json#exports` 确定所有跨包公开边界,跨 face 的边只能来自源码导入或重新导出。NPM 依赖拥有的类型(包括 `@types` 包中的全局声明)继续以 `external` 引用表示,不会被展开。 ## 分析模型 diff --git a/packages/typert/generator/src/analyzer.ts b/packages/typert/generator/src/analyzer.ts index 9f4d3e68e5..e5ade888cf 100644 --- a/packages/typert/generator/src/analyzer.ts +++ b/packages/typert/generator/src/analyzer.ts @@ -2564,8 +2564,12 @@ function hasPackageSurface(model: PackageModel): boolean { } function isDualFacePackage(manifest: Record): boolean { - return manifest.dshClient !== null - && typeof manifest.dshClient === 'object' + const dsh = manifest.dsh + const client = dsh !== null && typeof dsh === 'object' + ? (dsh as Record).client + : undefined + return client !== null + && typeof client === 'object' && clientExportSubpaths(manifest).length > 0 } diff --git a/packages/typert/generator/tests/remote-model.spec.ts b/packages/typert/generator/tests/remote-model.spec.ts index a41f0c0cd0..3faef9c8be 100644 --- a/packages/typert/generator/tests/remote-model.spec.ts +++ b/packages/typert/generator/tests/remote-model.spec.ts @@ -309,11 +309,11 @@ export interface RemainingSchema { const root = copyFixture() const manifestPath = join(root, 'packages/remote/package.json') const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as { - dshClient?: object + dsh?: { client?: object } exports: Record files: string[] } - manifest.dshClient = {} + manifest.dsh = { client: {} } manifest.exports['./client'] = './src/client.ts' manifest.exports['./client/typert'] = { types: './lib/typert.client.d.ts', @@ -332,8 +332,10 @@ export interface ClientMarker { } `) - expect(new WorkspaceTypertGenerator(root).generate().map(artifact => artifact.face)) - .toEqual(['host', 'client']) + const artifacts = new WorkspaceTypertGenerator(root).generate() + expect(artifacts.map(artifact => artifact.face)).toEqual(['host', 'client']) + expect(artifacts.find(artifact => artifact.face === 'host')?.dts).not.toContain('ClientMarker') + expect(artifacts.find(artifact => artifact.face === 'client')?.dts).toContain('ClientMarker') }) it.each([ diff --git a/packages/typert/generator/tests/type-model.spec.ts b/packages/typert/generator/tests/type-model.spec.ts index 13771d546e..81e7f0b981 100644 --- a/packages/typert/generator/tests/type-model.spec.ts +++ b/packages/typert/generator/tests/type-model.spec.ts @@ -869,7 +869,7 @@ describe('WorkspaceAnalyzer', { timeout: 60_000 }, () => { .toEqual(['@fixture/host']) }) - it('keeps both runtime faces for an ordinary dshClient project', () => { + it('keeps both runtime faces for an ordinary dsh.client project', () => { const root = copyFixture('typert-dual-runtime-') configureDualRuntimeClient(root, false) @@ -1227,10 +1227,10 @@ function configureDualRuntimeClient(root: string, splitProjects: boolean): void const packageRoot = join(root, 'packages/client') const manifestPath = join(packageRoot, 'package.json') const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as { - dshClient?: object + dsh?: { client?: object } exports: Record } - manifest.dshClient = {} + manifest.dsh = { client: {} } manifest.exports['./client'] = { types: './lib/types/client.d.ts', default: './lib/client.js', diff --git a/packages/typert/registry/package.json b/packages/typert/registry/package.json index e912808293..f094a0c11c 100644 --- a/packages/typert/registry/package.json +++ b/packages/typert/registry/package.json @@ -26,10 +26,12 @@ "./src/*": "./src/*", "./package.json": "./package.json" }, - "dshClient": { - "inject": [], - "platform": "web", - "immediately": true + "dsh": { + "client": { + "inject": [], + "platform": "web", + "immediately": true + } }, "scripts": { "bundle": "tsdown", diff --git a/scripts/client-bundle-purity.spec.ts b/scripts/client-bundle-purity.spec.ts index d43c87f08b..4077c806f3 100644 --- a/scripts/client-bundle-purity.spec.ts +++ b/scripts/client-bundle-purity.spec.ts @@ -97,9 +97,9 @@ describe('client bundle purity gate', () => { it('carries exactly one documented temporary exemption: runtime/client (store engine pending rehoming)', () => { expect(resolveId('@deepseek-ai/dsh-client-runtime/client')).toBeNull() - const dshClientChannels = CLIENT_EXTERNALS.filter( + const clientChannels = CLIENT_EXTERNALS.filter( entry => entry.startsWith('@deepseek-ai/') && entry.endsWith('/client')) - expect(dshClientChannels).toEqual(['@deepseek-ai/dsh-client-runtime/client']) + expect(clientChannels).toEqual(['@deepseek-ai/dsh-client-runtime/client']) }) }) diff --git a/scripts/dev-web.spec.ts b/scripts/dev-web.spec.ts index 71576caf9e..293c6e94f0 100644 --- a/scripts/dev-web.spec.ts +++ b/scripts/dev-web.spec.ts @@ -1,9 +1,28 @@ -import { mkdtemp, readFile, rm, symlink, writeFile } from 'node:fs/promises' +import { mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { expect, it } from 'vitest' import type { TsdownBundle } from 'tsdown' -import { watchClientPlugins } from './dev-web.ts' +import { discoverPluginDirs, watchClientPlugins } from './dev-web.ts' + +it('discovers dsh.client packages with sibling roles', async () => { + const root = await mkdtemp(join(tmpdir(), 'dsh-dev-web-discovery-')) + try { + const current = join(root, 'packages', 'client', 'current') + await mkdir(current, { recursive: true }) + await writeFile(join(current, 'package.json'), JSON.stringify({ + dsh: { + bundle: { patch: './cordis.patch.yml' }, + client: { platform: 'web' }, + profile: { bundles: [] }, + }, + })) + + expect(discoverPluginDirs(root)).toEqual(['packages/client/current']) + } finally { + await rm(root, { recursive: true, force: true }) + } +}) it('rebuilds a client-plugin bundle after its source changes', async () => { const root = await mkdtemp(join(tmpdir(), 'dsh-dev-web-watch-')) diff --git a/scripts/dev-web.ts b/scripts/dev-web.ts index 294e38002b..ea409a89d6 100644 --- a/scripts/dev-web.ts +++ b/scripts/dev-web.ts @@ -1,5 +1,5 @@ /** - * Watch-build for client-plugin HMR: runs every dshClient plugin package + * Watch-build for client-plugin HMR: runs every `dsh.client` plugin package * through the tsdown JS API in watch mode. Reload signaling is not this * script's business — the host webserver stat-polls the bundles it serves and * broadcasts `rebuilt` frames itself (`dsh web --dev`), so any process that @@ -27,7 +27,7 @@ const repoRoot = fileURLToPath(new URL('..', import.meta.url)) /** * Discover the watch workspace by declaration: every packages// - * whose package.json carries `dshClient` with platform "web" is a client + * whose package.json carries `dsh.client` with platform "web" is a client * plugin bundle emitter. Scanned once at startup — a package added while * watching means restarting this script. * @param root - repository root containing the grouped package directories. @@ -36,8 +36,10 @@ const repoRoot = fileURLToPath(new URL('..', import.meta.url)) export function discoverPluginDirs(root = repoRoot): string[] { const dirs: string[] = [] for (const manifestPath of globSync('packages/*/*/package.json', { cwd: root }).sort()) { - const manifest = JSON.parse(readFileSync(join(root, manifestPath), 'utf8')) as { dshClient?: { platform?: unknown } } - if (manifest.dshClient?.platform === 'web') dirs.push(dirname(manifestPath).split(sep).join('/')) + const manifest = JSON.parse(readFileSync(join(root, manifestPath), 'utf8')) as { + dsh?: { client?: { platform?: unknown } } + } + if (manifest.dsh?.client?.platform === 'web') dirs.push(dirname(manifestPath).split(sep).join('/')) } return dirs } @@ -88,7 +90,7 @@ const isMain = invokedPath !== undefined && import.meta.url === pathToFileURL(re if (isMain) { const pluginDirs = discoverPluginDirs() if (pluginDirs.length === 0) { - console.error('dev-web: no dshClient (platform "web") packages found under packages/') + console.error('dev-web: no dsh.client (platform "web") packages found under packages/') process.exit(1) } @@ -106,7 +108,7 @@ if (isMain) { await watchClientPlugins(repoRoot, pluginDirs, pollInterval) console.log( - `dev-web: watching ${String(pluginDirs.length)} dshClient plugin packages` + `dev-web: watching ${String(pluginDirs.length)} dsh.client plugin packages` + `${pollInterval !== undefined ? ` (polling ${String(pollInterval)}ms)` : ''}:\n ${pluginDirs.join('\n ')}`, ) }