diff --git a/.agents/notes/implemented/architecture/2026-07-27-compiler-independent-typert-model.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-27-compiler-independent-typert-model.i18n.yaml index d75e286145..1709d91e34 100644 --- a/.agents/notes/implemented/architecture/2026-07-27-compiler-independent-typert-model.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-27-compiler-independent-typert-model.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-07-27-compiler-independent-typert-model.md -2026-07-27-compiler-independent-typert-model.md: c9e7ec0e471c9deabcffda69346077a2a10ac42a -2026-07-27-compiler-independent-typert-model.zh.md: 9bd419f39c9d8a490dfb0baf12a9852fdbb817a8 +2026-07-27-compiler-independent-typert-model.md: 5083c55176b2f4d342d14d0c1cf2a7f0c2c14b3c +2026-07-27-compiler-independent-typert-model.zh.md: 70d6954fae0fcb48e76137231168d5f2417668bf diff --git a/.agents/notes/implemented/architecture/2026-07-27-compiler-independent-typert-model.md b/.agents/notes/implemented/architecture/2026-07-27-compiler-independent-typert-model.md index c9e7ec0e47..5083c55176 100644 --- a/.agents/notes/implemented/architecture/2026-07-27-compiler-independent-typert-model.md +++ b/.agents/notes/implemented/architecture/2026-07-27-compiler-independent-typert-model.md @@ -20,7 +20,7 @@ Each face independently owns a PackageModel and TypeGraph. Direct project refere PackageModel recognizes Cordis services, events, `@typert object` reference objects, and `@typert schema` data roots. Services and objects expose only public instance members, excluding constructors and static, private, and protected members; inheritance edges remain in TypeGraph instead of being copied into flattened members. When a public property, parameter, or return type lacks an annotation, `check` mode reports an error, while `write` mode writes the checker-inferred result, rebuilds the project, and analyzes it again in strict mode. -[`dsh-typert-registry`](../../../../packages/typert/registry/README.md) provides `ctx.typert` and handles runtime registration only: one contribution atomically carries package-face reflection and an optional Zod schema, and Cordis effect disposal revokes it. The registry neither analyzes TypeScript nor merges the two faces. JSON Schema is an on-demand projection of registered Zod schemas. +[`dsh-typert-registry`](../../../../packages/typert/registry/README.md) provides `ctx.typert` and handles runtime registration only: one contribution atomically carries package-face reflection and optional Zod schema factories, and Cordis effect disposal revokes it. The registry validates factories without invoking them, then materializes and caches each schema on its first `get()`, `resolve()`, `list()`, or JSON Schema projection. It neither analyzes TypeScript nor merges the two faces. Package artifact publication remains explicit opt-in through package exports. When invoked, `WorkspaceTypertGenerator` validates that each requested host face exposes the user-facing subpath `package/typert` from the root artifact `package/lib/typert.host.{js,d.ts}`, or that each requested client face exposes `package/client/typert` from `package/lib/typert.client.{js,d.ts}`; it never edits those exports. The later [Typert Remote design](2026-08-02-typert-remote-method-calls.md) adds a whole-workspace Host contract pass to root build, typecheck, lint, and documentation typecheck. For opted-in Host packages, that pass emits both local reflection and strict Host-for-Client `/remote` contracts before consumers resolve them. Generated local declarations keep `TYPERT` typed as `unknown`, so business packages do not depend on the registry. @@ -34,7 +34,7 @@ For every property in `SyntaxZoo`, the TypeScript printer normalizes the source Boundary cases pin explicit package imports within and across faces, cross-face named re-exports, exact export aliases, qualified `import()` links, and the External classification of global `@types` declarations; they reject TypeScript diagnostics originating in package-owned files, relative-path boundary crossings, references outside `package.json#exports`, and cross-face namespace re-exports without a model target. Interface declaration merging explicitly preserves every authored part; other merges that cannot be represented losslessly fail. -For each supported node kind and literal category, Zod emitter tests run both successful and failing parses; for each unsupported kind, they assert an explicit `TypertEmitError`. Emitter fixtures snapshot generated Zod JavaScript and `.d.ts` text, execute the JavaScript, and typecheck the declarations. `dsh-typert-registry` tests pin atomic registration, queries, JSON Schema, and effect disposal; `dsh-typert-loader` tests also prove delayed mounting, unloading, and disposal while a dynamic import remains pending. A real `dsh-tools` vertical slice generates a contribution from the model, loads it through the runtime registry, and compares its service, event, and related-type records with the committed static `SERVICE_API`, `EVENT_API`, and `TYPE_API`. A full-workspace projector test regenerates the two Cordis catalog documents and the `tool-cordis` API catalog and requires all three texts to be byte-for-byte identical to the committed artifacts. +For each supported node kind and literal category, Zod emitter tests run both successful and failing parses; for each unsupported kind, they assert an explicit `TypertEmitError`. Emitter fixtures snapshot generated Zod JavaScript and `.d.ts` text, execute each schema factory, and typecheck the declarations. `dsh-typert-registry` tests pin atomic registration, first-use materialization, successful-result caching, retry after factory failure, queries, JSON Schema, and effect disposal; `dsh-typert-loader` tests also prove delayed mounting, unloading, and disposal while a dynamic import remains pending. A real `dsh-tools` vertical slice generates a contribution from the model, loads it through the runtime registry, and compares its service, event, and related-type records with the committed static `SERVICE_API`, `EVENT_API`, and `TYPE_API`. A full-workspace projector test regenerates the two Cordis catalog documents and the `tool-cordis` API catalog and requires all three texts to be byte-for-byte identical to the committed artifacts. ## Alternatives considered @@ -50,4 +50,4 @@ For each supported node kind and literal category, Zod emitter tests run both su New generation targets and static checks can reuse the same TypeGraph, and business categories can extend PackageModel without parsing the AST again. Preserving pre-evaluation types and independent faces makes the model more complex than a flattened schema; emitters must explicitly declare their supported scope and fail on missing capabilities. -Explicit package opt-in keeps artifact publication and exports under package ownership. Repository orchestration may still run the whole-workspace Host contract pass for every opted-in package; that pass remains owned by the later Remote Gateway Agent Note. The static Cordis catalogs remain reproducible from the canonical model without coupling `tool-cordis` to runtime registry state. `ctx.typert` reflects only artifacts mounted in the current runtime, and unloading does not control Zod instances that consumers retain after importing them directly. +Explicit package opt-in keeps artifact publication and exports under package ownership. Repository orchestration may still run the whole-workspace Host contract pass for every opted-in package; that pass remains owned by the later Remote Gateway Agent Note. The static Cordis catalogs remain reproducible from the canonical model without coupling `tool-cordis` to runtime registry state. `ctx.typert` reflects only artifacts mounted in the current runtime, and unloading does not control Zod instances that consumers retain after materializing them. diff --git a/.agents/notes/implemented/architecture/2026-07-27-compiler-independent-typert-model.zh.md b/.agents/notes/implemented/architecture/2026-07-27-compiler-independent-typert-model.zh.md index 9bd419f39c..70d6954fae 100644 --- a/.agents/notes/implemented/architecture/2026-07-27-compiler-independent-typert-model.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-27-compiler-independent-typert-model.zh.md @@ -20,7 +20,7 @@ TypeGraph 保存开发者写下的计算前类型结构,包括泛型参数与 PackageModel 识别 Cordis service、event、`@typert object` 引用对象和 `@typert schema` 数据根。service 与 object 只暴露 public instance member,排除 constructor、static、private 和 protected;继承边保留在 TypeGraph 中,不复制为扁平成员。缺少 public property、parameter 或 return 类型标注时,`check` 模式报错,`write` 模式写入 checker 推断结果后重建 project 并再次以严格模式分析。 -[`dsh-typert-registry`](../../../../packages/typert/registry/README.zh.md) 提供 `ctx.typert`,且只负责运行时注册:一个 contribution 原子携带 package-face reflection 与可选 Zod schema,并随 Cordis effect 撤销。注册表不分析 TypeScript,也不合并两个 face。JSON Schema 是对已注册 Zod schema 的按需投影。 +[`dsh-typert-registry`](../../../../packages/typert/registry/README.zh.md) 提供 `ctx.typert`,且只负责运行时注册:一个 contribution 原子携带 package-face reflection 与可选 Zod schema factory,并随 Cordis effect 撤销。注册表校验 factory 时不会调用它;首次 `get()`、`resolve()`、`list()` 或 JSON Schema 投影才会物化并缓存各 schema。注册表不分析 TypeScript,也不合并两个 face。 包产物发布仍通过 package exports 采用显式 opt-in。`WorkspaceTypertGenerator` 仅在被调用时校验所请求 face 的根目录产物协议:host face 必须通过面向用户的 subpath `package/typert` 暴露 `package/lib/typert.host.{js,d.ts}`,client face 必须通过 `package/client/typert` 暴露 `package/lib/typert.client.{js,d.ts}`;它不会修改这些 exports。后续的 [Typert Remote 设计](2026-08-02-typert-remote-method-calls.zh.md) 为根目录 build、typecheck、lint 与文档类型检查增加了全仓 Host 约定 pass。对于已 opt-in 的 Host 包,该 pass 会在消费方解析两者之前生成本地反射产物与严格的 Host-for-Client `/remote` 约定。生成的本地声明将 `TYPERT` 类型保持为 `unknown`,因此业务包不依赖注册表。 @@ -34,7 +34,7 @@ PackageModel 识别 Cordis service、event、`@typert object` 引用对象和 `@ 边界用例固定同 face 与跨 face 的显式包导入、跨 face 命名 re-export、精确 export alias、qualified `import()` link 和全局 `@types` External 归属,并拒绝 package 自有 TypeScript 诊断、相对路径越界、`package.json#exports` 之外的引用,以及尚无模型 target 的跨 face namespace re-export。interface declaration merging 显式保留每个 authored part,无法无损表示的其他 merge 失败。 -Zod emitter 对支持的节点和各类 literal 逐类执行成功与失败 parse,对不支持的节点逐类断言明确的 `TypertEmitError`。Emitter fixture 对生成的 Zod JavaScript 与 `.d.ts` 文本做快照,执行 JavaScript,并对声明做类型检查。`dsh-typert-registry` 测试固定原子注册、查询、JSON Schema 和 effect 撤销,`dsh-typert-loader` 测试还证明延迟挂载、卸载及未完成 dynamic import 的释放行为。真实 `dsh-tools` 纵切从模型生成 contribution,经运行时注册表加载后,将其服务、事件与关联类型记录同已提交的静态 `SERVICE_API`、`EVENT_API` 和 `TYPE_API` 对照。全仓 projector 测试重新生成两份 Cordis catalog 文档与 `tool-cordis` API catalog,并要求三份文本同已提交产物逐字节一致。 +Zod emitter 对支持的节点和各类 literal 逐类执行成功与失败 parse,对不支持的节点逐类断言明确的 `TypertEmitError`。Emitter fixture 对生成的 Zod JavaScript 与 `.d.ts` 文本做快照,执行每个 schema factory,并对声明做类型检查。`dsh-typert-registry` 测试固定原子注册、首次使用物化、成功结果缓存、factory 失败后重试、查询、JSON Schema 和 effect 撤销,`dsh-typert-loader` 测试还证明延迟挂载、卸载及未完成 dynamic import 的释放行为。真实 `dsh-tools` 纵切从模型生成 contribution,经运行时注册表加载后,将其服务、事件与关联类型记录同已提交的静态 `SERVICE_API`、`EVENT_API` 和 `TYPE_API` 对照。全仓 projector 测试重新生成两份 Cordis catalog 文档与 `tool-cordis` API catalog,并要求三份文本同已提交产物逐字节一致。 ## Alternatives considered @@ -50,4 +50,4 @@ Zod emitter 对支持的节点和各类 literal 逐类执行成功与失败 pars 新增生成目标或静态检查可复用同一 TypeGraph,业务类目也可在 PackageModel 上扩展,而无需再次解析 AST。保留计算前类型和独立 face 的代价是模型比打平后的 schema 更复杂,emitter 必须显式声明支持范围并对缺失能力失败。 -包级显式 opt-in 使产物发布与 exports 由各包自行管理。仓库编排仍可为每个已 opt-in 的包运行全仓 Host 约定 pass;该 pass 仍由后续 Remote Gateway Agent Note 负责说明。静态 Cordis catalog 可从标准模型复现,同时不把 `tool-cordis` 与运行时注册表状态耦合。`ctx.typert` 只反映当前运行时中已挂载的产物;对于消费方直接导入后仍持有的 Zod 实例,卸载流程无法控制。 +包级显式 opt-in 使产物发布与 exports 由各包自行管理。仓库编排仍可为每个已 opt-in 的包运行全仓 Host 约定 pass;该 pass 仍由后续 Remote Gateway Agent Note 负责说明。静态 Cordis catalog 可从标准模型复现,同时不把 `tool-cordis` 与运行时注册表状态耦合。`ctx.typert` 只反映当前运行时中已挂载的产物;对于消费方物化后仍持有的 Zod 实例,卸载流程无法控制。 diff --git a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml index 059f67188e..bccec1ad13 100644 --- a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md -2026-08-02-typert-remote-method-calls.md: 73ab996d408c71ab70d25058677d0d02efe05804 -2026-08-02-typert-remote-method-calls.zh.md: 06b3f9ad454ca905d33e8d08dde51e6c4e99427e +2026-08-02-typert-remote-method-calls.md: b6551e1c7f8c94fb02a788aa62cb4acef1addffe +2026-08-02-typert-remote-method-calls.zh.md: 058ec47e6749ee7576fd84fdcacfda350eec3fb8 diff --git a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md index 73ab996d40..b6551e1c7f 100644 --- a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md +++ b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md @@ -147,7 +147,7 @@ The strict generator writes `scope` only when a direct method has exactly one lo Parameter order comes from the method signature. HTTP fields come from parameter names or lookup declarations. A cancellation descriptor reserves only the final `signal` position and keeps it outside named `args`; Connection or a direct Gateway caller supplies the actual signal. The Gateway does not infer optional fields, Context types, lookup types, or missing arguments from request contents, and it does not synthesize business defaults. -A LIB codec contains a Zod schema and a canonical `typeSymbol` consisting of "package + public subpath + export name." An SRC codec is marked only as `src-json`. When the Host and consumer run in different JavaScript realms, each holds its own Zod instances, but both sets are generated from the same Typert model and symbol keys. +A LIB codec contains a success-cached Zod schema factory and a canonical `typeSymbol` consisting of "package + public subpath + export name." Host and Client gateways invoke the factory only when that boundary first encodes or decodes a value. An SRC codec is marked only as `src-json`. When the Host and consumer run in different JavaScript realms, each holds its own Zod instances, but both sets are generated from the same Typert model and symbol keys. Descriptors exist only in the local registry on each side. The wire carries only the `/api` channel, endpoint, and `{ args }` payload. The Host uses its descriptor to decode and invoke the method, while the Client uses its corresponding descriptor to encode arguments and validate the result. diff --git a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md index 06b3f9ad45..058ec47e67 100644 --- a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md @@ -147,7 +147,7 @@ InvocationDescriptor { 参数顺序来自方法签名,HTTP 字段来自参数名或 lookup 声明。取消 descriptor 只保留最后一个 `signal` 位置,并使其不进入具名 `args`;实际 signal 由 Connection 或直接调用 Gateway 的调用方提供。Gateway 不根据请求内容推断可选字段、Context 类型、lookup 类型或缺失参数,也不会合成业务默认值。 -LIB codec 带有 Zod schema 和「package + 公共 subpath + export name」的规范 `typeSymbol`;SRC codec 只标记 `src-json`。Host 和消费端运行在不同 JavaScript realm 时会各自持有 Zod 实例,但这些实例由同一 Typert 模型和 symbol key 生成。 +LIB codec 带有只缓存成功结果的 Zod schema factory 和「package + 公共 subpath + export name」的规范 `typeSymbol`;Host 与 Client gateway 只在该边界首次编码或解码值时调用 factory。SRC codec 只标记 `src-json`。Host 和消费端运行在不同 JavaScript realm 时会各自持有 Zod 实例,但这些实例由同一 Typert 模型和 symbol key 生成。 descriptor 只存在于两端本地 registry。wire 上只有 `/api` channel、endpoint 和 `{ args }` payload;Host 用自己的 descriptor 解码和调用,Client 用自己的对应 descriptor 编码参数和验证结果。 diff --git a/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.i18n.yaml index d9bd4a9c76..57e892121c 100644 --- a/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.md -2026-08-15-client-shells-and-dynamic-packages.md: 89f89784512b86b70ee1b9460850e6f2f951a082 -2026-08-15-client-shells-and-dynamic-packages.zh.md: 5512d9262e979a94a65c25a6c1271b030e23ec70 +2026-08-15-client-shells-and-dynamic-packages.md: ad2bb26b84253e859e567775eec3b06d73b0003b +2026-08-15-client-shells-and-dynamic-packages.zh.md: 70563962b3c613caa611c61a0c690d2e0e777def diff --git a/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.md b/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.md index 89f8978451..ad2bb26b84 100644 --- a/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.md +++ b/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.md @@ -46,13 +46,17 @@ There is no general `dsh.client.provide` alias mechanism. Dynamic rows and stati The modules Node half injects the startup protocol into the served HTML in this order: 1. Install `window.__ModuleLoader__` in queue mode with `pendingQueue`, `load()`, and `create()`. -2. Start preloading every content-addressed application combo URL containing the rows other than modules. +2. Start preloading every revisioned application combo URL containing the rows other than modules. 3. Execute every blocking bootstrap combo URL; these currently contain the ordinary modules factory registration. 4. Assign `window.__DSH_BOOT__`, including all scheduling descriptors and every row's one-resource HMR combo URL. 5. Execute the Vite main module. The bootstrap combo currently registers only the modules factory. The startup kernel passes the raw graph and shell seeds to `__ModuleLoader__.create()`. The facade removes the modules registration, materializes it with a `require` function that rejects every external, and invokes its `createClientModuleSystem` export. The modules bundle parses the graph, constructs and returns `ClientModuleSystem`, caches its own exports as the modules row, and switches the same facade to live mode. The kernel installs that instance as its Loader's `internal`, and the modules plugin reads it there when it provides `ctx.modules`. The modules client face consequently has a zero-external bootstrap requirement and no module-global system identity. +The Host publishes graph and combo descriptors without concatenating response bodies. Each script URL shares one lazy Promise that concatenates its captured bundle bytes on first `GET` and appends the corresponding map URL; each map URL has a separate lazy Promise that reads and composes source maps only on its first `GET`. `HEAD` requests trigger neither body. The Web URL remains gated by Loader settlement and the required-entry audit, but that readiness point does not materialize combo bodies; an index request reads the current graph. + +The theme Host contribution is prepended to index collection. CSS in the head selects the initial document canvas palette, using `prefers-color-scheme` directly for the `system` preference; a body script applies the existing palette attribute and font-size variable before the loading page and application module. + After the `immediately` tier has registered its factories, the kernel creates all Loader entries, awaits Cordis quiescence, and requires every fiber to be ACTIVE. It then calls `ctx.uiRenderer.mount(container)`. The dynamic `ui-renderer` package owns React, slot rendering, hydration of the existing boot DOM, and the React root lifecycle; the startup kernel and failure page remain React-free. ### Dependency declarations @@ -79,7 +83,7 @@ Ordinary installed libraries remain `dependencies`: a dynamic build may bundle a Bundle contents stay stable when an internal DSH relationship is development-only, because each build face declares externality directly. Static libraries remain host-assembled, while dynamic packages retain uniform artifacts and lifecycle governance. The shipped profile owns the complete Client package roster, so individual Client packages do not ask npm to solve the same graph again through peer placement. -The startup protocol depends on the modules package id, and modules must remain self-contained at runtime. Combo generation preserves its ordinary package artifact and gives every other row one shared initial transport; HMR uses the same route with that row as its sole resource. A missing bootstrap registration fails before Cordis starts; later plugin import, apply, and service-wait failures remain visible through the boot page's ACTIVE scan. +The startup protocol depends on the modules package id, and modules must remain self-contained at runtime. Combo generation preserves its ordinary package artifact and gives every other row one shared initial transport; HMR uses the same route with that row as its sole resource. Deferring response bodies moves concatenation to first access, while separately deferring maps keeps debugger-only work off script delivery. A missing bootstrap registration fails before Cordis starts; later plugin import, apply, and service-wait failures remain visible through the boot page's ACTIVE scan. The shell consumes built `lib/` products, so source and browser artifacts can drift until the relevant build or watcher runs. Typechecking source alone does not prove the served application uses the same code. diff --git a/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.zh.md b/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.zh.md index 5512d9262e..70563962b3 100644 --- a/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.zh.md @@ -46,13 +46,17 @@ Client npm 依赖区段描述安装和开发关系,但不能可靠描述 bundl Modules Node 半按以下顺序向实际返回的 HTML 注入启动协议: 1. 以 queue 模式安装 `window.__ModuleLoader__`,包含 `pendingQueue`、`load()` 与 `create()`。 -2. 开始预加载所有按内容寻址的 application combo URL,其中包含 modules 之外的 row。 +2. 开始预加载所有带 revision 的 application combo URL,其中包含 modules 之外的 row。 3. 执行所有阻塞式 bootstrap combo URL;当前其中包含普通的 modules factory registration。 4. 赋值 `window.__DSH_BOOT__`,其中包含全部调度描述及每个 row 的单资源 HMR combo URL。 5. 执行 Vite 主模块。 Bootstrap combo 当前只登记 modules factory。启动内核把原始图与外壳 seed 传给 `__ModuleLoader__.create()`。Facade 移除 modules registration,用拒绝全部 external 的 `require` 函数将其物化,再调用其 `createClientModuleSystem` 导出。Modules bundle 解析图、构造并返回 `ClientModuleSystem`、把自身 exports 缓存为 modules row,并把同一 facade 切换到 live 模式。内核把该实例装成自身 Loader 的 `internal`,modules 插件从这里读取并提供 `ctx.modules`。因此 modules client face 保持零 external 的自举要求,也没有模块级系统身份。 +Host 发布 graph 与 combo descriptor 时不会拼接响应 body。每个脚本 URL 共用一个惰性 Promise,在首次 `GET` 时拼接捕获的 bundle 字节并追加对应的 map URL;每个 map URL 使用另一个惰性 Promise,只在首次 `GET` 时读取并组合 source map。`HEAD` 不触发任一 body。Web URL 仍由 Loader 结算和 required-entry audit 控制,但这个就绪点不会物化 combo body;index 请求读取当时的最新 graph。 + +Theme 的 Host 贡献会前置到 index 收集顺序。head 中的 CSS 选择初始文档画布调色板,`system` 偏好直接使用 `prefers-color-scheme`;body 脚本在加载页面和应用模块之前应用既有的调色板属性与字号变量。 + `immediately` 层级完成 factory 注册后,内核创建全部 Loader entry,等待 Cordis 静止,并要求每个 fiber 都进入 ACTIVE。随后调用 `ctx.uiRenderer.mount(container)`。动态 `ui-renderer` 包拥有 React、slot 渲染、已有启动 DOM 的 hydrate 和 React root 生命周期;启动内核与失败页保持 React-free。 ### 依赖声明 @@ -79,7 +83,7 @@ Bootstrap combo 当前只登记 modules factory。启动内核把原始图与外 内部 DSH 关系仅放在开发区段时,bundle 内容仍保持稳定,因为每个构建 face 都直接声明 external。静态库继续由宿主装配,动态包则保留统一产物与生命周期治理。发布 profile 拥有完整 Client 包名册,因此各 Client 包不再要求 npm 通过 peer placement 重复求解同一张图。 -启动协议依赖 modules 的 package id,modules 还必须保持运行期自包含。Combo 生成保留其普通 package 产物,并为其他全部 row 提供一条共享初始传输;HMR 使用同一条路由,并只把该 row 作为资源。缺少 bootstrap registration 会在 Cordis 启动前失败;后续插件 import、apply 与 service 等待失败仍由启动页的 ACTIVE 扫描呈现。 +启动协议依赖 modules 的 package id,modules 还必须保持运行期自包含。Combo 生成保留其普通 package 产物,并为其他全部 row 提供一条共享初始传输;HMR 使用同一条路由,并只把该 row 作为资源。响应 body 的延迟生成会把拼接移到首次访问,而 map 的独立延迟生成会让仅供调试器使用的工作不进入脚本交付路径。缺少 bootstrap registration 会在 Cordis 启动前失败;后续插件 import、apply 与 service 等待失败仍由启动页的 ACTIVE 扫描呈现。 外壳消费已构建 `lib/` 产品,因此在相关 build 或 watcher 运行前,源码与浏览器产物可能漂移。仅源码 typecheck 通过不能证明实际服务的应用使用同一份代码。 diff --git a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.i18n.yaml b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.i18n.yaml index b8da4cdd99..7117da46f7 100644 --- a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.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/process/2026-08-10-npm-release-sequences.md -2026-08-10-npm-release-sequences.md: 77b73b8c461238ad80afd7abd609a68df0bcf058 -2026-08-10-npm-release-sequences.zh.md: f56234fc50d776658d2d9191056b0b04e1609f1e +2026-08-10-npm-release-sequences.md: 6f52c00a31870939981994965b70504e8f8143ca +2026-08-10-npm-release-sequences.zh.md: effa5538cefbc8f25b2e047ee40e0dc7306bb2fb diff --git a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md index 77b73b8c46..6f52c00a31 100644 --- a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md +++ b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md @@ -96,6 +96,8 @@ A dependency in `optionalDependencies`, or a peer carrying `peerDependenciesMeta A violation names the package, the declaration that made it optional, and the way out in order — import it as a type, which is all that declaration merging needs, or restructure so module scope does not need the package. A dynamic `import()` only moves the failure to first use, so it belongs to a caller that genuinely requires the package and handles its absence; reaching for it is a sign the dependency is not optional, and the gate does not offer it as the remedy. +A required CommonJS-compatible Host dependency whose initialization is unrelated to startup may use `createLazyRequire(specifier, import.meta.url)`. The caller keeps a type-only import, supplies a literal dependency specifier, and invokes the returned loader at the owning operation. `verify-package-dependencies` recognizes that literal as a Host runtime edge, so Client/Host packages retain it in `dependencies` even though no static value import remains. The utility caches only a successful load and preserves caller-relative resolution; it does not make an optional dependency required or hide first-use failure. + ### Release family objects The entity in this domain is a **release family**: a set of packages sharing one version baseline and tag naming that publishes as a unit. Adding a family means adding a subclass and a workflow lane, not changing the core. diff --git a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md index f56234fc50..effa5538ce 100644 --- a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md +++ b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md @@ -96,6 +96,8 @@ registry 的两个行为决定了「怎么尝试一次发布」。写入之间 报错会点名这个包、点名是哪条声明把它标成 optional 的,并按顺序给出出路——把它作为类型引入(声明合并需要的仅此而已),或者调整写法让模块作用域不再需要这个包。动态 `import()` 只是把失败推迟到首次使用,它属于那种确实需要这个包、并且自己处理缺失的调用方;会想到它,往往说明这个依赖并不 optional,所以门禁不把它作为解法给出。 +初始化与启动无关、且兼容 CommonJS 的必需 Host 依赖可以使用 `createLazyRequire(specifier, import.meta.url)`。调用方保留 type-only import,传入字面量依赖 specifier,并在所属操作中调用返回的 loader。`verify-package-dependencies` 会把该字面量识别为 Host runtime edge,因此 Client/Host 包即使没有静态值 import,仍会把它保留在 `dependencies`。该工具只缓存成功加载,并保留调用方相对解析;它不会把 optional 依赖变成必需依赖,也不会隐藏首次使用失败。 + ### 发布族对象 这个领域里的实体是**发布族**:一组共享版本基线与 tag 命名、可整体发布的包。新增一族等于加一个子类和一条 workflow lane,不改核心。 diff --git a/apps/cli/package.json b/apps/cli/package.json index 177b4a94b4..930872568b 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh", "description": "dsh CLI: profile boot, plugin management, and the browser UI alias", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/apps/desktop-host/package.json b/apps/desktop-host/package.json index 9cf83fd4d7..fc38d4435f 100644 --- a/apps/desktop-host/package.json +++ b/apps/desktop-host/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-desktop-host", "description": "Private Node-mode host process for the Electron desktop application", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "private": true, "license": "MIT", "type": "module", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 98a6b2313f..a0dec88993 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-desktop", "description": "Electron desktop shell for a bundled dsh runtime and external plugins", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "private": true, "license": "MIT", "type": "module", diff --git a/apps/web/package.json b/apps/web/package.json index 598b4c9e7f..5750f4af30 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-web-frontend", "description": "Web application entry: vite build over the @deepseek-ai/dsh-client-web shell library; dist/ served by apps/cli's dsh web", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/apps/web/tests/settings-chrome.e2e.ts b/apps/web/tests/settings-chrome.e2e.ts index 4bf1c0c9f5..0730f56633 100644 --- a/apps/web/tests/settings-chrome.e2e.ts +++ b/apps/web/tests/settings-chrome.e2e.ts @@ -274,7 +274,7 @@ describe('web e2e: settings modal and General preferences', () => { return { attr: document.body.hasAttribute('data-ds-dark-theme'), background: getComputedStyle(boot).backgroundColor, - colorScheme: document.documentElement.style.colorScheme, + colorScheme: getComputedStyle(document.documentElement).colorScheme, } }) expect(state).toEqual({ diff --git a/benchmarks/package.json b/benchmarks/package.json index 3ce60f1d2d..fe74d6f65b 100644 --- a/benchmarks/package.json +++ b/benchmarks/package.json @@ -19,6 +19,7 @@ "@deepseek-ai/dsh-client-store": "workspace:^", "@deepseek-ai/dsh-client-ui-chat": "workspace:^", "@deepseek-ai/dsh-deque": "workspace:^", + "@deepseek-ai/dsh-lazy-require": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-sdk-client": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", diff --git a/docs/config-catalog.i18n.yaml b/docs/config-catalog.i18n.yaml index b6e49bd4a5..07c431a795 100644 --- a/docs/config-catalog.i18n.yaml +++ b/docs/config-catalog.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/config-catalog.md -config-catalog.md: 62a9ddd3a31c5a088f9f8ef7642d4a2a028f5ec2 -config-catalog.zh.md: 9eb7cf5dfd35bffda5fb03a7aa2b7a89dcf56967 +config-catalog.md: dffb33fedc3650f972661f77c3339628517153ad +config-catalog.zh.md: 35805a0428b4d9ad3410ed4c7b7da4bbc32a704d diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 62a9ddd3a3..dffb33fedc 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -3736,6 +3736,7 @@ Imported as libraries by other packages; a `cordis.yml` cannot load them. - `@deepseek-ai/dsh-hook-protocol` ([`packages/hooks/hook-protocol/src/index.ts`](../packages/hooks/hook-protocol/src/index.ts)) - `@deepseek-ai/dsh-http-proxy` ([`packages/util/http-proxy/src/index.ts`](../packages/util/http-proxy/src/index.ts)) - `@deepseek-ai/dsh-launch-environment` ([`packages/util/launch-environment/src/index.ts`](../packages/util/launch-environment/src/index.ts)) +- `@deepseek-ai/dsh-lazy-require` ([`packages/util/lazy-require/src/index.ts`](../packages/util/lazy-require/src/index.ts)) - `@deepseek-ai/dsh-llm-mock-server` ([`packages/test-support/llm-mock-server/src/index.ts`](../packages/test-support/llm-mock-server/src/index.ts)) - `@deepseek-ai/dsh-loader-smoke` ([`packages/test-support/loader-smoke/src/index.ts`](../packages/test-support/loader-smoke/src/index.ts)) - `@deepseek-ai/dsh-native-command` ([`packages/util/native-command/src/index.ts`](../packages/util/native-command/src/index.ts)) diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md index 9eb7cf5dfd..35805a0428 100644 --- a/docs/config-catalog.zh.md +++ b/docs/config-catalog.zh.md @@ -3737,6 +3737,7 @@ export interface Config { - `@deepseek-ai/dsh-hook-protocol`([`packages/hooks/hook-protocol/src/index.ts`](../packages/hooks/hook-protocol/src/index.ts)) - `@deepseek-ai/dsh-http-proxy`([`packages/util/http-proxy/src/index.ts`](../packages/util/http-proxy/src/index.ts)) - `@deepseek-ai/dsh-launch-environment`([`packages/util/launch-environment/src/index.ts`](../packages/util/launch-environment/src/index.ts)) +- `@deepseek-ai/dsh-lazy-require`([`packages/util/lazy-require/src/index.ts`](../packages/util/lazy-require/src/index.ts)) - `@deepseek-ai/dsh-llm-mock-server`([`packages/test-support/llm-mock-server/src/index.ts`](../packages/test-support/llm-mock-server/src/index.ts)) - `@deepseek-ai/dsh-loader-smoke`([`packages/test-support/loader-smoke/src/index.ts`](../packages/test-support/loader-smoke/src/index.ts)) - `@deepseek-ai/dsh-native-command`([`packages/util/native-command/src/index.ts`](../packages/util/native-command/src/index.ts)) diff --git a/docs/module-graph.i18n.yaml b/docs/module-graph.i18n.yaml index 214fff94a5..bec81e7848 100644 --- a/docs/module-graph.i18n.yaml +++ b/docs/module-graph.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/module-graph.md -module-graph.md: 57a8c8752fba0a1d628649c1a68c65f0927fc2cd -module-graph.zh.md: 91d5b371ec61feaa0df9c86c5e9a7d9c2999e001 +module-graph.md: ed2db7575519e3f7ed036ae5a3c15309d980fc30 +module-graph.zh.md: c9339896380e02c6022b4de0d474c328d8d08c6b diff --git a/docs/module-graph.md b/docs/module-graph.md index 57a8c8752f..ed2db75755 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -15,6 +15,7 @@ flowchart TD pkg_home_paths["home-paths"] pkg_http_proxy["http-proxy"] pkg_launch_environment["launch-environment"] + pkg_lazy_require["lazy-require"] pkg_native_command["native-command"] pkg_output_retention["output-retention"] pkg_package_manifest["package-manifest"] @@ -1280,6 +1281,7 @@ flowchart TD | [`home-paths`](../packages/util/home-paths) | `util` | — | | [`http-proxy`](../packages/util/http-proxy) | `util` | — | | [`launch-environment`](../packages/util/launch-environment) | `util` | — | +| [`lazy-require`](../packages/util/lazy-require) | `util` | — | | [`native-command`](../packages/util/native-command) | `util` | — | | [`output-retention`](../packages/util/output-retention) | `util` | — | | [`package-manifest`](../packages/util/package-manifest) | `util` | — | diff --git a/docs/module-graph.zh.md b/docs/module-graph.zh.md index 91d5b371ec..c933989638 100644 --- a/docs/module-graph.zh.md +++ b/docs/module-graph.zh.md @@ -17,6 +17,7 @@ flowchart TD pkg_home_paths["home-paths"] pkg_http_proxy["http-proxy"] pkg_launch_environment["launch-environment"] + pkg_lazy_require["lazy-require"] pkg_native_command["native-command"] pkg_output_retention["output-retention"] pkg_package_manifest["package-manifest"] @@ -1282,6 +1283,7 @@ flowchart TD | [`home-paths`](../packages/util/home-paths) | `util` | — | | [`http-proxy`](../packages/util/http-proxy) | `util` | — | | [`launch-environment`](../packages/util/launch-environment) | `util` | — | +| [`lazy-require`](../packages/util/lazy-require) | `util` | — | | [`native-command`](../packages/util/native-command) | `util` | — | | [`output-retention`](../packages/util/output-retention) | `util` | — | | [`package-manifest`](../packages/util/package-manifest) | `util` | — | diff --git a/docs/subsystems/client-modules.i18n.yaml b/docs/subsystems/client-modules.i18n.yaml index 33e35b5377..c7c6cbed58 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: 12925fa8ddb7c7193af76cf1bed5894f5d7d75e0 -client-modules.zh.md: 513391af8369a5ba4d9a439a14f7fb5eb1f45d27 +client-modules.md: 74aa2b4983f3c8c7fc6b3ba9b03646ac8141f3f1 +client-modules.zh.md: 259717a44a3275ef5b94df698fc01b5de7431fd0 diff --git a/docs/subsystems/client-modules.md b/docs/subsystems/client-modules.md index 12925fa8dd..74aa2b4983 100644 --- a/docs/subsystems/client-modules.md +++ b/docs/subsystems/client-modules.md @@ -36,7 +36,7 @@ interface WebBootEntry { ``` ```ts type-equiv -/** Initial scheduling phase for one content-addressed combo script. */ +/** Initial scheduling phase for one revisioned combo script. */ type WebBootBatchPhase = 'bootstrap' | 'application' ``` @@ -45,9 +45,9 @@ type WebBootBatchPhase = 'bootstrap' | 'application' interface WebBootBatch { /** Parser-blocking bootstrap or preloaded application scheduling. */ phase: WebBootBatchPhase - /** Content-addressed combo script endpoint. */ + /** Revisioned combo script endpoint. */ url: string - /** Revision over the combined plugin script bytes and indexed source map. */ + /** Revision derived from the ordered entry revisions. */ rev: string /** Graph entry ids whose factories the script registers, in execution order. */ entries: string[] @@ -57,7 +57,7 @@ interface WebBootBatch { ```ts type-equiv /** The composed client entry graph the host injects as `window.__DSH_BOOT__`. */ interface WebBootGraph { - /** Consistency anchor over the whole graph (content + bundle hashes). */ + /** Consistency anchor over the current entry and batch descriptors. */ rev: string /** * Composed entries in module-graph order — a dynamic package row precedes @@ -70,7 +70,7 @@ interface WebBootGraph { } ``` -Each initial row's `rev` is an opaque process nonce plus sequence, so graph composition does not hash every plugin artifact. After HMR observes a change, that row's revision becomes the hash of its new bundle and available source map. The initial descriptors partition rows into bootstrap and application scheduling phases, and either phase may contain several descriptors. Their URLs contain only the ordered package-resource list and revision; phase names do not enter the route. Graph composition preserves row order while greedily splitting before the map-form URL exceeds 3 KiB. Startup combo revisions hash the combined plugin script bytes and indexed source map, and the graph revision hashes both rows and descriptors. `immediately` marks the stage-one registration barrier; rows within one combo share its script transport, while separate combos load independently. +Each initial row's `rev` is an opaque process nonce plus sequence, so graph composition does not hash every plugin artifact. After HMR observes a bundle change, that row's revision becomes the hash of its new executable bytes. The initial descriptors partition rows into bootstrap and application scheduling phases, and either phase may contain several descriptors. Their URLs contain only the ordered package-resource list and a revision derived from those row revisions; phase names do not enter the route. Graph composition preserves row order while greedily splitting before the map-form URL exceeds 3 KiB, without concatenating scripts or reading maps. The graph revision hashes the entry and batch descriptors. `immediately` marks the stage-one registration barrier; rows within one combo share its script transport, while separate combos load independently. ## The scan @@ -82,7 +82,7 @@ Package metadata — including the negative "not a client package" verdict — i ## The bundle route and index injection -`GET`/`HEAD /plugins/??/client.js,/client.js&rev=` serves an exact generated combo script; a one-resource request uses the same form and is the HMR path. Its absolute `sourceMappingURL` changes every resource suffix in parallel, yielding `/plugins/??/client.js.map,/client.js.map&rev=`. The map is Indexed Source Map v3 even for one resource. An authored component map supplies its section; a component without one receives an identity section whose `sourcesContent` is the generated bundle and whose source name is its packaged `sourceURL` or plugin route. Every startup request URL is at most 3 KiB measured as UTF-8 bytes; partitioning uses the longer map form. All application URLs are preloaded, and all bootstrap URLs execute before the graph global and Vite entry. All advertised responses use long-lived immutable caching. Unknown or altered resource lists, missing revisions, and stale revisions answer 404 rather than serving different bytes or letting the SPA fallback return HTML as JavaScript; other methods are 405. The injection rows carry the current graph on every index render, so a reload always boots against the live composition. +`GET`/`HEAD /plugins/??/client.js,/client.js&rev=` addresses one generated combo script; a one-resource request uses the same form and is the HMR path. The script is concatenated once on its first `GET` and ends with an absolute `sourceMappingURL` whose resource suffixes are `.js.map`. The map files are not read by startup, index rendering, script `GET`, or `HEAD`; the first map `GET` reads and validates them, composes one Indexed Source Map v3, and caches that body. An authored component map supplies its section; a component without one receives an identity section whose `sourcesContent` is the captured bundle and whose source name is its packaged `sourceURL` or plugin route. Every startup request URL is at most 3 KiB measured as UTF-8 bytes; partitioning uses the longer map form. All application URLs are preloaded, and all bootstrap URLs execute before the graph global and Vite entry. Materialized responses use long-lived immutable caching. Unknown or altered resource lists, missing revisions, and stale revisions answer 404 rather than serving different bytes or letting the SPA fallback return HTML as JavaScript; other methods are 405. The injection rows carry the current graph on every index render, so a reload always boots against the live composition. ## The service @@ -98,9 +98,9 @@ interface ClientArtifactBaseline { } ``` -`ClientModuleRegistry` (`ctx.clientModules`, defined in [`packages/client/modules/src/index.ts`](../../packages/client/modules/src/index.ts)) exposes reads and the rebuild face; signatures are in the generated [service catalog](#ctxclientmodules--clientmoduleregistry). `graph()` returns the current composed graph (a stable object between changes), `clientPath(id)` returns the bundle's absolute path, and `artifactBaseline(id)` returns the bundle stat values captured before the current snapshot was read. `rebuilt(id)` is the only entry point through which changed bundle content reaches the graph: it re-hashes the bundle together with its current source map, and only a real rev change recomposes the graph and notifies. `onRebuilt` fires per changed bundle with the new rev; `onGraphChanged` fires after any flush that recomposed the graph (row added or removed, or a rebuilt rev change) and is pull-model — listeners re-read `graph()`. Both notification paths contain listener exceptions so one throwing subscriber cannot skip later subscribers or kill whatever triggered the flush. +`ClientModuleRegistry` (`ctx.clientModules`, defined in [`packages/client/modules/src/index.ts`](../../packages/client/modules/src/index.ts)) exposes reads and the rebuild face; signatures are in the generated [service catalog](#ctxclientmodules--clientmoduleregistry). `graph()` returns the current composed graph (a stable object between changes), `clientPath(id)` returns the bundle's absolute path, and `artifactBaseline(id)` returns the bundle stat values captured before the current snapshot was read. `fetchBundle()` resolves the same lazy response used by the HTTP route. `rebuilt(id)` is the only entry point through which changed bundle content reaches the graph: it re-hashes the bundle bytes, and only a real revision change recomposes the graph and notifies. `onRebuilt` fires per changed bundle with the new revision; `onGraphChanged` fires after any flush that recomposed the graph (row added or removed, or a rebuilt revision change) and is pull-model — listeners re-read `graph()`. Both notification paths contain listener exceptions so one throwing subscriber cannot skip later subscribers or kill whatever triggered the flush. -In development, [dsh-client-hmr](../../packages/client/hmr/README.md) is the registry's watch driver: its node half stat-polls every graph row's bundle from the module host's pre-read baseline, calls `rebuilt(id)` only for a changed or dirty row, resyncs its watch set through `onGraphChanged`, and broadcasts rev changes to the browser half over SSE. Source-map changes alone do not trigger a reload; the current map joins the snapshot when a bundle change does. Production graphs omit the HMR row entirely; the module host itself never watches files. +In development, [dsh-client-hmr](../../packages/client/hmr/README.md) is the registry's watch driver: its node half stat-polls every graph row's bundle from the module host's pre-read baseline, calls `rebuilt(id)` only for a changed or dirty row, resyncs its watch set through `onGraphChanged`, and broadcasts revision changes to the browser half over SSE. Source-map changes alone do not trigger a reload; a new combo-map URL appears only after a bundle revision changes, and each map body is fixed by its first `GET`. Production graphs omit the HMR row entirely; the module host itself never watches files. @@ -133,11 +133,12 @@ clientPath(id: string): string | undefined /** * Serve an advertised revisioned bundle or source map without a Web server. * Unknown URLs return 404, unsupported methods return 405, and `HEAD` - * returns the same immutable headers without a body. + * returns the same immutable headers without materializing a body. Each body + * is built once on its first `GET`; script construction never reads maps. * @param request - shell-carrier request for a `/plugins` resource. * @returns the exact response also exposed by the optional Web route. */ -fetchBundle(request: Request): Response +async fetchBundle(request: Request): Promise /** * Filesystem baseline captured before an entry's current bytes were read. diff --git a/docs/subsystems/client-modules.zh.md b/docs/subsystems/client-modules.zh.md index 513391af83..259717a44a 100644 --- a/docs/subsystems/client-modules.zh.md +++ b/docs/subsystems/client-modules.zh.md @@ -36,7 +36,7 @@ interface WebBootEntry { ``` ```ts type-equiv -/** Initial scheduling phase for one content-addressed combo script. */ +/** Initial scheduling phase for one revisioned combo script. */ type WebBootBatchPhase = 'bootstrap' | 'application' ``` @@ -45,9 +45,9 @@ type WebBootBatchPhase = 'bootstrap' | 'application' interface WebBootBatch { /** Parser-blocking bootstrap or preloaded application scheduling. */ phase: WebBootBatchPhase - /** Content-addressed combo script endpoint. */ + /** Revisioned combo script endpoint. */ url: string - /** Revision over the combined plugin script bytes and indexed source map. */ + /** Revision derived from the ordered entry revisions. */ rev: string /** Graph entry ids whose factories the script registers, in execution order. */ entries: string[] @@ -57,7 +57,7 @@ interface WebBootBatch { ```ts type-equiv /** The composed client entry graph the host injects as `window.__DSH_BOOT__`. */ interface WebBootGraph { - /** Consistency anchor over the whole graph (content + bundle hashes). */ + /** Consistency anchor over the current entry and batch descriptors. */ rev: string /** * Composed entries in module-graph order — a dynamic package row precedes @@ -70,7 +70,7 @@ interface WebBootGraph { } ``` -每个初始 row 的 `rev` 都是不透明的进程 nonce 加序号,因此组合图时不会哈希每个插件产物。HMR 观察到变化后,该 row 的 revision 才改为新 bundle 及其可用 sourcemap 的哈希。初始描述把 row 划入 bootstrap 与 application 两个调度阶段,每个阶段都可以包含多条描述。URL 只含有序 package 资源列表与 revision,阶段名不会进入路由。图组合保持 row 顺序,并在 map 形式 URL 超过 3 KiB 前贪心切分。启动 combo revision 对合并后的插件脚本字节与 indexed sourcemap 求哈希,图 revision 则对 row 与描述一并求哈希。`immediately` 标记第一阶段的 registration barrier;同一 combo 中的 row 共享脚本传输,不同 combo 则独立加载。 +每个初始 row 的 `rev` 都是不透明的进程 nonce 加序号,因此组合图时不会哈希每个插件产物。HMR 观察到 bundle 变化后,该 row 的 revision 才改为新可执行字节的哈希。初始 descriptor 把 row 划入 bootstrap 与 application 两个调度阶段,每个阶段都可以包含多条 descriptor。URL 只含有序 package 资源列表和从这些 row revision 派生的 revision,阶段名不会进入路由。图组合保持 row 顺序,并在 map 形式 URL 超过 3 KiB 前贪心切分,不拼接脚本,也不读取 map。图 revision 对 entry 与 batch descriptor 求哈希。`immediately` 标记第一阶段的 registration barrier;同一 combo 中的 row 共享脚本传输,不同 combo 则独立加载。 ## 扫描 @@ -82,7 +82,7 @@ interface WebBootGraph { ## bundle 路由与 index 注入 -`GET`/`HEAD /plugins/??/client.js,/client.js&rev=` 提供精确生成的 combo 脚本;单资源请求采用同一形式,也是 HMR 路径。其绝对 `sourceMappingURL` 平行改写每个资源后缀,得到 `/plugins/??/client.js.map,/client.js.map&rev=`。即使只有一个资源,map 仍采用 Indexed Source Map v3。组件有自带 map 时直接用于对应 section;没有时则获得 identity section,其 `sourcesContent` 是构建后 bundle,source 名取打包后的 `sourceURL` 或插件路由。每条启动请求 URL 按 UTF-8 字节计算都不超过 3 KiB;切分按更长的 map 形式计算。所有 application URL 都会预加载,所有 bootstrap URL 都会在图全局量与 Vite entry 之前执行。所有已发布响应都使用长期 immutable 缓存。未知或被修改的资源列表、缺少 revision 及陈旧 revision 都返回 404,绝不提供其他字节,也不会让 SPA fallback 把 HTML 当作 JavaScript 返回;其他方法返回 405。注入行在每次 index 渲染时携带当前图,因此重新加载总是基于实时组合启动。 +`GET`/`HEAD /plugins/??/client.js,/client.js&rev=` 寻址一份生成的 combo 脚本;单资源请求采用同一形式,也是 HMR 路径。脚本在首次 `GET` 时只拼接一次,并以绝对 `sourceMappingURL` 结尾,其中每个资源后缀改为 `.js.map`。启动、index 渲染、脚本 `GET` 和 `HEAD` 都不会读取 map 文件;首次 map `GET` 才会读取并校验这些文件、组合一份 Indexed Source Map v3,并缓存该 body。组件有自带 map 时直接用于对应 section;没有时则获得 identity section,其 `sourcesContent` 是捕获的 bundle,source 名取打包后的 `sourceURL` 或插件路由。每条启动请求 URL 按 UTF-8 字节计算都不超过 3 KiB;切分按更长的 map 形式计算。所有 application URL 都会预加载,所有 bootstrap URL 都会在图全局量与 Vite entry 之前执行。已物化响应使用长期 immutable 缓存。未知或被修改的资源列表、缺少 revision 及陈旧 revision 都返回 404,绝不提供其他字节,也不会让 SPA fallback 把 HTML 当作 JavaScript 返回;其他方法返回 405。注入行在每次 index 渲染时携带当前图,因此重新加载总是基于实时组合启动。 ## 服务 @@ -98,9 +98,9 @@ interface ClientArtifactBaseline { } ``` -`ClientModuleRegistry`(`ctx.clientModules`,定义于 [`packages/client/modules/src/index.ts`](../../packages/client/modules/src/index.ts))暴露读取面与重建面;签名见生成的[服务目录](#ctxclientmodules--clientmoduleregistry)。`graph()` 返回当前组合出的图(两次变更之间是同一个稳定对象),`clientPath(id)` 返回 bundle 的绝对路径,`artifactBaseline(id)` 返回读取当前快照前捕获的 bundle stat 值。`rebuilt(id)` 是变化后的 bundle 内容到达图的唯一入口:它把 bundle 与当前 source map 一起重新哈希,只有 rev 真正变化才会重新组合图并发出通知。`onRebuilt` 按发生变化的 bundle 逐个触发并携带新 rev;`onGraphChanged` 在任何一次重新组合了图的 flush 之后触发(行的增删,或 rebuilt 带来的 rev 变化),并采用拉取模型——监听器自行重读 `graph()`。两条通知路径都会兜住监听器异常,因此一个抛错的订阅者既不能让后续订阅者被跳过,也不能杀死触发这次 flush 的一方。 +`ClientModuleRegistry`(`ctx.clientModules`,定义于 [`packages/client/modules/src/index.ts`](../../packages/client/modules/src/index.ts))暴露读取面与重建面;签名见生成的[服务目录](#ctxclientmodules--clientmoduleregistry)。`graph()` 返回当前组合出的图(两次变更之间是同一个稳定对象),`clientPath(id)` 返回 bundle 的绝对路径,`artifactBaseline(id)` 返回读取当前快照前捕获的 bundle stat 值。`fetchBundle()` 解析 HTTP 路由所使用的同一份惰性响应。`rebuilt(id)` 是变化后的 bundle 内容到达图的唯一入口:它重新哈希 bundle 字节,只有 revision 真正变化才会重新组合图并发出通知。`onRebuilt` 按发生变化的 bundle 逐个触发并携带新 revision;`onGraphChanged` 在任何一次重新组合了图的 flush 之后触发(行的增删,或 rebuilt 带来的 revision 变化),并采用拉取模型——监听器自行重读 `graph()`。两条通知路径都会兜住监听器异常,因此一个抛错的订阅者既不能让后续订阅者被跳过,也不能杀死触发这次 flush 的一方。 -开发环境下,[dsh-client-hmr](../../packages/client/hmr/README.zh.md) 是注册表的监视驱动:它的 Node 半从 module host 读文件前记录的基线出发,对图中每一行的 bundle 做 stat 轮询,只为变化或标脏的 row 调用 `rebuilt(id)`,经 `onGraphChanged` 重新同步监视集合,并通过 SSE(Server-Sent Events)把 rev 变化广播给浏览器半。仅 source map 变化不会触发重载;bundle 变化时,当前 map 会一起进入快照。生产环境的图完全不含 HMR(热模块替换)行;module host 自身从不监视文件。 +开发环境下,[dsh-client-hmr](../../packages/client/hmr/README.zh.md) 是注册表的监视驱动:它的 Node 半从 module host 读文件前记录的基线出发,对图中每一行的 bundle 做 stat 轮询,只为变化或标脏的 row 调用 `rebuilt(id)`,经 `onGraphChanged` 重新同步监视集合,并通过 SSE(Server-Sent Events)把 revision 变化广播给浏览器半。仅 source map 变化不会触发重载;新 combo-map URL 只会在 bundle revision 变化后出现,每份 map body 由其首次 `GET` 固定。生产环境的图完全不含 HMR(热模块替换)行;module host 自身从不监视文件。 @@ -133,11 +133,12 @@ clientPath(id: string): string | undefined /** * Serve an advertised revisioned bundle or source map without a Web server. * Unknown URLs return 404, unsupported methods return 405, and `HEAD` - * returns the same immutable headers without a body. + * returns the same immutable headers without materializing a body. Each body + * is built once on its first `GET`; script construction never reads maps. * @param request - shell-carrier request for a `/plugins` resource. * @returns the exact response also exposed by the optional Web route. */ -fetchBundle(request: Request): Response +async fetchBundle(request: Request): Promise /** * Filesystem baseline captured before an entry's current bytes were read. diff --git a/docs/subsystems/typert.i18n.yaml b/docs/subsystems/typert.i18n.yaml index 0a8dc61e44..fb6146eda0 100644 --- a/docs/subsystems/typert.i18n.yaml +++ b/docs/subsystems/typert.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/typert.md -typert.md: 0f3d2b1afdc1b7713402abc5885dc9551e16ac14 -typert.zh.md: a3c06489707e18433ed9018363c57ec76732e606 +typert.md: 954c3dfb7e66b100132c6d4604c28f06fba04916 +typert.zh.md: dd2becdc43871ddbecdfb45909e076435ab5a447 diff --git a/docs/subsystems/typert.md b/docs/subsystems/typert.md index 0f3d2b1afd..954c3dfb7e 100644 --- a/docs/subsystems/typert.md +++ b/docs/subsystems/typert.md @@ -38,7 +38,7 @@ interface TypertLookupDefinition { ## Invocation descriptors -An `InvocationDescriptor` is local reflection, not a wire message. Host and consumer builds generate corresponding descriptors; the request sends only the endpoint and named `args`. Strict codecs carry generated schemas, while SRC codecs enforce JSON-safe values without structural type recovery. Cancellation is an out-of-band carrier signal injected after business parameters and never enters `args`. +An `InvocationDescriptor` is local reflection, not a wire message. Host and consumer builds generate corresponding descriptors; the request sends only the endpoint and named `args`. Strict codecs carry generated schema factories, while SRC codecs enforce JSON-safe values without structural type recovery. Cancellation is an out-of-band carrier signal injected after business parameters and never enters `args`. ```ts type-equiv /** Codec attached to one invocation parameter or result. */ @@ -46,7 +46,8 @@ type TypertCodec = | { readonly mode: 'strict' readonly typeSymbol: string - readonly schema: TypertSchema + /** Materialize and return the process-realm schema on first boundary use. */ + readonly create: () => TypertSchema } | { readonly mode: 'src-json' @@ -261,14 +262,14 @@ register(contribution: TypertContribution): TypertDisposer /** * Look up one schema by `#`. * @param key - global schema key. - * @returns the live schema record, or `undefined` when absent. + * @returns a record containing the cached schema, or `undefined` when absent. */ get(key: string): TypertSchemaRecord | undefined /** * Resolve one required schema. * @param key - global schema key. - * @returns the live schema record. + * @returns a record containing the cached schema. * @throws when the key is malformed, the package face is absent, or the schema is not contributed. */ resolve(key: string): TypertSchemaRecord @@ -276,7 +277,7 @@ resolve(key: string): TypertSchemaRecord /** * Enumerate live schemas in registration order. * @param filter - optional package and face restriction. - * @returns matching schema records. + * @returns matching records containing the cached schemas. */ list(filter: TypertSchemaFilter = {}): TypertSchemaRecord[] diff --git a/docs/subsystems/typert.zh.md b/docs/subsystems/typert.zh.md index a3c0648970..dd2becdc43 100644 --- a/docs/subsystems/typert.zh.md +++ b/docs/subsystems/typert.zh.md @@ -38,7 +38,7 @@ interface TypertLookupDefinition { ## 调用 descriptor -`InvocationDescriptor` 是本地反射信息,不是 wire message。Host 与消费方构建会生成彼此对应的 descriptor;请求只发送 endpoint 与具名 `args`。strict codec 携带生成的 schema,SRC codec 则在不恢复结构类型的前提下强制要求 JSON 安全值。取消通过带外 carrier signal 表达:它在业务参数之后注入,绝不进入 `args`。 +`InvocationDescriptor` 是本地反射信息,不是 wire message。Host 与消费方构建会生成彼此对应的 descriptor;请求只发送 endpoint 与具名 `args`。strict codec 携带生成的 schema factory,SRC codec 则在不恢复结构类型的前提下强制要求 JSON 安全值。取消通过带外 carrier signal 表达:它在业务参数之后注入,绝不进入 `args`。 ```ts type-equiv /** Codec attached to one invocation parameter or result. */ @@ -46,7 +46,8 @@ type TypertCodec = | { readonly mode: 'strict' readonly typeSymbol: string - readonly schema: TypertSchema + /** Materialize and return the process-realm schema on first boundary use. */ + readonly create: () => TypertSchema } | { readonly mode: 'src-json' @@ -261,14 +262,14 @@ register(contribution: TypertContribution): TypertDisposer /** * Look up one schema by `#`. * @param key - global schema key. - * @returns the live schema record, or `undefined` when absent. + * @returns a record containing the cached schema, or `undefined` when absent. */ get(key: string): TypertSchemaRecord | undefined /** * Resolve one required schema. * @param key - global schema key. - * @returns the live schema record. + * @returns a record containing the cached schema. * @throws when the key is malformed, the package face is absent, or the schema is not contributed. */ resolve(key: string): TypertSchemaRecord @@ -276,7 +277,7 @@ resolve(key: string): TypertSchemaRecord /** * Enumerate live schemas in registration order. * @param filter - optional package and face restriction. - * @returns matching schema records. + * @returns matching records containing the cached schemas. */ list(filter: TypertSchemaFilter = {}): TypertSchemaRecord[] diff --git a/package.json b/package.json index fbedba9f1f..d0d5d9817d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@deepseek-ai/dsh-root", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "license": "MIT", "private": true, "type": "module", diff --git a/packages/acp/acp/package.json b/packages/acp/acp/package.json index 1e18f54543..228ed34b52 100644 --- a/packages/acp/acp/package.json +++ b/packages/acp/acp/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-acp", "description": "Automation-only Agent Client Protocol server for driving DeepSeek Harness agents over JSON-RPC stdio", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/api/gateway/package.json b/packages/api/gateway/package.json index b84b7acb4e..2386131657 100644 --- a/packages/api/gateway/package.json +++ b/packages/api/gateway/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-gateway", "description": "Typert Remote Host dispatcher and Client API endpoint", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/api/gateway/src/client/index.ts b/packages/api/gateway/src/client/index.ts index edb58d3d25..a24d7de7fe 100644 --- a/packages/api/gateway/src/client/index.ts +++ b/packages/api/gateway/src/client/index.ts @@ -717,7 +717,7 @@ function parseInput(codec: TypertCodec, value: unknown, endpoint: string, field: throw new Error(`client api: generated Remote ${endpoint} field ${JSON.stringify(field)} has no strict codec`) } try { - return codec.schema.parse(value) + return codec.create().parse(value) } catch (cause) { throw new Error(`client api: ${endpoint} rejected ${JSON.stringify(field)}`, { cause }) } diff --git a/packages/api/gateway/src/index.ts b/packages/api/gateway/src/index.ts index ae813fde1a..89915a96a0 100644 --- a/packages/api/gateway/src/index.ts +++ b/packages/api/gateway/src/index.ts @@ -1140,7 +1140,7 @@ function decode( ): unknown { try { if (codec.mode === 'strict') { - value = codec.schema.parse(value) + value = codec.create().parse(value) /* v8 ignore next -- generated optional-input codecs are the only strict codecs that return undefined. */ if (value === undefined) return value } diff --git a/packages/api/gateway/tests/gateway-stream.host.spec.ts b/packages/api/gateway/tests/gateway-stream.host.spec.ts index 6244e1ec39..a167608f0d 100644 --- a/packages/api/gateway/tests/gateway-stream.host.spec.ts +++ b/packages/api/gateway/tests/gateway-stream.host.spec.ts @@ -954,7 +954,7 @@ function descriptors(): InvocationDescriptor[] { name: 'label', wire: 'label', source: 'json' as const, - codec: { mode: 'strict' as const, typeSymbol: '@fixture/feed#Label', schema: z.string() }, + codec: { mode: 'strict' as const, typeSymbol: '@fixture/feed#Label', create: () => z.string() }, } const stream = (method: string, parameters: InvocationDescriptor['parameters'], schema: z.ZodType): InvocationDescriptor => ({ id: `@fixture/feed#feed/${method}`, @@ -964,7 +964,7 @@ function descriptors(): InvocationDescriptor[] { mode: 'stream', invocation: { kind: 'direct' }, parameters, - result: { mode: 'strict', typeSymbol: '@fixture/feed#Item', schema }, + result: { mode: 'strict', typeSymbol: '@fixture/feed#Item', create: () => schema }, }) return [ { ...stream('follow', [label], z.string()), cancellation: { parameter: 'signal' } }, @@ -982,7 +982,7 @@ function descriptors(): InvocationDescriptor[] { method: 'unary', invocation: { kind: 'direct' }, parameters: [label], - result: { mode: 'strict', typeSymbol: '@fixture/feed#Item', schema: z.string() }, + result: { mode: 'strict', typeSymbol: '@fixture/feed#Item', create: () => z.string() }, }, ] } diff --git a/packages/api/gateway/tests/gateway.client.spec.ts b/packages/api/gateway/tests/gateway.client.spec.ts index eea7b53343..c05d1ff396 100644 --- a/packages/api/gateway/tests/gateway.client.spec.ts +++ b/packages/api/gateway/tests/gateway.client.spec.ts @@ -152,15 +152,15 @@ function directDescriptor(): InvocationDescriptor { wire: 'agentId', source: 'lookup', lookup: 'fixture', - codec: { mode: 'strict', typeSymbol: '@fixture#AgentId', schema: idSchema }, + codec: { mode: 'strict', typeSymbol: '@fixture#AgentId', create: () => idSchema }, }, { name: 'request', wire: 'request', source: 'json', - codec: { mode: 'strict', typeSymbol: '@fixture#CreateRequest', schema: requestSchema }, + codec: { mode: 'strict', typeSymbol: '@fixture#CreateRequest', create: () => requestSchema }, }], cancellation: { parameter: 'signal' }, - result: { mode: 'strict', typeSymbol: '@fixture#CreateResult', schema: createResultSchema }, + result: { mode: 'strict', typeSymbol: '@fixture#CreateResult', create: () => createResultSchema }, } } @@ -174,15 +174,15 @@ function contextDescriptor(): InvocationDescriptor { kind: 'context', context: 'fixture', wire: 'agentId', - codec: { mode: 'strict', typeSymbol: '@fixture#AgentId', schema: idSchema }, + codec: { mode: 'strict', typeSymbol: '@fixture#AgentId', create: () => idSchema }, }, parameters: [{ name: 'request', wire: 'request', source: 'json', - codec: { mode: 'strict', typeSymbol: '@fixture#RenameRequest', schema: requestSchema }, + codec: { mode: 'strict', typeSymbol: '@fixture#RenameRequest', create: () => requestSchema }, }], - result: { mode: 'strict', typeSymbol: '@fixture#RenameResult', schema: renameResultSchema }, + result: { mode: 'strict', typeSymbol: '@fixture#RenameResult', create: () => renameResultSchema }, } } @@ -199,9 +199,9 @@ function maybeDescriptor(): InvocationDescriptor { wire: 'value', source: 'json', acceptsUndefined: true, - codec: { mode: 'strict', typeSymbol: '@fixture#MaybeValue', schema }, + codec: { mode: 'strict', typeSymbol: '@fixture#MaybeValue', create: () => schema }, }], - result: { mode: 'strict', typeSymbol: '@fixture#MaybeValue', schema }, + result: { mode: 'strict', typeSymbol: '@fixture#MaybeValue', create: () => schema }, } } @@ -217,10 +217,10 @@ function streamDescriptor(): InvocationDescriptor { name: 'topic', wire: 'topic', source: 'json', - codec: { mode: 'strict', typeSymbol: '@fixture#Topic', schema: z.string().min(1) }, + codec: { mode: 'strict', typeSymbol: '@fixture#Topic', create: () => z.string().min(1) }, }], cancellation: { parameter: 'signal' }, - result: { mode: 'strict', typeSymbol: '@fixture#WatchItem', schema: z.string().min(1) }, + result: { mode: 'strict', typeSymbol: '@fixture#WatchItem', create: () => z.string().min(1) }, } } @@ -1155,7 +1155,7 @@ describe('Client Typert API', () => { ...direct, parameters: [...direct.parameters, { name: 'other', wire: 'otherId', source: 'lookup', lookup: 'fixture', - codec: { mode: 'strict', typeSymbol: '@fixture#AgentId', schema: idSchema }, + codec: { mode: 'strict', typeSymbol: '@fixture#AgentId', create: () => idSchema }, }], }], })).rejects.toThrow('scope must select its only lookup parameter') @@ -1271,7 +1271,7 @@ describe('Client Typert API', () => { name: 'value', wire: '__proto__', source: 'json', - codec: { mode: 'strict', typeSymbol: '@fixture#PrototypeValue', schema: z.string() }, + codec: { mode: 'strict', typeSymbol: '@fixture#PrototypeValue', create: () => z.string() }, }], } const dispose = await ctx.remote.$mount({ package: '@fixture/prototype', descriptors: [descriptor] }) diff --git a/packages/api/gateway/tests/gateway.host.spec.ts b/packages/api/gateway/tests/gateway.host.spec.ts index edbd83dc56..f591e9a331 100644 --- a/packages/api/gateway/tests/gateway.host.spec.ts +++ b/packages/api/gateway/tests/gateway.host.spec.ts @@ -1349,7 +1349,7 @@ function contextProvider(context: Context) { } function strictCodec(typeSymbol: string, schema: z.ZodType): InvocationDescriptor['result'] { - return { mode: 'strict', typeSymbol, schema } + return { mode: 'strict', typeSymbol, create: () => schema } } function createDescriptor(): InvocationDescriptor { diff --git a/packages/api/remotes/package.json b/packages/api/remotes/package.json index 3aefa8bddc..a81e3cc215 100644 --- a/packages/api/remotes/package.json +++ b/packages/api/remotes/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-remotes", "description": "Remote BFF assembly for application-selected Host capabilities", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/api/session-controller/package.json b/packages/api/session-controller/package.json index 068f9a5057..3c74c670b0 100644 --- a/packages/api/session-controller/package.json +++ b/packages/api/session-controller/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-session-controller", "description": "Session Remote commands, cold reads, and live control transport", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/api/settings-controller/package.json b/packages/api/settings-controller/package.json index c9bcbf578a..2516624567 100644 --- a/packages/api/settings-controller/package.json +++ b/packages/api/settings-controller/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-settings-controller", "description": "Remote owner for the configuration surfaces over the settings-domain seams", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/api/terminal-controller/package.json b/packages/api/terminal-controller/package.json index 0504a7dfcc..e4f015d4dd 100644 --- a/packages/api/terminal-controller/package.json +++ b/packages/api/terminal-controller/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-terminal-controller", "description": "Session-owned interactive terminals with shell discovery, screen recovery and typed Remote control", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, @@ -55,6 +55,7 @@ "license": "MIT", "dependencies": { "@deepseek-ai/dsh-deque": "workspace:^", + "@deepseek-ai/dsh-lazy-require": "workspace:^", "@deepseek-ai/dsh-typert-protocol": "workspace:^", "@deepseek-ai/schemastery": "workspace:^", "@xterm/headless": "^6.0.0", diff --git a/packages/api/terminal-controller/src/terminal.ts b/packages/api/terminal-controller/src/terminal.ts index 270179d145..b4f6623037 100644 --- a/packages/api/terminal-controller/src/terminal.ts +++ b/packages/api/terminal-controller/src/terminal.ts @@ -1,22 +1,15 @@ /** One PTY, a bounded terminal emulator and its detachable browser followers. */ -import { createRequire } from 'node:module' import { RemoteError } from '@deepseek-ai/dsh-typert-protocol' import type { Terminal as HeadlessTerminal } from '@xterm/headless' import type { SerializeAddon as Serializer } from '@xterm/addon-serialize' import type { SubprocessTerminalHandle } from '@deepseek-ai/dsh-subprocess' +import { createLazyRequire } from '@deepseek-ai/dsh-lazy-require' import { TerminalFollower } from './stream.ts' import { TerminalRetention, type TerminalRetentionPolicy } from './retention.ts' import type { TerminalAttachmentId, TerminalFrame, TerminalRetentionFrame, WebTerminalInfo } from './types.ts' -const { Terminal, SerializeAddon } = loadXterm() - -function loadXterm() { - // The Preview's CommonJS wrapper owns its outer require binding; these literal calls also retain the CJS entries. - const require = createRequire(import.meta.url) - const { Terminal } = require('@xterm/headless') as typeof import('@xterm/headless') - const { SerializeAddon } = require('@xterm/addon-serialize') as typeof import('@xterm/addon-serialize') - return { Terminal, SerializeAddon } -} +const requireHeadless = createLazyRequire('@xterm/headless', import.meta.url) +const requireSerialize = createLazyRequire('@xterm/addon-serialize', import.meta.url) /** Process lifetime is independent of follower and component lifetimes. */ export class BrowserTerminal { @@ -42,6 +35,8 @@ export class BrowserTerminal { scrollback: number, private readonly maxBufferedBytes: number, ) { + const { Terminal } = requireHeadless() + const { SerializeAddon } = requireSerialize() this.screen = new Terminal({ cols: info.cols, rows: info.rows, scrollback, allowProposedApi: true }) this.serializer = new SerializeAddon() this.screen.loadAddon(this.serializer) diff --git a/packages/api/terminal-controller/tsconfig.host.json b/packages/api/terminal-controller/tsconfig.host.json index 2e3fa0236f..ec31d46046 100644 --- a/packages/api/terminal-controller/tsconfig.host.json +++ b/packages/api/terminal-controller/tsconfig.host.json @@ -49,6 +49,9 @@ }, { "path": "../../util/deque" + }, + { + "path": "../../util/lazy-require" } ] } diff --git a/packages/api/workspace-controller/package.json b/packages/api/workspace-controller/package.json index 49b780da65..c673c1b66a 100644 --- a/packages/api/workspace-controller/package.json +++ b/packages/api/workspace-controller/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-workspace-controller", "description": "Workspace Remote commands and reconnect-safe state transport", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/api/workspace-files/package.json b/packages/api/workspace-files/package.json index c5130dbe89..1e94a4d7f1 100644 --- a/packages/api/workspace-files/package.json +++ b/packages/api/workspace-files/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-workspace-files", "description": "Workspace file service and Client resource provider: bounded reads, directory listing, and live metadata over the workspaceFiles Remote namespace", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/attachment/attachment-local/package.json b/packages/attachment/attachment-local/package.json index 1d2a37a9e4..f36412f79d 100644 --- a/packages/attachment/attachment-local/package.json +++ b/packages/attachment/attachment-local/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-attachment-local", "description": "Private content-addressed DSH_HOME attachment storage", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, @@ -26,6 +26,7 @@ "@deepseek-ai/cordis": "workspace:^" }, "dependencies": { + "@deepseek-ai/dsh-lazy-require": "workspace:^", "@deepseek-ai/schemastery": "workspace:^", "sharp": "^0.35.3" }, diff --git a/packages/attachment/attachment-local/src/image.ts b/packages/attachment/attachment-local/src/image.ts index c34944f676..402f076840 100644 --- a/packages/attachment/attachment-local/src/image.ts +++ b/packages/attachment/attachment-local/src/image.ts @@ -1,8 +1,9 @@ /** Raster inspection: full decode at admission, header-only probe on verified reads. */ -import sharp, { type Sharp } from 'sharp' +import type { Sharp } from 'sharp' import { AttachmentError } from '@deepseek-ai/dsh-attachment' import type { ImageMediaType } from '@deepseek-ai/dsh-attachment' +import { requireSharp } from './sharp.ts' /** Decoded metadata from a supported image. */ export interface DetectedImage { @@ -89,6 +90,7 @@ async function imageMetadata(image: Sharp): Promise { * @returns verified format and dimensions. */ export async function probeImage(data: Uint8Array): Promise { + const sharp = requireSharp() try { return await imageMetadata(sharp(data, { failOn: 'error', limitInputPixels: false })) } catch (error) { @@ -112,6 +114,7 @@ export interface DecodedImageLimits { * @returns verified format and dimensions. */ export async function detectImage(data: Uint8Array, limits?: DecodedImageLimits): Promise { + const sharp = requireSharp() try { const image = sharp(data, { failOn: 'error', limitInputPixels: false }) const detected = await imageMetadata(image) diff --git a/packages/attachment/attachment-local/src/normalization.ts b/packages/attachment/attachment-local/src/normalization.ts index 7d514786a2..2d3557b19a 100644 --- a/packages/attachment/attachment-local/src/normalization.ts +++ b/packages/attachment/attachment-local/src/normalization.ts @@ -1,11 +1,12 @@ /** Deterministic provider-independent image normalization. */ -import sharp, { type Sharp } from 'sharp' +import type { Sharp } from 'sharp' import { AttachmentError, requestImageDimensions } from '@deepseek-ai/dsh-attachment' import type { ImageMediaType } from '@deepseek-ai/dsh-attachment' import { encodeFirstWithinLimit, encodingLadder, isExhaustedEncoding } from './encoding.ts' import { detectImage, encodedAlphaIsCompatible } from './image.ts' import type { DetectedImage } from './image.ts' +import { requireSharp } from './sharp.ts' /** Deployment-resolved policy for the persisted normalized attachment. */ export interface NormalizationPolicy { @@ -70,7 +71,12 @@ async function verifyNormalizedImage( } /** Build one fixed-size, oriented, metadata-free sRGB pipeline from submitted bytes. */ -function preparedPipeline(data: Uint8Array, width: number, height: number): Sharp { +function preparedPipeline( + sharp: ReturnType, + data: Uint8Array, + width: number, + height: number, +): Sharp { return sharp(data, { failOn: 'error', limitInputPixels: false }) .rotate() .toColourspace('srgb') @@ -108,10 +114,11 @@ export async function normalizeImage( if (canPassThroughNormalization(detected, data.byteLength, policy)) { return { data, mediaType: detected.mediaType, width: detected.width, height: detected.height } } + const sharp = requireSharp() try { const { width, height } = initialDimensions(detected, policy) const encoded = await encodeFirstWithinLimit( - encodingLadder(preparedPipeline(data, width, height), detected.hasAlpha), + encodingLadder(preparedPipeline(sharp, data, width, height), detected.hasAlpha), policy.maxBytes, ) const chosen = isExhaustedEncoding(encoded) ? encoded.smallest : encoded diff --git a/packages/attachment/attachment-local/src/request-image.ts b/packages/attachment/attachment-local/src/request-image.ts index ca5016a1cf..74d4934b2f 100644 --- a/packages/attachment/attachment-local/src/request-image.ts +++ b/packages/attachment/attachment-local/src/request-image.ts @@ -3,7 +3,7 @@ import { createHash, randomUUID } from 'node:crypto' import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises' import { dirname, join } from 'node:path' -import sharp, { type Sharp } from 'sharp' +import type { Sharp } from 'sharp' import { AttachmentError, ImageVariantId } from '@deepseek-ai/dsh-attachment' import type { ImageMediaType, @@ -20,6 +20,7 @@ import { isExhaustedEncoding, } from './encoding.ts' import { detectImage, encodedAlphaIsCompatible, probeImage } from './image.ts' +import { requireSharp } from './sharp.ts' /** Transform version included in every cache and upload-index identity. */ export const REQUEST_IMAGE_TRANSFORM_VERSION = 'request-image-v6' @@ -90,6 +91,7 @@ function pipeline(attachment: StoredImageAttachment, target: ImageRequestTarget) } function sourcePipeline(attachment: StoredImageAttachment): Sharp { + const sharp = requireSharp() return sharp(attachment.data, { failOn: 'error', limitInputPixels: false }).toColourspace('srgb') } diff --git a/packages/attachment/attachment-local/src/sharp.ts b/packages/attachment/attachment-local/src/sharp.ts new file mode 100644 index 0000000000..798b21ae4b --- /dev/null +++ b/packages/attachment/attachment-local/src/sharp.ts @@ -0,0 +1,7 @@ +/** Process-realm lazy access to Sharp's CommonJS-compatible entry. */ + +import type sharp from 'sharp' +import { createLazyRequire } from '@deepseek-ai/dsh-lazy-require' + +/** Load Sharp on the first raster operation and retain its callable export. */ +export const requireSharp = createLazyRequire('sharp', import.meta.url) diff --git a/packages/attachment/attachment-local/tests/lazy-sharp-failure.spec.ts b/packages/attachment/attachment-local/tests/lazy-sharp-failure.spec.ts new file mode 100644 index 0000000000..970508fc80 --- /dev/null +++ b/packages/attachment/attachment-local/tests/lazy-sharp-failure.spec.ts @@ -0,0 +1,40 @@ +import { afterAll, describe, expect, it, vi } from 'vitest' + +const state = vi.hoisted(() => ({ failure: new Error('sharp native binding unavailable') })) + +vi.mock('../src/sharp.ts', () => ({ + requireSharp: () => { throw state.failure }, +})) + +import { detectImage, probeImage } from '../src/image.ts' +import { normalizeImage } from '../src/normalization.ts' + +afterAll(() => { + vi.doUnmock('../src/sharp.ts') +}) + +describe('lazy Sharp load failure', () => { + it.each([ + ['header probe', probeImage], + ['full decode', detectImage], + ] as const)('does not classify a %s as invalid image data', async (_name, operation) => { + await expect(operation(Uint8Array.of(1, 2, 3))).rejects.toBe(state.failure) + }) + + it('does not classify normalization startup as an encoding failure', async () => { + await expect(normalizeImage(Uint8Array.of(1, 2, 3), { + mediaType: 'image/png', + width: 3, + height: 2, + animated: false, + carriesMetadata: false, + depth: 'ushort', + space: 'rgb16', + hasAlpha: true, + }, { + maxPixels: 100, + maxDimension: 10, + maxBytes: 1_024, + })).rejects.toBe(state.failure) + }) +}) diff --git a/packages/attachment/attachment-local/tsconfig.json b/packages/attachment/attachment-local/tsconfig.json index 8466ea1ac4..cce6aaa326 100644 --- a/packages/attachment/attachment-local/tsconfig.json +++ b/packages/attachment/attachment-local/tsconfig.json @@ -7,6 +7,7 @@ { "path": "../../../vendor/cordis" }, { "path": "../../../vendor/schemastery" }, { "path": "../attachment" }, - { "path": "../../util/home-paths" } + { "path": "../../util/home-paths" }, + { "path": "../../util/lazy-require" } ] } diff --git a/packages/attachment/attachment/package.json b/packages/attachment/attachment/package.json index e030ce0e71..a595126838 100644 --- a/packages/attachment/attachment/package.json +++ b/packages/attachment/attachment/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-attachment", "description": "Durable immutable attachment storage seam for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/boot/app-boot/package.json b/packages/boot/app-boot/package.json index ff52fed547..35b2d8121d 100644 --- a/packages/boot/app-boot/package.json +++ b/packages/boot/app-boot/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-app-boot", "description": "Shared boot glue for the app bins: .env loading, fail-loud Loader guards, snapshot-aware config resolution, and the Loader boot sequence", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/boot/cmdline/package.json b/packages/boot/cmdline/package.json index bb453aaa94..bd1ff6a01f 100644 --- a/packages/boot/cmdline/package.json +++ b/packages/boot/cmdline/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-cmdline", "description": "Immutable command-line handoff from a dsh launcher to any app plugin that injects cmdlineArgs", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/browser-use/browser-use/package.json b/packages/browser-use/browser-use/package.json index 2202dbd8a9..1f8f8e05a2 100644 --- a/packages/browser-use/browser-use/package.json +++ b/packages/browser-use/browser-use/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-browser-use", "description": "Exclusive named browser-use provider registration", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/bundle/acp-app/package.json b/packages/bundle/acp-app/package.json index f9aca81482..c8a58ff21c 100644 --- a/packages/bundle/acp-app/package.json +++ b/packages/bundle/acp-app/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-acp-app", "description": "The dsh ACP profile bundle: automation-only JSON-RPC stdio and process lifecycle over dsh-base", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/bundle/base/package.json b/packages/bundle/base/package.json index b3b09e7415..672e76594b 100644 --- a/packages/bundle/base/package.json +++ b/packages/bundle/base/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-base", "description": "The shared dsh core as a profile bundle: the first patch layer of base-backed profiles, inserting core rows over the empty profile root", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/bundle/headless/package.json b/packages/bundle/headless/package.json index 59ead640d9..28621ba30f 100644 --- a/packages/bundle/headless/package.json +++ b/packages/bundle/headless/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-headless", "description": "The dsh one-shot bundle: a direct core Agent/Session runner over dsh-base with no Host, HTTP, or browser layer", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/bundle/sdk-app/package.json b/packages/bundle/sdk-app/package.json index 974b60623a..8d87fb7abe 100644 --- a/packages/bundle/sdk-app/package.json +++ b/packages/bundle/sdk-app/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-sdk-app", "description": "The dsh SDK profile bundle: stdio JSON-RPC serving and process lifecycle over dsh-base", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/bundle/sdk-minimal/package.json b/packages/bundle/sdk-minimal/package.json index 350c5635e4..9e6a4a0cda 100644 --- a/packages/bundle/sdk-minimal/package.json +++ b/packages/bundle/sdk-minimal/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-sdk-minimal", "description": "The standalone minimal SDK profile bundle: JSON-RPC, one DeepSeek adapter, persistent shell, and JSONL sessions", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/bundle/web-app/README.i18n.yaml b/packages/bundle/web-app/README.i18n.yaml index 9dc338281e..54536cd33e 100644 --- a/packages/bundle/web-app/README.i18n.yaml +++ b/packages/bundle/web-app/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/bundle/web-app/README.md -README.md: 23ff6e925eb097eb99e5fa7ce8678c87c61aeb9c -README.zh.md: 79b6335798e2bf522ce2bc1e201be745bb91e085 +README.md: c35bdf219c4c231e2ffde73a592b04070f96a4bc +README.zh.md: 9a4047525da16cfb8249e4732fc2102f95b9d89f diff --git a/packages/bundle/web-app/README.md b/packages/bundle/web-app/README.md index 23ff6e925e..c35bdf219c 100644 --- a/packages/bundle/web-app/README.md +++ b/packages/bundle/web-app/README.md @@ -81,7 +81,7 @@ A patch replaces the targeted row's whole `config`, so each web row restates eve ### Readiness -The URL line and browser handoff are readiness signals: supervisors RPC as soon as they observe the line, and a browser requests the page as soon as it opens, so both run only after the Loader tree settles, the required-startup audit passes, and Connection authentication is available — or immediately in a hand-built tree without a Loader. Optional plugin failures do not suppress readiness; a required startup failure or a tree disposed mid-boot announces nothing. +The URL line and browser handoff are readiness signals: supervisors RPC as soon as they observe the line, and a browser requests the page as soon as it opens, so both run only after the Loader tree settles, the required-startup audit passes, and Connection authentication is available — or immediately in a hand-built tree without a Loader. Client combo JavaScript and source maps remain unmaterialized at this point. Optional plugin failures do not suppress readiness; a required startup failure or a tree disposed mid-boot announces nothing. ### LAN trust sampling diff --git a/packages/bundle/web-app/README.zh.md b/packages/bundle/web-app/README.zh.md index 79b6335798..9a4047525d 100644 --- a/packages/bundle/web-app/README.zh.md +++ b/packages/bundle/web-app/README.zh.md @@ -81,7 +81,7 @@ patch 会替换目标行的整个 `config`,因此每个 Web 行都重述自己 ### 就绪宣告 -URL 行与浏览器交接都是就绪信号:监督方一观察到该行就发起 RPC,浏览器一打开就请求页面,因此两者只在 Loader 配置树结算、通过 required 启动检查且 Connection 认证可用后运行——在没有 Loader 的手工构建树中则立即运行。可选插件失败不会阻止就绪宣告;required 启动失败或启动中途被释放的树不会宣告任何内容。 +URL 行与浏览器交接都是就绪信号:监督方一观察到该行就发起 RPC,浏览器一打开就请求页面,因此两者只在 Loader 配置树结算、通过 required 启动检查且 Connection 认证可用后运行——在没有 Loader 的手工构建树中则立即运行。此时 client combo JavaScript 和 source map 仍未物化。可选插件失败不会阻止就绪宣告;required 启动失败或启动中途被释放的树不会宣告任何内容。 ### LAN 信任采样 diff --git a/packages/bundle/web-app/cordis.patch.yml b/packages/bundle/web-app/cordis.patch.yml index 1c13eb026b..b33bb895fa 100644 --- a/packages/bundle/web-app/cordis.patch.yml +++ b/packages/bundle/web-app/cordis.patch.yml @@ -145,8 +145,9 @@ # assembly fact of dsh-web-app, never user config), mounts the # frontend-static fallback owner, registers the web-surface prompt # section and the bash runtime variable, prints the URL line, and opens the - # canonical local URL after the full tree settles. The webStartup provider - # supplies invocation-only values; after the server binds, this row samples + # canonical local URL after the full Loader tree settles and the required- + # entry audit passes. The + # webStartup provider supplies invocation-only values; after the server binds, this row samples # LAN trust once and provides `webRuntime`. A complete agent-preset persona # suppresses the prompt section for that agent while retaining the host-owned # shell variable. diff --git a/packages/bundle/web-app/package.json b/packages/bundle/web-app/package.json index fbda35116d..d1bf1848ad 100644 --- a/packages/bundle/web-app/package.json +++ b/packages/bundle/web-app/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-web-app", "description": "The dsh browser-surface bundle: the web patch layer over dsh-base plus the runtime glue plugin (frontend dist serving, web-surface prompt, bash runtime variables, URL line)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/connection/package.json b/packages/client/connection/package.json index 913a7bf56e..583db7134b 100644 --- a/packages/client/connection/package.json +++ b/packages/client/connection/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-connection", "description": "Authenticated RPC transport and generation lifecycle", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/file-upload/package.json b/packages/client/file-upload/package.json index 5c606edbc5..368b7d085a 100644 --- a/packages/client/file-upload/package.json +++ b/packages/client/file-upload/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-file-upload", "description": "Agent-scoped browser file upload, streaming intake, and staged receipt service", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/hmr/package.json b/packages/client/hmr/package.json index a45088a92b..50d903db13 100644 --- a/packages/client/hmr/package.json +++ b/packages/client/hmr/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-hmr", "description": "Dev-only hot-reload driver for script-loaded client entries: SSE rebuilt frames → invalidate/prefetch → fiber swap through the vendored Loader entry", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/locale/package.json b/packages/client/locale/package.json index 4245200191..14cac7743e 100644 --- a/packages/client/locale/package.json +++ b/packages/client/locale/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-locale", "description": "Locale plugin: Host-backed preference, extensible language catalog, browser fallback, and typed built-in dictionaries", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/modules/README.i18n.yaml b/packages/client/modules/README.i18n.yaml index d163a406e8..7ead3af852 100644 --- a/packages/client/modules/README.i18n.yaml +++ b/packages/client/modules/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/client/modules/README.md -README.md: c83796fc8d6c364b9fad6d44e25316e2c2f7e7af -README.zh.md: 9ffb68ebc07b5970edc45a54c4cfde7cdf4ef7d8 +README.md: ef303761b164c2d75d1757d3bce1810b4795c12b +README.zh.md: 6c4c80e90ed2209901894b4d43ac28ad3b57e61e diff --git a/packages/client/modules/README.md b/packages/client/modules/README.md index c83796fc8d..ef303761b1 100644 --- a/packages/client/modules/README.md +++ b/packages/client/modules/README.md @@ -67,7 +67,7 @@ Executing a plugin bundle only registers its factory; every module-body side eff The Node half scans incrementally per package — no full-rescan path. Every `internal/plugin` emission marks the fiber's entry name dirty; a microtask flush reconciles each dirty name against the live loader entries, and the activation pass seeds the same dirty set and flushes synchronously, so first scan and steady state share one implementation. Package metadata is cached per Loader specifier and owning-tree base URL until restart, while the resolved manifest package name identifies the browser module. Distinct active Loader sources resolving to one package name are rejected; removing the conflict promotes the remaining source without requiring its fiber to restart. Bundle content changes reach the graph only through `rebuilt()` (the HMR hook). -The Node half snapshots each client bundle and available source map before publication. It groups resources into `/plugins/??...&rev=...` combo URLs, with one bootstrap combo for the modules row and one or more application combos for the other rows; each phase is partitioned before a URL exceeds 3 KiB. Every combo map is Indexed Source Map v3 and uses an authored section when available or an identity section for the packaged bundle. Initial per-plugin revisions use process nonces, so startup does not hash every plugin; HMR hashes only an artifact reported as changed. Advertised responses are immutable, and an unknown combination or revision returns 404. +The Node half snapshots each client bundle before publication and creates combo descriptors without building response bodies. It groups resources into `/plugins/??...&rev=...` combo URLs, with one bootstrap combo for the modules row and one or more application combos for the other rows; each phase is partitioned before a URL exceeds 3 KiB. A script body is combined once on its first `GET` and ends with its map URL. The corresponding map files are read, validated, and combined separately on the first map `GET`; `HEAD` materializes neither body. Every combo map is Indexed Source Map v3 and uses an authored section when available or an identity section for the packaged bundle. Initial per-plugin revisions use process nonces, HMR hashes only a changed bundle, and combo revisions derive from the ordered row revisions. Advertised responses are immutable after first materialization, and an unknown combination or revision returns 404. ### Boot manifest injection @@ -119,7 +119,8 @@ These limits define what the module system does not do. They are current package - **Flat module graph by design** — every bundle is one module node whose edges point only at table leaves; the interface (`loadCache`/`edges`/`invalidate`) already supports a general module graph, so the externalization granularity can change without an interface change. - **No unload bookkeeping of its own** — style removal and fiber teardown ordering live with the HMR driver (`@deepseek-ai/dsh-client-hmr`); the loader only inventories owned style tag ids per record. -- **Snapshot delivery retains artifact bytes** — the Host holds each bundle, optional source map, generated one-resource response, and current startup combo responses in memory; HMR additionally retains one prior startup generation. Memory scales as several copies of the composed client artifacts in exchange for immutable responses and one-generation race tolerance. +- **Lazy delivery retains requested bodies** — the Host holds each bundle and lazy response plan; a script or map body remains cached after its first `GET`, and HMR additionally retains one prior startup generation. Memory grows only for response bodies that clients request while preserving one-generation race tolerance. +- **An unrequested prior-generation map reads the current map file** — combo revisions track executable bundles, not debug artifacts. If HMR rebuilds a map before the retained prior URL receives its first map `GET`, that response uses the current authored map with the prior bundle offsets; requesting the map before the rebuild fixes that URL's response. ### Dev Note diff --git a/packages/client/modules/README.zh.md b/packages/client/modules/README.zh.md index 9ffb68ebc0..6c4c80e90e 100644 --- a/packages/client/modules/README.zh.md +++ b/packages/client/modules/README.zh.md @@ -67,7 +67,7 @@ application combo 脚本在启动时仅注册一次插件 factory;模块主体 Node 半侧逐包增量扫描——没有全量重扫路径。每次发出 `internal/plugin` 事件时,系统都会把该 fiber 的 entry 名标脏;微任务 flush 会把每个脏名与当前 loader 条目对账,激活 pass 会初始化同一个脏集合并同步 flush,因此首次扫描与稳态共用同一实现。包元数据按 Loader specifier 与所属 tree base URL 缓存至重启,解析出的 manifest(元数据清单)包名作为浏览器模块身份。若不同的 active Loader source 解析到同一包名,组合会失败;移除冲突来源后,剩余来源无需重启 fiber 即可接替。bundle 内容变更只能通过 `rebuilt()`(HMR 钩子)进入图。 -Node 半侧会在发布前快照每个客户端 bundle 及其现有 source map。它把资源分组到 `/plugins/??...&rev=...` combo URL:modules row 使用一个 bootstrap combo,其余 row 使用一个或多个 application combo;每个阶段都会在 URL 超过 3 KiB 之前分区。每个 combo map 都是 Indexed Source Map v3,并在可用时使用作者提供的 section,否则为已打包 bundle 生成 identity section。初始逐插件 revision 使用进程 nonce,所以启动时不哈希每个插件;HMR 只哈希被报告为已变化的产物。已公告响应不可变;未知组合或 revision 返回 404。 +Node 半侧会在发布前快照每个客户端 bundle,并在不构建响应 body 的情况下创建 combo descriptor。它把资源分组到 `/plugins/??...&rev=...` combo URL:modules row 使用一个 bootstrap combo,其余 row 使用一个或多个 application combo;每个阶段都会在 URL 超过 3 KiB 之前分区。脚本 body 在首次 `GET` 时只组合一次,并以对应 map URL 结尾;map 文件则在首次 map `GET` 时单独读取、校验并组合,`HEAD` 不会物化任一 body。每个 combo map 都是 Indexed Source Map v3,并在可用时使用作者提供的 section,否则为已打包 bundle 生成 identity section。初始逐插件 revision 使用进程 nonce,HMR 只哈希变化的 bundle,combo revision 从有序 row revision 派生。已公告响应在首次物化后保持不可变;未知组合或 revision 返回 404。 ### 启动 manifest 注入 @@ -119,7 +119,8 @@ bundle 路由随注入的 `webServer` 生命周期注册:服务就绪时注册 - **有意采用扁平模块图**——每个 bundle 是一个模块节点,其边只指向表中的叶节点;接口(`loadCache`/`edges`/`invalidate`)已经支持通用模块图,因此可以改变 externalization 粒度而不更改接口。 - **自身不维护卸载记录**——样式移除与 fiber 拆卸顺序属于 HMR 驱动器(`@deepseek-ai/dsh-client-hmr`);loader 只在每条记录中登记其拥有的样式标签 id。 -- **快照式提供会保留产物字节**——Host 在内存中保留每个 bundle、可选 source map、生成的单资源响应和当前启动 combo 响应;HMR 还会保留上一代启动响应。内存会随已组合客户端产物增长为数份副本,以换取不可变响应和一代竞态容忍。 +- **惰性提供会保留已请求的 body**——Host 在内存中保留每个 bundle 与惰性响应计划;脚本或 map body 在首次 `GET` 后保留缓存,HMR 还会保留上一代启动响应。内存仅随客户端实际请求的响应 body 增长,同时保留一代竞态容忍。 +- **从未请求的上一代 map 会读取当前 map 文件**——combo revision 跟踪可执行 bundle,而不跟踪调试产物。若 HMR 在保留的旧 URL 首次收到 map `GET` 前重建 map,该响应会把当前 authored map 与旧 bundle offset 组合;在重建前请求 map 会固定该 URL 的响应。 ### 开发备注 diff --git a/packages/client/modules/package.json b/packages/client/modules/package.json index 06ee59bb3e..511214ee56 100644 --- a/packages/client/modules/package.json +++ b/packages/client/modules/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-modules", "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.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/modules/src/client/manifest.ts b/packages/client/modules/src/client/manifest.ts index d6f8f99b8f..ce1fc999e8 100644 --- a/packages/client/modules/src/client/manifest.ts +++ b/packages/client/modules/src/client/manifest.ts @@ -63,16 +63,16 @@ export interface WebBootEntry { external?: string[] } -/** Initial scheduling phase for one content-addressed combo script. */ +/** Initial scheduling phase for one revisioned combo script. */ export type WebBootBatchPhase = 'bootstrap' | 'application' /** One initial combo script; a scheduling phase may span several descriptors. */ export interface WebBootBatch { /** Parser-blocking bootstrap or preloaded application scheduling. */ phase: WebBootBatchPhase - /** Content-addressed combo script endpoint. */ + /** Revisioned combo script endpoint. */ url: string - /** Revision over the combined plugin script bytes and indexed source map. */ + /** Revision derived from the ordered entry revisions. */ rev: string /** Graph entry ids whose factories the script registers, in execution order. */ entries: string[] @@ -80,7 +80,7 @@ export interface WebBootBatch { /** The composed client entry graph the host injects as `window.__DSH_BOOT__`. */ export interface WebBootGraph { - /** Consistency anchor over the whole graph (content + bundle hashes). */ + /** Consistency anchor over the current entry and batch descriptors. */ rev: string /** * Composed entries in module-graph order — a dynamic package row precedes @@ -98,7 +98,7 @@ export interface BootModuleRow { id: string /** Revisioned single-resource combo endpoint used after HMR invalidation. */ url: string - /** Content-addressed combo endpoint used before the first HMR invalidation. */ + /** Revisioned combo endpoint used before the first HMR invalidation. */ initialUrl: string /** Opaque plugin-artifact revision used after HMR invalidation. */ rev: string diff --git a/packages/client/modules/src/index.ts b/packages/client/modules/src/index.ts index 4ce759a2b8..30b3677ee2 100644 --- a/packages/client/modules/src/index.ts +++ b/packages/client/modules/src/index.ts @@ -139,22 +139,30 @@ interface WebPluginRecord { bundle: Buffer /** Pre-read filesystem baseline handed to the HMR watcher. */ baseline: ClientArtifactBaseline - /** Optional authored source map snapshot; generated-file identity mapping is the fallback. */ - sourceMap?: { body: Buffer; parsed: Record } } -/** Fields shared by every generated combo response. */ -interface ComboArtifactBase { +/** Immutable inputs captured for one resource in a generated combo. */ +interface ComboResource { + id: string + rev: string + clientPath: string + bundle: Buffer +} + +/** One lazily materialized immutable response body. */ +interface LazyResponse { + contentType: string + body: () => Promise +} + +/** Fields shared by every generated combo plan. */ +interface ComboArtifact { url: string rev: string entries: string[] - script: Buffer -} - -/** One generated combo response over an ordered list of plugin resources. */ -interface ComboArtifact extends ComboArtifactBase { - sourceMap: Buffer sourceMapUrl: string + scriptBody: () => Promise + sourceMapBody: () => Promise } /** One generated initial-load response and its wire descriptor. */ @@ -197,9 +205,9 @@ function framedHash(domain: string, parts: readonly Buffer[]): string { return hash.digest('hex').slice(0, HASH_REVISION_LENGTH) } -/** Hash every artifact input served after HMR observes one plugin change. */ -function artifactRevision(bundle: Buffer, sourceMap: WebPluginRecord['sourceMap']): string { - return framedHash('plugin-artifact', sourceMap === undefined ? [bundle] : [bundle, sourceMap.body]) +/** Hash the executable artifact served after HMR observes one plugin change. */ +function artifactRevision(bundle: Buffer): string { + return framedHash('plugin-artifact', [bundle]) } /** Address one ordered plugin-file list through the shared combo route. */ @@ -245,19 +253,19 @@ function partitionComboRecords(records: readonly WebPluginRecord[]): WebPluginRe } /** Executable source plus the generated-file name used when no authored map exists. */ -interface ComboSource { +interface PreparedSource { source: string fallbackSource: string } /** Remove bundle-local debug directives and retain their stable generated-file name. */ -function comboSource(record: WebPluginRecord): ComboSource { - let source = record.bundle.toString('utf8') +function prepareSource(resource: ComboResource): PreparedSource { + let source = resource.bundle.toString('utf8') const sourceUrl = SOURCE_URL_TRAILER.exec(source)?.[1] source = source.replace(SOURCE_URL_TRAILER, '').replace(SOURCE_MAP_TRAILER, '') if (!source.endsWith('\n')) source += '\n' const fallbackSource = sourceUrl === undefined - ? `/plugins/${record.entry.id}/client.js` + ? `/plugins/${resource.id}/client.js` : /^(?:[A-Za-z][A-Za-z\d+.-]*:|\/)/.test(sourceUrl) ? sourceUrl : `/${sourceUrl}` return { source, fallbackSource } } @@ -267,8 +275,8 @@ function comboScript(input: string, sourceMapUrl?: string): Buffer { return Buffer.from(sourceMapUrl === undefined ? input : `${input}//# sourceMappingURL=${sourceMapUrl}\n`) } -/** Parse an optional source-map artifact; missing maps do not prevent plugin execution. */ -function sourceMapSnapshot(clientPath: string): WebPluginRecord['sourceMap'] { +/** Read and parse an optional source map when its combo-map endpoint is requested. */ +function readSourceMap(clientPath: string): Record | undefined { let body: Buffer try { body = readFileSync(`${clientPath}.map`) @@ -289,7 +297,7 @@ function sourceMapSnapshot(clientPath: string): WebPluginRecord['sourceMap'] { ) { throw new Error(`client-modules: ${clientPath}.map is not a regular Source Map v3 object`) } - return { body, parsed } + return parsed } /** Count generated lines while assembling indexed-map section offsets. */ @@ -300,13 +308,10 @@ function newlineCount(value: string): number { } /** Resolve section sources against their original per-plugin map URL before combo relocation. */ -function comboSectionMap(record: WebPluginRecord): Record { - const original = record.sourceMap?.parsed - /* v8 ignore next -- callers add sections only for records with a source map. */ - if (original === undefined) throw new Error(`client-modules: source map missing for ${record.entry.id}`) +function comboSectionMap(resource: ComboResource, original: Record): Record { const sourcePaths = original.sources as string[] const sourceRoot = typeof original.sourceRoot === 'string' ? original.sourceRoot : '' - const base = new URL(`/plugins/${record.entry.id}/client.js.map`, 'http://dsh.invalid') + const base = new URL(`/plugins/${resource.id}/client.js.map`, 'http://dsh.invalid') const relocated = sourcePaths.map((source) => { const separator = sourceRoot !== '' && !sourceRoot.endsWith('/') && !source.startsWith('/') ? '/' : '' const resolved = new URL(`${sourceRoot}${separator}${source}`, base) @@ -332,33 +337,88 @@ function identitySectionMap(source: string, sourceUrl: string): Record Buffer): () => Promise { + let result: Promise | undefined + return () => { + result ??= Promise.resolve().then(produce) + return result + } +} + +/** Derive one combo revision from the ordered immutable row revisions. */ +function comboRevision(resources: readonly ComboResource[]): string { + return framedHash('combo', resources.flatMap(resource => [ + Buffer.from(resource.id), + Buffer.from(resource.rev), + ])) +} + +/** Concatenate one or more factory registrations without reading or composing source maps. */ +function buildComboScript(resources: readonly ComboResource[], sourceMapUrl: string): Buffer { let source = '' + for (const resource of resources) source += `${prepareSource(resource).source};\n` + return comboScript(source, sourceMapUrl) +} + +/** Compose one indexed map from source-map files read only for this request. */ +function buildComboSourceMap( + resources: readonly ComboResource[], + sourceMapOf: (clientPath: string) => Record | undefined, +): Buffer { const sections: { offset: { line: number; column: 0 }; map: Record }[] = [] let line = 0 - for (const record of records) { - const prepared = comboSource(record) - const section = record.sourceMap === undefined - ? identitySectionMap(prepared.source, prepared.fallbackSource) - : comboSectionMap(record) + for (const resource of resources) { + const prepared = prepareSource(resource) + const sourceMap = sourceMapOf(resource.clientPath) + let section = identitySectionMap(prepared.source, prepared.fallbackSource) + if (sourceMap !== undefined) { + try { + section = comboSectionMap(resource, sourceMap) + } catch { + // An invalid authored source URL is a malformed map, so the generated + // bundle remains debuggable through the same identity fallback. + } + } sections.push({ offset: { line, column: 0 }, map: section }) - const bundle = `${prepared.source};\n` - source += bundle - line += newlineCount(bundle) + line += newlineCount(`${prepared.source};\n`) } - const sourceMap = Buffer.from(`${JSON.stringify({ version: 3, file: 'client.js', sections })}\n`) - const sourceBytes = Buffer.from(source) - const rev = revision ?? framedHash('combo', [sourceBytes, sourceMap]) - const entries = records.map(record => record.entry.id) + return Buffer.from(`${JSON.stringify({ version: 3, file: 'client.js', sections })}\n`) +} + +/** Describe one combo and defer its executable and debug payloads independently. */ +function buildCombo( + records: readonly WebPluginRecord[], + sourceMapOf: (clientPath: string) => Record | undefined, + revision?: string, +): ComboArtifact { + const resources = records.map(record => ({ + id: record.entry.id, + rev: record.entry.rev, + clientPath: record.meta.clientPath, + bundle: record.bundle, + })) + const rev = revision ?? comboRevision(resources) + const entries = resources.map(resource => resource.id) const url = comboUrl(entries, rev) const sourceMapUrl = comboUrl(entries, rev, true) - return { url, rev, entries, script: comboScript(source, sourceMapUrl), sourceMap, sourceMapUrl } + return { + url, + rev, + entries, + sourceMapUrl, + scriptBody: lazyBody(() => buildComboScript(resources, sourceMapUrl)), + sourceMapBody: lazyBody(() => buildComboSourceMap(resources, sourceMapOf)), + } } /** Add initial-load scheduling metadata to a combo artifact. */ -function buildBatch(phase: WebBootBatchPhase, records: readonly WebPluginRecord[]): BatchArtifact { - const artifact = buildCombo(records) +function buildBatch( + phase: WebBootBatchPhase, + records: readonly WebPluginRecord[], + sourceMapOf: (clientPath: string) => Record | undefined, +): BatchArtifact { + const artifact = buildCombo(records, sourceMapOf) return { ...artifact, descriptor: { phase, url: artifact.url, rev: artifact.rev, entries: artifact.entries }, @@ -495,10 +555,10 @@ export class ClientModuleRegistry extends Service { private readonly dirty = new Set() private readonly initialRevisionNonce = randomBytes(8).toString('hex') private nextInitialRevision = 0 - private responses = new Map() - private batchResponses = new Map() + private responses = new Map() + private batchResponses = new Map() /** One prior graph generation covers a request racing the HMR recomposition that replaced its URL. */ - private previousBatchResponses = new Map() + private previousBatchResponses = new Map() private flushQueued = false private composed: WebBootGraph @@ -567,12 +627,13 @@ export class ClientModuleRegistry extends Service { /** * Serve an advertised revisioned bundle or source map without a Web server. * Unknown URLs return 404, unsupported methods return 405, and `HEAD` - * returns the same immutable headers without a body. + * returns the same immutable headers without materializing a body. Each body + * is built once on its first `GET`; script construction never reads maps. * @param request - shell-carrier request for a `/plugins` resource. * @returns the exact response also exposed by the optional Web route. */ - fetchBundle(request: Request): Response { - const resource = this.bundleResource(request.method, request.url) + async fetchBundle(request: Request): Promise { + const resource = await this.bundleResource(request.method, request.url) const body = resource.body === undefined ? null : Uint8Array.from(resource.body) return new Response(body, { status: resource.status, @@ -604,14 +665,11 @@ export class ClientModuleRegistry extends Service { if (record === undefined) return undefined const baseline = this.captureArtifactBaseline(record.meta.clientPath) const bundle = readFileSync(record.meta.clientPath) - const sourceMap = this.readSourceMapSnapshot(record.meta.clientPath) - const rev = artifactRevision(bundle, sourceMap) + const rev = artifactRevision(bundle) record.baseline = baseline if (rev === record.entry.rev) return rev record.entry = graphRow(id, rev, record.meta) record.bundle = bundle - if (sourceMap === undefined) delete record.sourceMap - else record.sourceMap = sourceMap this.composed = this.compose() for (const notify of this.rebuildListeners) { // Containment: rebuilt() runs inside the HMR watch callback — a @@ -659,32 +717,32 @@ export class ClientModuleRegistry extends Service { .filter((record): record is WebPluginRecord => record !== undefined) const artifacts: BatchArtifact[] = [] for (const records of partitionComboRecords(bootstrap)) { - artifacts.push(buildBatch('bootstrap', records)) + artifacts.push(buildBatch('bootstrap', records, this.readSourceMap)) } for (const records of partitionComboRecords(application)) { - artifacts.push(buildBatch('application', records)) + artifacts.push(buildBatch('application', records, this.readSourceMap)) } - const batchResponses = new Map() + const batchResponses = new Map() for (const artifact of artifacts) { - batchResponses.set(artifact.descriptor.url, { - body: artifact.script, + batchResponses.set(artifact.descriptor.url, this.responses.get(artifact.descriptor.url) ?? { + body: artifact.scriptBody, contentType: 'text/javascript; charset=utf-8', }) - batchResponses.set(artifact.sourceMapUrl, { - body: artifact.sourceMap, + batchResponses.set(artifact.sourceMapUrl, this.responses.get(artifact.sourceMapUrl) ?? { + body: artifact.sourceMapBody, contentType: 'application/json; charset=utf-8', }) } const responses = new Map(batchResponses) for (const record of this.table.values()) { - const artifact = buildCombo([record], record.entry.rev) - responses.set(artifact.url, { - body: artifact.script, + const artifact = buildCombo([record], this.readSourceMap, record.entry.rev) + responses.set(artifact.url, responses.get(artifact.url) ?? this.responses.get(artifact.url) ?? { + body: artifact.scriptBody, contentType: 'text/javascript; charset=utf-8', }) - responses.set(artifact.sourceMapUrl, { - body: artifact.sourceMap, + responses.set(artifact.sourceMapUrl, responses.get(artifact.sourceMapUrl) ?? this.responses.get(artifact.sourceMapUrl) ?? { + body: artifact.sourceMapBody, contentType: 'application/json; charset=utf-8', }) } @@ -838,7 +896,7 @@ export class ClientModuleRegistry extends Service { } /** - * Read the activation-time bundle and optional source-map snapshots. + * Read the activation-time bundle snapshot. * @param pkgName - package that declares the client bundle. * @param clientPath - absolute path of the built client artifact. * @returns the immutable bytes plus the pre-read filesystem baseline. @@ -847,23 +905,21 @@ export class ClientModuleRegistry extends Service { private initialBundleSnapshot(pkgName: string, clientPath: string): { bundle: Buffer baseline: ClientArtifactBaseline - sourceMap?: WebPluginRecord['sourceMap'] } { try { const baseline = this.captureArtifactBaseline(clientPath) const bundle = readFileSync(clientPath) - const sourceMap = this.readSourceMapSnapshot(clientPath) - return { bundle, baseline, ...(sourceMap === undefined ? {} : { sourceMap }) } + return { bundle, baseline } } catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error throw new MissingClientBundleError(pkgName, clientPath, error) } } - /** Treat a missing, torn, or malformed development map as an identity-mapped artifact revision. */ - private readSourceMapSnapshot(clientPath: string): WebPluginRecord['sourceMap'] { + /** Treat a missing, torn, or malformed development map as an identity section. */ + private readonly readSourceMap = (clientPath: string): Record | undefined => { try { - return sourceMapSnapshot(clientPath) + return readSourceMap(clientPath) } catch (error) { this.ctx.logger.warn(error) return undefined @@ -938,7 +994,6 @@ export class ClientModuleRegistry extends Service { meta: source.meta, bundle: snapshot.bundle, baseline: snapshot.baseline, - ...(snapshot.sourceMap === undefined ? {} : { sourceMap: snapshot.sourceMap }), }) return true } @@ -971,11 +1026,11 @@ export class ClientModuleRegistry extends Service { this.notifyGraphChanged() } - private bundleResource(method: string | undefined, url: string): { + private async bundleResource(method: string | undefined, url: string): Promise<{ status: number headers?: Record body?: Buffer - } { + }> { if (method !== 'GET' && method !== 'HEAD') return { status: 405 } const requestUrl = new URL(url, 'http://x') const resourceUrl = `${requestUrl.pathname}${requestUrl.search}` @@ -984,7 +1039,7 @@ export class ClientModuleRegistry extends Service { return { status: 200, headers: { 'content-type': response.contentType, 'cache-control': IMMUTABLE_CACHE }, - ...(method === 'HEAD' ? {} : { body: response.body }), + ...(method === 'HEAD' ? {} : { body: await response.body() }), } } // Anything else under /plugins (including unadvertised combinations and @@ -992,9 +1047,9 @@ export class ClientModuleRegistry extends Service { return { status: 404 } } - private readonly serveBundle = (req: IncomingMessage, res: ServerResponse): void => { + private readonly serveBundle = async (req: IncomingMessage, res: ServerResponse): Promise => { /* v8 ignore next -- `?? '/'` arm: node:http always sets url on server requests. */ - const response = this.bundleResource(req.method, req.url ?? '/') + const response = await this.bundleResource(req.method, req.url ?? '/') res.writeHead(response.status, response.headers) res.end(response.body) } diff --git a/packages/client/modules/tests/node-half.client.spec.ts b/packages/client/modules/tests/node-half.client.spec.ts index 95a5396e91..9c423b8245 100644 --- a/packages/client/modules/tests/node-half.client.spec.ts +++ b/packages/client/modules/tests/node-half.client.spec.ts @@ -52,7 +52,7 @@ it.each([false, true])('tracks the Web carrier lifetime when server-first is %s' const modules = await ctx.plugin(ClientModuleRegistry) const service = ctx.get('clientModules')! expect(service.graph().entries).toEqual([]) - expect(service.fetchBundle(new Request('http://localhost/plugins/missing')).status).toBe(404) + expect((await service.fetchBundle(new Request('http://localhost/plugins/missing'))).status).toBe(404) if (!serverFirst) { expect(routes.size).toBe(0) server = await mountServer() @@ -61,7 +61,7 @@ it.each([false, true])('tracks the Web carrier lifetime when server-first is %s' await server!.dispose() await expect.poll(() => routes.size).toBe(0) expect(modules.state).toBe(FiberState.ACTIVE) - expect(service.fetchBundle(new Request('http://localhost/plugins/missing')).status).toBe(404) + expect((await service.fetchBundle(new Request('http://localhost/plugins/missing'))).status).toBe(404) await mountServer() await expect.poll(() => routes.size).toBe(1) await modules.dispose() @@ -507,6 +507,85 @@ describe('client bundle activation', () => { writeFileSync(`${clientPath}.map`, '{"version":3,"sources":[null]}\n') expect(() => construct([packageName])).not.toThrow() + + writeFileSync(`${clientPath}.map`, JSON.stringify({ + version: 3, + names: [], + mappings: 'AAAA', + sourceRoot: 'http://[', + sources: ['src/index.ts'], + })) + const invalidUrl = constructWithRoute([packageName]) + const invalidMapUrl = mapUrl(invalidUrl.service.graph().batches[0]!.url) + expect(JSON.parse((await routeRequest(invalidUrl.route, invalidMapUrl)).body.toString('utf8'))).toMatchObject({ + sections: [{ map: { sources: [`/plugins/${packageName}/client.js`] } }], + }) + }) + + it('reads a source map only on its first map GET', async () => { + const packageName = '@fixture/lazy-source-map' + const clientPath = writePackage(packageName) + mkdirSync(dirname(clientPath), { recursive: true }) + writeFileSync(clientPath, 'module.exports = {}\n//# sourceMappingURL=client.js.map') + writeFileSync(`${clientPath}.map`, '{') + const { service, route } = constructWithRoute([packageName]) + const batch = service.graph().batches[0]! + const sourceMapUrl = mapUrl(batch.url) + + const script = await routeRequest(route, batch.url) + expect(script.status).toBe(200) + expect((await routeRequest(route, sourceMapUrl, 'HEAD')).body).toHaveLength(0) + writeFileSync(`${clientPath}.map`, JSON.stringify({ + version: 3, + names: [], + mappings: 'AAAA', + sources: ['src/first.ts'], + })) + const first = await routeRequest(route, sourceMapUrl) + expect(JSON.parse(first.body.toString('utf8'))).toMatchObject({ + sections: [{ map: { sources: [`/plugins/${packageName}/src/first.ts`] } }], + }) + + writeFileSync(`${clientPath}.map`, JSON.stringify({ + version: 3, + names: [], + mappings: 'AAAA', + sources: ['src/second.ts'], + })) + expect((await routeRequest(route, sourceMapUrl)).body).toEqual(first.body) + expect((await routeRequest(route, batch.url)).body).toEqual(script.body) + }) + + it('retains a materialized resource when an unrelated row recomposes the graph', async () => { + const stablePackage = '@fixture/stable-source-map' + const rebuiltPackage = '@fixture/rebuilt-neighbor' + const stablePath = writePackage(stablePackage) + const rebuiltPath = writePackage(rebuiltPackage) + for (const clientPath of [stablePath, rebuiltPath]) { + mkdirSync(dirname(clientPath), { recursive: true }) + writeFileSync(clientPath, 'module.exports = {}\n//# sourceMappingURL=client.js.map') + writeFileSync(`${clientPath}.map`, JSON.stringify({ + version: 3, + names: [], + mappings: 'AAAA', + sources: ['src/first.ts'], + })) + } + const { service, route } = constructWithRoute([stablePackage, rebuiltPackage]) + const stableUrl = service.graph().entries.find(entry => entry.id === stablePackage)!.url + const stableMapUrl = mapUrl(stableUrl) + const first = await routeRequest(route, stableMapUrl) + + writeFileSync(`${stablePath}.map`, JSON.stringify({ + version: 3, + names: [], + mappings: 'AAAA', + sources: ['src/second.ts'], + })) + writeFileSync(rebuiltPath, 'module.exports = { rebuilt: true }\n') + service.rebuilt(rebuiltPackage) + + expect((await routeRequest(route, stableMapUrl)).body).toEqual(first.body) }) it('maps packed combo sections back to each generated client bundle', async () => { @@ -668,7 +747,7 @@ describe('client bundle activation', () => { expect(batchScript.status).toBe(200) expect(batchScript.headers?.['cache-control']).toBe('public, max-age=31536000, immutable') expect(batchScript.body.toString('utf8')).toContain(`//# sourceMappingURL=${mapUrl(batch.url)}`) - const shellResponse = service.fetchBundle(new Request(`dsh-app://app${batch.url}`)) + const shellResponse = await service.fetchBundle(new Request(`dsh-app://app${batch.url}`)) expect(shellResponse.status).toBe(200) expect(shellResponse.headers.get('cache-control')).toBe('public, max-age=31536000, immutable') expect(await shellResponse.text()).toBe(batchScript.body.toString('utf8')) diff --git a/packages/client/resources/package.json b/packages/client/resources/package.json index dd6b9baa2f..84f669f4f5 100644 --- a/packages/client/resources/package.json +++ b/packages/client/resources/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-resources", "description": "Unified client resource model: protocol-registered providers turn URL addresses into live values, consumed through the useResource global standard hook", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/store/package.json b/packages/client/store/package.json index 59325a1517..1f0f18c65a 100644 --- a/packages/client/store/package.json +++ b/packages/client/store/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-store", "description": "React-free observable and snapshot-store contracts with the shared Zustand/Immer engine", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-agent-preset/package.json b/packages/client/ui-agent-preset/package.json index 2dce990410..513c584779 100644 --- a/packages/client/ui-agent-preset/package.json +++ b/packages/client/ui-agent-preset/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-agent-preset", "description": "Agent-preset surfaces: the default for later sessions, this session's seat, and the composition editor", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-approval/package.json b/packages/client/ui-approval/package.json index 765176239f..196f1adfb3 100644 --- a/packages/client/ui-approval/package.json +++ b/packages/client/ui-approval/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-approval", "description": "Approval composer takeover over the scoped Remote Event waterfall", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-attachment/package.json b/packages/client/ui-attachment/package.json index f994246609..00a21ce93f 100644 --- a/packages/client/ui-attachment/package.json +++ b/packages/client/ui-attachment/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-attachment", "description": "Dynamic attachment presentation plugin for conversation input, message-image, and trajectory image slots", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-brand-official/package.json b/packages/client/ui-brand-official/package.json index 1113b8a013..24c8702e21 100644 --- a/packages/client/ui-brand-official/package.json +++ b/packages/client/ui-brand-official/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-brand-official", "description": "Official DeepSeek Harness brand occupants for the Web client's sidebar slots", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-chat/package.json b/packages/client/ui-chat/package.json index 1caf5c0aec..88d97cea84 100644 --- a/packages/client/ui-chat/package.json +++ b/packages/client/ui-chat/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-chat", "description": "Chat Conversation target, node definitions, renderers, and details surface", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-commands/package.json b/packages/client/ui-commands/package.json index f3313850a3..ed228b129a 100644 --- a/packages/client/ui-commands/package.json +++ b/packages/client/ui-commands/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-commands", "description": "Client command surface: global directory cache, '/' source, three command UI kinds, popupSelect registry", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-conversation/package.json b/packages/client/ui-conversation/package.json index 1af4c6a6fe..16b78db6d2 100644 --- a/packages/client/ui-conversation/package.json +++ b/packages/client/ui-conversation/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-conversation", "description": "Target-neutral Conversation assembly, shell, composer, queue, and view navigation", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-deliverables/package.json b/packages/client/ui-deliverables/package.json index 676e3569fd..b52421bdd5 100644 --- a/packages/client/ui-deliverables/package.json +++ b/packages/client/ui-deliverables/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-deliverables", "description": "Produced-files turn tail and clickable final-response file references for Web", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-directory-picker-browse/package.json b/packages/client/ui-directory-picker-browse/package.json index 019e02f3fe..401e53de9c 100644 --- a/packages/client/ui-directory-picker-browse/package.json +++ b/packages/client/ui-directory-picker-browse/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-directory-picker-browse", "description": "In-app directory browsing surface: the workspace directory-flow owner rendering the host's listing and creation primitives", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-directory-picker-native/package.json b/packages/client/ui-directory-picker-native/package.json index e984a73db2..facf8cd78b 100644 --- a/packages/client/ui-directory-picker-native/package.json +++ b/packages/client/ui-directory-picker-native/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-directory-picker-native", "description": "Native directory-picker surface: the renderless workspace directory-flow occupant driving the host's OS chooser", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-dockkit/package.json b/packages/client/ui-dockkit/package.json index 570cdebef4..f557139f4f 100644 --- a/packages/client/ui-dockkit/package.json +++ b/packages/client/ui-dockkit/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-dockkit", "description": "Docking layout kit: split-tree engine with invertible operations, and the React components that render and drive it (zero cordis)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-goal/package.json b/packages/client/ui-goal/package.json index b59be8fb9a..58d80d7bf5 100644 --- a/packages/client/ui-goal/package.json +++ b/packages/client/ui-goal/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-goal", "description": "Session goal surface: GoalBar docked above the composer, read from the goal session projection", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-input-trigger/package.json b/packages/client/ui-input-trigger/package.json index 4cf4959203..40765d18cb 100644 --- a/packages/client/ui-input-trigger/package.json +++ b/packages/client/ui-input-trigger/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-input-trigger", "description": "Input trigger pipeline: '/' and '@' detection, candidate menu, pick routing to registered sources", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-jobs/package.json b/packages/client/ui-jobs/package.json index d36767585b..c67f43705f 100644 --- a/packages/client/ui-jobs/package.json +++ b/packages/client/ui-jobs/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-jobs", "description": "Session-header background-job list: live registry state mirrored from session/jobs frames", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "type": "module", "main": "lib/index.js", "types": "lib/types/index.d.ts", diff --git a/packages/client/ui-layout/package.json b/packages/client/ui-layout/package.json index 5c56f8dfe9..a7d9da8368 100644 --- a/packages/client/ui-layout/package.json +++ b/packages/client/ui-layout/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-layout", "description": "Shell plugin: three-column AppFrame with drag handles, ctx.layout viewing-state service (navigation + panels)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-message-feedback/package.json b/packages/client/ui-message-feedback/package.json index 08b42a5819..9efbff738c 100644 --- a/packages/client/ui-message-feedback/package.json +++ b/packages/client/ui-message-feedback/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-message-feedback", "description": "The Web feedback surface: per-message Like/Dislike in the assistant-message action strip and the feedback dialog behind both ratings and /feedback, backed by the messageFeedback and sessionFeedback Host Remotes", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-model-selection/package.json b/packages/client/ui-model-selection/package.json index bf37cfed12..d04cdf7e3d 100644 --- a/packages/client/ui-model-selection/package.json +++ b/packages/client/ui-model-selection/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-model-selection", "description": "Model selection over the shared model catalog, Session projection, and session.selectModel", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-open-in-app/package.json b/packages/client/ui-open-in-app/package.json index 923abdf40e..0b8b93b67d 100644 --- a/packages/client/ui-open-in-app/package.json +++ b/packages/client/ui-open-in-app/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-open-in-app", "description": "Web Session-header \"Open In...\" split button opening the session workspace directory in a locally installed application", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-permission-presets/package.json b/packages/client/ui-permission-presets/package.json index b14d59e5c5..b868fdb882 100644 --- a/packages/client/ui-permission-presets/package.json +++ b/packages/client/ui-permission-presets/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-permission-presets", "description": "Permission surfaces: a new-session default in General settings and a current-session /permission popup over the permissions projection", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-plan/package.json b/packages/client/ui-plan/package.json index 49d6f41358..c8ada3d792 100644 --- a/packages/client/ui-plan/package.json +++ b/packages/client/ui-plan/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-plan", "description": "Plan-mode composer control: the conversation.input.plan seat over the plan projection and the /plan command channel", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-primitives/package.json b/packages/client/ui-primitives/package.json index 98677069b0..798b43c22d 100644 --- a/packages/client/ui-primitives/package.json +++ b/packages/client/ui-primitives/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-primitives", "description": "Pure React atoms for the dsh web UI: controls, icons, markdown, and JSON inspectors (zero cordis)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-reference/package.json b/packages/client/ui-reference/package.json index f2cc3c40f2..4eb09811d9 100644 --- a/packages/client/ui-reference/package.json +++ b/packages/client/ui-reference/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-reference", "description": "Unified Web @file and @session reference source", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-renderer/package.json b/packages/client/ui-renderer/package.json index 17edf34172..f40da5ddd2 100644 --- a/packages/client/ui-renderer/package.json +++ b/packages/client/ui-renderer/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-renderer", "description": "Browser UI renderer: React slot bindings, ctx.uiRenderer, and the assembled application root", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-schedule/package.json b/packages/client/ui-schedule/package.json index b4717e39a2..ceed76c90f 100644 --- a/packages/client/ui-schedule/package.json +++ b/packages/client/ui-schedule/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-schedule", "description": "Read-only active Schedule catalog in the Web Session header", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "type": "module", "main": "lib/index.js", "types": "lib/types/index.d.ts", diff --git a/packages/client/ui-session/package.json b/packages/client/ui-session/package.json index e7943cbdbf..c09b157d04 100644 --- a/packages/client/ui-session/package.json +++ b/packages/client/ui-session/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-session", "description": "Session Controller adapter for React and session-scoped slots", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-settings-general/package.json b/packages/client/ui-settings-general/package.json index f884624c13..842a287b1c 100644 --- a/packages/client/ui-settings-general/package.json +++ b/packages/client/ui-settings-general/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-settings-general", "description": "Settings ownerless-copy and product onboarding plugin: the General section, shell trigger/header chrome content, settings dictionaries, and the versioned welcome notice", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-settings-models/package.json b/packages/client/ui-settings-models/package.json index 6702d7d942..1ec7c8c277 100644 --- a/packages/client/ui-settings-models/package.json +++ b/packages/client/ui-settings-models/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-settings-models", "description": "Models settings and shared product-onboarding dialogs over existing settings and credential joins", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-settings-plugin-inventory/package.json b/packages/client/ui-settings-plugin-inventory/package.json index a0e43dbaa4..d979ece7d5 100644 --- a/packages/client/ui-settings-plugin-inventory/package.json +++ b/packages/client/ui-settings-plugin-inventory/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-settings-plugin-inventory", "description": "Read-only Cordis Loader inventory tab in Web Plugins settings", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-settings-plugins/package.json b/packages/client/ui-settings-plugins/package.json index a9ee3769c3..64d594d376 100644 --- a/packages/client/ui-settings-plugins/package.json +++ b/packages/client/ui-settings-plugins/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-settings-plugins", "description": "Plugins settings section with feature-owned tabs and configurable host-plane plugin cards", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-settings-unarchive-sessions/package.json b/packages/client/ui-settings-unarchive-sessions/package.json index f8c5a307ea..1a04e81163 100644 --- a/packages/client/ui-settings-unarchive-sessions/package.json +++ b/packages/client/ui-settings-unarchive-sessions/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-settings-unarchive-sessions", "description": "Archived-session settings page: the registry-global archive set with one Unarchive action per row", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-settings/package.json b/packages/client/ui-settings/package.json index d8c8fd594a..a163eea015 100644 --- a/packages/client/ui-settings/package.json +++ b/packages/client/ui-settings/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-settings", "description": "Settings domain base plugin: the settings-namespace scope service and the canonical settings slot-type contract", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-sidebar-documentpreview/package.json b/packages/client/ui-sidebar-documentpreview/package.json index c84f696313..970c62db66 100644 --- a/packages/client/ui-sidebar-documentpreview/package.json +++ b/packages/client/ui-sidebar-documentpreview/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-sidebar-documentpreview", "description": "Extensible document previews for Sidebar files: Markdown, highlighted code, images, PDF, HTML, and plain text", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-sidebar-files/package.json b/packages/client/ui-sidebar-files/package.json index c9b0f30663..3c0c62be27 100644 --- a/packages/client/ui-sidebar-files/package.json +++ b/packages/client/ui-sidebar-files/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-sidebar-files", "description": "Workspace file tree tab type for the right Sidebar: lazy directory listing over the workspaceFiles Remote namespace, opening files into the Sidebar", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-sidebar-right/package.json b/packages/client/ui-sidebar-right/package.json index 0ac2ba4dc6..2057a9dcae 100644 --- a/packages/client/ui-sidebar-right/package.json +++ b/packages/client/ui-sidebar-right/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-sidebar-right", "description": "Right Sidebar: the docking surface's session-bound state, its panel and header expand control, and the navigation service over it", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-sidebar-terminal/package.json b/packages/client/ui-sidebar-terminal/package.json index 1d546c4ab9..1ffc27e0a9 100644 --- a/packages/client/ui-sidebar-terminal/package.json +++ b/packages/client/ui-sidebar-terminal/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-sidebar-terminal", "description": "Interactive shell tabs for the right Sidebar", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-sidebar/package.json b/packages/client/ui-sidebar/package.json index e3329d2ad5..812bbb8c6d 100644 --- a/packages/client/ui-sidebar/package.json +++ b/packages/client/ui-sidebar/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-sidebar", "description": "Sidebar plugin: session multi-level tree, search, grouping, state dots", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-skill/package.json b/packages/client/ui-skill/package.json index 9f1df843ed..b028fd198b 100644 --- a/packages/client/ui-skill/package.json +++ b/packages/client/ui-skill/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-skill", "description": "Web skill references and the dedicated skill tool row", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-slots/package.json b/packages/client/ui-slots/package.json index aa3ea2b5db..19c69fbe57 100644 --- a/packages/client/ui-slots/package.json +++ b/packages/client/ui-slots/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-slots", "description": "Slot registry pure core: SlotMap declaration merging, single register composition API, four-share props types, store-seat types, renderer install seam", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-subagent/package.json b/packages/client/ui-subagent/package.json index 013105cce6..2f3eda7951 100644 --- a/packages/client/ui-subagent/package.json +++ b/packages/client/ui-subagent/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-subagent", "description": "Subagent conversation catalog, continuation routing UI, and '@' reference source", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-theme/README.i18n.yaml b/packages/client/ui-theme/README.i18n.yaml index 36b96066e3..bbb71eb326 100644 --- a/packages/client/ui-theme/README.i18n.yaml +++ b/packages/client/ui-theme/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/client/ui-theme/README.md -README.md: 291492b9b41df6570722fb2dbd14565c660ec350 -README.zh.md: 08cef7aca2f6df648fe75b3d27aed93c44a2d30c +README.md: a25f0202d278d52448d52a22f451612295325f87 +README.zh.md: 08a0c37dc3c80b4385173e6ca74bb9254b4870ea diff --git a/packages/client/ui-theme/README.md b/packages/client/ui-theme/README.md index 291492b9b4..a25f0202d2 100644 --- a/packages/client/ui-theme/README.md +++ b/packages/client/ui-theme/README.md @@ -37,7 +37,7 @@ A composition can register a third-party theme id with alias-token overrides thr ### Pre-plugin palette -When the host composition includes an HTTP server, the host half embeds the registered `ui-theme` settings, or schema defaults, into each index response. Before the loading page renders, the browser sets `color-scheme`, `body[data-ds-dark-theme]`, and `--dsh-content-font-size`, so the first paint uses the selected palette and text size. +When the host composition includes an HTTP server, the host half embeds the registered `ui-theme` settings, or schema defaults, into each index response. Head CSS selects the document canvas color scheme before any script runs, including a `prefers-color-scheme` query for the `system` preference. A body script then sets `body[data-ds-dark-theme]` and `--dsh-content-font-size` before the loading page and application scripts, so the first paint uses the selected palette and text size. ----- diff --git a/packages/client/ui-theme/README.zh.md b/packages/client/ui-theme/README.zh.md index 08cef7aca2..08a0c37dc3 100644 --- a/packages/client/ui-theme/README.zh.md +++ b/packages/client/ui-theme/README.zh.md @@ -37,7 +37,7 @@ kind: "package-reference" ### 插件前调色板 -当主机组合包含 HTTP 服务器时,宿主侧会把已注册的 `ui-theme` 设置或 schema 默认值嵌入每份 index 响应。浏览器在加载页面渲染前设置 `color-scheme`、`body[data-ds-dark-theme]` 与 `--dsh-content-font-size`,因此首帧绘制就采用所选调色板与字号。 +当主机组合包含 HTTP 服务器时,宿主侧会把已注册的 `ui-theme` 设置或 schema 默认值嵌入每份 index 响应。head CSS 会在任何脚本运行前选择文档画布的配色方案,其中 `system` 偏好使用 `prefers-color-scheme` 查询;随后,body 脚本会在加载页面和应用脚本之前设置 `body[data-ds-dark-theme]` 与 `--dsh-content-font-size`,因此首帧绘制就采用所选调色板与字号。 ----- diff --git a/packages/client/ui-theme/package.json b/packages/client/ui-theme/package.json index 5a7a7fb249..0acb236659 100644 --- a/packages/client/ui-theme/package.json +++ b/packages/client/ui-theme/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-theme", "description": "Theme plugin: Host bootstrap for the pre-plugin palette; DOM-free ThemeRuntime for light/dark/system state; --dsw-* token styles and Appearance settings row", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-theme/src/boot-theme.ts b/packages/client/ui-theme/src/boot-theme.ts index 1bc4131c02..6d91060608 100644 --- a/packages/client/ui-theme/src/boot-theme.ts +++ b/packages/client/ui-theme/src/boot-theme.ts @@ -1,37 +1,52 @@ /** * Theme bootstrap row for the browser's pre-plugin interval. Each index - * render embeds the current durable built-in preference and content font size; - * the browser resolves only `system`, then writes the same DOM fields - * ui-layout's ThemePresenter owns after the client plugin tree activates. + * render embeds the current durable built-in preference and content font size. + * Head CSS colors the document canvas before script execution; the body script + * installs the palette selector and font size that the client presenters adopt. */ import type { IndexInjection } from '@deepseek-ai/dsh-host-webserver' import { DEFAULT_FONT_SIZE, DEFAULT_PREFERENCE, type ThemePreference } from './theme-settings.ts' -/** Build the inline script body for one schema-validated durable theme section. */ -function bootThemeScript(preference: ThemePreference, fontSize: number): string { +const LIGHT_BACKGROUND = '#fff' +const DARK_BACKGROUND = '#151517' + +/** CSS that colors the document canvas before any script executes. */ +function bootThemeStyle(preference: ThemePreference): string { + const light = `:root{color-scheme:light}body{background-color:${LIGHT_BACKGROUND};--dsh-boot-bg:${LIGHT_BACKGROUND}}` + const dark = `:root{color-scheme:dark}body{background-color:${DARK_BACKGROUND};--dsh-boot-bg:${DARK_BACKGROUND}}` + if (preference === 'light') return light + if (preference === 'dark') return dark + return `${light}@media(prefers-color-scheme:dark){${dark}}` +} + +/** Build the body script that installs the palette selector and content size. */ +function bootThemeBodyScript(preference: ThemePreference, fontSize: number): string { return `(() => { const preference = ${JSON.stringify(preference)} const systemDark = preference === 'system' && typeof matchMedia !== 'undefined' && matchMedia('(prefers-color-scheme: dark)').matches const dark = preference === 'dark' || systemDark - document.documentElement.style.colorScheme = dark ? 'dark' : 'light' document.body.toggleAttribute('data-ds-dark-theme', dark) document.body.style.setProperty('--dsh-content-font-size', ${JSON.stringify(`${fontSize}px`)}) })()` } /** - * The theme bootstrap as an injection row: an inline script immediately after - * the opening body tag, before the shell mount and module script. + * Theme bootstrap rows: head CSS colors the document canvas before + * first paint, then the body script installs the palette selector and font + * size before the shell mount and module script. * @param preference - Current Host-backed built-in preference. * @param fontSize - Current Host-backed content font size in px. - * @returns the body script row. + * @returns head and body script rows in execution order. */ -export function bootThemeInjection( +export function bootThemeInjections( preference: ThemePreference = DEFAULT_PREFERENCE, fontSize: number = DEFAULT_FONT_SIZE, -): IndexInjection { - return { kind: 'script', placement: 'body', text: bootThemeScript(preference, fontSize) } +): IndexInjection[] { + return [ + { kind: 'style', text: bootThemeStyle(preference) }, + { kind: 'script', placement: 'body', text: bootThemeBodyScript(preference, fontSize) }, + ] } diff --git a/packages/client/ui-theme/src/index.ts b/packages/client/ui-theme/src/index.ts index 2b0d22a7c9..cabb8eb2fa 100644 --- a/packages/client/ui-theme/src/index.ts +++ b/packages/client/ui-theme/src/index.ts @@ -3,7 +3,7 @@ import type { Context } from '@deepseek-ai/cordis' import type {} from '@deepseek-ai/dsh-host-webserver' import type {} from '@deepseek-ai/dsh-settings' -import { bootThemeInjection } from './boot-theme.ts' +import { bootThemeInjections } from './boot-theme.ts' import { DEFAULT_FONT_SIZE, DEFAULT_PREFERENCE, THEME_SETTINGS_NAMESPACE, ThemeSettingsSchema, type ThemePreference, type ThemeSettings, @@ -39,6 +39,6 @@ export function apply(ctx: Context): void { }) ctx.on('webserver/index-inject', (table) => { const section = readSection(ctx) - table.push(bootThemeInjection(section.preference, section.fontSize)) - }) + table.push(...bootThemeInjections(section.preference, section.fontSize)) + }, { prepend: true }) } diff --git a/packages/client/ui-theme/src/styles/design-platform.css b/packages/client/ui-theme/src/styles/design-platform.css index bc4712b223..990f39d819 100644 --- a/packages/client/ui-theme/src/styles/design-platform.css +++ b/packages/client/ui-theme/src/styles/design-platform.css @@ -244,6 +244,8 @@ body { --dsw-specific-sidebar-nav-item-active: var(--dsw-static-neutral-bluish-100); --dsw-specific-sidebar-nav-item-hover: var(--dsw-static-neutral-bluish-75); --dsw-specific-tip: var(--dsw-static-neutral-bluish-60); + + background-color: var(--dsw-alias-bg-base); } body[data-ds-dark-theme] { diff --git a/packages/client/ui-theme/tests/boot-theme.client.spec.ts b/packages/client/ui-theme/tests/boot-theme.client.spec.ts index ef13929961..289b284364 100644 --- a/packages/client/ui-theme/tests/boot-theme.client.spec.ts +++ b/packages/client/ui-theme/tests/boot-theme.client.spec.ts @@ -2,7 +2,7 @@ /** The theme bootstrap injection row and the resulting pre-plugin browser theme. */ import { runInNewContext } from 'node:vm' import { afterEach, describe, expect, it, vi } from 'vitest' -import { bootThemeInjection } from '../src/boot-theme.ts' +import { bootThemeInjections } from '../src/boot-theme.ts' import type { ThemePreference } from '../src/theme-settings.ts' const DARK_ATTRIBUTE = 'data-ds-dark-theme' @@ -12,51 +12,63 @@ function mockSystemDark(matches: boolean): void { } function executeBootstrap(preference?: ThemePreference, fontSize?: number): void { - const row = bootThemeInjection(preference, fontSize) - if (row.kind !== 'script') throw new Error('theme bootstrap row is not a script') - runInNewContext(row.text, { document, matchMedia: globalThis.matchMedia }) + for (const row of bootThemeInjections(preference, fontSize)) { + if (row.kind === 'script') runInNewContext(row.text, { document, matchMedia: globalThis.matchMedia }) + } } afterEach(() => { vi.restoreAllMocks() vi.unstubAllGlobals() - document.documentElement.style.removeProperty('color-scheme') document.body.removeAttribute(DARK_ATTRIBUTE) document.body.style.removeProperty('--dsh-content-font-size') }) describe('theme bootstrap row', () => { - it('is a body script row, so it runs before the shell mount', () => { + it('colors the body with head CSS before applying body state', () => { mockSystemDark(false) - const row = bootThemeInjection('dark') - expect(row).toMatchObject({ kind: 'script', placement: 'body' }) - executeBootstrap('dark') - expect(document.documentElement.style.colorScheme).toBe('dark') + const [head, body] = bootThemeInjections('dark') + expect(head).toMatchObject({ kind: 'style' }) + expect(body).toMatchObject({ kind: 'script', placement: 'body' }) + if (head?.kind !== 'style') throw new Error('theme head bootstrap row is not a style') + expect(head.text).toBe(':root{color-scheme:dark}body{background-color:#151517;--dsh-boot-bg:#151517}') + expect(document.body.hasAttribute(DARK_ATTRIBUTE)).toBe(false) + if (body?.kind !== 'script') throw new Error('theme body bootstrap row is not a script') + runInNewContext(body.text, { document, matchMedia: globalThis.matchMedia }) expect(document.body.hasAttribute(DARK_ATTRIBUTE)).toBe(true) }) it('lets durable light override a dark OS and clears stale dark state', () => { document.body.setAttribute(DARK_ATTRIBUTE, '') mockSystemDark(true) + const [head] = bootThemeInjections('light') + if (head?.kind !== 'style') throw new Error('theme head bootstrap row is not a style') + expect(head.text).toBe(':root{color-scheme:light}body{background-color:#fff;--dsh-boot-bg:#fff}') executeBootstrap('light') - expect(document.documentElement.style.colorScheme).toBe('light') expect(document.body.hasAttribute(DARK_ATTRIBUTE)).toBe(false) }) it.each([ - [true, 'dark', true], - [false, 'light', false], - ] as const)('resolves system=%s to %s', (matches, colorScheme, dark) => { + [true, true], + [false, false], + ] as const)('resolves system=%s for the body palette', (matches, dark) => { mockSystemDark(matches) executeBootstrap('system') - expect(document.documentElement.style.colorScheme).toBe(colorScheme) expect(document.body.hasAttribute(DARK_ATTRIBUTE)).toBe(dark) }) + it('uses a media query for the system canvas palette', () => { + const [head] = bootThemeInjections('system') + if (head?.kind !== 'style') throw new Error('theme head bootstrap row is not a style') + expect(head.text).toBe( + ':root{color-scheme:light}body{background-color:#fff;--dsh-boot-bg:#fff}' + + '@media(prefers-color-scheme:dark){:root{color-scheme:dark}body{background-color:#151517;--dsh-boot-bg:#151517}}', + ) + }) + it('defaults to system and falls back to light when matchMedia is unavailable', () => { vi.stubGlobal('matchMedia', undefined) executeBootstrap() - expect(document.documentElement.style.colorScheme).toBe('light') expect(document.body.hasAttribute(DARK_ATTRIBUTE)).toBe(false) }) diff --git a/packages/client/ui-theme/tests/host.client.spec.ts b/packages/client/ui-theme/tests/host.client.spec.ts index 0e208834ec..ffa38b61a6 100644 --- a/packages/client/ui-theme/tests/host.client.spec.ts +++ b/packages/client/ui-theme/tests/host.client.spec.ts @@ -21,9 +21,9 @@ function collect(ctx: Context): IndexInjection[] { return table } -/** Narrow the theme row and return its script body. */ -function scriptText(row: IndexInjection | undefined): string { - if (row?.kind !== 'script') throw new Error('expected a script row') +/** Narrow a theme style or script row and return its text. */ +function rowText(row: IndexInjection | undefined): string { + if (row?.kind !== 'script' && row?.kind !== 'style') throw new Error('expected a style or script row') return row.text } @@ -47,24 +47,31 @@ describe('ui-theme host', () => { it('answers each collection with the current durable preference until disposal', async () => { const ctx = new Context() await ctx.plugin(MemorySettings).await() + ctx.on('webserver/index-inject', (table) => { + table.push({ kind: 'script', placement: 'head', text: 'window.afterTheme=true' }) + }) const fiber = ctx.plugin({ apply }) await fiber.await() const rows = collect(ctx) - expect(rows).toHaveLength(1) - expect(rows[0]).toMatchObject({ kind: 'script', placement: 'body' }) - expect(scriptText(rows[0])).toContain('const preference = "system"') - expect(scriptText(rows[0])).toContain('"14px"') + expect(rows).toHaveLength(3) + expect(rows[0]).toMatchObject({ kind: 'style' }) + expect(rows[1]).toMatchObject({ kind: 'script', placement: 'body' }) + expect(rows[2]).toMatchObject({ kind: 'script', placement: 'head', text: 'window.afterTheme=true' }) + expect(rowText(rows[0])).toContain('@media(prefers-color-scheme:dark)') + expect(rowText(rows[1])).toContain('const preference = "system"') + expect(rowText(rows[1])).toContain('"14px"') await ctx.settings.update(THEME_SETTINGS_NAMESPACE, { preference: 'dark', fontSize: 17 }) - expect(scriptText(collect(ctx)[0])).toContain('const preference = "dark"') - expect(scriptText(collect(ctx)[0])).toContain('"17px"') + expect(rowText(collect(ctx)[0])).toContain('color-scheme:dark') + expect(rowText(collect(ctx)[1])).toContain('const preference = "dark"') + expect(rowText(collect(ctx)[1])).toContain('"17px"') await fiber.dispose() - expect(collect(ctx)).toEqual([]) + expect(collect(ctx)).toEqual([{ kind: 'script', placement: 'head', text: 'window.afterTheme=true' }]) }) it('uses the system preference without a settings provider', async () => { const ctx = new Context() await ctx.plugin({ apply }).await() - expect(scriptText(collect(ctx)[0])).toContain('const preference = "system"') + expect(rowText(collect(ctx)[1])).toContain('const preference = "system"') }) it('falls back to the schema default while the theme namespace holds no section', async () => { @@ -73,6 +80,6 @@ describe('ui-theme host', () => { const ctx = new Context() ctx.provide('settings', { register: () => () => {}, get: () => undefined } as never) await ctx.plugin({ apply }).await() - expect(scriptText(collect(ctx)[0])).toContain('const preference = "system"') + expect(rowText(collect(ctx)[1])).toContain('const preference = "system"') }) }) diff --git a/packages/client/ui-tool/package.json b/packages/client/ui-tool/package.json index 7722edfaa0..b84ce6d5ed 100644 --- a/packages/client/ui-tool/package.json +++ b/packages/client/ui-tool/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-tool", "description": "Client Tool call-tree renderer and keyed per-tool presentation slot", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-trajectory/package.json b/packages/client/ui-trajectory/package.json index 6eb6fc741f..55d143f118 100644 --- a/packages/client/ui-trajectory/package.json +++ b/packages/client/ui-trajectory/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-trajectory", "description": "Trajectory event ledger with an interactive timing overview: pure-consumer plugin registering into the conversation ViewMap (no service)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-user-questions/package.json b/packages/client/ui-user-questions/package.json index 43a56f951a..6ff89373c3 100644 --- a/packages/client/ui-user-questions/package.json +++ b/packages/client/ui-user-questions/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-user-questions", "description": "Web ask_user_question composer takeover and plan-review presentation UI", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-workflow-run/package.json b/packages/client/ui-workflow-run/package.json index ac5afa61bf..cd176e602b 100644 --- a/packages/client/ui-workflow-run/package.json +++ b/packages/client/ui-workflow-run/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-workflow-run", "description": "Durable workflow-run Conversation Node and nested member disclosure for dsh web", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-workspace/package.json b/packages/client/ui-workspace/package.json index 086992fd19..bde0e263ff 100644 --- a/packages/client/ui-workspace/package.json +++ b/packages/client/ui-workspace/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-workspace", "description": "Workspace picker plugin: one WorkspacePicker registered into the sidebar and empty-state workspace slots", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/web/package.json b/packages/client/web/package.json index 4b84e01cf3..6a1881fb73 100644 --- a/packages/client/web/package.json +++ b/packages/client/web/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-web", "description": "Web boot kernel: static module table, Cordis loader, framework-free boot page, and UI-renderer handoff", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/web/src/boot-page.module.css b/packages/client/web/src/boot-page.module.css index 2a8bf33c79..f154c42761 100644 --- a/packages/client/web/src/boot-page.module.css +++ b/packages/client/web/src/boot-page.module.css @@ -1,7 +1,6 @@ /* The framework-free boot page cannot depend on theme delivery succeeding. */ .boot { - --dsh-boot-bg: #fff; --dsh-boot-label-primary: #0f1115; --dsh-boot-label-secondary: #61666b; --dsh-boot-label-tertiary: #81858c; @@ -11,11 +10,10 @@ height: 100%; display: grid; place-items: center; - background: var(--dsw-alias-bg-base, var(--dsh-boot-bg)); + background: var(--dsw-alias-bg-base, var(--dsh-boot-bg, Canvas)); } :global(body[data-ds-dark-theme]) .boot { - --dsh-boot-bg: #151517; --dsh-boot-label-primary: #f9fafb; --dsh-boot-label-secondary: #cfd3d6; --dsh-boot-label-tertiary: #adb2b8; diff --git a/packages/compaction/command-compact/package.json b/packages/compaction/command-compact/package.json index ac4c49b3b6..f68e082d3e 100644 --- a/packages/compaction/command-compact/package.json +++ b/packages/compaction/command-compact/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-command-compact", "description": "Human-facing slash command for explicit session compaction", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/compaction/compaction-basic/package.json b/packages/compaction/compaction-basic/package.json index 281cac91fc..1c77752acb 100644 --- a/packages/compaction/compaction-basic/package.json +++ b/packages/compaction/compaction-basic/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-compaction-basic", "description": "Token-meter-driven compaction policy and LLM summarization backend for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/compaction/compaction-image-offload/package.json b/packages/compaction/compaction-image-offload/package.json index a00b22b0d1..638e2809a9 100644 --- a/packages/compaction/compaction-image-offload/package.json +++ b/packages/compaction/compaction-image-offload/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-compaction-image-offload", "description": "Durable image offload for image-capable routes: replace over-budget request images with placeholders and retry", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/compaction/compaction-tool-result-pruner/package.json b/packages/compaction/compaction-tool-result-pruner/package.json index b693fed200..7f674838de 100644 --- a/packages/compaction/compaction-tool-result-pruner/package.json +++ b/packages/compaction/compaction-tool-result-pruner/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-compaction-tool-result-pruner", "description": "Replay-safe model-free head/middle/tail pruning for tool-result surface nodes", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/compaction/compaction/package.json b/packages/compaction/compaction/package.json index 4382613ca4..fb871602e9 100644 --- a/packages/compaction/compaction/package.json +++ b/packages/compaction/compaction/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-compaction", "description": "Abstract compaction service seam (ctx.compaction) for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/computer-use/computer-use/package.json b/packages/computer-use/computer-use/package.json index cbf0dbea38..c8a2299fc0 100644 --- a/packages/computer-use/computer-use/package.json +++ b/packages/computer-use/computer-use/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-computer-use", "description": "Exclusive named computer-use provider registration", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/context/agent-instructions/package.json b/packages/context/agent-instructions/package.json index 7fa0bba69e..bb29627236 100644 --- a/packages/context/agent-instructions/package.json +++ b/packages/context/agent-instructions/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-agent-instructions", "description": "Workspace context loader for AGENTS.md/CLAUDE.md instruction files", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/context/file-reference-local/package.json b/packages/context/file-reference-local/package.json index 998c6e2e52..01a58ef28f 100644 --- a/packages/context/file-reference-local/package.json +++ b/packages/context/file-reference-local/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-file-reference-local", "description": "Local-filesystem ctx.fileReferences provider with bounded fuzzy indexes", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/context/file-reference/package.json b/packages/context/file-reference/package.json index abc2a1cc2a..8c703f47d4 100644 --- a/packages/context/file-reference/package.json +++ b/packages/context/file-reference/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-file-reference", "description": "File-reference discovery contract and shared @file grammar", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/context/session-reference/package.json b/packages/context/session-reference/package.json index e4e17c536b..3b44e78d1f 100644 --- a/packages/context/session-reference/package.json +++ b/packages/context/session-reference/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-reference", "description": "Cross-session snapshot references and durable untrusted model context (ctx.sessionReferenceResolver)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/context/time-context/package.json b/packages/context/time-context/package.json index 94023206a8..f97126fc36 100644 --- a/packages/context/time-context/package.json +++ b/packages/context/time-context/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-time-context", "description": "Opt-in durable per-step context with the current time and elapsed time", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/context/tmux-context/package.json b/packages/context/tmux-context/package.json index 95a04c31b9..8a9d88b5c1 100644 --- a/packages/context/tmux-context/package.json +++ b/packages/context/tmux-context/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tmux-context", "description": "Opt-in durable per-step context with this agent's tmux pane and window location", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/core/agent-default-model/package.json b/packages/core/agent-default-model/package.json index 109ae3394d..cf87be5d32 100644 --- a/packages/core/agent-default-model/package.json +++ b/packages/core/agent-default-model/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-agent-default-model", "description": "Default model selection shared by Agent entry points", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/core/agent-loop/package.json b/packages/core/agent-loop/package.json index 708c7c514a..1c23ad9719 100644 --- a/packages/core/agent-loop/package.json +++ b/packages/core/agent-loop/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-agent-loop", "description": "The concrete agent loop plugin for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/core/agent-tool-presentation/package.json b/packages/core/agent-tool-presentation/package.json index 09c088d5d4..1e0b0d6b31 100644 --- a/packages/core/agent-tool-presentation/package.json +++ b/packages/core/agent-tool-presentation/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-agent-tool-presentation", "description": "Agent-plane presentation selector: composes one agent's tools as PTC mode, native, or both", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/core/agent/package.json b/packages/core/agent/package.json index b34895cb88..f3f72b360e 100644 --- a/packages/core/agent/package.json +++ b/packages/core/agent/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-agent", "description": "Agent interface, registry, initiator scope, and event vocabulary for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/core/scope/package.json b/packages/core/scope/package.json index 3ac55af4cb..7503f8c406 100644 --- a/packages/core/scope/package.json +++ b/packages/core/scope/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-scope", "description": "Scoped-context registration primitive (scope tags, scope-filtered event dispatch) for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/core/session/package.json b/packages/core/session/package.json index 4f43ac08dd..0ed5dff091 100644 --- a/packages/core/session/package.json +++ b/packages/core/session/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session", "description": "Event-sourced session store for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/core/system-prompt/package.json b/packages/core/system-prompt/package.json index 0df2d6de53..c07bca72d7 100644 --- a/packages/core/system-prompt/package.json +++ b/packages/core/system-prompt/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-system-prompt", "description": "System prompt assembly registry for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/core/tools/package.json b/packages/core/tools/package.json index 80b2511eaa..c54b96c786 100644 --- a/packages/core/tools/package.json +++ b/packages/core/tools/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tools", "description": "Tool registry and execution pipeline for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/credentials/authorization/package.json b/packages/credentials/authorization/package.json index 830bdf51fe..947f1cb2d1 100644 --- a/packages/credentials/authorization/package.json +++ b/packages/credentials/authorization/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-authorization", "description": "Authorization seam (ctx.authorization): plugin-owned flows that obtain a credential through a conversation with the human", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/credentials/credentials-local/package.json b/packages/credentials/credentials-local/package.json index c33473e37f..71c423ad56 100644 --- a/packages/credentials/credentials-local/package.json +++ b/packages/credentials/credentials-local/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-credentials-local", "description": "File-backed credentials provider ($DSH_HOME/.env under the live process environment) for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/credentials/credentials/package.json b/packages/credentials/credentials/package.json index 2b442b47c8..f64cf8c9d5 100644 --- a/packages/credentials/credentials/package.json +++ b/packages/credentials/credentials/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-credentials", "description": "Abstract credential seam (ctx.credentials): settings carry references to secrets, providers own the values", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/agent-team-profile/package.json b/packages/experimental/agent-team-profile/package.json index 51fefe782d..400b82db62 100644 --- a/packages/experimental/agent-team-profile/package.json +++ b/packages/experimental/agent-team-profile/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-agent-team-profile", "description": "Experimental profile bundle enabling Agent Teams over dsh-base", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/agent-team-web-profile/package.json b/packages/experimental/agent-team-web-profile/package.json index e4df1779f9..ad91bced8d 100644 --- a/packages/experimental/agent-team-web-profile/package.json +++ b/packages/experimental/agent-team-web-profile/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-agent-team-web-profile", "description": "Experimental Web profile layer for Agent Teams Remote and UI plugins", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/agent-team/package.json b/packages/experimental/agent-team/package.json index b61057e3aa..a74c72f939 100644 --- a/packages/experimental/agent-team/package.json +++ b/packages/experimental/agent-team/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-agent-team", "description": "Implicit-root Agent Teams roster, durable peer mailbox, and shared task DAG", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/auto-review/package.json b/packages/experimental/auto-review/package.json index b0ead7fdd9..586639a1f2 100644 --- a/packages/experimental/auto-review/package.json +++ b/packages/experimental/auto-review/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-auto-review", "description": "Per-tool LLM authorization review for the DeepSeek Harness Auto permission preset", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "repository": { "type": "git", "url": "git+https://github.com/deepseek-ai/deepseek-harness.git", diff --git a/packages/experimental/browser-use-chrome-devtools-mcp/package.json b/packages/experimental/browser-use-chrome-devtools-mcp/package.json index a39f08d275..9ad760be38 100644 --- a/packages/experimental/browser-use-chrome-devtools-mcp/package.json +++ b/packages/experimental/browser-use-chrome-devtools-mcp/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-browser-use-chrome-devtools-mcp", "description": "Experimental per-Session Chromium browser tools through chrome-devtools-mcp", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/browser-use-playwright-mcp/package.json b/packages/experimental/browser-use-playwright-mcp/package.json index cb1f002ed2..e5dbfe057a 100644 --- a/packages/experimental/browser-use-playwright-mcp/package.json +++ b/packages/experimental/browser-use-playwright-mcp/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-browser-use-playwright-mcp", "description": "Experimental per-Session Chromium browser tools through @playwright/mcp", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/browser-use-runtime/package.json b/packages/experimental/browser-use-runtime/package.json index a43288d50f..11a52cc37a 100644 --- a/packages/experimental/browser-use-runtime/package.json +++ b/packages/experimental/browser-use-runtime/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-browser-use-runtime", "description": "Session-owned browser resource lifecycles and MCP integration for experimental providers", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/browser-use-stagehand-native/package.json b/packages/experimental/browser-use-stagehand-native/package.json index 9f84b55aa4..89f52dbace 100644 --- a/packages/experimental/browser-use-stagehand-native/package.json +++ b/packages/experimental/browser-use-stagehand-native/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-browser-use-stagehand-native", "description": "Experimental Stagehand browser tools with separately configured native models", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/client-ui-agent-team/package.json b/packages/experimental/client-ui-agent-team/package.json index 4932c980ba..ad4a1299a7 100644 --- a/packages/experimental/client-ui-agent-team/package.json +++ b/packages/experimental/client-ui-agent-team/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-client-ui-agent-team", "description": "Web Agent Teams roster, task board, and teammate navigation", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/computer-use-cua-driver-mcp/package.json b/packages/experimental/computer-use-cua-driver-mcp/package.json index 69ca143e56..cb3bbb5074 100644 --- a/packages/experimental/computer-use-cua-driver-mcp/package.json +++ b/packages/experimental/computer-use-cua-driver-mcp/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-computer-use-cua-driver-mcp", "description": "Experimental computer use through an installed Cua Driver MCP executable", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/computer-use-cua-driver-native/package.json b/packages/experimental/computer-use-cua-driver-native/package.json index a805632081..dc84730248 100644 --- a/packages/experimental/computer-use-cua-driver-native/package.json +++ b/packages/experimental/computer-use-cua-driver-native/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-computer-use-cua-driver-native", "description": "Experimental computer-use provider embedding the Cua Driver native npm SDK", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/inspector/package.json b/packages/experimental/inspector/package.json index 6e3f65cd6e..11af6268c6 100644 --- a/packages/experimental/inspector/package.json +++ b/packages/experimental/inspector/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-inspector", "description": "Experimental cross-realm CDP hub for Host debugging and Client Runtime inspection", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/ptc-runtime-python/package.json b/packages/experimental/ptc-runtime-python/package.json index 01c6af30bd..f70aea8334 100644 --- a/packages/experimental/ptc-runtime-python/package.json +++ b/packages/experimental/ptc-runtime-python/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-ptc-runtime-python", "description": "CPython subprocess implementation of the DeepSeek Harness PTC execution seam", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "repository": { "type": "git", "url": "git+https://github.com/deepseek-ai/deepseek-harness.git", diff --git a/packages/experimental/tool-agent-team/package.json b/packages/experimental/tool-agent-team/package.json index 6783456a2d..93bd42f7e6 100644 --- a/packages/experimental/tool-agent-team/package.json +++ b/packages/experimental/tool-agent-team/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-tool-agent-team", "description": "Scoped model-facing Agent Teams tools over ctx.agentTeams", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/webworker-packer/package.json b/packages/experimental/webworker-packer/package.json index 6a97bcfd9c..e62ace335a 100644 --- a/packages/experimental/webworker-packer/package.json +++ b/packages/experimental/webworker-packer/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-webworker-packer", "description": "Build-time packer for the browser runtime's base VFS image and ordered data-overlay archives", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/webworker-runtime/package.json b/packages/experimental/webworker-runtime/package.json index 0dbca274f9..762f3a2e3f 100644 --- a/packages/experimental/webworker-runtime/package.json +++ b/packages/experimental/webworker-runtime/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-experimental-webworker-runtime", "description": "Browser-only harness runtime: in-memory VFS, module transform and loader, postMessage tunnel, and the dedicated Web Worker assembly, with the Node-compatibility layer that lets the host tree run unchanged", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/experimental/webworker-runtime/tests/compile/transform-corpus-check.ts b/packages/experimental/webworker-runtime/tests/compile/transform-corpus-check.ts index 4139846db2..c1f7a0c40c 100644 --- a/packages/experimental/webworker-runtime/tests/compile/transform-corpus-check.ts +++ b/packages/experimental/webworker-runtime/tests/compile/transform-corpus-check.ts @@ -29,10 +29,7 @@ const DOCKKIT_BUNDLE = 'packages/client/ui-dockkit/lib/index.js' * Files Node's ESM loader cannot import in this repository. None is a finding: * each is listed with the reason the import fails, and the run refuses a * listed file that imports cleanly so the list stays current in both - * directions. The koffi entry depends on corpus order: sandbox-windows-acl - * imports the win32-process package earlier in the serial sweep (a distinct - * module instance under its node_modules URL), so win32-process's own file-URL - * import re-registers koffi's type names and fails as the second load. + * directions. * * The Dockkit entry admits only Node's refusal of a `.css` import, not one * exact stylesheet: the swept bundle's first unresolvable stylesheet depends on @@ -43,7 +40,6 @@ const BASELINE_EXEMPT: ReadonlyMap = new Map([ [DOCKKIT_BUNDLE, 'imports .css, which bare Node cannot load'], ['packages/client/ui-primitives/lib/index.js', 'imports .css, which bare Node cannot load'], ['packages/client/web/lib/index.js', 'imports .css, which bare Node cannot load'], - ['packages/subprocess/win32-process/lib/index.js', 'koffi type-name collision on a second load'], ['packages/test-support/client-runtime/lib/index.js', "needs vitest's internal state"], ]) diff --git a/packages/extensions/cordis-client-runner/package.json b/packages/extensions/cordis-client-runner/package.json index 4b96b17335..ba27b7bf20 100644 --- a/packages/extensions/cordis-client-runner/package.json +++ b/packages/extensions/cordis-client-runner/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-cordis-client-runner", "description": "Browser half of dynamic dual-half plugin packages: event subscription, closure evaluation, guard facade, and loader entries", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/extensions/cordis-host-runner/package.json b/packages/extensions/cordis-host-runner/package.json index a4578ed8c6..0f7be96672 100644 --- a/packages/extensions/cordis-host-runner/package.json +++ b/packages/extensions/cordis-host-runner/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-cordis-host-runner", "description": "Dynamic package definition registry, host-half sandbox lifecycle, and invoke handler table for model-mounted dual-half packages", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/extensions/tool-cordis/package.json b/packages/extensions/tool-cordis/package.json index 77e0883e7b..eb419cf895 100644 --- a/packages/extensions/tool-cordis/package.json +++ b/packages/extensions/tool-cordis/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-cordis", "description": "Self-referential cordis toolset: inspect the live runtime, mount and dispose model-written plugins", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/extensions/tool-cordis/src/api-catalog.ts b/packages/extensions/tool-cordis/src/api-catalog.ts index d367df7865..76309b2975 100644 --- a/packages/extensions/tool-cordis/src/api-catalog.ts +++ b/packages/extensions/tool-cordis/src/api-catalog.ts @@ -625,8 +625,8 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [ returns: 'the path, or undefined for an unknown id.', }, { - signature: 'fetchBundle(request: Request): Response', - description: 'Serve an advertised revisioned bundle or source map without a Web server. Unknown URLs return 404, unsupported methods return 405, and `HEAD` returns the same immutable headers without a body.', + signature: 'async fetchBundle(request: Request): Promise', + description: 'Serve an advertised revisioned bundle or source map without a Web server. Unknown URLs return 404, unsupported methods return 405, and `HEAD` returns the same immutable headers without materializing a body. Each body is built once on its first `GET`; script construction never reads maps.', parameters: [{ name: 'request', description: 'shell-carrier request for a `/plugins` resource.' }], returns: 'the exact response also exposed by the optional Web route.', }, @@ -2813,20 +2813,20 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [ signature: 'get(key: string): TypertSchemaRecord | undefined', description: 'Look up one schema by `#`.', parameters: [{ name: 'key', description: 'global schema key.' }], - returns: 'the live schema record, or `undefined` when absent.', + returns: 'a record containing the cached schema, or `undefined` when absent.', }, { signature: 'resolve(key: string): TypertSchemaRecord', description: 'Resolve one required schema.', parameters: [{ name: 'key', description: 'global schema key.' }], - returns: 'the live schema record.', + returns: 'a record containing the cached schema.', throws: ['when the key is malformed, the package face is absent, or the schema is not contributed.'], }, { signature: 'list(filter: TypertSchemaFilter = {}): TypertSchemaRecord[]', description: 'Enumerate live schemas in registration order.', parameters: [{ name: 'filter', description: 'optional package and face restriction.' }], - returns: 'matching schema records.', + returns: 'matching records containing the cached schemas.', }, { signature: 'getPackage(packageName: string, face: TypertFace = \'host\'): TypertPackageRecord | undefined', @@ -6370,11 +6370,11 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'TypertCodec', - declaration: 'export type TypertCodec = {\n readonly mode: \'strict\';\n readonly typeSymbol: string;\n readonly schema: TypertSchema;\n} | {\n readonly mode: \'src-json\';\n};', + declaration: 'export type TypertCodec = {\n readonly mode: \'strict\';\n readonly typeSymbol: string;\n readonly create: () => TypertSchema;\n} | {\n readonly mode: \'src-json\';\n};', }, { name: 'TypertContribution', - declaration: 'export interface TypertContribution {\n readonly package: string;\n readonly face: TypertFace;\n readonly schemas: readonly TypertSchema[];\n readonly model: TypertPackageModel;\n readonly invocations: readonly InvocationDescriptor[];\n}', + declaration: 'export interface TypertContribution {\n readonly package: string;\n readonly face: TypertFace;\n readonly schemas: readonly TypertSchemaFactory[];\n readonly model: TypertPackageModel;\n readonly invocations: readonly InvocationDescriptor[];\n}', }, { name: 'TypertDisposer', @@ -6448,13 +6448,21 @@ export const TYPE_API: readonly TypeApiEntry[] = [ name: 'TypertRemoteService', declaration: 'export abstract class TypertRemoteService extends Service {\n readonly typertRemote: TypertGatewayBinding;\n}', }, + { + name: 'TypertSchema', + declaration: 'export interface TypertSchema {\n parse(value: unknown): Output;\n}', + }, + { + name: 'TypertSchemaFactory', + declaration: 'export interface TypertSchemaFactory {\n readonly name: string;\n readonly create: () => z.ZodType;\n}', + }, { name: 'TypertSchemaFilter', declaration: 'export interface TypertSchemaFilter {\n readonly package?: string;\n readonly face?: TypertFace;\n}', }, { name: 'TypertSchemaRecord', - declaration: 'export interface TypertSchemaRecord extends TypertSchema {\n readonly package: string;\n readonly face: TypertFace;\n readonly key: string;\n}', + declaration: 'export interface TypertSchemaRecord {\n readonly name: string;\n readonly schema: z.ZodType;\n readonly package: string;\n readonly face: TypertFace;\n readonly key: string;\n}', }, { name: 'TypertServiceModel', diff --git a/packages/extensions/ui-cordis/package.json b/packages/extensions/ui-cordis/package.json index 4bad5de8e9..b5f289540a 100644 --- a/packages/extensions/ui-cordis/package.json +++ b/packages/extensions/ui-cordis/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-cordis", "description": "Cordis dynamic-plugin definition card: the keyed cordis_define tool row with its run/stop switch", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/feedback/command-feedback/package.json b/packages/feedback/command-feedback/package.json index 3b0414d813..f65ffd4aa1 100644 --- a/packages/feedback/command-feedback/package.json +++ b/packages/feedback/command-feedback/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-command-feedback", "description": "Log-only session feedback: the record event, the sessionFeedback Host Remote, and the human-facing slash command", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/feedback/message-feedback/package.json b/packages/feedback/message-feedback/package.json index 90134101c3..219a250f6f 100644 --- a/packages/feedback/message-feedback/package.json +++ b/packages/feedback/message-feedback/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-message-feedback", "description": "Canonical Session-log ratings and notes for finalized assistant messages", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/fs/fs-local/package.json b/packages/fs/fs-local/package.json index 2a70ac51a7..a7bb580235 100644 --- a/packages/fs/fs-local/package.json +++ b/packages/fs/fs-local/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-fs-local", "description": "Local-filesystem implementation of the DeepSeek Harness filesystem seam (ctx.fs)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/fs/fs-observation-policy/package.json b/packages/fs/fs-observation-policy/package.json index 978f3d0ae7..f4a1123217 100644 --- a/packages/fs/fs-observation-policy/package.json +++ b/packages/fs/fs-observation-policy/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-fs-observation-policy", "description": "File-context policy plugin for the DeepSeek Harness — observed-state, read-before-edit, and version-guarded write/edit added over the ctx.fs provider seam through the fs/* event gate (no service API)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/fs/fs-sandbox/package.json b/packages/fs/fs-sandbox/package.json index e1261a7f14..8f8e4c891b 100644 --- a/packages/fs/fs-sandbox/package.json +++ b/packages/fs/fs-sandbox/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-fs-sandbox", "description": "Sandbox-enforcing implementation of the DeepSeek Harness filesystem seam: fences write/edit by the per-call sandbox mode (read-only denies mutation, workspace-write contains it to the workspace + temp roots) while reads pass through", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/fs/fs/package.json b/packages/fs/fs/package.json index 800220fbbb..8a6819b216 100644 --- a/packages/fs/fs/package.json +++ b/packages/fs/fs/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-fs", "description": "Abstract filesystem capability seam (ctx.fs) for the DeepSeek Harness — vocabulary types, the FileSystem service (text IO + optional version-guarded atomic mutations), and the fs/* policy event vocabulary", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/fs/tool-fs-search/package.json b/packages/fs/tool-fs-search/package.json index b4bab8a8c4..e834eb4da8 100644 --- a/packages/fs/tool-fs-search/package.json +++ b/packages/fs/tool-fs-search/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-fs-search", "description": "Model-facing filesystem discovery tools (glob, grep) backed by the packaged ripgrep binary (@vscode/ripgrep)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/fs/tool-fs/package.json b/packages/fs/tool-fs/package.json index c98e205477..2ebc75972c 100644 --- a/packages/fs/tool-fs/package.json +++ b/packages/fs/tool-fs/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-fs", "description": "Model-facing filesystem tools (read, write, edit) over the DeepSeek Harness filesystem seam (ctx.fs)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/fs/tool-present/package.json b/packages/fs/tool-present/package.json index 18e5bb3d4a..4dd8f81bcc 100644 --- a/packages/fs/tool-present/package.json +++ b/packages/fs/tool-present/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-present", "description": "Explicit workspace file delivery declarations for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/fs/tool-str-replace-editor/package.json b/packages/fs/tool-str-replace-editor/package.json index 92b0be3892..bd0f284528 100644 --- a/packages/fs/tool-str-replace-editor/package.json +++ b/packages/fs/tool-str-replace-editor/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-str-replace-editor", "description": "Model-facing view, create, literal replace, and line insert tool over the Harness filesystem service", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/goal/command-goal/package.json b/packages/goal/command-goal/package.json index 1b0dfe53d3..e4b1bb273d 100644 --- a/packages/goal/command-goal/package.json +++ b/packages/goal/command-goal/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-command-goal", "description": "Human-facing slash command for persisted same-session goals", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/goal/goal-round-driver/package.json b/packages/goal/goal-round-driver/package.json index 6a43469b1d..d08c024a99 100644 --- a/packages/goal/goal-round-driver/package.json +++ b/packages/goal/goal-round-driver/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-goal-round-driver", "description": "Race-fenced same-session goal-round driver", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/goal/goal/package.json b/packages/goal/goal/package.json index e4664dffef..98f4821c04 100644 --- a/packages/goal/goal/package.json +++ b/packages/goal/goal/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-goal", "description": "Event-sourced same-session goal state and lifecycle service for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/goal/tool-goal/package.json b/packages/goal/tool-goal/package.json index 69f3285384..7bd609a601 100644 --- a/packages/goal/tool-goal/package.json +++ b/packages/goal/tool-goal/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-goal", "description": "Model-facing same-session goal tools with execution-time authority checks", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/guard/repeat-tool-reminder/package.json b/packages/guard/repeat-tool-reminder/package.json index e8fcc60605..d3eff6baaa 100644 --- a/packages/guard/repeat-tool-reminder/package.json +++ b/packages/guard/repeat-tool-reminder/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-repeat-tool-reminder", "description": "Repeat-tool-call guard plugin: advisory reminders when an agent loops on identical tool calls", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/guard/timeout-policy/package.json b/packages/guard/timeout-policy/package.json index b7f22c3002..cfd5908ac4 100644 --- a/packages/guard/timeout-policy/package.json +++ b/packages/guard/timeout-policy/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-call-timeout-policy", "description": "Tool-call timeout policy: a tools/execute wrapper that arms a per-tool deadline on exec.signal and returns TOOL_TIMEOUT when it wins", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/hooks/hook-protocol/package.json b/packages/hooks/hook-protocol/package.json index a2400f2893..48cb21aaf5 100644 --- a/packages/hooks/hook-protocol/package.json +++ b/packages/hooks/hook-protocol/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-hook-protocol", "description": "Shared Claude Code / Codex hook wire protocol: matcher engine, stdin/exit-code/stdout codec, multi-hook merge, and hook/* session events", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/hooks/hooks-claude-code/package.json b/packages/hooks/hooks-claude-code/package.json index 1db3521903..0a8339412f 100644 --- a/packages/hooks/hooks-claude-code/package.json +++ b/packages/hooks/hooks-claude-code/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-hooks-claude-code", "description": "Bridge plugin: run a Claude Code hooks.json / settings hook config on the DeepSeek Harness interception seams", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/hooks/hooks-codex/package.json b/packages/hooks/hooks-codex/package.json index 3ed5db1650..4400f98352 100644 --- a/packages/hooks/hooks-codex/package.json +++ b/packages/hooks/hooks-codex/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-hooks-codex", "description": "Bridge plugin: run a Codex hooks.json hook config on the DeepSeek Harness interception seams", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/host/directory-picker-auto/package.json b/packages/host/directory-picker-auto/package.json index fc35b3d067..e58afc7e68 100644 --- a/packages/host/directory-picker-auto/package.json +++ b/packages/host/directory-picker-auto/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-host-directory-picker-auto", "description": "Adaptive chooser of the directory-picker seam: resolves the host situation at boot and mounts the native or browse backend for the DeepSeek Harness web GUI host", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/host/directory-picker-browse/package.json b/packages/host/directory-picker-browse/package.json index 850081c48a..241b7a610b 100644 --- a/packages/host/directory-picker-browse/package.json +++ b/packages/host/directory-picker-browse/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-host-directory-picker-browse", "description": "In-app browsing backend of the directory-picker seam (listing/creation primitives over the host filesystem)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/host/directory-picker-native/package.json b/packages/host/directory-picker-native/package.json index 36d4c902da..6f4f894168 100644 --- a/packages/host/directory-picker-native/package.json +++ b/packages/host/directory-picker-native/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-host-directory-picker-native", "description": "Native-OS-chooser backend of the directory-picker seam for the DeepSeek Harness web GUI host", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/host/directory-picker/package.json b/packages/host/directory-picker/package.json index a930512ac0..418fcf519b 100644 --- a/packages/host/directory-picker/package.json +++ b/packages/host/directory-picker/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-host-directory-picker", "description": "Abstract workspace-directory picking seam (ctx.directoryPicker) for the DeepSeek Harness web GUI host", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/host/frontend-static/package.json b/packages/host/frontend-static/package.json index 3caae0efac..fee79f6702 100644 --- a/packages/host/frontend-static/package.json +++ b/packages/host/frontend-static/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-host-frontend-static", "description": "SPA dist server for the Web shell: owns the webserver fallback seat, serving explicit index entries and static assets with traversal rejection and 404 misses", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/host/open-in-app/package.json b/packages/host/open-in-app/package.json index 9c070ef429..81c74e6599 100644 --- a/packages/host/open-in-app/package.json +++ b/packages/host/open-in-app/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-host-open-in-app", "description": "Host half of open-in-app: resolved application catalog, icons, and the launch endpoint as three webServer routes", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/host/plugin-inventory/package.json b/packages/host/plugin-inventory/package.json index 616032b4ee..87867cb923 100644 --- a/packages/host/plugin-inventory/package.json +++ b/packages/host/plugin-inventory/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-host-plugin-inventory", "description": "Read-only Remote projection of current Cordis Loader plugin state", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/host/webserver/package.json b/packages/host/webserver/package.json index 8fbede6744..ffd322bea2 100644 --- a/packages/host/webserver/package.json +++ b/packages/host/webserver/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-host-webserver", "description": "Web route-registration plugin: HTTP and upgrade routes, index transform taps, and static dist fallback; knows no harness concepts", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/identity/anonymous-user-id/package.json b/packages/identity/anonymous-user-id/package.json index 53d9671751..213b735fc9 100644 --- a/packages/identity/anonymous-user-id/package.json +++ b/packages/identity/anonymous-user-id/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-anonymous-user-id", "description": "Shared anonymous user identity for DeepSeek Harness telemetry and feedback correlation", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/interaction/commands/package.json b/packages/interaction/commands/package.json index 0be908a553..db49b1df19 100644 --- a/packages/interaction/commands/package.json +++ b/packages/interaction/commands/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-commands", "description": "Plugin-owned human command registry for DeepSeek Harness UIs", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/interaction/permission-presets/package.json b/packages/interaction/permission-presets/package.json index 49b8efe369..990a11ac78 100644 --- a/packages/interaction/permission-presets/package.json +++ b/packages/interaction/permission-presets/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-permission-presets", "description": "User-facing permission presets (ctx.permissionPresets) for the DeepSeek Harness: one product-level Permissions select bundling the sandbox-mode and approval-policy knobs, written through to their own session events", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/interaction/tool-ask-user/package.json b/packages/interaction/tool-ask-user/package.json index 03efa1b837..68ac229ab1 100644 --- a/packages/interaction/tool-ask-user/package.json +++ b/packages/interaction/tool-ask-user/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-ask-user", "description": "Model-facing ask_user_question tool over the ctx.userQuestions seam", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/interaction/user-approval/package.json b/packages/interaction/user-approval/package.json index 6e8354834c..e3f639233a 100644 --- a/packages/interaction/user-approval/package.json +++ b/packages/interaction/user-approval/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-user-approval", "description": "User-approval seam (ctx.approval) for the DeepSeek Harness: one-shot permission decisions dispatched to composed answerers over the approval/request waterfall, fail-closed by default", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/interaction/user-questions/package.json b/packages/interaction/user-questions/package.json index cbe8a2854b..4c89a6feab 100644 --- a/packages/interaction/user-questions/package.json +++ b/packages/interaction/user-questions/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-user-questions", "description": "Abstract user-questions seam (ctx.userQuestions) for asking the human during agent runs", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/jobs/jobs-local/package.json b/packages/jobs/jobs-local/package.json index 110ba43f3e..189daeb656 100644 --- a/packages/jobs/jobs-local/package.json +++ b/packages/jobs/jobs-local/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-jobs-local", "description": "Process-local implementation of the DeepSeek Harness background job registry seam", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/jobs/jobs/package.json b/packages/jobs/jobs/package.json index ff310752ce..a10de05063 100644 --- a/packages/jobs/jobs/package.json +++ b/packages/jobs/jobs/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-jobs", "description": "Background job registry (ctx.jobs) for the DeepSeek Harness — shared ids, owner isolation, polling, cancellation, and completion listeners for long-running tool work", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/jobs/tool-jobs/package.json b/packages/jobs/tool-jobs/package.json index 8c6adfb1c3..83b9183c2c 100644 --- a/packages/jobs/tool-jobs/package.json +++ b/packages/jobs/tool-jobs/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-jobs", "description": "Model-facing background job control tools (job_output, job_list, job_kill) over the ctx.jobs registry", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/llm/deepseek-llm-api-extensions/package.json b/packages/llm/deepseek-llm-api-extensions/package.json index 88bb421385..3034745232 100644 --- a/packages/llm/deepseek-llm-api-extensions/package.json +++ b/packages/llm/deepseek-llm-api-extensions/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-deepseek-llm-api-extensions", "description": "Additive request-field registry for the official DeepSeek LLM API adapter", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/llm/llm-deepseek/package.json b/packages/llm/llm-deepseek/package.json index 75f70cfd84..3036ceb8fe 100644 --- a/packages/llm/llm-deepseek/package.json +++ b/packages/llm/llm-deepseek/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-llm-deepseek", "description": "DeepSeek adapter with Chat Completions and Messages protocols", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/llm/llm-pi-ai/package.json b/packages/llm/llm-pi-ai/package.json index 6a10d5a6bb..458156d7a9 100644 --- a/packages/llm/llm-pi-ai/package.json +++ b/packages/llm/llm-pi-ai/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-llm-pi-ai", "description": "pi-ai-backed DeepSeek adapter for the DeepSeek Harness LLM seam (design-verification twin of dsh-llm-deepseek)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/llm/llm-retry/package.json b/packages/llm/llm-retry/package.json index f226562e4d..e8757685fb 100644 --- a/packages/llm/llm-retry/package.json +++ b/packages/llm/llm-retry/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-llm-retry", "description": "Provider-routed LLM request retry policy for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/llm/llm/package.json b/packages/llm/llm/package.json index 570ac9af86..730227c937 100644 --- a/packages/llm/llm/package.json +++ b/packages/llm/llm/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-llm", "description": "Provider-neutral LLM service interface for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/llm/plugin-package-inventory-deepseek/package.json b/packages/llm/plugin-package-inventory-deepseek/package.json index 7b94b7dc81..34d5c6cc3d 100644 --- a/packages/llm/plugin-package-inventory-deepseek/package.json +++ b/packages/llm/plugin-package-inventory-deepseek/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-plugin-package-inventory-deepseek", "description": "Active Loader-backed plugin package inventory for official DeepSeek LLM API requests", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/llm/token-meter/package.json b/packages/llm/token-meter/package.json index 263910342e..ad50e9b574 100644 --- a/packages/llm/token-meter/package.json +++ b/packages/llm/token-meter/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-token-meter", "description": "Replay-aware token measurement service (ctx.tokenMeter) for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/lsp/lsp-stdio/package.json b/packages/lsp/lsp-stdio/package.json index 6428068eb0..2b25f3382e 100644 --- a/packages/lsp/lsp-stdio/package.json +++ b/packages/lsp/lsp-stdio/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-lsp-stdio", "description": "Generic stdio language-server provider for the DeepSeek Harness LSP capability seam (ctx.lsp) — spawns configured servers, translates JSON-RPC, and serves transient-open goToDefinition/findReferences/goToImplementation/hover queries in the host filesystem namespace", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/lsp/lsp/package.json b/packages/lsp/lsp/package.json index 4d8df70f5a..2456b6b95c 100644 --- a/packages/lsp/lsp/package.json +++ b/packages/lsp/lsp/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-lsp", "description": "Abstract LSP capability seam (ctx.lsp) for the DeepSeek Harness — language-server provider registry keyed by branded id and extension mapping, order-independent per-query selection, normalized definition/references/implementation/hover requests and results, and the LspError taxonomy", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/lsp/tool-lsp/package.json b/packages/lsp/tool-lsp/package.json index f4df13c8a1..65f60e8f3c 100644 --- a/packages/lsp/tool-lsp/package.json +++ b/packages/lsp/tool-lsp/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-lsp", "description": "Model-facing lsp tool over the DeepSeek Harness LSP capability seam (ctx.lsp) — one read-only tool with goToDefinition/findReferences/goToImplementation/hover operations, one-based UTF-16 cursor coordinates, bounded location rendering, and hover normalization", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/mcp/mcp-client/package.json b/packages/mcp/mcp-client/package.json index d52b05e1e4..3f1a952ce9 100644 --- a/packages/mcp/mcp-client/package.json +++ b/packages/mcp/mcp-client/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-mcp-client", "description": "MCP client bridge: connects to MCP servers and registers their tools on ctx.tools", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/mcp/mcp-resources/package.json b/packages/mcp/mcp-resources/package.json index 579e7246b4..f567fc98be 100644 --- a/packages/mcp/mcp-resources/package.json +++ b/packages/mcp/mcp-resources/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-mcp-resources", "description": "Scoped MCP resource discovery and reading through shared model tools", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/plan/plan-mode/package.json b/packages/plan/plan-mode/package.json index fd8ca71888..bbe229bd52 100644 --- a/packages/plan/plan-mode/package.json +++ b/packages/plan/plan-mode/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-plan-mode", "description": "Logged per-agent plan mode with deployment guidance, a direct slash command, and a user-reviewed exit", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/preset/agent-presets/package.json b/packages/preset/agent-presets/package.json index 587b47f1c7..b6771f259b 100644 --- a/packages/preset/agent-presets/package.json +++ b/packages/preset/agent-presets/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-agent-presets", "description": "Per-session agent composition from preset cordis.yml files for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/preset/persona/package.json b/packages/preset/persona/package.json index ec116bd3dd..6b6fbfe358 100644 --- a/packages/preset/persona/package.json +++ b/packages/preset/persona/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-persona", "description": "Composition-authored deployment persona section for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/ptc-runtime/ptc-runtime-node/package.json b/packages/ptc-runtime/ptc-runtime-node/package.json index e2bce8414a..8fb0615ddb 100644 --- a/packages/ptc-runtime/ptc-runtime-node/package.json +++ b/packages/ptc-runtime/ptc-runtime-node/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-ptc-runtime-node", "description": "Sandboxed Node process implementation of the DeepSeek Harness PTC execution capability", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/ptc-runtime/ptc-runtime/package.json b/packages/ptc-runtime/ptc-runtime/package.json index 6d34c19b64..ad6bb0008e 100644 --- a/packages/ptc-runtime/ptc-runtime/package.json +++ b/packages/ptc-runtime/ptc-runtime/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-ptc-runtime", "description": "Abstract PTC execution seam (ctx.ptcRuntime) for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/runtime-diagnostics/invariants/package.json b/packages/runtime-diagnostics/invariants/package.json index 2d10a8814f..12d6030732 100644 --- a/packages/runtime-diagnostics/invariants/package.json +++ b/packages/runtime-diagnostics/invariants/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-invariants", "description": "Registry service for package-owned DeepSeek Harness runtime invariants", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/sandbox/sandbox-local/package.json b/packages/sandbox/sandbox-local/package.json index 76bf039d88..0a5ca66418 100644 --- a/packages/sandbox/sandbox-local/package.json +++ b/packages/sandbox/sandbox-local/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-sandbox-local", "description": "Local process-sandbox backends for the DeepSeek Harness sandbox seam: bwrap, the npm-distributed landlock-run launcher, macOS Seatbelt, or the Windows ACL restricted-token runner — functionally probed, fail-closed", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/sandbox/sandbox-policy/package.json b/packages/sandbox/sandbox-policy/package.json index 686d4266ba..20866a0190 100644 --- a/packages/sandbox/sandbox-policy/package.json +++ b/packages/sandbox/sandbox-policy/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-sandbox-policy", "description": "Per-call sandbox policy resolver and current model context: deployment fallbacks plus each session's mode and workspace root, shared by every enforcing capability family", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/sandbox/sandbox-windows-acl/package.json b/packages/sandbox/sandbox-windows-acl/package.json index 611a09d0a1..97b05ad68c 100644 --- a/packages/sandbox/sandbox-windows-acl/package.json +++ b/packages/sandbox/sandbox-windows-acl/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-sandbox-windows-acl", "description": "Windows ACL write-restriction sandbox backend (restricted-token spawn with capability-SID write allowlist) for the DeepSeek Harness sandbox seam", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, @@ -37,6 +37,7 @@ "@deepseek-ai/dsh-subprocess": "workspace:^" }, "dependencies": { + "@deepseek-ai/dsh-lazy-require": "workspace:^", "@deepseek-ai/dsh-win32-process": "workspace:^", "koffi": "^3.1.0" }, diff --git a/packages/sandbox/sandbox-windows-acl/src/ffi.ts b/packages/sandbox/sandbox-windows-acl/src/ffi.ts index 983f5953f8..e65e1631d3 100644 --- a/packages/sandbox/sandbox-windows-acl/src/ffi.ts +++ b/packages/sandbox/sandbox-windows-acl/src/ffi.ts @@ -1,6 +1,6 @@ /** ACL/token bindings layered on the shared Win32 process owner. */ -import koffi from 'koffi' +import { createLazyRequire } from '@deepseek-ai/dsh-lazy-require' import { ERROR_INSUFFICIENT_BUFFER, Win32Error, @@ -22,9 +22,18 @@ export { } from '@deepseek-ai/dsh-win32-process' export type { NativePtr } from '@deepseek-ai/dsh-win32-process' -type Ptr = ReturnType -const PVOID: Ptr = koffi.pointer('void') -const PPVOID: Ptr = koffi.pointer(PVOID) +type Koffi = typeof import('koffi')['default'] +type Ptr = ReturnType + +const requireKoffi = createLazyRequire('koffi', import.meta.url) +let cachedTypes: { PVOID: Ptr; PPVOID: Ptr } | undefined + +function ffiTypes(): { PVOID: Ptr; PPVOID: Ptr } { + if (cachedTypes !== undefined) return cachedTypes + const koffi = requireKoffi() + const PVOID = koffi.pointer('void') + return cachedTypes = { PVOID, PPVOID: koffi.pointer(PVOID) } +} /** ACL/token calls composed with the generic Win32 process binding table. */ export interface Win32Bindings extends Win32ProcessBindings { @@ -115,7 +124,7 @@ export function isInvalidHandle(handle: NativePtr | null | undefined): boolean { * @param value - unsigned value to store. */ export function encodeUint32(slot: NativePtr, value: number): void { - koffi.encode(slot, 'uint32', value) + requireKoffi().encode(slot, 'uint32', value) } /** @@ -124,7 +133,7 @@ export function encodeUint32(slot: NativePtr, value: number): void { * @returns pointer address. */ export function ptrAddress(ptr: NativePtr): bigint { - return koffi.address(ptr) + return requireKoffi().address(ptr) } /** @@ -133,7 +142,7 @@ export function ptrAddress(ptr: NativePtr): bigint { * @returns allocated pointer. */ export function allocBytes(length: number): NativePtr { - return koffi.alloc('uint8', length) as NativePtr + return requireKoffi().alloc('uint8', length) as NativePtr } /** @@ -153,7 +162,7 @@ export function allocOverlapped(): NativePtr { * @returns decoded pointer, or null for address zero. */ export function decodePtrAt(buffer: Buffer, offset: number): NativePtr | null { - const value = koffi.decode(buffer, offset, PVOID) as NativePtr | null + const value = requireKoffi().decode(buffer, offset, ffiTypes().PVOID) as NativePtr | null return isNullPtr(value) ? null : value } @@ -164,7 +173,7 @@ export function decodePtrAt(buffer: Buffer, offset: number): NativePtr | null { * @returns decoded value. */ export function decodeUint8At(ptr: NativePtr, offset: number): number { - return koffi.decode(ptr, offset, 'uint8') as number + return requireKoffi().decode(ptr, offset, 'uint8') as number } /** @@ -174,7 +183,7 @@ export function decodeUint8At(ptr: NativePtr, offset: number): number { * @returns decoded value. */ export function decodeUint16At(ptr: NativePtr, offset: number): number { - return koffi.decode(ptr, offset, 'uint16') as number + return requireKoffi().decode(ptr, offset, 'uint16') as number } /** @@ -184,7 +193,7 @@ export function decodeUint16At(ptr: NativePtr, offset: number): number { * @returns decoded value. */ export function decodeUint32At(ptr: NativePtr, offset: number): number { - return koffi.decode(ptr, offset, 'uint32') as number + return requireKoffi().decode(ptr, offset, 'uint32') as number } /** @@ -221,6 +230,8 @@ let cached: Win32Bindings | undefined function bindings(): Win32Bindings { if (cached !== undefined) return cached + const koffi = requireKoffi() + const { PVOID, PPVOID } = ffiTypes() cached = extendWin32ProcessBindings(({ kernel32, advapi32, bind }) => ({ openProcess: bind(kernel32, 'OpenProcess', PVOID, ['uint32', 'int', 'uint32']), openProcessToken: bind(advapi32, 'OpenProcessToken', 'int', [PVOID, 'uint32', PPVOID]), diff --git a/packages/sandbox/sandbox-windows-acl/tests/index-failure-paths.spec.ts b/packages/sandbox/sandbox-windows-acl/tests/index-failure-paths.spec.ts index 0d07389301..50a4a9987e 100644 --- a/packages/sandbox/sandbox-windows-acl/tests/index-failure-paths.spec.ts +++ b/packages/sandbox/sandbox-windows-acl/tests/index-failure-paths.spec.ts @@ -13,7 +13,7 @@ import { tmpdir } from 'node:os' import { join, resolve } from 'node:path' import { Win32Error } from '@deepseek-ai/dsh-win32-process' import { ERROR_BROKEN_PIPE } from '@deepseek-ai/dsh-win32-process/src/abi.ts' -import { PROCESS_INFORMATION } from '@deepseek-ai/dsh-win32-process/src/ffi.ts' +import { processInformationType } from '@deepseek-ai/dsh-win32-process/src/ffi.ts' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' import koffi from 'koffi' @@ -133,7 +133,7 @@ function happyStubs(): HappyStubs { _token: unknown, _app: unknown, _cmd: unknown, _pa: unknown, _ta: unknown, _inherit: unknown, _flags: unknown, _env: unknown, _cwd: unknown, _si: unknown, processInfo: NativePtr, ) => { - koffi.encode(processInfo, PROCESS_INFORMATION, { hProcess: fresh(), hThread: fresh(), dwProcessId: 1234, dwThreadId: 5678 }) + koffi.encode(processInfo, processInformationType(), { hProcess: fresh(), hThread: fresh(), dwProcessId: 1234, dwThreadId: 5678 }) return 1 }) const peekNamedPipe = vi.fn(() => 0) diff --git a/packages/sandbox/sandbox-windows-acl/tsconfig.json b/packages/sandbox/sandbox-windows-acl/tsconfig.json index 6676f0c13d..d6a476cce8 100644 --- a/packages/sandbox/sandbox-windows-acl/tsconfig.json +++ b/packages/sandbox/sandbox-windows-acl/tsconfig.json @@ -22,6 +22,9 @@ }, { "path": "../../subprocess/subprocess" + }, + { + "path": "../../util/lazy-require" } ] } diff --git a/packages/sandbox/sandbox/package.json b/packages/sandbox/sandbox/package.json index 05e539d567..acad37c554 100644 --- a/packages/sandbox/sandbox/package.json +++ b/packages/sandbox/sandbox/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-sandbox", "description": "Abstract process-sandbox seam (ctx.sandbox) for the DeepSeek Harness: same-world confinement vocabulary and the SandboxProvider contract", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/schedule/schedule/package.json b/packages/schedule/schedule/package.json index eb43fbbed4..d7edb02911 100644 --- a/packages/schedule/schedule/package.json +++ b/packages/schedule/schedule/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-schedule", "description": "Agent-scoped durable after, at, and fixed-rate reminders over the session event log", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/sdk/client/package.json b/packages/sdk/client/package.json index 272fbab849..4648e9ad79 100644 --- a/packages/sdk/client/package.json +++ b/packages/sdk/client/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-sdk-client", "description": "TypeScript client SDK for driving a DeepSeek Harness runtime subprocess over stdio JSON-RPC: the DeepSeekHarness high-level turns API and the lower-level HarnessClient", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/sdk/protocol/package.json b/packages/sdk/protocol/package.json index e65cf001d0..8ddeb9d856 100644 --- a/packages/sdk/protocol/package.json +++ b/packages/sdk/protocol/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-sdk-protocol", "description": "Shared wire protocol for the DeepSeek Harness SDK runtime: the newline-delimited JSON-RPC stdio transport and the named request, result, and notification types spoken between the runtime server and SDK clients", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/sdk/server/package.json b/packages/sdk/server/package.json index 56d1e7cad5..376fd47ad1 100644 --- a/packages/sdk/server/package.json +++ b/packages/sdk/server/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-sdk-jsonrpc-server", "description": "Stdio JSON-RPC server plugin for out-of-process DeepSeek Harness SDK clients", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session-query/session-log-export/package.json b/packages/session-query/session-log-export/package.json index f63702c964..75d743253d 100644 --- a/packages/session-query/session-log-export/package.json +++ b/packages/session-query/session-log-export/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-log-export", "description": "Web Session-log export command and shared download dialog", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session-query/session-query-sqlite/package.json b/packages/session-query/session-query-sqlite/package.json index ed79f3840e..2f7ca61073 100644 --- a/packages/session-query/session-query-sqlite/package.json +++ b/packages/session-query/session-query-sqlite/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-query-sqlite", "description": "Concrete ctx.sessionQuery backend with SQLite FTS5 search", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session-query/session-query/package.json b/packages/session-query/session-query/package.json index 17eb37f1ae..bd0ffdc57c 100644 --- a/packages/session-query/session-query/package.json +++ b/packages/session-query/session-query/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-query", "description": "Combined session query service contract with concrete reads, traces, and filters", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session-query/tool-session-query/package.json b/packages/session-query/tool-session-query/package.json index 1ad9a77b5c..eaf82ff1dc 100644 --- a/packages/session-query/tool-session-query/package.json +++ b/packages/session-query/tool-session-query/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-session-query", "description": "Workspace-authorized model-facing session history search, trace, and event read tools", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-checkpoint-policy/package.json b/packages/session/session-checkpoint-policy/package.json index a817b06e4e..2b9b2e81e5 100644 --- a/packages/session/session-checkpoint-policy/package.json +++ b/packages/session/session-checkpoint-policy/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-checkpoint-policy", "description": "Semantic session durability checkpoints before model requests and tool side effects", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-format-catalog/package.json b/packages/session/session-format-catalog/package.json index a34af0786d..05574cd6b5 100644 --- a/packages/session/session-format-catalog/package.json +++ b/packages/session/session-format-catalog/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-format-catalog", "description": "Build-static first-party Session format codec and migration catalog", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-format-v0-to-v1/package.json b/packages/session/session-format-v0-to-v1/package.json index ca5d77d640..9b4fb2debd 100644 --- a/packages/session/session-format-v0-to-v1/package.json +++ b/packages/session/session-format-v0-to-v1/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-format-v0-to-v1", "description": "Frozen released-v0 Session codec and identity migration to v1", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-format-v1-to-v2/package.json b/packages/session/session-format-v1-to-v2/package.json index a44eef9856..1cb43e63f4 100644 --- a/packages/session/session-format-v1-to-v2/package.json +++ b/packages/session/session-format-v1-to-v2/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-format-v1-to-v2", "description": "Frozen released-v1 Session codec and assistant-stream migration to v2", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-format-v2-to-v3/package.json b/packages/session/session-format-v2-to-v3/package.json index 637cd4f0fe..030450b861 100644 --- a/packages/session/session-format-v2-to-v3/package.json +++ b/packages/session/session-format-v2-to-v3/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-format-v2-to-v3", "description": "Streaming system-prompt, canonical-envelope and PTC migration into V3", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-format/package.json b/packages/session/session-format/package.json index 4eaefdbea2..a5048bca75 100644 --- a/packages/session/session-format/package.json +++ b/packages/session/session-format/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-format", "description": "Streaming adjacent Session format migration machinery", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-log-deepseek/package.json b/packages/session/session-log-deepseek/package.json index 4cbdc9e4f1..12e378a3f5 100644 --- a/packages/session/session-log-deepseek/package.json +++ b/packages/session/session-log-deepseek/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-log-deepseek", "description": "Incremental lossless session-log request extension for the official DeepSeek LLM API", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-persistence-jsonl/package.json b/packages/session/session-persistence-jsonl/package.json index 2756ed8ebd..d5fad6d730 100644 --- a/packages/session/session-persistence-jsonl/package.json +++ b/packages/session/session-persistence-jsonl/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-persistence-jsonl", "description": "JSONL durable session persistence backend for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-persistence/package.json b/packages/session/session-persistence/package.json index 1562f0f190..ceed4ed16d 100644 --- a/packages/session/session-persistence/package.json +++ b/packages/session/session-persistence/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-persistence", "description": "Abstract durable session persistence seam (ctx.sessionPersistence) for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-projection-cache/package.json b/packages/session/session-projection-cache/package.json index bd1672e91a..3c68beafdb 100644 --- a/packages/session/session-projection-cache/package.json +++ b/packages/session/session-projection-cache/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-projection-cache", "description": "Persisted projection cache (ctx.sessionProjectionCache): durable per-session checkpoint records on the session_projcache storage domain (per-record layout), throttled write-behind, and the cached listing read", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-projection/package.json b/packages/session/session-projection/package.json index 6f426ab889..f3640e115b 100644 --- a/packages/session/session-projection/package.json +++ b/packages/session/session-projection/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-projection", "description": "Session-projection seam: the merge-extensible projection type table, the provider contract, and the ctx.sessionProjections registry serving whole current values of log-derived per-session state", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-stats/package.json b/packages/session/session-stats/package.json index c62eb3b449..ca41dac0c4 100644 --- a/packages/session/session-stats/package.json +++ b/packages/session/session-stats/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-stats", "description": "Whole-log conversation counts and wall times projection (sessionStats) for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-telemetry-otel/package.json b/packages/session/session-telemetry-otel/package.json index 2da6d75acb..8b657ca991 100644 --- a/packages/session/session-telemetry-otel/package.json +++ b/packages/session/session-telemetry-otel/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-telemetry-otel", "description": "OpenTelemetry backend for the DeepSeek Harness telemetry seam: hands captured session records to the OTel JS SDK's log pipeline", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-telemetry/package.json b/packages/session/session-telemetry/package.json index 1278879299..8a1134f3cb 100644 --- a/packages/session/session-telemetry/package.json +++ b/packages/session/session-telemetry/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-telemetry", "description": "SessionTelemetryBackend seam for the DeepSeek Harness: session-event capture, projection, redaction, and handoff to a reporting backend", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-title-all-prompts-llm/package.json b/packages/session/session-title-all-prompts-llm/package.json index 4ceac7914e..0778189f3a 100644 --- a/packages/session/session-title-all-prompts-llm/package.json +++ b/packages/session/session-title-all-prompts-llm/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-title-all-prompts-llm", "description": "All-user-messages LLM provider plugin for DeepSeek Harness session titles", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-title-first-prompt-llm/package.json b/packages/session/session-title-first-prompt-llm/package.json index e88246e9e6..cd6cb70969 100644 --- a/packages/session/session-title-first-prompt-llm/package.json +++ b/packages/session/session-title-first-prompt-llm/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-title-first-prompt-llm", "description": "First-message LLM provider plugin for DeepSeek Harness session titles", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-title-llm/package.json b/packages/session/session-title-llm/package.json index b7eef1d212..97f7cfd36e 100644 --- a/packages/session/session-title-llm/package.json +++ b/packages/session/session-title-llm/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-title-llm", "description": "Shared LLM generation policy for DeepSeek Harness session-title providers", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-title/package.json b/packages/session/session-title/package.json index a7e9894f61..d016b8669d 100644 --- a/packages/session/session-title/package.json +++ b/packages/session/session-title/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-title", "description": "Log-backed session title service and provider registry for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/session/session-turn-outline/package.json b/packages/session/session-turn-outline/package.json index 14153bed1f..61daf88d9c 100644 --- a/packages/session/session-turn-outline/package.json +++ b/packages/session/session-turn-outline/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-turn-outline", "description": "Whole-log turn outline projection (turnOutline) for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/settings/settings-file/package.json b/packages/settings/settings-file/package.json index 6ab3a5b2b0..0979e6cb65 100644 --- a/packages/settings/settings-file/package.json +++ b/packages/settings/settings-file/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-settings-file", "description": "File-backed settings provider (settings.yaml) for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/settings/settings/package.json b/packages/settings/settings/package.json index 65a086cf07..8939609546 100644 --- a/packages/settings/settings/package.json +++ b/packages/settings/settings/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-settings", "description": "Abstract user-settings seam (ctx.settings) for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/shell/bash-local/package.json b/packages/shell/bash-local/package.json index b5ce1e5135..3702b11c7b 100644 --- a/packages/shell/bash-local/package.json +++ b/packages/shell/bash-local/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-bash-local", "description": "Local-subprocess implementation of the DeepSeek Harness bash executor seam", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/shell/bash-sandbox/package.json b/packages/shell/bash-sandbox/package.json index bfe649350b..d3a97c634f 100644 --- a/packages/shell/bash-sandbox/package.json +++ b/packages/shell/bash-sandbox/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-bash-sandbox", "description": "Sandbox-consuming implementation of the DeepSeek Harness bash executor seam (confines every command via ctx.sandbox, reports denial/enforcement result facts)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/shell/pwsh-local/package.json b/packages/shell/pwsh-local/package.json index 6925fc8509..540a431264 100644 --- a/packages/shell/pwsh-local/package.json +++ b/packages/shell/pwsh-local/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-pwsh-local", "description": "Local PowerShell implementation of the DeepSeek Harness bash executor seam", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/shell/pwsh-sandbox/package.json b/packages/shell/pwsh-sandbox/package.json index ad46833902..feaebd2942 100644 --- a/packages/shell/pwsh-sandbox/package.json +++ b/packages/shell/pwsh-sandbox/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-pwsh-sandbox", "description": "Sandbox-consuming implementation of the DeepSeek Harness PowerShell executor seam (confines every command via ctx.sandbox, reports denial/enforcement result facts)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/shell/shell-env/package.json b/packages/shell/shell-env/package.json index ddc0df2fd2..e79645caaf 100644 --- a/packages/shell/shell-env/package.json +++ b/packages/shell/shell-env/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-shell-env", "description": "Tool-independent managed DSH_* shell environment registry", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/shell/shell/package.json b/packages/shell/shell/package.json index 57a8573d6b..8ad644cf0d 100644 --- a/packages/shell/shell/package.json +++ b/packages/shell/shell/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-shell", "description": "Abstract bash executor seam (ctx.shell) for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/shell/tool-bash-persistent/package.json b/packages/shell/tool-bash-persistent/package.json index 8551bd66d5..d19cc2163a 100644 --- a/packages/shell/tool-bash-persistent/package.json +++ b/packages/shell/tool-bash-persistent/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-bash-persistent", "description": "Model-facing owner-scoped persistent Bash tool backed by the Harness PTY service", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/shell/tool-bash/package.json b/packages/shell/tool-bash/package.json index c6d4a937b1..482cc30cca 100644 --- a/packages/shell/tool-bash/package.json +++ b/packages/shell/tool-bash/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-bash", "description": "Model-facing bash tool with optional generic background-job and sandbox-escalation support", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/shell/tool-pwsh-persistent/package.json b/packages/shell/tool-pwsh-persistent/package.json index ae0d009f52..f38fb74ef2 100644 --- a/packages/shell/tool-pwsh-persistent/package.json +++ b/packages/shell/tool-pwsh-persistent/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-pwsh-persistent", "description": "Model-facing owner-scoped persistent PowerShell tool backed by the Harness PTY service", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/shell/tool-pwsh/package.json b/packages/shell/tool-pwsh/package.json index 30642f2ef5..256f81897d 100644 --- a/packages/shell/tool-pwsh/package.json +++ b/packages/shell/tool-pwsh/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-pwsh", "description": "Model-facing pwsh tool over the bash executor seam", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/skill/skill-badge/package.json b/packages/skill/skill-badge/package.json index dc0d6728e2..f731310364 100644 --- a/packages/skill/skill-badge/package.json +++ b/packages/skill/skill-badge/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-skill-badge", "description": "Bundled dsh badge skill provider for DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/skill/skill-filesystem/package.json b/packages/skill/skill-filesystem/package.json index afdb646aa6..47da833ad8 100644 --- a/packages/skill/skill-filesystem/package.json +++ b/packages/skill/skill-filesystem/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-skill-filesystem", "description": "Local filesystem skill provider for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/skill/skill/package.json b/packages/skill/skill/package.json index 01be87e133..3fa675609e 100644 --- a/packages/skill/skill/package.json +++ b/packages/skill/skill/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-skill", "description": "Agent skill provider registry for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/skill/tool-skill/package.json b/packages/skill/tool-skill/package.json index a12fcde6eb..9549143903 100644 --- a/packages/skill/tool-skill/package.json +++ b/packages/skill/tool-skill/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-skill", "description": "Model-facing skill loading tool for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/spill/spill-local/package.json b/packages/spill/spill-local/package.json index 510acaf6b1..cb53d0deac 100644 --- a/packages/spill/spill-local/package.json +++ b/packages/spill/spill-local/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-spill-local", "description": "Local-filesystem implementation of the DeepSeek Harness spill storage seam (private session-scoped files)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/spill/spill-policy/package.json b/packages/spill/spill-policy/package.json index 3aa64c3270..fb50c07906 100644 --- a/packages/spill/spill-policy/package.json +++ b/packages/spill/spill-policy/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-spill-policy", "description": "Tool-result spill policy for the DeepSeek Harness — replaces oversized plain-text tool results with a retained preview plus a spill-file path (no service API)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/spill/spill/package.json b/packages/spill/spill/package.json index b4a3c32097..183cca1800 100644 --- a/packages/spill/spill/package.json +++ b/packages/spill/spill/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-spill", "description": "Abstract spill storage seam (ctx.spillStore) for the DeepSeek Harness — save oversized tool text and return a retrieval locator", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/ssh/fs-ssh/package.json b/packages/ssh/fs-ssh/package.json index b70ded8eb0..e652a7eefc 100644 --- a/packages/ssh/fs-ssh/package.json +++ b/packages/ssh/fs-ssh/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-fs-ssh", "description": "Filesystem provider over the shared POSIX SSH helper", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/ssh/sandbox-ssh/package.json b/packages/ssh/sandbox-ssh/package.json index 89838c8e6c..d2633cf79e 100644 --- a/packages/ssh/sandbox-ssh/package.json +++ b/packages/ssh/sandbox-ssh/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-sandbox-ssh", "description": "Remote POSIX sandbox argv provider over the shared SSH helper", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/ssh/ssh/package.json b/packages/ssh/ssh/package.json index 725a6da222..a4ab500bd6 100644 --- a/packages/ssh/ssh/package.json +++ b/packages/ssh/ssh/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-ssh", "description": "Shared OpenSSH connection and versioned POSIX remote helper", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/ssh/subprocess-ssh/package.json b/packages/ssh/subprocess-ssh/package.json index f33823903e..1132c63339 100644 --- a/packages/ssh/subprocess-ssh/package.json +++ b/packages/ssh/subprocess-ssh/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-subprocess-ssh", "description": "Subprocess and terminal provider over the shared POSIX SSH helper", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/storage/storage-domain/package.json b/packages/storage/storage-domain/package.json index 8fe6912da9..1ddf8a14dc 100644 --- a/packages/storage/storage-domain/package.json +++ b/packages/storage/storage-domain/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-storage-domain", "description": "Domain data form (ctx.storage.domain): schema-validated, event-emitting KV domains over storage backends for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/storage/storage-json/package.json b/packages/storage/storage-json/package.json index 9d21ecfec2..32f0c0c9c5 100644 --- a/packages/storage/storage-json/package.json +++ b/packages/storage/storage-json/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-storage-json", "description": "JSON file KV storage backend for the DeepSeek Harness storage hub", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/storage/storage-sqlite/package.json b/packages/storage/storage-sqlite/package.json index d8dfc56910..b554fbd8ca 100644 --- a/packages/storage/storage-sqlite/package.json +++ b/packages/storage/storage-sqlite/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-storage-sqlite", "description": "SQLite storage backend (kv facet) for the DeepSeek Harness storage hub", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/storage/storage/package.json b/packages/storage/storage/package.json index a418159f5c..841da3e2f9 100644 --- a/packages/storage/storage/package.json +++ b/packages/storage/storage/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-storage", "description": "Storage hub (ctx.storage): named backend registry plus mounted data-form facilities for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/subagent/subagent-acp/package.json b/packages/subagent/subagent-acp/package.json index 45e0f6c99c..6fa9ac8837 100644 --- a/packages/subagent/subagent-acp/package.json +++ b/packages/subagent/subagent-acp/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-subagent-acp", "description": "Out-of-process ACP subagent backend: drives a child agent in a spawned subprocess over the Agent Client Protocol", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/subagent/subagent-claude-code/package.json b/packages/subagent/subagent-claude-code/package.json index 19d90f5a92..7eaab9a4e1 100644 --- a/packages/subagent/subagent-claude-code/package.json +++ b/packages/subagent/subagent-claude-code/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-subagent-claude-code", "description": "One-shot Claude Code subagent provider over the official Agent SDK", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/subagent/subagent-codex/package.json b/packages/subagent/subagent-codex/package.json index c434b0b52d..952f7ad6fc 100644 --- a/packages/subagent/subagent-codex/package.json +++ b/packages/subagent/subagent-codex/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-subagent-codex", "description": "One-shot Codex subagent provider over the official app-server protocol", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/subagent/subagent-dsh-sdk/package.json b/packages/subagent/subagent-dsh-sdk/package.json index 1a378f53f2..b78524ca54 100644 --- a/packages/subagent/subagent-dsh-sdk/package.json +++ b/packages/subagent/subagent-dsh-sdk/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-subagent-dsh-sdk", "description": "Out-of-process SDK subagent backend: drives a child DeepSeek Harness runtime subprocess over stdio JSON-RPC through the TypeScript SDK client", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/subagent/subagent-fork-in-process/package.json b/packages/subagent/subagent-fork-in-process/package.json index 22d147631e..1d054f12a3 100644 --- a/packages/subagent/subagent-fork-in-process/package.json +++ b/packages/subagent/subagent-fork-in-process/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-subagent-fork-in-process", "description": "In-process fork subagent backend: runs a child agent seeded with a prefix of the parent's log", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/subagent/subagent-in-process-driver/package.json b/packages/subagent/subagent-in-process-driver/package.json index 2c3ede08bd..9c8aa2d912 100644 --- a/packages/subagent/subagent-in-process-driver/package.json +++ b/packages/subagent/subagent-in-process-driver/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-subagent-in-process-driver", "description": "Shared in-process subagent run driver: drives a child agent on ctx.agents (used by the spawn and fork backends)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/subagent/subagent-spawn-in-process/package.json b/packages/subagent/subagent-spawn-in-process/package.json index a7c5e2f2a6..34cd0a3727 100644 --- a/packages/subagent/subagent-spawn-in-process/package.json +++ b/packages/subagent/subagent-spawn-in-process/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-subagent-spawn-in-process", "description": "In-process spawn subagent backend: runs a fresh child agent on ctx.agents", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/subagent/subagent/package.json b/packages/subagent/subagent/package.json index 898b3dcb4a..28ea97f913 100644 --- a/packages/subagent/subagent/package.json +++ b/packages/subagent/subagent/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-subagent", "description": "Abstract subagent seam (ctx.subagents): named-provider registry for delegating to child agents", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/subagent/tool-subagent-control/package.json b/packages/subagent/tool-subagent-control/package.json index 2272a1c6a2..cd80b1d5d2 100644 --- a/packages/subagent/tool-subagent-control/package.json +++ b/packages/subagent/tool-subagent-control/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-subagent-control", "description": "Globally named send_message, interrupt_agent, and list_agents tools over ctx.subagents continuations", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/subagent/tool-subagent/package.json b/packages/subagent/tool-subagent/package.json index aba9a126eb..b9f3a18c33 100644 --- a/packages/subagent/tool-subagent/package.json +++ b/packages/subagent/tool-subagent/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-subagent", "description": "Model-facing subagent delegation tool over the ctx.subagents seam", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/subprocess/subprocess-local/package.json b/packages/subprocess/subprocess-local/package.json index c3da72d058..f5d703257d 100644 --- a/packages/subprocess/subprocess-local/package.json +++ b/packages/subprocess/subprocess-local/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-subprocess-local", "description": "Local-subprocess implementation of the DeepSeek Harness subprocess seam", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, @@ -47,6 +47,7 @@ "@deepseek-ai/cordis": "workspace:^" }, "dependencies": { + "@deepseek-ai/dsh-lazy-require": "workspace:^", "@deepseek-ai/dsh-win32-process": "workspace:^", "koffi": "^3.1.0", "node-pty": "1.2.0-beta.15" diff --git a/packages/subprocess/subprocess-local/src/index.ts b/packages/subprocess/subprocess-local/src/index.ts index 9aad496696..ff03daebdf 100644 --- a/packages/subprocess/subprocess-local/src/index.ts +++ b/packages/subprocess/subprocess-local/src/index.ts @@ -14,8 +14,9 @@ import { userInfo } from 'node:os' import { delimiter, extname, isAbsolute, resolve } from 'node:path' import type { Duplex } from 'node:stream' import { Context } from '@deepseek-ai/cordis' -import * as nodePty from 'node-pty' +import type * as NodePty from 'node-pty' import type { IPtyForkOptions } from 'node-pty' +import { createLazyRequire } from '@deepseek-ai/dsh-lazy-require' import { SubprocessRuntime, SubprocessExecutableNotFoundError } from '@deepseek-ai/dsh-subprocess' import type { SubprocessHandle, @@ -46,6 +47,8 @@ import type { ProcessInspector } from './process-inspector.ts' import { LocalTerminalHandle } from './terminal.ts' import { prepareShellActivity } from './shell-activity.ts' +const requireNodePty = createLazyRequire('node-pty', import.meta.url) + /** * Local subprocess service: platform-selected managed ranges, Node-shaped stdio * dispositions (raw pipes, inherit, bounded tail-keep collection with spill @@ -273,13 +276,13 @@ export class LocalSubprocessRuntime extends SubprocessRuntime { env: { ...activity?.env ?? env, TERM: spec.terminalType }, } let scope: ReturnType | undefined - let terminal: nodePty.IPty + let terminal: NodePty.IPty try { scope = containmentMode === 'linux-scope' ? prepareLinuxTerminalScope(launch, { ...activity?.env ?? env, PWD: spec.cwd, TERM: spec.terminalType }) : undefined if (scope !== undefined) { options.cwd = scope.cwd; options.env = scope.env } - terminal = nodePty.spawn( + terminal = requireNodePty().spawn( scope?.command ?? file, scope?.args ?? [...launch.argv.slice(1)], options, diff --git a/packages/subprocess/subprocess-local/src/linux-execve.ts b/packages/subprocess/subprocess-local/src/linux-execve.ts index 250e255a50..430a0006bd 100644 --- a/packages/subprocess/subprocess-local/src/linux-execve.ts +++ b/packages/subprocess/subprocess-local/src/linux-execve.ts @@ -1,8 +1,10 @@ /** Lazy libc execve and descriptor bindings used by the one-shot Linux bootstrap. */ import { getSystemErrorMessage, getSystemErrorName } from 'node:util' -import koffi from 'koffi' import { SUBPROCESS_CONTROL_FD } from '@deepseek-ai/dsh-subprocess/control' +import { createLazyRequire } from '@deepseek-ai/dsh-lazy-require' + +const requireKoffi = createLazyRequire('koffi', import.meta.url) /** Replace the current process image while preserving the supplied argv and environment. */ export type LinuxExecve = ( @@ -46,6 +48,7 @@ function systemError(errno: number, syscall: string, path?: string): Error { */ export function loadLinuxExecve(): LinuxExecve { if (cachedExecve !== undefined) return cachedExecve + const koffi = requireKoffi() const libc = koffi.load(null) const nativeExecve = libc.func( 'int execve(const char *pathname, const char **argv, const char **envp)', diff --git a/packages/subprocess/subprocess-local/src/windows-inspector.ts b/packages/subprocess/subprocess-local/src/windows-inspector.ts index 14715c65aa..783ee7ae46 100644 --- a/packages/subprocess/subprocess-local/src/windows-inspector.ts +++ b/packages/subprocess/subprocess-local/src/windows-inspector.ts @@ -10,10 +10,15 @@ */ import { spawnSync } from 'node:child_process' -import koffi from 'koffi' import type { SubprocessTerminalSignal } from '@deepseek-ai/dsh-subprocess' +import { createLazyRequire } from '@deepseek-ai/dsh-lazy-require' import type { ProcessIdentity, ProcessInspector, ProcessSnapshot } from './process-inspector.ts' +type Koffi = typeof import('koffi')['default'] +type KoffiType = ReturnType + +const requireKoffi = createLazyRequire('koffi', import.meta.url) + /** One Toolhelp32 process-table row. */ export interface ProcessEntry { pid: number @@ -183,16 +188,16 @@ interface Win32Bindings { closeHandle(handle: NativePtr): number } -const PVOID: ReturnType = koffi.pointer('void') - /** * Resolve the koffi Win32 struct types once. Registration is lazy and cached * because koffi's type registry is global per process: test runners that * re-evaluate this module (a hoisted `vi.mock` re-imports the graph) must not * re-register the names. */ -function win32Structs(): { PROCESSENTRY32W: ReturnType; FILETIME: ReturnType } { +function win32Structs(): { PVOID: KoffiType; PROCESSENTRY32W: ReturnType; FILETIME: ReturnType } { if (cachedStructs !== undefined) return cachedStructs + const koffi = requireKoffi() + const PVOID = koffi.pointer('void') // koffi PROCESSENTRY32W layout (tlhelp32.h); the size assert pins the x64 layout. const PROCESSENTRY32W = koffi.struct('PROCESSENTRY32W', { dwSize: 'uint32', @@ -216,7 +221,7 @@ function win32Structs(): { PROCESSENTRY32W: ReturnType; FIL throw new Error(`PROCESSENTRY32W layout mismatch: koffi computed ${PROCESSENTRY32W.size}, Windows headers say 568`) } /* v8 ignore stop */ - cachedStructs = { PROCESSENTRY32W, FILETIME } + cachedStructs = { PVOID, PROCESSENTRY32W, FILETIME } return cachedStructs } @@ -236,7 +241,8 @@ let cachedBindings: Win32Bindings | undefined */ function win32Bindings(): Win32Bindings { if (cachedBindings !== undefined) return cachedBindings - const { PROCESSENTRY32W, FILETIME } = win32Structs() + const koffi = requireKoffi() + const { PVOID, PROCESSENTRY32W, FILETIME } = win32Structs() const kernel32 = koffi.load('kernel32.dll') const bind = ( name: string, @@ -268,13 +274,15 @@ function win32Bindings(): Win32Bindings { * @param count - element count. * @returns the branded allocation pointer. */ -function allocNative(type: Parameters[0], count: number): NativePtr { +function allocNative(type: Parameters[0], count: number): NativePtr { + const koffi = requireKoffi() const value: unknown = koffi.alloc(type, count) return value as NativePtr } /** Enumerate the current process table through Toolhelp32. */ function snapshotWindowsProcesses(bindings: Win32Bindings): ProcessEntry[] { + const koffi = requireKoffi() const { PROCESSENTRY32W } = win32Structs() const snapshot = bindings.createToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) /* v8 ignore next -- an invalid snapshot for the process flag is not producible through the public API; @@ -301,6 +309,7 @@ function snapshotWindowsProcesses(bindings: Win32Bindings): ProcessEntry[] { /** Read one process's creation identity and current wait state. */ function windowsProcessState(bindings: Win32Bindings, pid: number): WindowsProcessState | undefined { + const koffi = requireKoffi() const { FILETIME } = win32Structs() const handle = bindings.openProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, 0, pid) if (isInvalidHandle(handle)) return undefined diff --git a/packages/subprocess/subprocess-local/tests/linux-execve.spec.ts b/packages/subprocess/subprocess-local/tests/linux-execve.spec.ts index d16fca8956..bd00dffdf0 100644 --- a/packages/subprocess/subprocess-local/tests/linux-execve.spec.ts +++ b/packages/subprocess/subprocess-local/tests/linux-execve.spec.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest' afterEach(() => { - vi.doUnmock('koffi') + vi.doUnmock('@deepseek-ai/dsh-lazy-require') vi.resetModules() }) @@ -17,7 +17,9 @@ describe.skipIf(process.platform !== 'linux')('Linux libc execve binding', () => : nativeFcntl) const load = vi.fn(() => ({ func })) const errno = vi.fn(() => 2) - vi.doMock('koffi', () => ({ default: { errno, load } })) + vi.doMock('@deepseek-ai/dsh-lazy-require', () => ({ + createLazyRequire: () => () => ({ errno, load }), + })) const { loadLinuxExecve } = await import('../src/linux-execve.ts') const execve = loadLinuxExecve() @@ -65,7 +67,9 @@ describe.skipIf(process.platform !== 'linux')('Linux libc execve binding', () => ? nativeExecve : nativeFcntl) const errno = vi.fn(() => 9) - vi.doMock('koffi', () => ({ default: { errno, load: () => ({ func }) } })) + vi.doMock('@deepseek-ai/dsh-lazy-require', () => ({ + createLazyRequire: () => () => ({ errno, load: () => ({ func }) }), + })) const { loadLinuxExecve } = await import('../src/linux-execve.ts') expect(() => loadLinuxExecve()('/bin/tool', ['tool'], {})).toThrow(expect.objectContaining({ @@ -87,7 +91,9 @@ describe.skipIf(process.platform !== 'linux')('Linux libc execve binding', () => ? nativeExecve : nativeFcntl) const errno = vi.fn(() => 5) - vi.doMock('koffi', () => ({ default: { errno, load: () => ({ func }) } })) + vi.doMock('@deepseek-ai/dsh-lazy-require', () => ({ + createLazyRequire: () => () => ({ errno, load: () => ({ func }) }), + })) const { loadLinuxExecve } = await import('../src/linux-execve.ts') expect(() => loadLinuxExecve()('/bin/tool', ['tool'], {})).toThrow(expect.objectContaining({ diff --git a/packages/subprocess/subprocess-local/tests/local.spec.ts b/packages/subprocess/subprocess-local/tests/local.spec.ts index e59a048f34..966128da88 100644 --- a/packages/subprocess/subprocess-local/tests/local.spec.ts +++ b/packages/subprocess/subprocess-local/tests/local.spec.ts @@ -20,6 +20,19 @@ function unmockWin32ForIsolatedRuntime(): void { vi.doUnmock('@deepseek-ai/dsh-win32-process') } +function mockNodePtyForIsolatedRuntime(spawn: unknown): void { + vi.doMock('@deepseek-ai/dsh-lazy-require', () => ({ + createLazyRequire: (specifier: string) => () => { + if (specifier === 'node-pty') return { spawn } + throw new Error(`unexpected lazy dependency ${specifier}`) + }, + })) +} + +function unmockLazyRequireForIsolatedRuntime(): void { + vi.doUnmock('@deepseek-ai/dsh-lazy-require') +} + function spec(command: string, overrides: Partial = {}): SubprocessSpawnSpec { // Windows has no bash; the suite's simple commands translate to node one-liners. const argv = process.platform === 'win32' @@ -443,7 +456,7 @@ describe('LocalSubprocessRuntime', () => { } vi.resetModules() mockWin32ForIsolatedRuntime() - vi.doMock('node-pty', () => ({ spawn: () => terminal })) + mockNodePtyForIsolatedRuntime(() => terminal) vi.doMock('../src/process-inspector.ts', async importOriginal => ({ ...await importOriginal(), createProcessInspector: () => inspector, @@ -466,7 +479,7 @@ describe('LocalSubprocessRuntime', () => { await expect.poll(() => (service as unknown as { terminals: Set }).terminals.size).toBe(0) await fiber.dispose() } finally { - vi.doUnmock('node-pty') + unmockLazyRequireForIsolatedRuntime() vi.doUnmock('../src/process-inspector.ts') vi.doUnmock('../src/linux-scope.ts') unmockWin32ForIsolatedRuntime() @@ -534,7 +547,7 @@ describe('LocalSubprocessRuntime', () => { vi.resetModules() mockWin32ForIsolatedRuntime() - vi.doMock('node-pty', () => ({ spawn: nodePtySpawn })) + mockNodePtyForIsolatedRuntime(nodePtySpawn) vi.doMock('../src/linux-scope.ts', () => ({ signalLinuxDirectProcess, launchLinuxScope: vi.fn(), @@ -600,7 +613,7 @@ describe('LocalSubprocessRuntime', () => { } finally { await fiber?.dispose() directProbe.mockRestore() - vi.doUnmock('node-pty') + unmockLazyRequireForIsolatedRuntime() vi.doUnmock('../src/linux-scope.ts') unmockWin32ForIsolatedRuntime() vi.resetModules() @@ -635,7 +648,7 @@ describe('LocalSubprocessRuntime', () => { vi.resetModules() mockWin32ForIsolatedRuntime() - vi.doMock('node-pty', () => ({ spawn: nodePtySpawn })) + mockNodePtyForIsolatedRuntime(nodePtySpawn) vi.doMock('../src/linux-scope.ts', () => ({ signalLinuxDirectProcess, launchLinuxScope: vi.fn(), @@ -658,7 +671,7 @@ describe('LocalSubprocessRuntime', () => { expect(cleanup).toHaveBeenCalledOnce() } finally { await fiber?.dispose() - vi.doUnmock('node-pty') + unmockLazyRequireForIsolatedRuntime() vi.doUnmock('../src/linux-scope.ts') unmockWin32ForIsolatedRuntime() vi.resetModules() @@ -679,7 +692,7 @@ describe('LocalSubprocessRuntime', () => { } vi.resetModules() mockWin32ForIsolatedRuntime() - vi.doMock('node-pty', () => ({ spawn: () => terminal })) + mockNodePtyForIsolatedRuntime(() => terminal) try { const { default: IsolatedLocalSubprocessRuntime } = await import('../src/index.ts') const ctx = new Context() @@ -714,7 +727,7 @@ describe('LocalSubprocessRuntime', () => { await fiber.dispose() expect(disposalErrors).toHaveLength(1) } finally { - vi.doUnmock('node-pty') + unmockLazyRequireForIsolatedRuntime() unmockWin32ForIsolatedRuntime() vi.resetModules() } diff --git a/packages/subprocess/subprocess-local/tsconfig.json b/packages/subprocess/subprocess-local/tsconfig.json index 58822fc3f4..f7181f494c 100644 --- a/packages/subprocess/subprocess-local/tsconfig.json +++ b/packages/subprocess/subprocess-local/tsconfig.json @@ -22,6 +22,9 @@ }, { "path": "../../util/timeout" + }, + { + "path": "../../util/lazy-require" } ] } diff --git a/packages/subprocess/subprocess/package.json b/packages/subprocess/subprocess/package.json index 202fa14525..ab6df68c51 100644 --- a/packages/subprocess/subprocess/package.json +++ b/packages/subprocess/subprocess/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-subprocess", "description": "Subprocess seam (ctx.subprocess) for the DeepSeek Harness \u2014 managed process groups, bounded spill-backed output, and escalated kills behind one abstract service", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/subprocess/win32-process/package.json b/packages/subprocess/win32-process/package.json index 0338895db5..1c93ab8f84 100644 --- a/packages/subprocess/win32-process/package.json +++ b/packages/subprocess/win32-process/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-win32-process", "description": "Shared low-level Win32 process, stdio, and Job Object primitives", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, @@ -30,6 +30,7 @@ "@deepseek-ai/cordis": "workspace:^" }, "dependencies": { + "@deepseek-ai/dsh-lazy-require": "workspace:^", "koffi": "^3.1.0" }, "devDependencies": { diff --git a/packages/subprocess/win32-process/src/ffi.ts b/packages/subprocess/win32-process/src/ffi.ts index 88b8831d7c..2b5fb47c70 100644 --- a/packages/subprocess/win32-process/src/ffi.ts +++ b/packages/subprocess/win32-process/src/ffi.ts @@ -1,26 +1,24 @@ /** Lazy Koffi bindings for generic Win32 process, stdio, and Job operations. */ -import koffi from 'koffi' import * as abi from './abi.ts' import { Win32Error } from './errors.ts' +import { requireKoffi, type Koffi } from './koffi.ts' declare const nativePtr: unique symbol /** Koffi native pointer branded against accidental numeric use. */ export type NativePtr = bigint & { readonly [nativePtr]: true } -type Ptr = ReturnType -const PVOID: Ptr = koffi.pointer('void') -const PPVOID: Ptr = koffi.pointer(PVOID) +type Ptr = ReturnType /** Loaded Win32 libraries and the shared stdcall binder used by process extensions. */ export interface Win32BindingContext { /** Kernel process, handle, pipe, and Job APIs. */ - readonly kernel32: ReturnType + readonly kernel32: ReturnType /** Token and security APIs. */ - readonly advapi32: ReturnType + readonly advapi32: ReturnType /** Bind one stdcall function from a loaded Win32 library. */ readonly bind: ( - library: ReturnType, + library: ReturnType, name: string, result: Ptr | string, args: Array, @@ -129,51 +127,65 @@ export interface CurrentTokenProcessBindings extends Win32ProcessBindings { uvGetOsfhandle(fileDescriptor: number): NativePtr | null } -/** Koffi STARTUPINFOW layout. */ -export const STARTUPINFOW = koffi.struct('DSH_STARTUPINFOW', { - cb: 'uint32', - lpReserved: 'str16', - lpDesktop: 'str16', - lpTitle: 'str16', - dwX: 'uint32', - dwY: 'uint32', - dwXSize: 'uint32', - dwYSize: 'uint32', - dwXCountChars: 'uint32', - dwYCountChars: 'uint32', - dwFillAttribute: 'uint32', - dwFlags: 'uint32', - wShowWindow: 'uint16', - cbReserved2: 'uint16', - lpReserved2: koffi.pointer('uint8'), - hStdInput: PVOID, - hStdOutput: PVOID, - hStdError: PVOID, -}) - -/** Koffi PROCESS_INFORMATION layout. */ -export const PROCESS_INFORMATION = koffi.struct('DSH_PROCESS_INFORMATION', { - hProcess: PVOID, - hThread: PVOID, - dwProcessId: 'uint32', - dwThreadId: 'uint32', -}) - -/* v8 ignore start -- ABI guards are pinned by native header probes. */ -if (STARTUPINFOW.size !== abi.STARTUPINFOW_SIZE) { - throw new Error(`STARTUPINFOW layout mismatch: koffi computed ${STARTUPINFOW.size}, expected ${abi.STARTUPINFOW_SIZE}`) +interface Win32Types { + PVOID: Ptr + PPVOID: Ptr + STARTUPINFOW: ReturnType + PROCESS_INFORMATION: ReturnType } -if (PROCESS_INFORMATION.size !== abi.PROCESS_INFORMATION_SIZE) { - throw new Error(`PROCESS_INFORMATION layout mismatch: koffi computed ${PROCESS_INFORMATION.size}, expected ${abi.PROCESS_INFORMATION_SIZE}`) + +let cachedTypes: Win32Types | undefined + +/** Resolve Koffi pointer and process layouts on the first native operation. */ +function win32Types(): Win32Types { + if (cachedTypes !== undefined) return cachedTypes + const koffi = requireKoffi() + const PVOID = koffi.pointer('void') + const PPVOID = koffi.pointer(PVOID) + const STARTUPINFOW = koffi.struct('DSH_STARTUPINFOW', { + cb: 'uint32', lpReserved: 'str16', lpDesktop: 'str16', lpTitle: 'str16', + dwX: 'uint32', dwY: 'uint32', dwXSize: 'uint32', dwYSize: 'uint32', + dwXCountChars: 'uint32', dwYCountChars: 'uint32', dwFillAttribute: 'uint32', + dwFlags: 'uint32', wShowWindow: 'uint16', cbReserved2: 'uint16', + lpReserved2: koffi.pointer('uint8'), hStdInput: PVOID, hStdOutput: PVOID, hStdError: PVOID, + }) + const PROCESS_INFORMATION = koffi.struct('DSH_PROCESS_INFORMATION', { + hProcess: PVOID, hThread: PVOID, dwProcessId: 'uint32', dwThreadId: 'uint32', + }) + /* v8 ignore start -- ABI guards are pinned by native header probes. */ + if (STARTUPINFOW.size !== abi.STARTUPINFOW_SIZE) { + throw new Error(`STARTUPINFOW layout mismatch: koffi computed ${STARTUPINFOW.size}, expected ${abi.STARTUPINFOW_SIZE}`) + } + if (PROCESS_INFORMATION.size !== abi.PROCESS_INFORMATION_SIZE) { + throw new Error(`PROCESS_INFORMATION layout mismatch: koffi computed ${PROCESS_INFORMATION.size}, expected ${abi.PROCESS_INFORMATION_SIZE}`) + } + /* v8 ignore stop */ + return cachedTypes = { PVOID, PPVOID, STARTUPINFOW, PROCESS_INFORMATION } +} + +/** + * Materialize the Koffi STARTUPINFOW layout on first native use. + * @returns the cached native struct type. + */ +export function startupInfoType(): ReturnType { + return win32Types().STARTUPINFOW +} + +/** + * Materialize the Koffi PROCESS_INFORMATION layout on first native use. + * @returns the cached native struct type. + */ +export function processInformationType(): ReturnType { + return win32Types().PROCESS_INFORMATION } -/* v8 ignore stop */ /** * Allocate a pointer-sized out-parameter slot. * @returns allocated native slot. */ export function allocPtrSlot(): NativePtr { - return koffi.alloc(PVOID, 1) as NativePtr + const { PVOID } = win32Types() + return requireKoffi().alloc(PVOID, 1) as NativePtr } /** @@ -181,7 +193,7 @@ export function allocPtrSlot(): NativePtr { * @returns allocated native slot. */ export function allocUint32(): NativePtr { - return koffi.alloc('uint32', 1) as NativePtr + return requireKoffi().alloc('uint32', 1) as NativePtr } /** @@ -190,7 +202,7 @@ export function allocUint32(): NativePtr { * @returns decoded pointer, or null for address zero. */ export function decodePtr(slot: NativePtr): NativePtr | null { - const value = koffi.decode(slot, PVOID) as NativePtr | null + const value = requireKoffi().decode(slot, win32Types().PVOID) as NativePtr | null return isNullPtr(value) ? null : value } @@ -200,7 +212,7 @@ export function decodePtr(slot: NativePtr): NativePtr | null { * @returns decoded unsigned value. */ export function decodeUint32(slot: NativePtr): number { - return koffi.decode(slot, 'uint32') as number + return requireKoffi().decode(slot, 'uint32') as number } /** @@ -208,7 +220,7 @@ export function decodeUint32(slot: NativePtr): number { * @returns allocated struct pointer. */ export function allocStartupInfo(): NativePtr { - return koffi.alloc(STARTUPINFOW, 1) as NativePtr + return requireKoffi().alloc(win32Types().STARTUPINFOW, 1) as NativePtr } /** @@ -217,7 +229,7 @@ export function allocStartupInfo(): NativePtr { * @param fields - fields required for inherited stdio. */ export function encodeStartupInfo(startupInfo: NativePtr, fields: StartupInfoInput): void { - koffi.encode(startupInfo, STARTUPINFOW, fields) + requireKoffi().encode(startupInfo, win32Types().STARTUPINFOW, fields) } /** @@ -225,7 +237,7 @@ export function encodeStartupInfo(startupInfo: NativePtr, fields: StartupInfoInp * @returns allocated struct pointer. */ export function allocProcessInfo(): NativePtr { - return koffi.alloc(PROCESS_INFORMATION, 1) as NativePtr + return requireKoffi().alloc(win32Types().PROCESS_INFORMATION, 1) as NativePtr } /** @@ -234,7 +246,7 @@ export function allocProcessInfo(): NativePtr { * @returns process/thread handles and ids. */ export function decodeProcessInfo(processInfo: NativePtr): ProcessInfoOutput { - return koffi.decode(processInfo, PROCESS_INFORMATION) as ProcessInfoOutput + return requireKoffi().decode(processInfo, win32Types().PROCESS_INFORMATION) as ProcessInfoOutput } let cachedContext: Win32BindingContext | undefined @@ -243,6 +255,7 @@ let cached: CurrentTokenProcessBindings | undefined /* v8 ignore start -- exercised by native Windows ABI and sandbox jobs. */ function bindingContext(): Win32BindingContext { if (cachedContext !== undefined) return cachedContext + const koffi = requireKoffi() const kernel32 = koffi.load('kernel32.dll') const advapi32 = koffi.load('advapi32.dll') const bind = ( @@ -257,6 +270,8 @@ function bindingContext(): Win32BindingContext { function bindings(): CurrentTokenProcessBindings { if (cached !== undefined) return cached + const koffi = requireKoffi() + const { PVOID, PPVOID, STARTUPINFOW, PROCESS_INFORMATION } = win32Types() const { kernel32, advapi32, bind } = bindingContext() const node = koffi.load(null) cached = { diff --git a/packages/subprocess/win32-process/src/koffi.ts b/packages/subprocess/win32-process/src/koffi.ts new file mode 100644 index 0000000000..4fd21407d3 --- /dev/null +++ b/packages/subprocess/win32-process/src/koffi.ts @@ -0,0 +1,10 @@ +/** Process-realm lazy access to Koffi's CommonJS entry. */ + +import type koffi from 'koffi' +import { createLazyRequire } from '@deepseek-ai/dsh-lazy-require' + +/** Koffi runtime export type. */ +export type Koffi = typeof koffi + +/** Load Koffi on the first Win32 native operation. */ +export const requireKoffi = createLazyRequire('koffi', import.meta.url) diff --git a/packages/subprocess/win32-process/src/process.ts b/packages/subprocess/win32-process/src/process.ts index 504cbc7db3..12354a104b 100644 --- a/packages/subprocess/win32-process/src/process.ts +++ b/packages/subprocess/win32-process/src/process.ts @@ -1,6 +1,5 @@ /** Typed Win32 process operations over the shared binding table. */ -import koffi from 'koffi' import * as abi from './abi.ts' import { inheritedControlStdio } from './control-stdio.ts' import { @@ -17,6 +16,7 @@ import { throwWin32, } from './ffi.ts' import type { CurrentTokenProcessBindings, NativePtr, Win32ProcessBindings } from './ffi.ts' +import { requireKoffi } from './koffi.ts' /** * Quote one argument according to CommandLineToArgvW parsing. @@ -133,7 +133,7 @@ interface PipePair { } function freeNative(pointer: NativePtr | undefined): void { - if (pointer !== undefined) koffi.free(pointer) + if (pointer !== undefined) requireKoffi().free(pointer) } function closeBestEffort(api: Win32ProcessBindings, handle: NativePtr | null | undefined): void { @@ -158,7 +158,7 @@ function createPipe(api: Win32ProcessBindings, owned: Set): PipePair return { read, write } } finally { freeNative(writeSlot) - koffi.free(readSlot) + requireKoffi().free(readSlot) } } @@ -442,6 +442,7 @@ function spawnJobProcess( ? undefined : inheritedControlStdio(api, { ...stdio, control: stdio.control }) if (controlBytes !== undefined) { + const koffi = requireKoffi() controlDescriptorBlock = { pointer: koffi.alloc('uint8', controlBytes.length) as NativePtr, length: controlBytes.length } koffi.encode(controlDescriptorBlock.pointer, 'uint8', controlBytes, controlBytes.length) } @@ -591,7 +592,7 @@ export function pollProcessExit(api: Win32ProcessBindings, process: NativePtr): if (api.getExitCodeProcess(process, exitCodeSlot) === 0) throwLastError(api, 'GetExitCodeProcess') return decodeUint32(exitCodeSlot) } finally { - koffi.free(exitCodeSlot) + requireKoffi().free(exitCodeSlot) } } diff --git a/packages/subprocess/win32-process/tests/ffi.spec.ts b/packages/subprocess/win32-process/tests/ffi.spec.ts index dfe27537f3..efb4db205a 100644 --- a/packages/subprocess/win32-process/tests/ffi.spec.ts +++ b/packages/subprocess/win32-process/tests/ffi.spec.ts @@ -8,13 +8,13 @@ import { throwLastError, } from '../src/index.ts' import { PROCESS_INFORMATION_SIZE, STARTUPINFOW_SIZE } from '../src/abi.ts' -import { PROCESS_INFORMATION, STARTUPINFOW, errorText } from '../src/ffi.ts' +import { errorText, processInformationType, startupInfoType } from '../src/ffi.ts' import type { NativePtr, Win32ProcessBindings } from '../src/index.ts' describe('shared Win32 process ABI', () => { it('matches the verified x64 structure sizes', () => { - expect(STARTUPINFOW.size).toBe(STARTUPINFOW_SIZE) - expect(PROCESS_INFORMATION.size).toBe(PROCESS_INFORMATION_SIZE) + expect(startupInfoType().size).toBe(STARTUPINFOW_SIZE) + expect(processInformationType().size).toBe(PROCESS_INFORMATION_SIZE) }) it('handles NULL pointer out-parameters', () => { diff --git a/packages/subprocess/win32-process/tests/ordinary-process.spec.ts b/packages/subprocess/win32-process/tests/ordinary-process.spec.ts index 719346ce15..d43c6ae962 100644 --- a/packages/subprocess/win32-process/tests/ordinary-process.spec.ts +++ b/packages/subprocess/win32-process/tests/ordinary-process.spec.ts @@ -17,7 +17,7 @@ import { JobObjectBasicAccountingInformation, WAIT_TIMEOUT, } from '../src/abi.ts' -import { PROCESS_INFORMATION, STARTUPINFOW } from '../src/ffi.ts' +import { processInformationType, startupInfoType } from '../src/ffi.ts' import type { CurrentTokenProcessSpawnOptions, CurrentTokenProcessBindings, @@ -50,7 +50,7 @@ function api(overrides: Partial = {}): CurrentToken uvGetOsfhandle: vi.fn((fileDescriptor: number) => BigInt(100 + fileDescriptor)), setHandleInformation: vi.fn(() => 1), createProcessW: vi.fn((_app, _line, _pa, _ta, _inherit, _flags, _env, _cwd, _startup, info) => { - koffi.encode(info, PROCESS_INFORMATION, { + koffi.encode(info, processInformationType(), { hProcess: 60n, hThread: 61n, dwProcessId: 1234, @@ -82,9 +82,9 @@ describe('ordinary Job process operations', () => { getFileType: vi.fn(handle => handle === 107n ? 3 : standardType), setHandleInformation: flags, createProcessW: vi.fn((_app, _line, _pa, _ta, _inherit, _flags, _env, _cwd, startupPointer, processInfo) => { - const startup = koffi.decode(startupPointer, STARTUPINFOW) as { cbReserved2: number; lpReserved2: NativePtr } + const startup = koffi.decode(startupPointer, startupInfoType()) as { cbReserved2: number; lpReserved2: NativePtr } descriptorBytes = Buffer.from(koffi.decode(startup.lpReserved2, 'uint8', startup.cbReserved2) as number[]) - koffi.encode(processInfo, PROCESS_INFORMATION, { hProcess: 60n, hThread: 61n, dwProcessId: 1234, dwThreadId: 5678 }) + koffi.encode(processInfo, processInformationType(), { hProcess: 60n, hThread: 61n, dwProcessId: 1234, dwThreadId: 5678 }) return 1 }), }) @@ -129,7 +129,7 @@ describe('ordinary Job process operations', () => { info: NativePtr, ) => { events.push('create') - koffi.encode(info, PROCESS_INFORMATION, { hProcess: 60n, hThread: 61n, dwProcessId: 1234, dwThreadId: 5678 }) + koffi.encode(info, processInformationType(), { hProcess: 60n, hThread: 61n, dwProcessId: 1234, dwThreadId: 5678 }) return 1 }) const bindings = api({ @@ -190,8 +190,8 @@ describe('ordinary Job process operations', () => { setHandleInformation, uvGetOsfhandle, createProcessW: vi.fn((_app, _line, _pa, _ta, _inherit, _flags, _env, _cwd, infoPtr, processInfo) => { - startup = koffi.decode(infoPtr, STARTUPINFOW) as Record - koffi.encode(processInfo, PROCESS_INFORMATION, { + startup = koffi.decode(infoPtr, startupInfoType()) as Record + koffi.encode(processInfo, processInformationType(), { hProcess: 60n, hThread: 61n, dwProcessId: 1234, diff --git a/packages/subprocess/win32-process/tests/process-allocation-failure.spec.ts b/packages/subprocess/win32-process/tests/process-allocation-failure.spec.ts index fe3c603c02..3d79ad536c 100644 --- a/packages/subprocess/win32-process/tests/process-allocation-failure.spec.ts +++ b/packages/subprocess/win32-process/tests/process-allocation-failure.spec.ts @@ -1,5 +1,5 @@ import koffi from 'koffi' -import { afterEach, describe, expect, it, vi } from 'vitest' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { drainPipe, spawnInheritedJobProcess, @@ -7,13 +7,19 @@ import { waitForProcessExit, } from '../src/index.ts' import * as ffi from '../src/ffi.ts' -import { PROCESS_INFORMATION } from '../src/ffi.ts' +import { processInformationType } from '../src/ffi.ts' +import * as koffiLoader from '../src/koffi.ts' import type { NativePtr, Win32ProcessBindings } from '../src/ffi.ts' vi.mock('../src/ffi.ts', { spy: true }) +vi.mock('../src/koffi.ts', { spy: true }) const PVOID = koffi.pointer('void') +beforeEach(() => { + vi.mocked(koffiLoader.requireKoffi).mockReturnValue(koffi) +}) + afterEach(() => { vi.restoreAllMocks() }) @@ -47,7 +53,7 @@ describe('spawnInheritedJobProcess allocation cleanup', () => { getStdHandle: vi.fn((selector: number) => BigInt(100 - selector)), setHandleInformation: vi.fn(() => 1), createProcessAsUserW: vi.fn((_token, _app, _line, _pa, _ta, _inherit, _flags, _env, _cwd, _startup, info) => { - koffi.encode(info, PROCESS_INFORMATION, { + koffi.encode(info, processInformationType(), { hProcess: 60n, hThread: 61n, dwProcessId: 1234, @@ -83,7 +89,7 @@ describe('shared process allocation cleanup', () => { }), setHandleInformation: vi.fn(() => 1), createProcessAsUserW: vi.fn((_token, _app, _line, _pa, _ta, _inherit, _flags, _env, _cwd, _startup, info) => { - koffi.encode(info, PROCESS_INFORMATION, { + koffi.encode(info, processInformationType(), { hProcess: 60n, hThread: 61n, dwProcessId: 1234, diff --git a/packages/subprocess/win32-process/tests/process-failure-paths.spec.ts b/packages/subprocess/win32-process/tests/process-failure-paths.spec.ts index 41ba438516..4b3dbe1d03 100644 --- a/packages/subprocess/win32-process/tests/process-failure-paths.spec.ts +++ b/packages/subprocess/win32-process/tests/process-failure-paths.spec.ts @@ -17,7 +17,7 @@ import { } from '../src/index.ts' import type { NativePtr, Win32ProcessBindings } from '../src/index.ts' import * as abi from '../src/abi.ts' -import { PROCESS_INFORMATION } from '../src/ffi.ts' +import { processInformationType } from '../src/ffi.ts' const PVOID = koffi.pointer('void') @@ -88,7 +88,7 @@ function pipeOkApi(overrides: Partial = {}): { _token: unknown, _app: unknown, _cmd: unknown, _pa: unknown, _ta: unknown, _inherit: unknown, _flags: unknown, _env: unknown, _cwd: unknown, _si: unknown, processInfo: NativePtr, ) => { - koffi.encode(processInfo, PROCESS_INFORMATION, { hProcess: 200n, hThread: 201n, dwProcessId: 1234, dwThreadId: 5678 }) + koffi.encode(processInfo, processInformationType(), { hProcess: 200n, hThread: 201n, dwProcessId: 1234, dwThreadId: 5678 }) return 1 }), getLastError: vi.fn(() => 5), @@ -152,7 +152,7 @@ describe('spawn pipe failures close their handles', () => { _token: unknown, _app: unknown, _cmd: unknown, _pa: unknown, _ta: unknown, _inherit: unknown, _flags: unknown, _env: unknown, _cwd: unknown, _si: unknown, processInfo: NativePtr, ) => { - koffi.encode(processInfo, PROCESS_INFORMATION, { hProcess: null, hThread: null, dwProcessId: 1234, dwThreadId: 5678 }) + koffi.encode(processInfo, processInformationType(), { hProcess: null, hThread: null, dwProcessId: 1234, dwThreadId: 5678 }) return 1 }), }) @@ -185,7 +185,7 @@ describe('spawnInheritedJobProcess failure paths', () => { _token: unknown, _app: unknown, _cmd: unknown, _pa: unknown, _ta: unknown, _inherit: unknown, _flags: unknown, _env: unknown, _cwd: unknown, _si: unknown, processInfo: NativePtr, ) => { - koffi.encode(processInfo, PROCESS_INFORMATION, { hProcess: 200n, hThread: 201n, dwProcessId: 1234, dwThreadId: 5678 }) + koffi.encode(processInfo, processInformationType(), { hProcess: 200n, hThread: 201n, dwProcessId: 1234, dwThreadId: 5678 }) return 1 }), assignProcessToJobObject: vi.fn(() => 1), @@ -243,7 +243,7 @@ describe('spawnInheritedJobProcess failure paths', () => { _token: unknown, _app: unknown, _cmd: unknown, _pa: unknown, _ta: unknown, _inherit: unknown, _flags: unknown, _env: unknown, _cwd: unknown, _si: unknown, processInfo: NativePtr, ) => { - koffi.encode(processInfo, PROCESS_INFORMATION, { hProcess: null, hThread: null, dwProcessId: 1234, dwThreadId: 5678 }) + koffi.encode(processInfo, processInformationType(), { hProcess: null, hThread: null, dwProcessId: 1234, dwThreadId: 5678 }) return 1 }), }) diff --git a/packages/subprocess/win32-process/tests/process.spec.ts b/packages/subprocess/win32-process/tests/process.spec.ts index 7b6e5c41ba..d82fe7cfe1 100644 --- a/packages/subprocess/win32-process/tests/process.spec.ts +++ b/packages/subprocess/win32-process/tests/process.spec.ts @@ -7,7 +7,7 @@ import { spawnPipedProcess, } from '../src/index.ts' import { CREATE_SUSPENDED } from '../src/abi.ts' -import { PROCESS_INFORMATION } from '../src/ffi.ts' +import { processInformationType } from '../src/ffi.ts' import type { NativePtr, Win32ProcessBindings } from '../src/index.ts' const PVOID = koffi.pointer('void') @@ -24,7 +24,7 @@ function inheritedApi(overrides: Partial = {}): { overrides.createProcessAsUserW ?? ((_token, _app, _line, _pa, _ta, _inherit, _flags, _env, _cwd, _startup, info) => { events.push('create') - koffi.encode(info, PROCESS_INFORMATION, { + koffi.encode(info, processInformationType(), { hProcess: 60n, hThread: 61n, dwProcessId: 1234, @@ -176,7 +176,7 @@ describe('spawnInheritedJobProcess', () => { closeHandle, terminateProcess, createProcessAsUserW: vi.fn((_token, _app, _line, _pa, _ta, _inherit, _flags, _env, _cwd, _startup, info) => { - koffi.encode(info, PROCESS_INFORMATION, { + koffi.encode(info, processInformationType(), { hProcess: 60n, hThread: 0n, dwProcessId: 1234, @@ -232,7 +232,7 @@ describe('wait and pipe cleanup', () => { }), setHandleInformation: vi.fn(() => 1), createProcessAsUserW: vi.fn((_token, _app, _line, _pa, _ta, _inherit, _flags, _env, _cwd, _startup, info) => { - koffi.encode(info, PROCESS_INFORMATION, { + koffi.encode(info, processInformationType(), { hProcess: 60n, hThread: 0n, dwProcessId: 1234, diff --git a/packages/subprocess/win32-process/tsconfig.json b/packages/subprocess/win32-process/tsconfig.json index d6035c59a9..93abec7c46 100644 --- a/packages/subprocess/win32-process/tsconfig.json +++ b/packages/subprocess/win32-process/tsconfig.json @@ -8,6 +8,9 @@ "references": [ { "path": "../../../vendor/cordis" + }, + { + "path": "../../util/lazy-require" } ] } diff --git a/packages/terminal/terminal-bash/package.json b/packages/terminal/terminal-bash/package.json index 619cd97da3..43b17df506 100644 --- a/packages/terminal/terminal-bash/package.json +++ b/packages/terminal/terminal-bash/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-terminal-bash", "description": "Persistent shell PTY backend over the DeepSeek Harness subprocess terminal primitive", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, @@ -37,6 +37,7 @@ "@deepseek-ai/dsh-session-projection": "workspace:^" }, "dependencies": { + "@deepseek-ai/dsh-lazy-require": "workspace:^", "@deepseek-ai/dsh-pwsh-local": "workspace:^", "@deepseek-ai/schemastery": "workspace:^", "@xterm/headless": "^6.0.0" diff --git a/packages/terminal/terminal-bash/src/index.ts b/packages/terminal/terminal-bash/src/index.ts index 2371121f59..fb22f39e20 100644 --- a/packages/terminal/terminal-bash/src/index.ts +++ b/packages/terminal/terminal-bash/src/index.ts @@ -170,6 +170,16 @@ async function startupSession( } } +/** Reject a failed startup only after its unpublished resources reach quiescence. */ +async function rejectAfterStartupCleanup(error: unknown, cleanup: () => Promise): Promise { + try { + await cleanup() + } catch (cleanupError: unknown) { + throw new TerminalBackendCleanupError(error, cleanupError) + } + throw error +} + /** Local shell backend registered under the configured type. */ export class BashTerminalBackend implements TerminalBackend { readonly type: string @@ -205,17 +215,17 @@ export class BashTerminalBackend implements TerminalBackend { graceMs: this.config.disposeGraceMs, signal: spec.signal, }) - const session = this.createSession(terminal, this.config) + let session: LocalPtySession + try { + session = this.createSession(terminal, this.config) + } catch (error) { + return rejectAfterStartupCleanup(error, () => terminal.terminate()) + } try { await startupSession(session, this.config.shellDialect, this.config.timeoutMs, spec.signal) return session } catch (error) { - try { - await session.close('PTY startup failed') - } catch (closeError: unknown) { - throw new TerminalBackendCleanupError(error, closeError) - } - throw error + return rejectAfterStartupCleanup(error, () => session.close('PTY startup failed')) } } } diff --git a/packages/terminal/terminal-bash/src/session.ts b/packages/terminal/terminal-bash/src/session.ts index 7b43087855..6bc9ee3f4a 100644 --- a/packages/terminal/terminal-bash/src/session.ts +++ b/packages/terminal/terminal-bash/src/session.ts @@ -1,8 +1,8 @@ /** Persistent PTY session with bounded output, readiness, and terminal-protocol replies. */ import { Buffer } from 'node:buffer' -import { createRequire } from 'node:module' import type { IDisposable, Terminal as HeadlessTerminalType } from '@xterm/headless' +import { createLazyRequire } from '@deepseek-ai/dsh-lazy-require' import type { SubprocessOutcome, SubprocessTerminalForeground, @@ -25,8 +25,7 @@ import type { import type { ResolvedConfig } from './config.ts' import { CONTROLLED_PROMPT, TerminalSanitizer } from './sanitize.ts' -// Node exposes this package's CommonJS main as default-only, so load its named export through require. -const { Terminal: HeadlessTerminal } = createRequire(import.meta.url)('@xterm/headless') as typeof import('@xterm/headless') +const requireHeadless = createLazyRequire('@xterm/headless', import.meta.url) function utf8Tail(text: string, maxBytes: number): { text: string; truncated: boolean } { if (Buffer.byteLength(text) <= maxBytes) return { text, truncated: false } @@ -281,6 +280,7 @@ export class LocalPtySession implements TerminalBackendSession { private readonly config: ResolvedConfig, ) { this.pid = terminal.pid + const { Terminal: HeadlessTerminal } = requireHeadless() this.emulator = new HeadlessTerminal({ cols: config.cols, rows: config.rows, scrollback: 0 }) this.emulatorData = this.emulator.onData((data) => { this.pendingResponseWrites += 1 diff --git a/packages/terminal/terminal-bash/tests/index.spec.ts b/packages/terminal/terminal-bash/tests/index.spec.ts index 5aa8ff2921..16b7f9ffce 100644 --- a/packages/terminal/terminal-bash/tests/index.spec.ts +++ b/packages/terminal/terminal-bash/tests/index.spec.ts @@ -159,6 +159,41 @@ describe('BashTerminalBackend startup rollback', () => { } satisfies Partial)) }) + it('awaits terminal cleanup when session construction fails', async () => { + const ctx = new Context() + await ctx.plugin(SessionProjectionRegistry) + await ctx.plugin(SandboxPolicyService, { mode: 'danger-full-access', workspaceRoot: '/tmp' }) + const quiescent = Promise.withResolvers() + const terminal = { + ...terminalHandle(), + terminate: vi.fn(() => quiescent.promise), + } + const constructionStarted = Promise.withResolvers() + const failure = new Error('terminal emulator unavailable') + const backend = new BashTerminalBackend( + ctx, + config(), + async () => terminal, + () => { + constructionStarted.resolve(undefined) + throw failure + }, + ) + + const spawning = backend.spawn(spec(agent(ctx))) + await constructionStarted.promise + expect(terminal.terminate).toHaveBeenCalledOnce() + let settled = false + void spawning.then( + () => { settled = true }, + () => { settled = true }, + ) + await Promise.resolve() + expect(settled).toBe(false) + quiescent.resolve(undefined) + await expect(spawning).rejects.toBe(failure) + }) + it('starts startup rollback when cancellation wins a stalled initialization', async () => { const ctx = new Context() await ctx.plugin(SessionProjectionRegistry) diff --git a/packages/terminal/terminal-bash/tsconfig.json b/packages/terminal/terminal-bash/tsconfig.json index 6f50367beb..bb9c72ded3 100644 --- a/packages/terminal/terminal-bash/tsconfig.json +++ b/packages/terminal/terminal-bash/tsconfig.json @@ -37,6 +37,9 @@ }, { "path": "../../subprocess/subprocess" + }, + { + "path": "../../util/lazy-require" } ] } diff --git a/packages/terminal/terminal/package.json b/packages/terminal/terminal/package.json index 6ae9f278ee..57d490236f 100644 --- a/packages/terminal/terminal/package.json +++ b/packages/terminal/terminal/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-terminal", "description": "Persistent PTY session seam for the DeepSeek Harness — owner-scoped ids, backend registry, interactive sends, reads, signals, and awaited cleanup", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/terminal/tool-terminal/package.json b/packages/terminal/tool-terminal/package.json index b9467f6204..7f676a2914 100644 --- a/packages/terminal/tool-terminal/package.json +++ b/packages/terminal/tool-terminal/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-terminal", "description": "Six model-facing persistent PTY tools with owner isolation and generic background-job integration", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/test-support/agent-loop-testkit/package.json b/packages/test-support/agent-loop-testkit/package.json index e476648bfa..be34d9a301 100644 --- a/packages/test-support/agent-loop-testkit/package.json +++ b/packages/test-support/agent-loop-testkit/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-agent-loop-testkit", "description": "Prerequisite mounting, production AgentLoop drivers, and Inbox stubs for tests", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/test-support/client-runtime/package.json b/packages/test-support/client-runtime/package.json index 944536df62..3c9c579e86 100644 --- a/packages/test-support/client-runtime/package.json +++ b/packages/test-support/client-runtime/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-test-runtime", "description": "Browser test runtimes: a jsdom slot bench with test-owned Session and Workspace doubles, and a whole-client tier that boots the web roster through the production bootClient over an endpoint-named Remote mock", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/test-support/llm-mock-server/package.json b/packages/test-support/llm-mock-server/package.json index 7a238460fb..9ec41f038a 100644 --- a/packages/test-support/llm-mock-server/package.json +++ b/packages/test-support/llm-mock-server/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-llm-mock-server", "description": "Scriptable OpenAI-compatible HTTP/SSE fault server for LLM recovery tests", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/test-support/llm-replay/package.json b/packages/test-support/llm-replay/package.json index d3e4e4540e..6116352e0c 100644 --- a/packages/test-support/llm-replay/package.json +++ b/packages/test-support/llm-replay/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-llm-replay", "description": "Replay LLM plugin: short-circuits llm/stream with model chunks reconstructed from a recorded session JSONL (keyless snapshot tests)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/test-support/loader-smoke/package.json b/packages/test-support/loader-smoke/package.json index 7958561a39..3d13037b97 100644 --- a/packages/test-support/loader-smoke/package.json +++ b/packages/test-support/loader-smoke/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-loader-smoke", "description": "Shared subprocess and direct-agent harness for keyless real-Loader example smoke tests", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/test-support/remote-mock/package.json b/packages/test-support/remote-mock/package.json index 4304430075..86d9059d6f 100644 --- a/packages/test-support/remote-mock/package.json +++ b/packages/test-support/remote-mock/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-remote-mock", "description": "Endpoint-named mock for Typert Remote traffic: unary answers and stream scripts per /, live stream control, a log, and the Connection carrier face whole-client specs install", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/test-support/session-snapshot/package.json b/packages/test-support/session-snapshot/package.json index f3a6468992..3a8a78cefa 100644 --- a/packages/test-support/session-snapshot/package.json +++ b/packages/test-support/session-snapshot/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-session-snapshot", "description": "Session-log snapshot core with an ACP protocol adapter, expected-output normalization, and fixture invariants", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/todo/tool-todo/package.json b/packages/todo/tool-todo/package.json index 7eb98180dd..1f206137e3 100644 --- a/packages/todo/tool-todo/package.json +++ b/packages/todo/tool-todo/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-todo", "description": "Model-facing todo_write tool over the DeepSeek Harness event-sourced session log", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/typert/generator/README.i18n.yaml b/packages/typert/generator/README.i18n.yaml index 372868ea92..e8639b64a0 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: 7177e6a34520ab07645727edb15c5a726b9a2a4c -README.zh.md: c1add1907c7c14ee7dd440dfaceee0ece0aa036e +README.md: bcbde084425a3a61deb8c9a596dca808f144c1da +README.zh.md: 450573b57d9236f9f64ad37a339afdb6e428655b diff --git a/packages/typert/generator/README.md b/packages/typert/generator/README.md index 7177e6a345..bcbde08442 100644 --- a/packages/typert/generator/README.md +++ b/packages/typert/generator/README.md @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -`dsh-typert-generator` lets maintainers turn public TypeScript types into build artifacts and compiler-independent models. Packages opt in through the `./typert` and optional `./client/typert` exports, and generation rejects declarations, publish lists, Remote exports, or Zod projections that it cannot represent correctly. Repository builds can emit executable schemas and matching declarations, while tools can call `WorkspaceAnalyzer` for inspection or catalog generation without publishing artifacts. Generation runs only at build time and never in a live agent session. +`dsh-typert-generator` lets maintainers turn public TypeScript types into build artifacts and compiler-independent models. Packages opt in through the `./typert` and optional `./client/typert` exports, and generation rejects declarations, publish lists, Remote exports, or Zod projections that it cannot represent correctly. Repository builds emit executable schema factories and matching declarations, while tools can call `WorkspaceAnalyzer` for inspection or catalog generation without publishing artifacts. Generation runs only at build time and never in a live agent session. ## Table of Contents @@ -83,7 +83,7 @@ Host and Client are independent TypeScript programs. Direct project references e ### Emission and publication contract -`FaceModelEmitter` emits executable JavaScript containing supported Zod schemas and the `TYPERT` contribution, plus a declaration file whose schemas are typed `z.ZodType` through the package's public export; unsupported Zod projections fail. The Host face with Remote methods additionally emits `typert.remote-client.*` projections of Host Remote contracts for the Client. `WorkspaceTypertGenerator` validates each contributor's `package.json`: `./typert` and `./client/typert` (and `./remote` when Remote methods exist) must point at the exact generated files, and the `files` list must include them. +`FaceModelEmitter` emits executable JavaScript containing success-cached Zod schema factories and the `TYPERT` contribution, plus a declaration file whose factories return `z.ZodType` through the package's public export; unsupported Zod projections fail. The Host face with Remote methods additionally emits `typert.remote-client.*` projections of Host Remote contracts for the Client. `WorkspaceTypertGenerator` validates each contributor's `package.json`: `./typert` and `./client/typert` (and `./remote` when Remote methods exist) must point at the exact generated files, and the `files` list must include them. ### Catalog projection diff --git a/packages/typert/generator/README.zh.md b/packages/typert/generator/README.zh.md index c1add1907c..450573b57d 100644 --- a/packages/typert/generator/README.zh.md +++ b/packages/typert/generator/README.zh.md @@ -9,7 +9,7 @@ kind: "package-library" ## 概述 -`dsh-typert-generator` 让维护者把公开的 TypeScript 类型转换为构建产物和与编译器无关的模型。包通过 `./typert` 和可选的 `./client/typert` 导出选择加入;如果声明、发布清单、Remote 导出或 Zod 投影无法被正确表示,生成过程就会失败。仓库构建可以生成可执行 schema 与配套声明,工具也可以调用 `WorkspaceAnalyzer` 完成检查或目录生成而不发布产物。生成过程只在构建时运行,绝不会进入实时 agent(智能体)会话。 +`dsh-typert-generator` 让维护者把公开的 TypeScript 类型转换为构建产物和与编译器无关的模型。包通过 `./typert` 和可选的 `./client/typert` 导出选择加入;如果声明、发布清单、Remote 导出或 Zod 投影无法被正确表示,生成过程就会失败。仓库构建会生成可执行 schema factory 与配套声明,工具也可以调用 `WorkspaceAnalyzer` 完成检查或目录生成而不发布产物。生成过程只在构建时运行,绝不会进入实时 agent(智能体)会话。 ## 目录 @@ -83,7 +83,7 @@ Host 与 Client 是两个独立的 TypeScript 程序。直接项目引用确定 ### 生成与发布约定 -`FaceModelEmitter` 输出包含受支持 Zod schema 与 `TYPERT` 贡献的可执行 JavaScript,以及把 schema 通过包的公开导出标注为 `z.ZodType` 的声明文件;不支持的 Zod 投影会失败。含 Remote 方法的 Host face 还会额外为 Client 生成 Host Remote 约定的 `typert.remote-client.*` 投影。`WorkspaceTypertGenerator` 校验每个贡献方的 `package.json`:`./typert` 与 `./client/typert`(存在 Remote 方法时还有 `./remote`)必须指向精确的生成文件,且 `files` 清单必须包含它们。 +`FaceModelEmitter` 输出包含只缓存成功结果的 Zod schema factory 与 `TYPERT` 贡献的可执行 JavaScript,以及把 factory 通过包的公开导出标注为返回 `z.ZodType` 的声明文件;不支持的 Zod 投影会失败。含 Remote 方法的 Host face 还会额外为 Client 生成 Host Remote 约定的 `typert.remote-client.*` 投影。`WorkspaceTypertGenerator` 校验每个贡献方的 `package.json`:`./typert` 与 `./client/typert`(存在 Remote 方法时还有 `./remote`)必须指向精确的生成文件,且 `files` 清单必须包含它们。 ### 目录投影 diff --git a/packages/typert/generator/package.json b/packages/typert/generator/package.json index ab65157347..c5634ee64e 100644 --- a/packages/typert/generator/package.json +++ b/packages/typert/generator/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-typert-generator", "description": "TypeScript project analyzer and model-driven Typert artifact generator", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/typert/generator/src/emitter.ts b/packages/typert/generator/src/emitter.ts index 4b036372d6..018184d5b5 100644 --- a/packages/typert/generator/src/emitter.ts +++ b/packages/typert/generator/src/emitter.ts @@ -202,7 +202,7 @@ export class FaceModelEmitter { lines.push(` face: ${quote(this.face.face)},`) lines.push(' schemas: [') for (const schema of schemas.exports) { - lines.push(` { name: ${quote(schema.exportName)}, schema: ${schema.exportName} },`) + lines.push(` { name: ${quote(schema.exportName)}, create: ${schema.exportName} },`) } lines.push(' ],') lines.push(' invocations: [') @@ -232,7 +232,7 @@ export class FaceModelEmitter { } lines.push('') for (const schema of schemas.exports) { - lines.push(`export declare const ${schema.exportName}: z.ZodType<${schema.exportName}$source>`) + lines.push(`export declare const ${schema.exportName}: () => z.ZodType<${schema.exportName}$source>`) } if (schemas.exports.length > 0) lines.push('') // The Loader validates and narrows this generated module boundary before @@ -556,7 +556,8 @@ class SchemaEmitter { emit(): SchemaArtifact { const definitions = this.declarations.map(declaration => this.declarationDefinition(declaration)) for (const boundary of this.boundaries) { - definitions.push(`const ${this.boundaryName(boundary.key)} = ${this.typeSchema(boundary.type)}`) + const name = this.boundaryName(boundary.key) + definitions.push(`let ${name}$value\nconst ${name} = () => (${name}$value ??= ${this.typeSchema(boundary.type)})`) } const exports = this.schemas.map((model): SchemaExport => ({ model, @@ -573,7 +574,7 @@ class SchemaEmitter { private declarationDefinition(declaration: TypeDeclarationModel): string { const name = this.schemaName(declaration.id) if (declaration.typeParameters.length === 0) { - return `const ${name} = ${this.declarationSchema(declaration, new Map())}` + return `let ${name}$value\nconst ${name} = () => (${name}$value ??= ${this.declarationSchema(declaration, new Map())})` } const parameters = declaration.typeParameters.map((parameter, index) => [`type${String(index)}$schema`, parameter.id] as const) @@ -655,7 +656,7 @@ class SchemaEmitter { if (node.arguments.length > 0) { this.fail(node.name, `non-generic declaration received ${String(node.arguments.length)} type arguments`) } - return `z.lazy(() => ${name})` + return `z.lazy(() => ${name}())` } const arguments_ = this.declarationArguments(node, declaration, substitutions) return `z.lazy(() => ${name}(${arguments_.join(', ')}))` @@ -873,7 +874,7 @@ function strictCodec(boundary: RemoteBoundaryModel, schema: string): string { '{', ' mode: \'strict\',', ` typeSymbol: ${quote(boundary.typeSymbol)},`, - ` schema: ${schema},`, + ` create: ${schema},`, '}', ].join('\n') } diff --git a/packages/typert/generator/tests/__snapshots__/type-model.spec.ts.snap b/packages/typert/generator/tests/__snapshots__/type-model.spec.ts.snap index da7510736b..7072e80327 100644 --- a/packages/typert/generator/tests/__snapshots__/type-model.spec.ts.snap +++ b/packages/typert/generator/tests/__snapshots__/type-model.spec.ts.snap @@ -4,10 +4,11 @@ exports[`FaceModelEmitter > emits runnable Zod JavaScript, precise declarations, "/* Generated by @deepseek-ai/dsh-typert-generator from FaceModel — do not edit. */ import { z } from 'zod' -const Payload$schema = z.object({ +let Payload$schema$value +const Payload$schema = () => (Payload$schema$value ??= z.object({ 'name': z.string(), 'count': z.number().optional(), -}).describe('Runtime-validating data root.') +}).describe('Runtime-validating data root.')) export const Payload = Payload$schema @@ -15,7 +16,7 @@ export const TYPERT = { package: '@fixture/host', face: 'host', schemas: [ - { name: 'Payload', schema: Payload }, + { name: 'Payload', create: Payload }, ], invocations: [ ], @@ -321,7 +322,7 @@ exports[`FaceModelEmitter > emits runnable Zod JavaScript, precise declarations, import type { z } from 'zod' import type { Payload as Payload$source } from '@fixture/host' -export declare const Payload: z.ZodType +export declare const Payload: () => z.ZodType export declare const TYPERT: unknown " diff --git a/packages/typert/generator/tests/remote-model.spec.ts b/packages/typert/generator/tests/remote-model.spec.ts index 12ff2f4d07..635e12d334 100644 --- a/packages/typert/generator/tests/remote-model.spec.ts +++ b/packages/typert/generator/tests/remote-model.spec.ts @@ -25,9 +25,9 @@ interface RuntimeDescriptor { readonly parameters: readonly { readonly wire: string readonly acceptsUndefined?: true - readonly codec: { readonly schema: RuntimeSchema } + readonly codec: { readonly create: () => RuntimeSchema } }[] - readonly result: { readonly schema: RuntimeSchema } + readonly result: { readonly create: () => RuntimeSchema } } interface RuntimeRemoteModule { @@ -152,10 +152,10 @@ describe('Remote model generation', { timeout: 60_000 }, () => { expect(generated.TYPERT_REMOTE.package).toBe('@fixture/remote') const create = generated.TYPERT_REMOTE.descriptors[0] expect(create?.cancellation).toEqual({ parameter: 'signal' }) - expect(create?.parameters[1]?.codec.schema.safeParse({ title: 'ship' }).success).toBe(true) - expect(create?.parameters[1]?.codec.schema.safeParse({ title: 1 }).success).toBe(false) - expect(create?.result.schema.safeParse({ ref: 'goal-1' }).success).toBe(true) - expect(create?.result.schema.safeParse({ ref: 1 }).success).toBe(false) + expect(create?.parameters[1]?.codec.create().safeParse({ title: 'ship' }).success).toBe(true) + expect(create?.parameters[1]?.codec.create().safeParse({ title: 1 }).success).toBe(false) + expect(create?.result.create().safeParse({ ref: 'goal-1' }).success).toBe(true) + expect(create?.result.create().safeParse({ ref: 1 }).success).toBe(false) expect(generated.TYPERT_REMOTE.descriptors[2]?.mode).toBe('stream') const declarationMap = JSON.parse(artifact?.remote?.dtsMap ?? '') as RemoteDeclarationMap @@ -208,15 +208,15 @@ export type {`, const maybe = generated.TYPERT_REMOTE.descriptors.find(descriptor => descriptor.id.endsWith('/maybe')) const clear = generated.TYPERT_REMOTE.descriptors.find(descriptor => descriptor.id.endsWith('/clear')) expect(maybe?.parameters[0]?.acceptsUndefined).toBe(true) - expect(maybe?.parameters[0]?.codec.schema.safeParse(undefined).success).toBe(true) - expect(maybe?.result.schema.safeParse(undefined).success).toBe(true) - expect(clear?.result.schema.safeParse(undefined).success).toBe(true) - expect(clear?.result.schema.safeParse(null).success).toBe(false) + expect(maybe?.parameters[0]?.codec.create().safeParse(undefined).success).toBe(true) + expect(maybe?.result.create().safeParse(undefined).success).toBe(true) + expect(clear?.result.create().safeParse(undefined).success).toBe(true) + expect(clear?.result.create().safeParse(null).success).toBe(false) const labelled = generated.TYPERT_REMOTE.descriptors.find(descriptor => descriptor.id.endsWith('/labelled')) expect(labelled?.parameters[0]?.acceptsUndefined).toBeUndefined() expect(labelled?.parameters[1]?.acceptsUndefined).toBe(true) - expect(labelled?.parameters[1]?.codec.schema.safeParse(undefined).success).toBe(true) - expect(labelled?.parameters[1]?.codec.schema.safeParse(7).success).toBe(false) + expect(labelled?.parameters[1]?.codec.create().safeParse(undefined).success).toBe(true) + expect(labelled?.parameters[1]?.codec.create().safeParse(7).success).toBe(false) }) it('evaluates declaration-merged mapped and conditional boundaries for codecs without widening consumer types', async () => { @@ -282,13 +282,13 @@ export type GenericResult = { const executable = remoteJs.replace("from 'zod'", `from ${JSON.stringify(import.meta.resolve('zod'))}`) const generated = await import(`data:text/javascript,${encodeURIComponent(executable)}`) as RuntimeRemoteModule const dispatch = generated.TYPERT_REMOTE.descriptors.find(descriptor => descriptor.id.endsWith('/dispatch')) - const schema = dispatch?.parameters[0]?.codec.schema + const schema = dispatch?.parameters[0]?.codec.create() expect(schema?.safeParse({ kind: 'ship', payload: { count: 2, meta: { nested: [true, null] } } }).success).toBe(true) expect(schema?.safeParse({ kind: 'ship', payload: { count: '2', meta: {} } }).success).toBe(false) expect(schema?.safeParse({ kind: 'cancel', payload: { reason: 'obsolete' } }).success).toBe(true) expect(schema?.safeParse({ kind: 'unknown', payload: {} }).success).toBe(false) - expect(dispatch?.result.schema.safeParse({ kind: 'ship', value: { accepted: true } }).success).toBe(true) - expect(dispatch?.result.schema.safeParse({ kind: 'ship', value: { cancelled: true } }).success).toBe(false) + expect(dispatch?.result.create().safeParse({ kind: 'ship', value: { accepted: true } }).success).toBe(true) + expect(dispatch?.result.create().safeParse({ kind: 'ship', value: { cancelled: true } }).success).toBe(false) }) it('imports public type arguments nested under a named generic boundary', () => { diff --git a/packages/typert/generator/tests/schema-emitter.spec.ts b/packages/typert/generator/tests/schema-emitter.spec.ts index fcc6faa175..7e840dbd73 100644 --- a/packages/typert/generator/tests/schema-emitter.spec.ts +++ b/packages/typert/generator/tests/schema-emitter.spec.ts @@ -964,9 +964,9 @@ async function loadSchema(source: string): Promise<{ safeParse(value: unknown): const path = join(root, 'schema.mjs') writeFileSync(path, source) const generated = await import(`${pathToFileURL(path).href}?test=${Date.now()}-${String(temporaryRoots.length)}`) as { - Root: { safeParse(value: unknown): { success: boolean } } + Root: () => { safeParse(value: unknown): { success: boolean } } } - return generated.Root + return generated.Root() } function distinct(values: readonly string[]): string[] { diff --git a/packages/typert/generator/tests/type-model.spec.ts b/packages/typert/generator/tests/type-model.spec.ts index 686f046097..8afb3abff8 100644 --- a/packages/typert/generator/tests/type-model.spec.ts +++ b/packages/typert/generator/tests/type-model.spec.ts @@ -1605,18 +1605,19 @@ describe('FaceModelEmitter', { timeout: 60_000 }, () => { const modulePath = join(root, 'host.mjs') writeFileSync(modulePath, artifact.js) const generated = await import(`${pathToFileURL(modulePath).href}?test=${Date.now()}`) as { - Payload: { safeParse(value: unknown): { success: boolean } } + Payload: () => { safeParse(value: unknown): { success: boolean } } TYPERT: { package: string face: string - schemas: { name: string; schema: unknown }[] + schemas: { name: string; create: () => unknown }[] model: { services: { key: string; members: { signature: string }[] }[] } } } - expect(generated.Payload.safeParse({ name: 'ready', count: 2 }).success).toBe(true) - expect(generated.Payload.safeParse({ name: 'ready', count: 'two' }).success).toBe(false) + expect(generated.Payload().safeParse({ name: 'ready', count: 2 }).success).toBe(true) + expect(generated.Payload().safeParse({ name: 'ready', count: 'two' }).success).toBe(false) + expect(generated.Payload()).toBe(generated.Payload()) expect(generated.TYPERT).toMatchObject({ package: '@fixture/host', face: 'host' }) - expect(generated.TYPERT.schemas[0]?.schema).toBe(generated.Payload) + expect(generated.TYPERT.schemas[0]?.create).toBe(generated.Payload) const demo = generated.TYPERT.model.services.find(service => service.key === 'demo') expect(demo).toMatchObject({ key: 'demo' }) expect(demo?.members.map(member => member.signature)).toContain( @@ -1631,7 +1632,7 @@ describe('FaceModelEmitter', { timeout: 60_000 }, () => { 'import { Payload } from \'./host.js\'', 'import type { Payload as SourcePayload } from \'@fixture/host\'', 'import type { z } from \'zod\'', - 'const precise: z.ZodType = Payload', + 'const precise: () => z.ZodType = Payload', 'void precise', '', ].join('\n')) diff --git a/packages/typert/loader/README.i18n.yaml b/packages/typert/loader/README.i18n.yaml index 2b3a005df2..88b3683dfb 100644 --- a/packages/typert/loader/README.i18n.yaml +++ b/packages/typert/loader/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/loader/README.md -README.md: 378be98dea5f47e4d6b0a0c70600e2a4c221c80b -README.zh.md: e598127bba12e4057405231fe09199db18f9604d +README.md: c45fbdebe7c2a05491515c3679ca2a3338285ca1 +README.zh.md: 9cb6ae6da5da30292d74e05932cd147090aa4dc8 diff --git a/packages/typert/loader/README.md b/packages/typert/loader/README.md index 378be98dea..c45fbdebe7 100644 --- a/packages/typert/loader/README.md +++ b/packages/typert/loader/README.md @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -With `dsh-typert-loader` mounted, every package that mounts in a Loader composition automatically contributes its generated Typert reflection and schemas to the runtime registry — and withdraws them when the package or the plugin unmounts. Packages without the generated export are skipped, so adding the plugin to any composition is safe. An explicit `packages` list covers plugins nested behind another Loader entry, whose fibers carry no resolvable package specifier. It is a Node-only plugin and needs the config-tree resolution anchor to resolve packages. +With `dsh-typert-loader` mounted, every package that mounts in a Loader composition automatically contributes its generated Typert reflection and schema factories to the runtime registry — and withdraws them when the package or the plugin unmounts. Packages without the generated export are skipped, so adding the plugin to any composition is safe. An explicit `packages` list covers plugins nested behind another Loader entry, whose fibers carry no resolvable package specifier. It is a Node-only plugin and needs the config-tree resolution anchor to resolve packages. ## Table of Contents @@ -44,7 +44,7 @@ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-a ### What gets registered -Each qualifying Loader entry contributes its generated host-face reflection and schemas to the runtime registry. Registration follows the entry lifecycle: it is withdrawn when the entry or the plugin unmounts, and an import that settles after either owner is gone is discarded. +Each qualifying Loader entry contributes its generated host-face reflection and schema factories to the runtime registry. Registration follows the entry lifecycle: it is withdrawn when the entry or the plugin unmounts, and an import that settles after either owner is gone is discarded. ### Observable behavior and failures @@ -66,7 +66,7 @@ The plugin is an incremental scanner mirroring the client-modules node half: eve ### Manifest validation -`validateTypertManifest()` is the module/file boundary: the manifest crosses from a build artifact into the typed registry, so every field is checked. The manifest must name the package that exports it, carry face `host`, hold zod v4 schema instances, and keep well-formed service, event, object, member, type, and documentation records; invocation descriptors must use strict codecs. Every failure names the package and the defect. +`validateTypertManifest()` is the module/file boundary: the manifest crosses from a build artifact into the typed registry, so every field is checked. The manifest must name the package that exports it, carry face `host`, hold schema factories, and keep well-formed service, event, object, member, type, and documentation records; invocation descriptors must use strict codecs with factories. Every failure names the package and the defect. ### Caching and ownership diff --git a/packages/typert/loader/README.zh.md b/packages/typert/loader/README.zh.md index e598127bba..9cb6ae6da5 100644 --- a/packages/typert/loader/README.zh.md +++ b/packages/typert/loader/README.zh.md @@ -9,7 +9,7 @@ kind: "package-reference" ## 概述 -挂载 `dsh-typert-loader` 后,Loader 组合中每个挂载的包都会自动把其生成的 Typert 反射与 schema 贡献给运行时注册表——并在包或本插件卸载时自动撤销。没有该导出的包会被跳过,因此在任何 Loader 组合中挂载它都是安全的。显式 `packages` 用于覆盖嵌套在另一 Loader 配置项之下的插件,这些插件的 fiber 不携带可解析的包说明符。它是仅支持 Node 的插件,需要配置树解析锚点才能解析包。 +挂载 `dsh-typert-loader` 后,Loader 组合中每个挂载的包都会自动把其生成的 Typert 反射与 schema factory 贡献给运行时注册表——并在包或本插件卸载时自动撤销。没有该导出的包会被跳过,因此在任何 Loader 组合中挂载它都是安全的。显式 `packages` 用于覆盖嵌套在另一 Loader 配置项之下的插件,这些插件的 fiber 不携带可解析的包说明符。它是仅支持 Node 的插件,需要配置树解析锚点才能解析包。 ## 目录 @@ -44,7 +44,7 @@ kind: "package-reference" ### 注册什么 -每个符合条件的 Loader 配置项都会把其生成的宿主侧反射与 schema 贡献给运行时注册表。注册跟随配置项生命周期:配置项或本插件卸载时撤销;在配置项或本插件任一方卸载后才结束的导入操作会被丢弃。 +每个符合条件的 Loader 配置项都会把其生成的宿主侧反射与 schema factory 贡献给运行时注册表。注册跟随配置项生命周期:配置项或本插件卸载时撤销;在配置项或本插件任一方卸载后才结束的导入操作会被丢弃。 ### 可观察行为与失败 @@ -66,7 +66,7 @@ kind: "package-reference" ### Manifest 校验 -`validateTypertManifest()` 是模块/文件边界:manifest 从构建产物进入类型化注册表,因此每个字段都会被检查。manifest 必须指名导出它的包、携带 `host` face、持有 zod v4 schema 实例,并保持服务、事件、对象、成员、类型与文档记录格式正确;调用描述符必须使用严格编解码器。每次失败都会指名包与缺陷。 +`validateTypertManifest()` 是模块/文件边界:manifest 从构建产物进入类型化注册表,因此每个字段都会被检查。manifest 必须指名导出它的包、携带 `host` face、持有 schema factory,并保持服务、事件、对象、成员、类型与文档记录格式正确;调用描述符必须使用带 factory 的严格编解码器。每次失败都会指名包与缺陷。 ### 缓存与归属 diff --git a/packages/typert/loader/package.json b/packages/typert/loader/package.json index 74cd3b8aaf..506a9b3bbb 100644 --- a/packages/typert/loader/package.json +++ b/packages/typert/loader/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-typert-loader", "description": "Loader integration for generated Typert package contributions", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/typert/loader/src/index.ts b/packages/typert/loader/src/index.ts index 5dfad5787b..004a2fdc17 100644 --- a/packages/typert/loader/src/index.ts +++ b/packages/typert/loader/src/index.ts @@ -108,8 +108,8 @@ export function validateTypertManifest(pkgName: string, exported: unknown): Type } const schema = value as Record requireString(pkgName, schema, 'name', 'schema') - if (typeof schema.schema !== 'object' || schema.schema === null || !('_zod' in schema.schema)) { - throw new Error(`typert-loader: ${pkgName} TYPERT schema "${schema.name as string}" is not a zod v4 schema instance`) + if (typeof schema.create !== 'function') { + throw new Error(`typert-loader: ${pkgName} TYPERT schema "${schema.name as string}" has no create() factory`) } } const model = requireObject(pkgName, manifest.model, 'TYPERT.model') @@ -273,11 +273,8 @@ function requireStrictCodec(pkgName: string, value: unknown, subject: string): v throw new Error(`typert-loader: ${pkgName} ${subject} must use a strict codec`) } requireString(pkgName, codec, 'typeSymbol', subject) - if (typeof codec.schema !== 'object' - || codec.schema === null - || !('_zod' in codec.schema) - || typeof (codec.schema as { parse?: unknown }).parse !== 'function') { - throw new Error(`typert-loader: ${pkgName} ${subject} is not backed by a zod v4 schema`) + if (typeof codec.create !== 'function') { + throw new Error(`typert-loader: ${pkgName} ${subject} has no create() factory`) } } diff --git a/packages/typert/loader/tests/loader.spec.ts b/packages/typert/loader/tests/loader.spec.ts index 09a626d341..748f005d4a 100644 --- a/packages/typert/loader/tests/loader.spec.ts +++ b/packages/typert/loader/tests/loader.spec.ts @@ -58,11 +58,11 @@ async function writePackage( function typertSource(pkgName: string, entryName: string): string { return [ 'import { z } from \'zod\'', - `export const ${entryName} = z.object({ id: z.string() })`, + `export const ${entryName} = () => z.object({ id: z.string() })`, 'export const TYPERT = {', ` package: '${pkgName}',`, ' face: \'host\',', - ` schemas: [{ name: '${entryName}', schema: ${entryName} }],`, + ` schemas: [{ name: '${entryName}', create: ${entryName} }],`, ' model: { services: [], events: [], objects: [] },', ' invocations: [],', '}', @@ -73,7 +73,7 @@ function typertSource(pkgName: string, entryName: string): string { function invocationTypertSource(pkgName: string): string { return [ 'import { z } from \'zod\'', - 'const Text = z.string()', + 'const Text = () => z.string()', 'export const TYPERT = {', ` package: '${pkgName}',`, ' face: \'host\',', @@ -85,10 +85,10 @@ function invocationTypertSource(pkgName: string): string { ' invocation: { kind: \'direct\' },', ' parameters: [{', ' name: \'request\', wire: \'request\', source: \'json\',', - ` codec: { mode: 'strict', typeSymbol: '${pkgName}/types#Request', schema: Text },`, + ` codec: { mode: 'strict', typeSymbol: '${pkgName}/types#Request', create: Text },`, ' }],', " cancellation: { parameter: 'signal' },", - ` result: { mode: 'strict', typeSymbol: '${pkgName}/types#Result', schema: Text },`, + ` result: { mode: 'strict', typeSymbol: '${pkgName}/types#Result', create: Text },`, ' sourceLocation: { file: \'src/index.ts\', line: 8, column: 3 },', ' }],', '}', @@ -168,7 +168,7 @@ describe('typert loader', () => { }) expect(descriptor?.parameters[0]?.codec.mode).toBe('strict') if (descriptor?.parameters[0]?.codec.mode === 'strict') { - expect(descriptor.parameters[0].codec.schema.parse('request')).toBe('request') + expect(descriptor.parameters[0].codec.create().parse('request')).toBe('request') } await fiber.dispose() @@ -289,11 +289,11 @@ describe('typert loader', () => { 'import { z } from \'zod\'', 'globalThis.__dshTypertLoaderGate.started()', 'await globalThis.__dshTypertLoaderGate.wait', - 'export const Pending = z.object({ id: z.string() })', + 'export const Pending = () => z.object({ id: z.string() })', 'export const TYPERT = {', ' package: \'@fixture/pending\',', ' face: \'host\',', - ' schemas: [{ name: \'Pending\', schema: Pending }],', + ' schemas: [{ name: \'Pending\', create: Pending }],', ' model: { services: [], events: [], objects: [] },', ' invocations: [],', '}', @@ -329,7 +329,7 @@ describe('typert loader', () => { root = await mkdtemp(join(tmpdir(), 'dsh-typert-loader-')) await linkZod(root) await writePackage(root, '@fixture/broken', { - typertSource: 'export const TYPERT = { package: \'@fixture/broken\', face: \'host\', schemas: [{ name: \'\', schema: {} }], model: { services: [], events: [], objects: [] }, invocations: [] }\n', + typertSource: 'export const TYPERT = { package: \'@fixture/broken\', face: \'host\', schemas: [{ name: \'\', create: {} }], model: { services: [], events: [], objects: [] }, invocations: [] }\n', }) const ctx = await boot() await ctx.loader.create({ name: '@fixture/broken' }) @@ -443,7 +443,7 @@ describe('validateTypertManifest', () => { expect(validateTypertManifest('pkg', { package: 'pkg', face: 'host', - schemas: [{ name: 'A', schema: zodish }], + schemas: [{ name: 'A', create: () => zodish }], model: { services: [], events: [], objects: [] }, invocations: [], }).schemas).toHaveLength(1) @@ -453,10 +453,10 @@ describe('validateTypertManifest', () => { expect(() => validateTypertManifest('pkg', { package: 'pkg', face: 'client' })).toThrow('TYPERT.face is not "host"') expect(() => validateTypertManifest('pkg', { package: 'pkg', face: 'host', schemas: 'x' })).toThrow('schemas must be an array') expect(() => validateTypertManifest('pkg', { package: 'pkg', face: 'host', schemas: [null] })).toThrow('non-object schema') - expect(() => validateTypertManifest('pkg', { package: 'pkg', face: 'host', schemas: [{ name: '', schema: zodish }] })) + expect(() => validateTypertManifest('pkg', { package: 'pkg', face: 'host', schemas: [{ name: '', create: () => zodish }] })) .toThrow('missing or empty name') - expect(() => validateTypertManifest('pkg', { package: 'pkg', face: 'host', schemas: [{ name: 'A', schema: {} }] })) - .toThrow('not a zod v4 schema instance') + expect(() => validateTypertManifest('pkg', { package: 'pkg', face: 'host', schemas: [{ name: 'A', create: {} }] })) + .toThrow('has no create() factory') expect(() => validateTypertManifest('pkg', { package: 'pkg', face: 'host', @@ -572,8 +572,8 @@ describe('validateTypertManifest', () => { })).toThrow('cancellation parameter must be "signal"') expect(() => validateTypertManifest('pkg', { ...base, - invocations: [{ ...descriptor, result: { mode: 'strict', typeSymbol: 'pkg#Result', schema: zodish } }], - })).toThrow('result codec is not backed by a zod v4 schema') + invocations: [{ ...descriptor, result: { mode: 'strict', typeSymbol: 'pkg#Result', create: zodish } }], + })).toThrow('result codec has no create() factory') expect(() => validateTypertManifest('pkg', { ...base, invocations: [{ @@ -667,7 +667,7 @@ describe('validateTypertManifest', () => { }) function strictCodec(typeSymbol: string) { - return { mode: 'strict', typeSymbol, schema: z.string() } + return { mode: 'strict', typeSymbol, create: () => z.string() } } function strictInvocation() { @@ -694,7 +694,7 @@ function completeManifest(zodish: object) { return { package: 'pkg', face: 'host', - schemas: [{ name: 'Schema', schema: zodish }], + schemas: [{ name: 'Schema', create: () => zodish }], invocations: [], model: { services: [{ diff --git a/packages/typert/protocol/README.i18n.yaml b/packages/typert/protocol/README.i18n.yaml index f277e5eef6..f2fe668d10 100644 --- a/packages/typert/protocol/README.i18n.yaml +++ b/packages/typert/protocol/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/protocol/README.md -README.md: 43147c2c2f1746ff0c806c0842b5dba65457775a -README.zh.md: c88432ff3587cba1a3222a3a4d75cd8fe0e2d514 +README.md: 99883e322bf6e528f8fd4effe9c4fc1e418abbdb +README.zh.md: 429c08587a5297e254ac4c0f3fb9d82fc680a455 diff --git a/packages/typert/protocol/README.md b/packages/typert/protocol/README.md index 43147c2c2f..99883e322b 100644 --- a/packages/typert/protocol/README.md +++ b/packages/typert/protocol/README.md @@ -91,7 +91,7 @@ The merge-extensible protocol maps keep static associations in the type system, ### Wire identity grammar -Every namespace, method, lookup, and Context segment must satisfy `isTypertRemoteSegment()`, so generated names cross the shared RPC carrier unchanged. Strict codecs carry generated schemas; `src-json` codecs identify the weaker source-launch path. +Every namespace, method, lookup, and Context segment must satisfy `isTypertRemoteSegment()`, so generated names cross the shared RPC carrier unchanged. Strict codecs carry generated schema factories; `src-json` codecs identify the weaker source-launch path. ### Source map diff --git a/packages/typert/protocol/README.zh.md b/packages/typert/protocol/README.zh.md index c88432ff35..429c08587a 100644 --- a/packages/typert/protocol/README.zh.md +++ b/packages/typert/protocol/README.zh.md @@ -91,7 +91,7 @@ Host 装配以转发给消费方的 Cordis 事件扩展 `TypertRemoteEventSelect ### Wire 标识文法 -每个命名空间、方法、查找与 Context 段都必须满足 `isTypertRemoteSegment()`,生成的名字才能原样跨共享 RPC 载体传输。严格编解码器携带生成的 schema;`src-json` 编解码器标识约束更弱的源码启动路径。 +每个命名空间、方法、查找与 Context 段都必须满足 `isTypertRemoteSegment()`,生成的名字才能原样跨共享 RPC 载体传输。严格编解码器携带生成的 schema factory;`src-json` 编解码器标识约束更弱的源码启动路径。 ### 源码地图 diff --git a/packages/typert/protocol/package.json b/packages/typert/protocol/package.json index 199936fcc5..34b474a4c9 100644 --- a/packages/typert/protocol/package.json +++ b/packages/typert/protocol/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-typert-protocol", "description": "Compiler-independent Remote metadata and Typert provider protocols", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/typert/protocol/src/types.ts b/packages/typert/protocol/src/types.ts index b0e7689c4b..d1fcfcb94b 100644 --- a/packages/typert/protocol/src/types.ts +++ b/packages/typert/protocol/src/types.ts @@ -223,7 +223,8 @@ export type TypertCodec = | { readonly mode: 'strict' readonly typeSymbol: string - readonly schema: TypertSchema + /** Materialize and return the process-realm schema on first boundary use. */ + readonly create: () => TypertSchema } | { readonly mode: 'src-json' diff --git a/packages/typert/registry/README.i18n.yaml b/packages/typert/registry/README.i18n.yaml index 89238fa0e3..d2b1d12036 100644 --- a/packages/typert/registry/README.i18n.yaml +++ b/packages/typert/registry/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/registry/README.md -README.md: c1ab98ae8ebe720a29eb4fb339f39e00ce593120 -README.zh.md: 7bf90ff8fd02c4fb17c4f57d1af251a7a4bd3116 +README.md: 75a3d2bc495db97067818fe9c16e835e8e1f98bc +README.zh.md: b47d735c17ca4e51f41c82916a4b402ddf84e853 diff --git a/packages/typert/registry/README.md b/packages/typert/registry/README.md index c1ab98ae8e..75a3d2bc49 100644 --- a/packages/typert/registry/README.md +++ b/packages/typert/registry/README.md @@ -1,5 +1,5 @@ --- -description: "The runtime Typert registry: stores generated package reflection, live Zod schemas, and Remote invocation descriptors, and resolves them for consumers." +description: "The runtime Typert registry: stores generated package reflection, lazy Zod schema factories, and Remote invocation descriptors, and resolves them for consumers." kind: "package-reference" --- @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -`dsh-typert-registry` makes generated Typert artifacts queryable at runtime: each package's reflection — services, events, and objects — its live Zod schemas, and Remote invocation descriptors live under stable keys that consumers can query or resolve on demand. Registrations are atomic and fiber-scoped: a contribution lands whole or not at all and is withdrawn automatically when the registering component unloads. The same service hosts the lookup and scoped-Context provider registries that Remote calls resolve through. It performs no TypeScript analysis and generates no schemas; the generator and the loader handle those. +`dsh-typert-registry` makes generated Typert artifacts queryable at runtime: each package's reflection, lazy Zod schema factories, and Remote invocation descriptors live under stable keys. A schema is materialized and cached when a consumer first requests it. Registrations are atomic and fiber-scoped: a contribution lands whole or not at all and is withdrawn automatically when the registering component unloads. The same service hosts the lookup and scoped-Context provider registries that Remote calls resolve through. It performs no TypeScript analysis and generates no schemas; the generator and the loader handle those. ## Table of Contents @@ -37,7 +37,7 @@ Load the registry plugin; the Client face is installed the same way by the Clien ### Querying schemas and reflection -Consumers read schemas with `get(key)`, `resolve(key)`, or `list(filter?)` and package reflection with `getPackage(name, face?)` or `listPackages(filter?)`. `resolve()` distinguishes a malformed key, an absent package, and a registered package that contributes no schema under that name, each with its own error. `toJSONSchema(key)` projects a live Zod schema to JSON Schema without caching. +Consumers read schemas with `get(key)`, `resolve(key)`, or `list(filter?)` and package reflection with `getPackage(name, face?)` or `listPackages(filter?)`. The first schema read materializes one process-realm instance; later reads reuse it. `resolve()` distinguishes a malformed key, an absent package, and a registered package that contributes no schema under that name, each with its own error. `toJSONSchema(key)` projects the materialized Zod schema to JSON Schema without caching the projection. ### Registering a contribution @@ -72,7 +72,7 @@ Each sub-registry publishes `TypertRegistryChange` events to subscribed listener ### Identity and validation -Keys are stable: `#` for reflection, `#` for schemas, and `/` for endpoints. Validation rejects names containing `#`, wire names outside the RPC segment grammar, duplicate keys, and lookup definitions whose wire declaration changes during the registry lifetime; strict codecs must carry a parseable schema. +Keys are stable: `#` for reflection, `#` for schemas, and `/` for endpoints. Validation rejects names containing `#`, wire names outside the RPC segment grammar, duplicate keys, and lookup definitions whose wire declaration changes during the registry lifetime; schema entries and strict codecs must carry a factory. ### Source map diff --git a/packages/typert/registry/README.zh.md b/packages/typert/registry/README.zh.md index 7bf90ff8fd..b47d735c17 100644 --- a/packages/typert/registry/README.zh.md +++ b/packages/typert/registry/README.zh.md @@ -1,5 +1,5 @@ --- -description: "运行时 Typert 注册表:保存生成的包反射、实时 Zod schema 与 Remote 调用描述符,并按需为消费方解析。" +description: "运行时 Typert 注册表:保存生成的包反射、惰性 Zod schema factory 与 Remote 调用描述符,并按需为消费方解析。" kind: "package-reference" --- @@ -9,7 +9,7 @@ kind: "package-reference" ## 概述 -`dsh-typert-registry` 让生成的 Typert 产物在运行时可按需查询:每个包的反射——服务、事件与对象——其实时 Zod schema 与 Remote 调用描述符都保存在稳定键下,消费方可以按需查询或解析。注册是原子且按 fiber 作用域的:贡献要么整体落地要么完全不落地,并在注册组件卸载时自动撤销。同一服务还托管 Remote 调用所经由的 lookup 与作用域 Context 提供方注册表。它不执行 TypeScript 分析,也不生成 schema;这些由生成器与 loader 负责。 +`dsh-typert-registry` 让生成的 Typert 产物在运行时可按需查询:每个包的反射、惰性 Zod schema factory 与 Remote 调用描述符都保存在稳定键下。消费方首次请求 schema 时才会物化并缓存它。注册是原子且按 fiber 作用域的:贡献要么整体落地要么完全不落地,并在注册组件卸载时自动撤销。同一服务还托管 Remote 调用所经由的 lookup 与作用域 Context 提供方注册表。它不执行 TypeScript 分析,也不生成 schema;这些由生成器与 loader 负责。 ## 目录 @@ -37,7 +37,7 @@ kind: "package-reference" ### 查询 schema 与反射 -消费方用 `get(key)`、`resolve(key)` 或 `list(filter?)` 读取 schema,用 `getPackage(name, face?)` 或 `listPackages(filter?)` 读取包反射。`resolve()` 能区分格式错误的键、未注册的包,以及已注册但未以该名称提供 schema 的包,各自给出不同的错误。`toJSONSchema(key)` 把实时 Zod schema 投影为 JSON Schema,且不缓存结果。 +消费方用 `get(key)`、`resolve(key)` 或 `list(filter?)` 读取 schema,用 `getPackage(name, face?)` 或 `listPackages(filter?)` 读取包反射。首次读取会物化一个进程 realm 内的 schema 实例,后续读取复用该实例。`resolve()` 能区分格式错误的键、未注册的包,以及已注册但未以该名称提供 schema 的包,各自给出不同的错误。`toJSONSchema(key)` 把已物化的 Zod schema 投影为 JSON Schema,但不缓存该投影。 ### 注册贡献 @@ -72,7 +72,7 @@ Remote 调用通过 `ctx.typert.lookups` 与 `ctx.typert.contexts` 解析 Host ### 标识与校验 -键是稳定的:反射用 `#`,schema 用 `#`,端点用 `/`。校验会拒绝含 `#` 的名称、超出 RPC 端点段文法的 wire 名称、重复键,以及在其注册表生命周期内改变 wire 声明的 lookup 定义;严格编解码器必须携带可解析的 schema。 +键是稳定的:反射用 `#`,schema 用 `#`,端点用 `/`。校验会拒绝含 `#` 的名称、超出 RPC 端点段文法的 wire 名称、重复键,以及在其注册表生命周期内改变 wire 声明的 lookup 定义;schema 条目与严格编解码器必须携带 factory。 ### 源码地图 diff --git a/packages/typert/registry/package.json b/packages/typert/registry/package.json index 307410ed20..c40c0c6a5a 100644 --- a/packages/typert/registry/package.json +++ b/packages/typert/registry/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-typert-registry", "description": "Runtime registry for generated package reflection and Zod schemas", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/typert/registry/src/service.ts b/packages/typert/registry/src/service.ts index 20eaa32b7c..73c772fdd0 100644 --- a/packages/typert/registry/src/service.ts +++ b/packages/typert/registry/src/service.ts @@ -35,6 +35,7 @@ import type { TypertFace, TypertPackageFilter, TypertPackageRecord, + TypertSchemaFactory, TypertSchemaFilter, TypertSchemaRecord, } from './types.ts' @@ -444,7 +445,7 @@ interface HostContextResolverEntry { * @typert service typert */ export class TypertRegistry extends Service implements TypertRegistryContract { - private readonly schemas = new Map() + private readonly schemas = new Map() private readonly packages = new Map() private readonly localStore: DescriptorStore private readonly remoteStore: RemoteStore @@ -522,21 +523,22 @@ export class TypertRegistry extends Service implements TypertRegistryContract { /** * Look up one schema by `#`. * @param key - global schema key. - * @returns the live schema record, or `undefined` when absent. + * @returns a record containing the cached schema, or `undefined` when absent. */ get(key: string): TypertSchemaRecord | undefined { - return this.schemas.get(key) + const record = this.schemas.get(key) + return record === undefined ? undefined : materializeSchema(record) } /** * Resolve one required schema. * @param key - global schema key. - * @returns the live schema record. + * @returns a record containing the cached schema. * @throws when the key is malformed, the package face is absent, or the schema is not contributed. */ resolve(key: string): TypertSchemaRecord { const record = this.schemas.get(key) - if (record !== undefined) return record + if (record !== undefined) return materializeSchema(record) const hash = key.indexOf('#') if (hash <= 0 || hash === key.length - 1) { throw new Error(`typert: invalid schema key "${key}" — expected "#"`) @@ -553,10 +555,10 @@ export class TypertRegistry extends Service implements TypertRegistryContract { /** * Enumerate live schemas in registration order. * @param filter - optional package and face restriction. - * @returns matching schema records. + * @returns matching records containing the cached schemas. */ list(filter: TypertSchemaFilter = {}): TypertSchemaRecord[] { - return [...this.schemas.values()].filter(record => matches(record, filter)) + return [...this.schemas.values()].filter(record => matches(record, filter)).map(materializeSchema) } /** @@ -606,11 +608,14 @@ export class TypertRegistry extends Service implements TypertRegistryContract { } } - private validateSchemas(contribution: TypertContribution): TypertSchemaRecord[] { - const records: TypertSchemaRecord[] = [] + private validateSchemas(contribution: TypertContribution): TypertSchemaFactoryRecord[] { + const records: TypertSchemaFactoryRecord[] = [] const batch = new Set() for (const schema of contribution.schemas) { validateSegment('schema name', schema.name) + if (typeof schema.create !== 'function') { + throw new Error(`typert: schema "${schema.name}" has no create() factory`) + } const key = typertKey(contribution.package, schema.name) if (batch.has(key) || this.schemas.has(key)) { throw new Error(`typert: schema "${key}" is already registered`) @@ -627,6 +632,24 @@ export class TypertRegistry extends Service implements TypertRegistryContract { } } +interface TypertSchemaFactoryRecord extends TypertSchemaFactory { + readonly package: string + readonly face: TypertFace + readonly key: string + value?: z.ZodType +} + +function materializeSchema(record: TypertSchemaFactoryRecord): TypertSchemaRecord { + const schema = record.value ??= record.create() + return { + name: record.name, + schema, + package: record.package, + face: record.face, + key: record.key, + } +} + function matches( record: { readonly package: string; readonly face: TypertFace }, filter: { readonly package?: string; readonly face?: TypertFace }, @@ -697,8 +720,8 @@ function validateInvocation(descriptor: InvocationDescriptor): void { function validateCodec(codec: InvocationDescriptor['result'], subject: string): void { if (codec.mode === 'src-json') return validateNonempty(`${subject} type symbol`, codec.typeSymbol) - if (typeof codec.schema.parse !== 'function') { - throw new Error(`typert: ${subject} strict codec has no parse() method`) + if (typeof codec.create !== 'function') { + throw new Error(`typert: ${subject} strict codec has no create() factory`) } } diff --git a/packages/typert/registry/src/types.ts b/packages/typert/registry/src/types.ts index 2450ea2331..101f4a3bcc 100644 --- a/packages/typert/registry/src/types.ts +++ b/packages/typert/registry/src/types.ts @@ -71,24 +71,27 @@ export interface TypertPackageModel { readonly objects: readonly TypertObjectModel[] } -/** One generated live Zod schema. */ -export interface TypertSchema { +/** One generated Zod schema factory. */ +export interface TypertSchemaFactory { readonly name: string - readonly schema: z.ZodType + /** Materialize and return the process-realm schema on first use. */ + readonly create: () => z.ZodType } /** One generated package contribution registered and withdrawn atomically. */ export interface TypertContribution { readonly package: string readonly face: TypertFace - readonly schemas: readonly TypertSchema[] + readonly schemas: readonly TypertSchemaFactory[] readonly model: TypertPackageModel /** Host invocation definitions, empty when the package exports no Remote methods. */ readonly invocations: readonly InvocationDescriptor[] } /** A live schema plus its contribution identity. */ -export interface TypertSchemaRecord extends TypertSchema { +export interface TypertSchemaRecord { + readonly name: string + readonly schema: z.ZodType readonly package: string readonly face: TypertFace readonly key: string diff --git a/packages/typert/registry/tests/typert.spec.ts b/packages/typert/registry/tests/typert.spec.ts index 73e0862d2e..936ed5c279 100644 --- a/packages/typert/registry/tests/typert.spec.ts +++ b/packages/typert/registry/tests/typert.spec.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from 'vitest' +import { describe, expect, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' import { z } from 'zod' import TypertRegistry, { @@ -36,7 +36,7 @@ function toolsContribution(schema: z.ZodType = z.object({ name: z.string() })): return { package: '@deepseek-ai/dsh-tools', face: 'host', - schemas: [{ name: 'ToolInput', schema }], + schemas: [{ name: 'ToolInput', create: () => schema }], invocations: [], model: { services: [{ @@ -111,7 +111,7 @@ describe('TypertRegistry', () => { face: 'host', name: 'ToolInput', }) - expect(ctx.typert.get('@deepseek-ai/dsh-tools#ToolInput')?.schema).toBe(contribution.schemas[0]?.schema) + expect(ctx.typert.get('@deepseek-ai/dsh-tools#ToolInput')?.schema).toBe(contribution.schemas[0]?.create()) expect(ctx.typert.getPackage('@deepseek-ai/dsh-tools', 'host')).toMatchObject({ key: '@deepseek-ai/dsh-tools#host', model: { services: [{ key: 'tools' }] }, @@ -153,14 +153,14 @@ describe('TypertRegistry', () => { ctx.typert.register(original) expect(() => ctx.typert.register(toolsContribution(z.never()))).toThrow('package face') - expect(ctx.typert.get('@deepseek-ai/dsh-tools#ToolInput')?.schema).toBe(original.schemas[0]?.schema) + expect(ctx.typert.get('@deepseek-ai/dsh-tools#ToolInput')?.schema).toBe(original.schemas[0]?.create()) const duplicateBatch: TypertContribution = { ...toolsContribution(), package: '@fixture/duplicate', schemas: [ - { name: 'Same', schema: z.string() }, - { name: 'Same', schema: z.number() }, + { name: 'Same', create: () => z.string() }, + { name: 'Same', create: () => z.number() }, ], } expect(() => ctx.typert.register(duplicateBatch)).toThrow('schema "@fixture/duplicate#Same" is already registered') @@ -180,7 +180,7 @@ describe('TypertRegistry', () => { expect(() => ctx.typert.register({ ...toolsContribution(), package: '@fixture/schema-name', - schemas: [{ name: 'bad#name', schema: z.string() }], + schemas: [{ name: 'bad#name', create: () => z.string() }], })).toThrow('invalid schema name') expect(ctx.typert.list({ package: '@fixture/absent' })).toEqual([]) @@ -191,9 +191,15 @@ describe('TypertRegistry', () => { it('resolves required schemas and projects fresh JSON Schema documents', async () => { const ctx = await makeCtx() - ctx.typert.register(toolsContribution()) + const schema = z.object({ name: z.string() }) + const create = vi.fn(() => schema) + ctx.typert.register({ ...toolsContribution(), schemas: [{ name: 'ToolInput', create }] }) + expect(create).not.toHaveBeenCalled() expect(ctx.typert.resolve('@deepseek-ai/dsh-tools#ToolInput').name).toBe('ToolInput') + expect(create).toHaveBeenCalledOnce() + expect(ctx.typert.resolve('@deepseek-ai/dsh-tools#ToolInput').schema).toBe(schema) + expect(create).toHaveBeenCalledOnce() expect(() => ctx.typert.resolve('@deepseek-ai/dsh-tools#Missing')).toThrow('contributes no schema named "Missing"') expect(() => ctx.typert.resolve('@fixture/absent#Value')).toThrow('has no registered contribution') expect(() => ctx.typert.resolve('invalid')).toThrow('expected "#"') @@ -483,7 +489,7 @@ describe('TypertRegistry', () => { const strict = { mode: 'strict' as const, typeSymbol: '@fixture#Value', - schema: z.string(), + create: () => z.string(), } const strictInvocation: InvocationDescriptor = { ...invocation('@fixture/remote#strict'), @@ -539,12 +545,12 @@ describe('TypertRegistry', () => { }, 'repeats wire field'], [{ ...invocation(), - result: { mode: 'strict', typeSymbol: '', schema: z.string() }, + result: { mode: 'strict', typeSymbol: '', create: () => z.string() }, }, 'type symbol'], [{ ...invocation(), - result: { mode: 'strict', typeSymbol: '@fixture#Broken', schema: {} as z.ZodType }, - }, 'has no parse'], + result: { mode: 'strict', typeSymbol: '@fixture#Broken', create: {} as () => z.ZodType }, + }, 'has no create'], ] for (const [index, [descriptor, message]] of malformed.entries()) { expect(() => ctx.typert.remotes.register({ diff --git a/packages/util/README.i18n.yaml b/packages/util/README.i18n.yaml index 13c71abe9b..2499f0a247 100644 --- a/packages/util/README.i18n.yaml +++ b/packages/util/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/README.md -README.md: c6fd72b85fe302392c41818263396c316f987422 -README.zh.md: a4e9265cbc3705d30e86a8d571f9ed17370119fc +README.md: a51f8304e621e1c4fba79e5492c8eddc4d786a8e +README.zh.md: d2b8734543a7332b8a18fd9202c6fcf3b9736af9 diff --git a/packages/util/README.md b/packages/util/README.md index c6fd72b85f..a51f8304e6 100644 --- a/packages/util/README.md +++ b/packages/util/README.md @@ -36,6 +36,7 @@ Each package provides one primitive; open a package page for how to use it. | [`http-proxy/`](http-proxy/README.md) | Resolves one outbound proxy policy and installs it for `fetch`, SDK agents, and spawned children | | [`launch-environment/`](launch-environment/README.md) | Frozen launch environment that remembers which layer supplied each value | | [`atomic-write/`](atomic-write/README.md) | Atomic file replacement and cross-process writer locking | +| [`lazy-require/`](lazy-require/README.md) | Loads CommonJS-compatible Host dependencies on first use with caller-relative resolution | | [`native-command/`](native-command/README.md) | Runs host-native commands directly, never through a shell string | | [`workspace-path/`](workspace-path/README.md) | Provides browser-safe Workspace path and display helpers | | [`output-retention/`](output-retention/README.md) | Bounds model-facing output and reports exact omission metadata | diff --git a/packages/util/README.zh.md b/packages/util/README.zh.md index a4e9265cbc..d2b8734543 100644 --- a/packages/util/README.zh.md +++ b/packages/util/README.zh.md @@ -36,6 +36,7 @@ kind: "package-group" | [`http-proxy/`](http-proxy/README.zh.md) | 解析出唯一的出站代理策略,并为 `fetch`、SDK agent(智能体)与 spawn 的子进程安装它 | | [`launch-environment/`](launch-environment/README.zh.md) | 冻结的启动环境,记住每个值来自哪一层 | | [`atomic-write/`](atomic-write/README.zh.md) | 原子文件替换与跨进程写锁 | +| [`lazy-require/`](lazy-require/README.zh.md) | 在首次使用时按调用方位置加载兼容 CommonJS 的 Host 依赖 | | [`native-command/`](native-command/README.zh.md) | 直接运行宿主原生命令,绝不拼 shell 字符串 | | [`workspace-path/`](workspace-path/README.zh.md) | 提供浏览器安全的 Workspace 路径与显示辅助函数 | | [`output-retention/`](output-retention/README.zh.md) | 限制面向模型的输出并报告精确的省略元数据 | diff --git a/packages/util/atomic-write/package.json b/packages/util/atomic-write/package.json index 7aa769a1ba..309147ebf3 100644 --- a/packages/util/atomic-write/package.json +++ b/packages/util/atomic-write/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-atomic-write", "description": "Zero-dependency atomic file replacement: exclusive-create random-suffix temp + rename carrying the caller-stated permissions (writeFileAtomic)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/util/brand/package.json b/packages/util/brand/package.json index b108caf9cc..af463d13f0 100644 --- a/packages/util/brand/package.json +++ b/packages/util/brand/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-brand", "description": "Stateless branded primitive types for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/util/chunked-list/package.json b/packages/util/chunked-list/package.json index e93ccec925..25138d7289 100644 --- a/packages/util/chunked-list/package.json +++ b/packages/util/chunked-list/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-chunked-list", "description": "Persistent append-only chunked lists with bounded copying and JSON checkpoint validation", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/util/crypto/package.json b/packages/util/crypto/package.json index 3d1fb4e136..cffcc690ee 100644 --- a/packages/util/crypto/package.json +++ b/packages/util/crypto/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-util-crypto", "description": "Zero-dependency browser-safe UUID and byte-encoding helpers", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/util/deque/package.json b/packages/util/deque/package.json index 37f6d8d589..d037ed3513 100644 --- a/packages/util/deque/package.json +++ b/packages/util/deque/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-deque", "description": "Zero-dependency circular deque with amortized constant-time end operations and bounded vacant storage", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/util/home-paths/package.json b/packages/util/home-paths/package.json index 020b85dce1..1fdddd572b 100644 --- a/packages/util/home-paths/package.json +++ b/packages/util/home-paths/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-home-paths", "description": "Shared filesystem path helpers for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/util/http-proxy/package.json b/packages/util/http-proxy/package.json index 87d0ddcc77..60706d88b8 100644 --- a/packages/util/http-proxy/package.json +++ b/packages/util/http-proxy/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-http-proxy", "description": "Process-wide outbound HTTP proxy policy for DeepSeek Harness: resolve it from the launch environment and install it as undici's global dispatcher", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/util/launch-environment/package.json b/packages/util/launch-environment/package.json index 36f1f4be95..c518babad2 100644 --- a/packages/util/launch-environment/package.json +++ b/packages/util/launch-environment/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-launch-environment", "description": "Immutable DeepSeek Harness launch environment that records which layer supplied each value", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/util/lazy-require/README.i18n.yaml b/packages/util/lazy-require/README.i18n.yaml new file mode 100644 index 0000000000..6b42906dd7 --- /dev/null +++ b/packages/util/lazy-require/README.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write packages/util/lazy-require/README.md +README.md: d75a7626da61522939f17f6caec1f5d14e6da6c6 +README.zh.md: 7d8e66f15957a990ae37ed04b627dd4a4f147fa0 diff --git a/packages/util/lazy-require/README.md b/packages/util/lazy-require/README.md new file mode 100644 index 0000000000..d75a7626da --- /dev/null +++ b/packages/util/lazy-require/README.md @@ -0,0 +1,88 @@ +--- +description: "Caller-relative lazy loading for CommonJS-compatible Host dependencies whose initialization is not needed during application startup." +kind: "package-library" +--- + +# @deepseek-ai/dsh-lazy-require + +English | [中文](README.zh.md) + +## Summary + +`dsh-lazy-require` keeps a CommonJS-compatible Host dependency unloaded until its first real operation. Resolution remains relative to the consuming package, and one successful module value is reused for the rest of the process realm. + +## Table of Contents + +- [Use this package](#use-this-package) +- [Understand the implementation](#understand-the-implementation) +- [Further Exploration](#further-exploration) +- [Model Experience](#model-experience) +- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work) +- [Dev Note](#dev-note) + +----- + + +## Use this package + +Pass the dependency's literal specifier and the caller's `import.meta.url`: + +```ts +import { createLazyRequire } from '@deepseek-ai/dsh-lazy-require' + +interface NativeModule { open(): void } +const requireNative = createLazyRequire('native-package', import.meta.url) +``` + +Calling `requireNative()` loads the dependency once. A failed load is not cached. + +----- + + +## Understand the implementation + +
+Implementation internals — click to expand + +The utility creates Node's `require` from the supplied caller URL and caches only a successful return value. The explicit caller URL preserves package-local dependency resolution after publication. + +### Source map + +| File | Role | +|---|---| +| [`src/index.ts`](src/index.ts) | Caller-relative loader and successful-result cache | + +
+ +----- + + +## Further Exploration + +- [Utility package map](../README.md) — adjacent shared primitives. +- [NPM release sequences](../../../.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md) — published dependency classification and first-use loading policy. + +----- + + +## Model Experience + +None, as this Host utility registers no model-facing behavior. + +#### KV Cache effect + +None. + +## Known Limitations and Deferred Work + + + +- **CommonJS-compatible dependencies only** — ESM-only packages require an asynchronous factory owned by their caller. +- **WebWorker packaging needs an explicit request** — the static packer does not discover dependencies named only in `createLazyRequire()` calls. A package used in a Preview image must keep the dependency reachable through a supported literal request until the packer recognizes this helper. + +No runtime invariant companion is published because the loader holds no independently observable mutable relationship. + + +### Dev Note + +None. diff --git a/packages/util/lazy-require/README.zh.md b/packages/util/lazy-require/README.zh.md new file mode 100644 index 0000000000..7d8e66f159 --- /dev/null +++ b/packages/util/lazy-require/README.zh.md @@ -0,0 +1,88 @@ +--- +description: "按调用方解析并惰性加载 CommonJS 兼容的 Host 依赖,使应用启动时不初始化尚未使用的依赖。" +kind: "package-library" +--- + +# @deepseek-ai/dsh-lazy-require + +[English](README.md) | 中文 + +## 概述 + +`dsh-lazy-require` 会让 CommonJS 兼容的 Host 依赖保持未加载状态,直到首次实际操作。解析仍以消费方 package 为基准,同一进程 realm 会复用一次成功加载的模块值。 + +## 目录 + +- [使用本包](#use-this-package) +- [理解实现](#understand-the-implementation) +- [进一步探索](#further-exploration) +- [模型体验](#model-experience) +- [已知限制与延期工作](#known-limitations-and-deferred-work) +- [开发备注](#dev-note) + +----- + + +## 使用本包 + +传入依赖的字面量 specifier 与调用方的 `import.meta.url`: + +```ts +import { createLazyRequire } from '@deepseek-ai/dsh-lazy-require' + +interface NativeModule { open(): void } +const requireNative = createLazyRequire('native-package', import.meta.url) +``` + +调用 `requireNative()` 时才加载依赖,并只加载一次。失败的加载不会被缓存。 + +----- + + +## 理解实现 + +
+实现细节——点击展开 + +本工具使用传入的调用方 URL 创建 Node `require`,并且只缓存成功返回的值。显式调用方 URL 会在发布后保留 package 局部的依赖解析。 + +### 源码地图 + +| 文件 | 职责 | +|---|---| +| [`src/index.ts`](src/index.ts) | 按调用方解析的 loader 与成功结果缓存 | + +
+ +----- + + +## 进一步探索 + +- [工具包地图](../README.zh.md)——相邻的共享原语。 +- [NPM 发布序列](../../../.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md)——发布依赖分类与首次使用加载策略。 + +----- + + +## 模型体验 + +无,因为本 Host 工具不注册任何模型可见行为。 + +#### KV Cache 影响 + +无。 + +## 已知限制与延期工作 + + + +- **仅限兼容 CommonJS 的依赖**——仅 ESM package 需要由调用方拥有异步 factory。 +- **WebWorker 打包需要显式请求**——静态 packer 无法发现仅在 `createLazyRequire()` 调用中命名的依赖。Preview image 使用的 package 必须通过受支持的字面量请求保持该依赖可达,直到 packer 能够识别此 helper。 + +本包不发布运行时 invariant companion,因为 loader 不持有可独立观测的可变关系。 + + +### 开发备注 + +无。 diff --git a/packages/util/lazy-require/package.json b/packages/util/lazy-require/package.json new file mode 100644 index 0000000000..4cc3a07420 --- /dev/null +++ b/packages/util/lazy-require/package.json @@ -0,0 +1,35 @@ +{ + "name": "@deepseek-ai/dsh-lazy-require", + "description": "Caller-relative, success-cached lazy loading for CommonJS-compatible Host dependencies", + "version": "0.1.6-alpha.1", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/deepseek-ai/deepseek-harness.git", + "directory": "packages/util/lazy-require" + }, + "type": "module", + "main": "lib/index.js", + "types": "lib/types/index.d.ts", + "exports": { + ".": { + "types": "./lib/types/index.d.ts", + "default": "./lib/index.js" + }, + "./src/*": "./src/*", + "./package.json": "./package.json" + }, + "files": [ + "lib/index.js", + "lib/types/**/*.d.ts" + ], + "license": "MIT", + "peerDependencies": { + "@deepseek-ai/cordis": "workspace:^" + }, + "devDependencies": { + "@deepseek-ai/cordis": "workspace:^" + } +} diff --git a/packages/util/lazy-require/src/index.ts b/packages/util/lazy-require/src/index.ts new file mode 100644 index 0000000000..6996513f83 --- /dev/null +++ b/packages/util/lazy-require/src/index.ts @@ -0,0 +1,24 @@ +/** Caller-relative lazy loading for CommonJS-compatible Host dependencies. */ + +import { createRequire } from 'node:module' + +/** + * Create a successful-result cache around Node's caller-relative `require`. + * A failed load is not cached, so a corrected installation can be retried. + * @param specifier - Literal dependency specifier declared by the caller package. + * @param parentURL - Caller's `import.meta.url`, which owns package resolution. + * @returns a zero-argument loader that resolves the dependency on first use. + */ +// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- preserves caller-supplied module types +export function createLazyRequire(specifier: string, parentURL: string | URL): () => T { + const require = createRequire(parentURL) + let loaded = false + let value: T + return () => { + if (!loaded) { + value = require(specifier) as T + loaded = true + } + return value + } +} diff --git a/packages/util/lazy-require/tests/fixtures/value.cjs b/packages/util/lazy-require/tests/fixtures/value.cjs new file mode 100644 index 0000000000..8e416e9a69 --- /dev/null +++ b/packages/util/lazy-require/tests/fixtures/value.cjs @@ -0,0 +1,2 @@ +globalThis.__dshLazyRequireLoads = (globalThis.__dshLazyRequireLoads ?? 0) + 1 +module.exports = { value: 42 } diff --git a/packages/util/lazy-require/tests/index.spec.ts b/packages/util/lazy-require/tests/index.spec.ts new file mode 100644 index 0000000000..9b7100b50a --- /dev/null +++ b/packages/util/lazy-require/tests/index.spec.ts @@ -0,0 +1,27 @@ +import { afterEach, expect, it } from 'vitest' +import { createLazyRequire } from '../src/index.ts' + +interface FixtureGlobal { + __dshLazyRequireLoads?: number +} + +const fixtureGlobal = globalThis as FixtureGlobal + +afterEach(() => { + delete fixtureGlobal.__dshLazyRequireLoads +}) + +it('resolves from the caller and caches the successful module value', () => { + const load = createLazyRequire<{ value: number }>('./fixtures/value.cjs', import.meta.url) + expect(fixtureGlobal.__dshLazyRequireLoads).toBeUndefined() + const first = load() + expect(first).toEqual({ value: 42 }) + expect(load()).toBe(first) + expect(fixtureGlobal.__dshLazyRequireLoads).toBe(1) +}) + +it('does not cache a failed load', () => { + const load = createLazyRequire('./fixtures/missing.cjs', import.meta.url) + expect(load).toThrow(/missing\.cjs/u) + expect(load).toThrow(/missing\.cjs/u) +}) diff --git a/packages/util/lazy-require/tsconfig.json b/packages/util/lazy-require/tsconfig.json new file mode 100644 index 0000000000..749cb0208e --- /dev/null +++ b/packages/util/lazy-require/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib/types" + }, + "include": [ + "src" + ], + "references": [] +} diff --git a/packages/util/native-command/package.json b/packages/util/native-command/package.json index 31d5770858..57fb62fca7 100644 --- a/packages/util/native-command/package.json +++ b/packages/util/native-command/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-native-command", "description": "Host-native command and path-opening utilities with shell-free execution, cancellation, desktop detection, and WSL handoff", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/util/output-retention/package.json b/packages/util/output-retention/package.json index 1de1a53bd5..26a4ce8087 100644 --- a/packages/util/output-retention/package.json +++ b/packages/util/output-retention/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-output-retention", "description": "Zero-dependency bounded-retention primitive: ItemRetainer/TextRetainer + neutral notice helpers (what did we keep, what did we omit)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/util/package-manifest/package.json b/packages/util/package-manifest/package.json index 85b8a0b1ff..3ed3918fa5 100644 --- a/packages/util/package-manifest/package.json +++ b/packages/util/package-manifest/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-package-manifest", "description": "Shared type declarations for package.json.dsh configuration fields", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/util/time/package.json b/packages/util/time/package.json index a191aa8376..5bd229714d 100644 --- a/packages/util/time/package.json +++ b/packages/util/time/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-util-time", "description": "Zero-dependency time vocabulary shared by wire boundaries: canonicalClientTimeZone (IANA zone validation and canonicalization only, no formatting)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/util/timeout/package.json b/packages/util/timeout/package.json index c1cbd2bae2..709cbd5d44 100644 --- a/packages/util/timeout/package.json +++ b/packages/util/timeout/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-timeout", "description": "Zero-dependency timeout/deadline primitive: clampTimeout, deadline, timeoutOf, TimeoutReason (timing + classification only, no termination)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/util/values/package.json b/packages/util/values/package.json index b46eed22cc..135014b3fb 100644 --- a/packages/util/values/package.json +++ b/packages/util/values/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-util-values", "description": "Duplicate-install-safe value primitives for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/util/workspace-path/package.json b/packages/util/workspace-path/package.json index a8dca334e3..44b7f75fca 100644 --- a/packages/util/workspace-path/package.json +++ b/packages/util/workspace-path/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-util-workspace-path", "description": "Browser-safe Workspace path and display helpers", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/web/tool-web/package.json b/packages/web/tool-web/package.json index b3fe173fad..ece0f44915 100644 --- a/packages/web/tool-web/package.json +++ b/packages/web/tool-web/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-web", "description": "Model-facing web tools (web_search, web_fetch) over the DeepSeek Harness web capability seam (ctx.web)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/web/web-fetch-http/package.json b/packages/web/web-fetch-http/package.json index 85ab4871d8..7d224ede35 100644 --- a/packages/web/web-fetch-http/package.json +++ b/packages/web/web-fetch-http/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-web-fetch-http", "description": "Anonymous public HTTP(S) fetch provider for the DeepSeek Harness web capability seam (ctx.web)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/web/web-search-deepseek/package.json b/packages/web/web-search-deepseek/package.json index 26eeb35a32..d93da09e1f 100644 --- a/packages/web/web-search-deepseek/package.json +++ b/packages/web/web-search-deepseek/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-web-search-deepseek", "description": "DeepSeek-backed search provider (native web_search via the Anthropic-compatible API) for the DeepSeek Harness web capability seam (ctx.web)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/web/web-search-exa/package.json b/packages/web/web-search-exa/package.json index 85bedd66d4..ca2d3a26c1 100644 --- a/packages/web/web-search-exa/package.json +++ b/packages/web/web-search-exa/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-web-search-exa", "description": "Exa-backed search provider for the DeepSeek Harness web capability seam (ctx.web)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/web/web-search-perplexity/package.json b/packages/web/web-search-perplexity/package.json index 1e5cc82fc5..d4036d6bce 100644 --- a/packages/web/web-search-perplexity/package.json +++ b/packages/web/web-search-perplexity/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-web-search-perplexity", "description": "Perplexity-backed search provider for the DeepSeek Harness web capability seam (ctx.web)", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/web/web/package.json b/packages/web/web/package.json index 58fa29784a..9c51fe730d 100644 --- a/packages/web/web/package.json +++ b/packages/web/web/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-web", "description": "Abstract web access capability seam (ctx.web) for the DeepSeek Harness — search/fetch provider registry, registration-order-independent selection, request/result vocabulary, and the WebError taxonomy", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/webhook/webhook-github/package.json b/packages/webhook/webhook-github/package.json index e94244a5b8..8a5f2b628d 100644 --- a/packages/webhook/webhook-github/package.json +++ b/packages/webhook/webhook-github/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-webhook-github", "description": "Signed GitHub HTTP webhook adapter for the DeepSeek Harness webhook runtime", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/webhook/webhook/package.json b/packages/webhook/webhook/package.json index ca075c0b3d..efef3aba99 100644 --- a/packages/webhook/webhook/package.json +++ b/packages/webhook/webhook/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-webhook", "description": "Fire-and-forget webhook rule runtime that creates Workspace-backed DeepSeek Harness Sessions", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/workflow/tool-ralph/package.json b/packages/workflow/tool-ralph/package.json index 9f3093b757..8eb84554af 100644 --- a/packages/workflow/tool-ralph/package.json +++ b/packages/workflow/tool-ralph/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-ralph", "description": "Model-facing fresh-agent Ralph loop over the workflow and subagent seams", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/workflow/tool-workflow/package.json b/packages/workflow/tool-workflow/package.json index 7acaef6f09..26f3402f13 100644 --- a/packages/workflow/tool-workflow/package.json +++ b/packages/workflow/tool-workflow/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-tool-workflow", "description": "Model-facing workflow tool: run a JavaScript orchestration script over ctx.workflowEngine", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/workflow/workflow-ptc/package.json b/packages/workflow/workflow-ptc/package.json index 010feaac30..43b53637e1 100644 --- a/packages/workflow/workflow-ptc/package.json +++ b/packages/workflow/workflow-ptc/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-workflow-ptc", "description": "Workflow orchestration in the shared sandboxed Node PTC runtime", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/workflow/workflow/package.json b/packages/workflow/workflow/package.json index 8b958f9ceb..b03e78c943 100644 --- a/packages/workflow/workflow/package.json +++ b/packages/workflow/workflow/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-workflow", "description": "Workflow capability seam: ctx.workflowEngine service, run vocabulary, and workflow/* events", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/workspace/workspace/package.json b/packages/workspace/workspace/package.json index 35df2dc687..2142d5fc72 100644 --- a/packages/workspace/workspace/package.json +++ b/packages/workspace/workspace/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-workspace", "description": "Workspace entity registry (ctx.workspaceRegistry): durable workspace records with validated session attachment over the domain data form for the DeepSeek Harness", - "version": "0.1.5-rc.2", + "version": "0.1.6-alpha.1", "publishConfig": { "access": "public" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f9f22075e4..3832c3bebf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -730,6 +730,9 @@ importers: '@deepseek-ai/dsh-deque': specifier: workspace:^ version: link:../packages/util/deque + '@deepseek-ai/dsh-lazy-require': + specifier: workspace:^ + version: link:../packages/util/lazy-require '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../packages/llm/llm @@ -1205,6 +1208,9 @@ importers: '@deepseek-ai/dsh-deque': specifier: workspace:^ version: link:../../util/deque + '@deepseek-ai/dsh-lazy-require': + specifier: workspace:^ + version: link:../../util/lazy-require '@deepseek-ai/dsh-typert-protocol': specifier: workspace:^ version: link:../../typert/protocol @@ -1358,6 +1364,9 @@ importers: packages/attachment/attachment-local: dependencies: + '@deepseek-ai/dsh-lazy-require': + specifier: workspace:^ + version: link:../../util/lazy-require '@deepseek-ai/schemastery': specifier: link:../../../vendor/schemastery version: link:../../../vendor/schemastery @@ -8422,6 +8431,9 @@ importers: packages/sandbox/sandbox-windows-acl: dependencies: + '@deepseek-ai/dsh-lazy-require': + specifier: workspace:^ + version: link:../../util/lazy-require '@deepseek-ai/dsh-win32-process': specifier: workspace:^ version: link:../../subprocess/win32-process @@ -10755,6 +10767,9 @@ importers: packages/subprocess/subprocess-local: dependencies: + '@deepseek-ai/dsh-lazy-require': + specifier: workspace:^ + version: link:../../util/lazy-require '@deepseek-ai/dsh-win32-process': specifier: workspace:^ version: link:../win32-process @@ -10780,6 +10795,9 @@ importers: packages/subprocess/win32-process: dependencies: + '@deepseek-ai/dsh-lazy-require': + specifier: workspace:^ + version: link:../../util/lazy-require koffi: specifier: ^3.1.0 version: 3.1.1 @@ -10808,6 +10826,9 @@ importers: packages/terminal/terminal-bash: dependencies: + '@deepseek-ai/dsh-lazy-require': + specifier: workspace:^ + version: link:../../util/lazy-require '@deepseek-ai/dsh-pwsh-local': specifier: workspace:^ version: link:../../shell/pwsh-local @@ -11371,6 +11392,12 @@ importers: specifier: workspace:^ version: link:../../../vendor/cordis + packages/util/lazy-require: + devDependencies: + '@deepseek-ai/cordis': + specifier: workspace:^ + version: link:../../../vendor/cordis + packages/util/native-command: devDependencies: '@deepseek-ai/cordis': diff --git a/scripts/doc-standard.spec.ts b/scripts/doc-standard.spec.ts index 9ac69613e7..df3f98f2ca 100644 --- a/scripts/doc-standard.spec.ts +++ b/scripts/doc-standard.spec.ts @@ -92,6 +92,7 @@ const PACKAGE_LIBRARIES: Readonly> = { 'packages/util/chunked-list': 'Persistent collection operations and checkpoint validation without a plugin surface.', 'packages/util/home-paths': 'Zero-dependency harness-home path resolver.', 'packages/util/launch-environment': 'Zero-dependency environment resolver.', + 'packages/util/lazy-require': 'Caller-relative CommonJS-compatible dependency loader.', 'packages/util/native-command': 'Host-side subprocess runner utility.', 'packages/util/output-retention': 'Zero-dependency retention utility.', 'packages/util/package-manifest': 'Shared package manifest declarations with type-only exports.', diff --git a/scripts/package-dependency-policy.ts b/scripts/package-dependency-policy.ts index 835c252675..98b3bbb051 100644 --- a/scripts/package-dependency-policy.ts +++ b/scripts/package-dependency-policy.ts @@ -32,6 +32,7 @@ const CONFIGURATION_ONLY_DEV_DEPENDENCIES = { /** Workspace packages whose complete runtime surface is safe across duplicate installations. */ const DUPLICATE_SAFE_PACKAGES: readonly string[] = [ '@deepseek-ai/dsh-brand', + '@deepseek-ai/dsh-lazy-require', '@deepseek-ai/dsh-typert-protocol', '@deepseek-ai/dsh-util-crypto', '@deepseek-ai/dsh-util-values', diff --git a/scripts/verify-package-dependencies.spec.ts b/scripts/verify-package-dependencies.spec.ts index 4f49bebc5d..292dccbce9 100644 --- a/scripts/verify-package-dependencies.spec.ts +++ b/scripts/verify-package-dependencies.spec.ts @@ -204,6 +204,7 @@ describe('package dependency scope', () => { }) expect(PACKAGE_DEPENDENCY_POLICY.duplicateSafePackages).toEqual([ '@deepseek-ai/dsh-brand', + '@deepseek-ai/dsh-lazy-require', '@deepseek-ai/dsh-typert-protocol', '@deepseek-ai/dsh-util-crypto', '@deepseek-ai/dsh-util-values', @@ -612,12 +613,20 @@ describe('face-aware source classification', () => { "export * from '@f/star'", "void import('@f/dynamic')", "void require('@f/required')", + "import { createLazyRequire as lazy } from '@deepseek-ai/dsh-lazy-require'", + "import * as lazyModule from '@deepseek-ai/dsh-lazy-require'", + "void lazy('@f/lazy', import.meta.url)", + "void lazyModule.createLazyRequire('@f/lazy-namespace', import.meta.url)", 'void defaultValue; void local; void namespace', ].join('\n') const uses = collectRuntimeSourceExportUses('probe.ts', source) expect(uses.map(({ specifier, exportName }) => ({ specifier, exportName }))).toEqual([ + { specifier: '@deepseek-ai/dsh-lazy-require', exportName: '*' }, + { specifier: '@deepseek-ai/dsh-lazy-require', exportName: 'createLazyRequire' }, { specifier: '@f/dynamic', exportName: '*' }, { specifier: '@f/effect', exportName: '(side effect)' }, + { specifier: '@f/lazy', exportName: '*' }, + { specifier: '@f/lazy-namespace', exportName: '*' }, { specifier: '@f/namespace', exportName: '*' }, { specifier: '@f/reexport', exportName: 'source' }, { specifier: '@f/required', exportName: '*' }, diff --git a/scripts/verify-package-dependencies.ts b/scripts/verify-package-dependencies.ts index 537fac154a..9cd091131d 100644 --- a/scripts/verify-package-dependencies.ts +++ b/scripts/verify-package-dependencies.ts @@ -211,6 +211,23 @@ export function collectRuntimeSourceExportUses(path: string, source: string): Ru const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true) const uses = new Map() const sourceLines = source.split(/\r?\n/u) + const lazyRequireBindings = new Set() + const lazyRequireNamespaces = new Set() + for (const statement of sourceFile.statements) { + if (!ts.isImportDeclaration(statement) + || !ts.isStringLiteralLike(statement.moduleSpecifier) + || statement.moduleSpecifier.text !== '@deepseek-ai/dsh-lazy-require') continue + const bindings = statement.importClause?.namedBindings + if (bindings !== undefined && ts.isNamespaceImport(bindings)) { + lazyRequireNamespaces.add(bindings.name.text) + } else if (bindings !== undefined) { + for (const element of bindings.elements) { + if ((element.propertyName ?? element.name).text === 'createLazyRequire') { + lazyRequireBindings.add(element.name.text) + } + } + } + } const record = (specifier: string, exportName: string, locationNode: ts.Node): void => { const key = `${specifier}\0${exportName}` if (uses.has(key)) return @@ -267,10 +284,16 @@ export function collectRuntimeSourceExportUses(path: string, source: string): Ru && !node.isTypeOnly && ts.isExternalModuleReference(node.moduleReference)) { add(node.moduleReference.expression, NAMESPACE_RUNTIME_EXPORT, node.name) - } else if (ts.isCallExpression(node) - && (node.expression.kind === ts.SyntaxKind.ImportKeyword - || ts.isIdentifier(node.expression) && node.expression.text === 'require')) { - add(node.arguments[0], NAMESPACE_RUNTIME_EXPORT) + } else if (ts.isCallExpression(node)) { + const lazyRequire = ts.isIdentifier(node.expression) + ? lazyRequireBindings.has(node.expression.text) + : ts.isPropertyAccessExpression(node.expression) + && ts.isIdentifier(node.expression.expression) + && lazyRequireNamespaces.has(node.expression.expression.text) + && node.expression.name.text === 'createLazyRequire' + if (node.expression.kind === ts.SyntaxKind.ImportKeyword + || ts.isIdentifier(node.expression) && node.expression.text === 'require' + || lazyRequire) add(node.arguments[0], NAMESPACE_RUNTIME_EXPORT) } else if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node) || ts.isJsxFragment(node)) { record('react/jsx-runtime', NAMESPACE_RUNTIME_EXPORT, node) } diff --git a/scripts/verify-package-readme-model-experience.ts b/scripts/verify-package-readme-model-experience.ts index 24f8cdea60..9724817a41 100644 --- a/scripts/verify-package-readme-model-experience.ts +++ b/scripts/verify-package-readme-model-experience.ts @@ -68,6 +68,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly> = { 'packages/util/deque': { kind: 'none', reason: 'In-process collection primitive; registers nothing model-facing.' }, 'packages/util/chunked-list': { kind: 'none', reason: 'Immutable collection primitive; registers nothing model-facing.' }, 'packages/util/package-manifest': { kind: 'none', reason: 'Type declarations only; registers nothing model-facing.' }, + 'packages/util/lazy-require': { kind: 'none', reason: 'Host module-loading primitive; registers nothing model-facing.' }, 'packages/util/time': { kind: 'indirect', reason: 'Pure zone validation; the consumer that records a canonical zone owns the model-visible line derived from it.' }, 'packages/core/agent-default-model': { kind: 'indirect', reason: 'The service supplies a ModelSelection; request assembly and adapters own the model-visible request.' }, 'packages/llm/deepseek-llm-api-extensions': { kind: 'indirect', reason: 'The registry contributes model-hidden provider fields; dsh-llm-deepseek owns their wire placement.' }, diff --git a/tsconfig.base.json b/tsconfig.base.json index 643de045a9..09d902cccd 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -339,6 +339,7 @@ "@deepseek-ai/dsh-jobs/invariant": ["./packages/jobs/jobs/src/invariant.ts"], "@deepseek-ai/dsh-jobs-local": ["./packages/jobs/jobs-local/src"], "@deepseek-ai/dsh-launch-environment": ["./packages/util/launch-environment/src"], + "@deepseek-ai/dsh-lazy-require": ["./packages/util/lazy-require/src"], "@deepseek-ai/dsh-llm": ["./packages/llm/llm/src"], "@deepseek-ai/dsh-llm/invariant": ["./packages/llm/llm/src/invariant.ts"], "@deepseek-ai/dsh-llm-deepseek": ["./packages/llm/llm-deepseek/src"], diff --git a/tsconfig.host.json b/tsconfig.host.json index 8d9b721ac5..e5841338d1 100644 --- a/tsconfig.host.json +++ b/tsconfig.host.json @@ -167,6 +167,7 @@ { "path": "./packages/util/package-manifest" }, { "path": "./packages/util/http-proxy" }, { "path": "./packages/util/launch-environment" }, + { "path": "./packages/util/lazy-require" }, { "path": "./packages/util/native-command" }, { "path": "./packages/util/home-paths" }, { "path": "./packages/util/time" },