Przeglądaj źródła

Revert "fix(client): declare browser-only externals as devDependencies"

imccyu 3 tygodni temu
rodzic
commit
f2830fec6d
54 zmienionych plików z 291 dodań i 1090 usunięć
  1. 2 2
      .agents/notes/implemented/process/2026-07-30-generated-third-party-notices.i18n.yaml
  2. 0 0
      .agents/notes/implemented/process/2026-07-30-generated-third-party-notices.md
  3. 0 0
      .agents/notes/implemented/process/2026-07-30-generated-third-party-notices.zh.md
  4. 0 6
      .agents/notes/proposed/process/2026-08-14-client-build-time-deps.i18n.yaml
  5. 0 100
      .agents/notes/proposed/process/2026-08-14-client-build-time-deps.md
  6. 0 100
      .agents/notes/proposed/process/2026-08-14-client-build-time-deps.zh.md
  7. 4 4
      THIRD_PARTY_NOTICES.md
  8. 4 4
      apps/web/package.json
  9. 0 1
      apps/web/vite.config.ts
  10. 1 1
      lefthook.yml
  11. 1 2
      package.json
  12. 1 10
      packages/client/AGENTS.md
  13. 2 1
      packages/client/locale/package.json
  14. 5 5
      packages/client/runtime/package.json
  15. 2 1
      packages/client/ui-agent-preset/package.json
  16. 5 5
      packages/client/ui-attachment/package.json
  17. 6 3
      packages/client/ui-commands/package.json
  18. 4 3
      packages/client/ui-conversation/package.json
  19. 4 2
      packages/client/ui-deliverables/package.json
  20. 6 3
      packages/client/ui-directory-picker-browse/package.json
  21. 2 1
      packages/client/ui-directory-picker-native/package.json
  22. 2 1
      packages/client/ui-goal/package.json
  23. 6 3
      packages/client/ui-input-trigger/package.json
  24. 4 2
      packages/client/ui-jobs/package.json
  25. 2 1
      packages/client/ui-layout/package.json
  26. 2 1
      packages/client/ui-message-feedback/package.json
  27. 3 1
      packages/client/ui-model-selection/package.json
  28. 2 1
      packages/client/ui-permission-presets/package.json
  29. 2 1
      packages/client/ui-plan/package.json
  30. 7 5
      packages/client/ui-primitives/package.json
  31. 5 4
      packages/client/ui-settings-general/package.json
  32. 2 1
      packages/client/ui-settings-models/package.json
  33. 2 1
      packages/client/ui-settings-plugin-inventory/package.json
  34. 6 3
      packages/client/ui-settings-plugins/package.json
  35. 2 1
      packages/client/ui-settings/package.json
  36. 6 3
      packages/client/ui-sidebar/package.json
  37. 2 1
      packages/client/ui-skill/package.json
  38. 4 2
      packages/client/ui-subagent/package.json
  39. 3 2
      packages/client/ui-theme/package.json
  40. 5 2
      packages/client/ui-tool/package.json
  41. 8 4
      packages/client/ui-trajectory/package.json
  42. 4 4
      packages/client/ui-user-questions/package.json
  43. 4 2
      packages/client/ui-workflow-run/package.json
  44. 6 3
      packages/client/ui-workspace/package.json
  45. 4 4
      packages/client/web-react/package.json
  46. 4 4
      packages/client/web/package.json
  47. 2 1
      packages/extensions/cordis-client-runner/package.json
  48. 2 1
      packages/extensions/ui-cordis/package.json
  49. 8 26
      packages/session-query/session-log-export/package.json
  50. 114 101
      pnpm-lock.yaml
  51. 0 230
      scripts/browser-bundled-externals.ts
  52. 3 21
      scripts/gen-third-party-notices.spec.ts
  53. 16 31
      scripts/gen-third-party-notices.ts
  54. 0 368
      scripts/verify-client-runtime-deps.ts

+ 2 - 2
.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.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-07-30-generated-third-party-notices.md
-2026-07-30-generated-third-party-notices.md: 2128537b09f9d5d68cb1175b1ecb195fa50e1865
-2026-07-30-generated-third-party-notices.zh.md: 906f5ed5b9a11f3ddd68491686b460c5d9f4a682
+2026-07-30-generated-third-party-notices.md: 9ca24c9634b0410f9b7cc255902344cd6014bb90
+2026-07-30-generated-third-party-notices.zh.md: b49e60565bbae62cf17026db17babc1ba7a408dc

Plik diff jest za duży
+ 0 - 0
.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.md


Plik diff jest za duży
+ 0 - 0
.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.zh.md


+ 0 - 6
.agents/notes/proposed/process/2026-08-14-client-build-time-deps.i18n.yaml

@@ -1,6 +0,0 @@
-# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
-# side as of the last confirmed-consistent state. Both languages carry equal authority;
-# after editing either side, bring the other along and re-record with:
-#   pnpm run verify-translation-pairing --write .agents/notes/proposed/process/2026-08-14-client-build-time-deps.md
-2026-08-14-client-build-time-deps.md: 0605225a70a16fed9004acfe26adba9b6166f201
-2026-08-14-client-build-time-deps.zh.md: 5caed3422288711532c40cd767f72c97693e68b4

+ 0 - 100
.agents/notes/proposed/process/2026-08-14-client-build-time-deps.md

@@ -1,100 +0,0 @@
-# Agent Note: Client build-time dependencies stay out of the install face
-
-Status: proposed
-
-English | [中文](2026-08-14-client-build-time-deps.zh.md)
-
-## Problem
-
-A browser artifact resolves nothing on the user's machine:
-
-- A `ui-*` plugin package's browser artifact is `lib/client.js`, where tsdown inlines every non-platform specifier (`noExternal` in `packages/client/tsdown.client.ts`). The specifiers that survive are answered by the loader's frozen module table, because `require` inside that bundle is a parameter the loader injects, not Node's.
-- Platform modules (`PLATFORM_MODULES`) come from the shell `dist`, never from Node resolution.
-- The shell's own imports are inlined by Vite into `@deepseek-ai/dsh-web-frontend`'s published `dist`; that package ships `dist` alone and has no `.` export.
-
-Every browser code path is therefore a build product, served as an asset or baked into `dist`. Yet the packages those artifacts are built from — react, react-dom, shiki, katex, clsx, the micromark and mdast families — sit in `dependencies` and non-optional `peerDependencies`, which npm installs for every consumer of the published package. Across the repository that is 79 such external declarations in 38 packages, downloaded by users who never load them.
-
-## Proposal
-
-### The rule
-
-**An external package only a browser artifact reaches belongs in `devDependencies`.** Two deliberate omissions are as much part of the rule:
-
-- **External packages only.** A `@deepseek-ai/*` name stays where its manifest puts it. Such a declaration also states which package supplies an injected service, which Remote contribution an assembly mounts, or which Loader row must resolve; [verify-runtime-closure](../../../../scripts/verify-runtime-closure.ts) and the Loader read it, and the app installs the package regardless — so moving one removes meaning without removing a download.
-- **Anything the node half reaches stays**, an erased type import included.
-
-Faces are walked from the entries a manifest publishes, not by a directory rule, so a module under `src/` that only the browser entry reaches counts as browser source:
-
-| kind | test | host face entries |
-| --- | --- | --- |
-| `bundle-half` | has a `./client` export | every export target except `./client` |
-| `browser-library` | under `packages/client/` with no `./client` export | `src/invariant.ts` alone — the companion the host mounts; `.` is browser code |
-| `prebuilt-dist` | no `.` export, ships a `dist` | none: the package offers Node no entry |
-
-### The gate: `scripts/verify-client-runtime-deps.ts`
-
-Wired into `pnpm run hygiene`, about 35 seconds — the cost of two bound Programs, the same order as `verify-optional-dependency-imports` in that lane. It reuses the repository's tooling rather than growing its own: `TypeScriptProject` (`scripts/ts-project.ts`) binds the host and client compiler faces separately (that file states why the two cannot share one program — the cordis Context merges collide), `ts.resolveModuleName` resolves relative specifiers, and the walk stops at the package boundary.
-
-Three findings decided the mechanism, after a first pass that scanned string literals:
-
-1. A package name must match as a name: the `react` substring inside `'@deepseek-ai/dsh-client-web-react'` silently swallowed react.
-2. Whether `./client` is the tsdown browser bundle is keyed on the **artifact path** (`./lib/client.js`), not the subpath name — `dsh-goal` publishes `./client` as `./lib/types/client.js`, a plain tsc-emitted browser-shared module.
-3. `require`, `require.resolve`, and dynamic `import()` on a literal each reach a package; `require.resolve('@deepseek-ai/dsh-web-frontend/dist/index.html')` is a real host resolution path.
-
-Two classes, both reported per entry:
-
-| class | count | test |
-| --- | --- | --- |
-| `browser` | 74 | only a browser artifact reaches it |
-| `nothing` | 5 | no reference names it: `client-runtime`'s `react` (which contradicts its own React-free layering red line), the peer `react` of `ui-settings` and `ui-theme`, `ui-trajectory`'s peer `react-dom`, and `ui-primitives`' `@types/mdast` |
-
-Each conservative rule below answers a false report or a semantic loss observed while building it:
-
-- **A type reference from the node half keeps its declaration.** `import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'` in `src/invariant.ts` is erased at run time, yet it states which package supplies the service that companion registers — `verify-runtime-closure`'s relation.
-- **A package publishing a Node entry with no source counterpart is skipped whole, and named in the output.** `dsh-goal`'s `./typert -> ./lib/typert.host.js` is emitted by the typert generator and carries its own `import { z } from 'zod'`, which no source states. Getting this test right removed four false reports, among them `api-gateway`'s `typert-registry`.
-- A `cordis*.yml` the package owns counts as host face: a Loader row names its plugin instead of importing it.
-- `@deepseek-ai/cordis` is exempt — check-workspace-constraints requires it as both peer and dev everywhere.
-
-`--json` output feeds the bulk edit and the install measurement.
-
-### What leaves an install
-
-Measured against a real `npm install` of the published CLI, with tarball bytes read from an isolated cache: 103 external tarballs stop being downloaded, 6.05 MB in total.
-
-| group | packages | saved |
-| --- | --- | --- |
-| syntax highlighting and math (shiki family, oniguruma family, katex) | 16 | 3.93 MB |
-| react and view libraries (react, react-dom, scheduler, immer, zustand, `@tanstack/*`, clsx, use-sync-external-store) | 11 | 1.47 MB |
-| markdown and ansi pipeline plus odds and ends (micromark, mdast, hast families, anser, a few `@types/*`) | 76 | 0.65 MB |
-
-Our own six browser-library packages (ui-primitives, ui-slots, web-react, ui-attachment, schema-form, client-web — 0.20 MB together) stay installed: code names them, and the rule above leaves those declarations alone.
-
-### How it lands, split by nature
-
-1. **Documentation first**: a declaration section in `packages/client/AGENTS.md`, and one clause in the new-plugin-package checklist.
-2. **The gate**: `scripts/verify-client-runtime-deps.ts`, its `package.json` script, its place in `hygiene`, and a counterexample spec.
-3. **The manifests**: 79 entries in 38 packages. 50 need a new `devDependencies` entry; the rest already carry one, so the change is a deleted line.
-4. **Re-measure after the next release** with the same method, confirming the 103 tarballs stay gone.
-
-## Alternatives considered
-
-- **Scanning string literals**: the first implementation, rejected by the three findings above — the react-inside-web-react substring had already produced a silent miss.
-- **Reading built artifacts (`lib/**/*.js`) instead of source**: that is Node's own view, but the gate would then depend on `pnpm run build`, and it still cannot judge a browser-library's `lib/index.js` (node platform, browser content), so the face test stays either way.
-- **Asking the checker whether a binding is used in a value position** (what `verify-optional-dependency-imports` does): tried, and it also judged 83 node-face type-only declarations movable — no download saved for a real loss of meaning, 53 of them `dsh-invariants`. This gate needs to know whether a reference exists, not whether it is a value.
-- **Also clearing our own six browser-library packages from the install face**, on the test that no install loads one: another 0.20 MB, at the price of deleting 74 workspace declarations that code genuinely names. Ruled out (2026-08-14): keep what the code names. The cleaner end state is to stop publishing those six packages, which is its own proposal.
-- **`peerDependenciesMeta.optional` instead of `devDependencies`**: npm does skip an optional peer, but the meaning is "a consumer may supply this", and there is no run-time consumer at all. The repository must install it to build, which is what `devDependencies` says.
-- **Leaving it to knip**: out of scope for knip, which reports a declared package nothing imports. These specifiers are imported; a bundler inlines them. The evidence is that they persisted on master with knip green. Only the five `nothing` entries overlap.
-- **`optionalDependencies`**: wrong meaning — it says "skip this if it cannot be installed".
-
-## Acceptance criteria
-
-- `pnpm run hygiene` includes `verify-client-runtime-deps` and passes; a counterexample spec proves one `dependencies.react` is rejected.
-- `pnpm run build`, `pnpm run test:gui`, and `DSH_SNAPSHOT=replay pnpm run test:web` pass — the move changes no build input, so artifacts stay byte-identical.
-- A real install after the next release no longer downloads the 103 tarballs above.
-
-## Risks
-
-- **The six browser-library packages that stay installed carry bare imports nothing resolves**: `ui-primitives/lib/index.js` is a rolldown artifact and still reads `from "anser"`, while anser is now dev-only. It is inert — only our Vite build reads that file, and no loader exists for it on a user's machine (verified: only browser code imports those packages, never the host). Retiring their publication is the way to erase it; see Alternatives.
-- **`@types/*` go unreported**: source never names them, so the rule cannot see them. `@types/mdast` was caught only because nothing referenced it either. They belong in dev regardless, and closing that gap is follow-up work.
-- **A skipped package is unprotected**: `dsh-goal` is skipped whole for its generated entry, so its browser-side declarations are now nobody's business. Reading a generated artifact's own run-time imports is what would let the exemption be withdrawn.
-- **A false report would delete a declaration something needs at run time**: three defenses hold that line — literal arguments to `require`, `require.resolve`, and dynamic `import()` count as references; a package's own `cordis*.yml` counts as host face; and no `@deepseek-ai/*` name is subject at all.

+ 0 - 100
.agents/notes/proposed/process/2026-08-14-client-build-time-deps.zh.md

@@ -1,100 +0,0 @@
-# Agent Note: 客户端构建期依赖不进安装面
-
-Status: proposed
-
-[English](2026-08-14-client-build-time-deps.md) | 中文
-
-## Problem
-
-浏览器产物不在用户机上解析任何 specifier:
-
-- `ui-*` 插件包的浏览器产物是 `lib/client.js`,tsdown 把每个非平台 specifier 直接内联(`packages/client/tsdown.client.ts` 的 `noExternal`)。留下来的 specifier 由 loader 的冻结模块表应答——那个 bundle 里的 `require` 是 loader 注入的形参,不是 Node 的。
-- 平台模块(`PLATFORM_MODULES`)由 shell `dist` 提供,不走 Node 解析。
-- shell 自身的 import 由 Vite 内联进 `@deepseek-ai/dsh-web-frontend` 已发布的 `dist`;该包只发 `dist`,连 `.` 导出都没有。
-
-所以浏览器的每条代码路径都是构建产物,或作为静态资源下发,或烤进 `dist`。但这些产物的构建输入——react、react-dom、shiki、katex、clsx、micromark 与 mdast 全族——现在写在 `dependencies` 和非 optional `peerDependencies` 里,而 npm 对每个消费者都会安装这两个区段。全仓 38 个包共 79 处这样的外部依赖声明,装给了永远不会加载它们的用户。
-
-## Proposal
-
-### 规则
-
-**只被浏览器产物触及的外部包落 `devDependencies`。** 两条留白同样是规则的一部分:
-
-- **只管外部依赖**。`@deepseek-ai/*` 一律留在原处:那些声明还表达「谁提供我注入的服务」「这个 assembly 挂载了谁的 Remote」「哪个 Loader 行必须能解析」,[verify-runtime-closure](../../../../scripts/verify-runtime-closure.ts) 与 Loader 都读它,而 app 无论如何都会装那个包——移走只是删掉语义,并没有减少下载。
-- **node 面触及的一律不动**,包括被擦除的类型引用。
-
-face 从 manifest 真正发布的入口走图,不用目录规则,所以 `src/` 下只被浏览器入口触及的模块就算浏览器代码:
-
-| kind | 判据 | host face 入口 |
-| --- | --- | --- |
-| `bundle-half` | 有 `./client` 导出 | 除 `./client` 外的每个导出目标 |
-| `browser-library` | `packages/client/` 下且无 `./client` 导出 | 只有 `src/invariant.ts`——宿主唯一能挂载的伴生模块;`.` 面是浏览器代码 |
-| `prebuilt-dist` | 无 `.` 导出、发布 `dist` | 没有:这个包不给 Node 提供任何入口 |
-
-### 门禁:`scripts/verify-client-runtime-deps.ts`
-
-接入 `pnpm run hygiene`,约 35 秒——两个绑定 Program 的开销,与同 lane 的 `verify-optional-dependency-imports` 同量级。复用仓内既有工具而不自造一套:`TypeScriptProject`(`scripts/ts-project.ts`)分别绑定 host 与 client 两个编译面(该文件写明两者不能合进一个 program——cordis Context merge 会撞),相对 specifier 交给 `ts.resolveModuleName` 解析,遍历到包边界即停。
-
-判据要害有三条,都是起手那版扫字符串字面量踩出来的:
-
-1. 包名必须按名匹配:`'@deepseek-ai/dsh-client-web-react'` 里的 `react` 子串会静默吞掉 react。
-2. `./client` 是不是 tsdown 浏览器 bundle,看的是**产物路径**(`./lib/client.js`)而不是子路径名——`dsh-goal` 的 `./client` 是 `./lib/types/client.js`,一个 tsc 直出的浏览器共享模块。
-3. `require`、`require.resolve`、动态 `import()` 的字面量实参都能触及一个包;`require.resolve('@deepseek-ai/dsh-web-frontend/dist/index.html')` 就是真实存在的宿主解析路径。
-
-两类判定,逐条报告:
-
-| 类 | 数量 | 判据 |
-| --- | --- | --- |
-| `browser` | 74 | 只有浏览器产物触及 |
-| `nothing` | 5 | 没有任何引用具名它:`client-runtime` 的 `react`(与它自己「零 React 引用」的分层红线矛盾)、`ui-settings` 与 `ui-theme` 的 peer `react`、`ui-trajectory` 的 peer `react-dom`、`ui-primitives` 的 `@types/mdast` |
-
-下面每条保守规则都对应一次实测到的误报或语义损失:
-
-- **node 面的类型引用保留声明。** `src/invariant.ts` 里的 `import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'` 运行期被擦除,但它声明了谁提供这个伴生插件要注册的服务——归 `verify-runtime-closure` 管的关系。
-- **发布了没有源码对应文件的 Node 入口的包整包跳过,并在输出里点名。** `dsh-goal` 的 `./typert -> ./lib/typert.host.js` 由 typert 生成器直出,自带 `import { z } from 'zod'`,没有任何源码陈述这件事。把这条判据修对,消掉了四处误报,其中包括 `api-gateway` 的 `typert-registry`。
-- 包自带的 `cordis*.yml` 算 host face:Loader 行是具名它的插件而不是 import 它。
-- `@deepseek-ai/cordis` 豁免——check-workspace-constraints 要求它在每个包里同时是 peer 和 dev。
-
-`--json` 输出供批量改写与安装体积实测复用。
-
-### 安装面少掉什么
-
-对已发布 CLI 真装一遍实测,tarball 字节从独立 cache 读出:103 个外部 tarball 不再下载,合计 6.05 MB。
-
-| 组 | 包数 | 省 |
-| --- | --- | --- |
-| 语法高亮与数学(shiki 族、oniguruma 族、katex) | 16 | 3.93 MB |
-| react 与视图库(react、react-dom、scheduler、immer、zustand、`@tanstack/*`、clsx、use-sync-external-store) | 11 | 1.47 MB |
-| markdown 与 ansi 管线及零碎(micromark、mdast、hast 全族、anser、若干 `@types/*`) | 76 | 0.65 MB |
-
-我们自己的 6 个浏览器库包(ui-primitives、ui-slots、web-react、ui-attachment、schema-form、client-web,合计 0.20 MB)仍留在安装面:代码确实具名它们,上面的规则不动那些声明。
-
-### 分刀落地
-
-1. **文档住顶刀**:`packages/client/AGENTS.md` 的依赖声明节,加新插件包 checklist 里的一句。
-2. **门禁**:`scripts/verify-client-runtime-deps.ts`、它的 `package.json` 脚本、它在 `hygiene` 里的位置,以及一条反例 spec。
-3. **manifest**:38 个包 79 处。其中 50 处需要新增 `devDependencies` 条目,其余包已有同名条目,改动就是删掉一行。
-4. **发版后按同一方法复测**,确认这 103 个 tarball 没有回来。
-
-## Alternatives considered
-
-- **扫字符串字面量**:起手就是这么实现的,被上面三条要害否掉——react-in-web-react 的子串已经造成过一次静默漏报。
-- **读构建产物(`lib/**/*.js`)而不是源码**:那是 Node 自己的视角,但门禁从此依赖 `pnpm run build`,而且它照样判不了浏览器库包的 `lib/index.js`(platform 是 node、内容是浏览器代码),face 判据两种走法都得有。
-- **用 checker 判绑定是否用在值位置**(`verify-optional-dependency-imports` 就是这么做的):试过,它把 83 处 node 面纯类型声明也判成可移出——没省下任何下载,却实打实损失语义,其中 53 处是 `dsh-invariants`。本门禁要知道的是引用是否存在,而不是它是值还是类型。
-- **顺带把我们自己的 6 个浏览器库包也清出安装面**,判据是任何安装都不加载它们:再省 0.20 MB,代价是删掉 74 处代码确实具名的 workspace 声明。已否(2026-08-14):代码用到的就保留。更干净的终态是这 6 个包不再发布,那是另一个提案。
-- **用 `peerDependenciesMeta.optional` 而不是 `devDependencies`**:npm 确实会跳过 optional peer,但那个语义是「消费者可以自行提供」,而这里根本没有运行期消费者。仓内必须装一份才能构建,这正是 `devDependencies` 的意思。
-- **交给 knip**:不属于 knip 的范畴,它报的是「声明了但没人 import」。这些 specifier 确实被 import,只是被打包器内联了。实证就是它们在 master 上长期存在而 knip 全绿。只有 `nothing` 那 5 条与它重叠。
-- **用 `optionalDependencies`**:语义错,它说的是「装不上就跳过」。
-
-## Acceptance criteria
-
-- `pnpm run hygiene` 包含 `verify-client-runtime-deps` 并通过;一条反例 spec 证明一处 `dependencies.react` 会被拒。
-- `pnpm run build`、`pnpm run test:gui`、`DSH_SNAPSHOT=replay pnpm run test:web` 通过——这次迁移不改任何构建输入,产物应逐字节等价。
-- 发版后真装一遍,上面那 103 个 tarball 不再被下载。
-
-## Risks
-
-- **留在安装面的 6 个浏览器库包会带着解析不了的 bare import**:`ui-primitives/lib/index.js` 是 rolldown 产物,仍写着 `from "anser"`,而 anser 已经只在 dev。它是惰性的——只有我们的 Vite 构建会读这个文件,用户机上没有任何加载者(已实证:只有浏览器代码 import 它们,宿主从不)。要彻底消掉就让这些包不再发布,见 Alternatives。
-- **`@types/*` 报不出来**:源码从不具名它们,规则看不见。`@types/mdast` 被抓到只是因为恰好也没有任何引用。它们本来就该在 dev,补这个缺口是后续的事。
-- **被跳过的包没人管**:`dsh-goal` 因生成入口整包跳过,它浏览器侧的声明现在没有门禁看着。能读到生成产物自身的运行期 import,这条豁免才能收回。
-- **误报会删掉运行期真需要的声明**:三层兜底守住这条线——`require`、`require.resolve`、动态 `import()` 的字面量实参都算引用;包自带的 `cordis*.yml` 算 host face;`@deepseek-ai/*` 整体不在判据范围内。

+ 4 - 4
THIRD_PARTY_NOTICES.md

@@ -27,7 +27,7 @@ The Cordis framework and its foundation libraries are source-vendored into this
 
 ## Runtime npm dependencies
 
-External packages that reach a user: a workspace package resolves them at runtime, or a published browser artifact carries a copy of their code. The tier covers every plugin a user can mount from `cordis.yml` — not only what the `dsh` CLI, Web UI, and Python SDK runtime load by default — and the packages the client build inlines into a plugin bundle or the shell `dist`, which are declared as `devDependencies` because nothing on a user's machine resolves their specifiers.
+External packages that a workspace package resolves at runtime. The tier covers every plugin a user can mount from `cordis.yml` — not only what the `dsh` CLI, Web UI, and Python SDK runtime load by default.
 
 | Package | License |
 | --- | --- |
@@ -48,6 +48,7 @@ External packages that reach a user: a workspace package resolves them at runtim
 | [`@shikijs/langs`](https://github.com/shikijs/shiki) | MIT |
 | [`@standard-schema/spec`](https://github.com/standard-schema/standard-schema) | MIT |
 | [`@tanstack/react-virtual`](https://github.com/TanStack/virtual) | MIT |
+| [`@types/mdast`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT |
 | [`@vscode/ripgrep`](https://github.com/microsoft/vscode-ripgrep) | MIT |
 | [`anser`](https://github.com/IonicaBizau/anser) | MIT |
 | [`chokidar`](https://github.com/paulmillr/chokidar) | MIT |
@@ -72,6 +73,7 @@ External packages that reach a user: a workspace package resolves them at runtim
 | [`micromark-util-classify-character`](https://github.com/micromark/micromark/tree/main/packages/micromark-util-classify-character) | MIT |
 | [`micromark-util-sanitize-uri`](https://github.com/micromark/micromark/tree/main/packages/micromark-util-sanitize-uri) | MIT |
 | [`micromark-util-symbol`](https://github.com/micromark/micromark/tree/main/packages/micromark-util-symbol) | MIT |
+| [`micromark-util-types`](https://github.com/micromark/micromark/tree/main/packages/micromark-util-types) | MIT |
 | [`node-addon-require-builtin`](https://www.npmjs.com/package/node-addon-require-builtin) | MIT |
 | [`node-pty`](https://github.com/microsoft/node-pty) | MIT |
 | [`picomatch`](https://github.com/micromatch/picomatch) | MIT |
@@ -113,7 +115,7 @@ The installed SDK 0.3.220 declares the following optional platform packages. Eac
 
 ## Development-only npm dependencies
 
-External packages **directly declared** only by repository tooling, test infrastructure, the documentation site, the demo leaves, or the native launcher's build workspace, and carried by no published artifact. No shipped surface names them itself. A package here may still be pulled in transitively by a runtime dependency — `pnpm-lock.yaml` is the authority on the full closure — so this tier records who declares a package, not what a build ultimately bundles.
+External packages **directly declared** only by repository tooling, test infrastructure, the documentation site, the demo leaves, or the native launcher's build workspace. No shipped surface names them itself. A package here may still be pulled in transitively by a runtime dependency — `pnpm-lock.yaml` is the authority on the full closure — so this tier records who declares a package, not what a build ultimately bundles.
 
 | Package | License |
 | --- | --- |
@@ -127,7 +129,6 @@ External packages **directly declared** only by repository tooling, test infrast
 | [`@types/babel__code-frame`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT |
 | [`@types/js-yaml`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT |
 | [`@types/jsdom`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT |
-| [`@types/mdast`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT |
 | [`@types/node`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT |
 | [`@types/picomatch`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT |
 | [`@types/react`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT |
@@ -153,7 +154,6 @@ External packages **directly declared** only by repository tooling, test infrast
 | [`lefthook`](https://github.com/evilmartians/lefthook) | MIT |
 | [`lightningcss`](https://github.com/parcel-bundler/lightningcss) | MPL-2.0 |
 | [`mermaid`](https://github.com/mermaid-js/mermaid) | MIT |
-| [`micromark-util-types`](https://github.com/micromark/micromark/tree/main/packages/micromark-util-types) | MIT |
 | [`oxlint`](https://github.com/oxc-project/oxc) | MIT |
 | [`oxlint-tsgolint`](https://github.com/oxc-project/tsgolint) | MIT |
 | [`playwright`](https://github.com/microsoft/playwright) | Apache-2.0 |

+ 4 - 4
apps/web/package.json

@@ -26,7 +26,9 @@
   },
   "license": "MIT",
   "dependencies": {
-    "@deepseek-ai/dsh-client-web": "workspace:^"
+    "@deepseek-ai/dsh-client-web": "workspace:^",
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/cordis-plugin-group": "workspace:^",
@@ -44,8 +46,6 @@
     "typescript": "^6.0.3",
     "vite": "^6.0.0",
     "vitest": "^4.1.8",
-    "fflate": "^0.8.2",
-    "react": "^18.2.0",
-    "react-dom": "^18.2.0"
+    "fflate": "^0.8.2"
   }
 }

+ 0 - 1
apps/web/vite.config.ts

@@ -93,7 +93,6 @@ export default defineConfig({
   plugins: [rejectStandaloneServe(), react()],
   build: {
     sourcemap: true,
-    modulePreload: { polyfill: false },
     rollupOptions: {
       output: {
         // Output layout: the two main chunks stay at assets/ root; lazy

+ 1 - 1
lefthook.yml

@@ -28,7 +28,7 @@ pre-commit:
     # lefthook only inspects files present on disk — so that one case still
     # falls through to the freshness assertion in the test lane.
     - name: third-party notices (staged)
-      glob: '{package.json,*/package.json,*/*/package.json,*/*/*/package.json,*/*/*/*/package.json,pnpm-workspace.yaml,*/*/pnpm-workspace.yaml,pnpm-lock.yaml,vendor/README.md,python/*/pyproject.toml,scripts/gen-third-party-notices.ts,scripts/browser-bundled-externals.ts,packages/client/tsdown.client.ts,scripts/build-exe-for-python-sdk.ts}'
+      glob: '{package.json,*/package.json,*/*/package.json,*/*/*/package.json,*/*/*/*/package.json,pnpm-workspace.yaml,*/*/pnpm-workspace.yaml,pnpm-lock.yaml,vendor/README.md,python/*/pyproject.toml,scripts/gen-third-party-notices.ts,scripts/build-exe-for-python-sdk.ts}'
       run: node_modules/.bin/tsx scripts/gen-third-party-notices.ts && git add THIRD_PARTY_NOTICES.md
 
     - name: whitespace (staged)

+ 1 - 2
package.json

@@ -102,7 +102,6 @@
     "rescope-vendor": "tsx scripts/rescope-vendor.ts",
     "rescope-vendor:check": "tsx scripts/rescope-vendor.ts --check",
     "verify-client-domain-graph": "tsx scripts/verify-client-domain-graph.ts",
-    "verify-client-runtime-deps": "tsx scripts/verify-client-runtime-deps.ts",
     "gen-cordis-catalog": "tsx scripts/gen-cordis-catalog.ts",
     "verify-cordis-catalog": "tsx scripts/gen-cordis-catalog.ts --check",
     "gen-cordis-api": "tsx scripts/gen-cordis-api.ts",
@@ -127,7 +126,7 @@
     "verify-module-graph": "tsx scripts/gen-module-graph.ts --check",
     "constraints": "tsx scripts/check-workspace-constraints.ts",
     "doc-sync": "tsx scripts/run-gates.ts doc-sync",
-    "hygiene": "pnpm run rescope-vendor:check && pnpm run knip && pnpm run publint && pnpm run constraints && pnpm run verify-dsh-package-licenses && pnpm run verify-package-invariants && pnpm run verify-built-package-invariants && pnpm run verify-cordis-config && pnpm run verify-node-next-types && pnpm run verify-optional-dependency-imports && pnpm run verify-runtime-closure && pnpm run verify-client-runtime-deps && pnpm run verify-vendored-links",
+    "hygiene": "pnpm run rescope-vendor:check && pnpm run knip && pnpm run publint && pnpm run constraints && pnpm run verify-dsh-package-licenses && pnpm run verify-package-invariants && pnpm run verify-built-package-invariants && pnpm run verify-cordis-config && pnpm run verify-node-next-types && pnpm run verify-optional-dependency-imports && pnpm run verify-runtime-closure && pnpm run verify-vendored-links",
     "publish:npm-baseline": "tsx scripts/publish-npm-baseline.ts",
     "release:dsh": "tsx scripts/release/bump.ts --family dsh",
     "release:vendor": "tsx scripts/release/bump.ts --family vendor",

+ 1 - 10
packages/client/AGENTS.md

@@ -35,15 +35,6 @@ The `/client` entrypoint of a UI plugin package is its public browser API, not a
 2. **Same-package tests import internals directly** — relative `../src/client/xxx.ts` from package tests, or the `./src/*` subpath where a spec lives outside the package. Never widen the public API to make a test compile.
 3. **Cross-package imports of another plugin's symbols are in principle forbidden.** The sanctioned routes are the slot system (register/renderSlot) and ctx services. If neither fits, stop and escalate — do not add an export to unblock yourself.
 
-## Dependency declaration
-
-A browser artifact resolves nothing on the user's machine: tsdown inlines every non-platform specifier into `lib/client.js`, the shell `dist` answers `PLATFORM_MODULES` from its frozen module table, and Vite inlines the shell's own imports into the published `dist`.
-
-- **An external package only browser code reaches belongs in `devDependencies`.** npm installs `dependencies` and non-optional `peerDependencies` for every consumer, so react, shiki, katex, or clsx declared there ships to users who never load it.
-- **A workspace name stays where it is.** Such a declaration also states which package supplies an injected service or a mounted Remote contribution — [verify-runtime-closure](../../scripts/verify-runtime-closure.ts) and the Loader read it, and the app installs the package regardless.
-- **The node half decides.** Anything its published entries reach — an erased type import, a `require.resolve`, and a Loader row in the package's own `cordis*.yml` included — stays declared as it is.
-- `pnpm run verify-client-runtime-deps` (inside `hygiene`) names each offending entry; knip owns whether the surviving declaration is used at all.
-
 ## ctx discipline (components never see ctx)
 
 `ctx` belongs to the apply world only: the plugin body and the inject factories closed over it. Components — every `.tsx` under a feature domain — receive all data and callbacks **through the four props shares**; they never call a hook that reaches ctx, never import a service class to poke it, never read a React context (business components see zero contexts — `BindingContext` and its kin are renderer-internal). If a component needs something new, the answer is a prop threaded from its share's source (owner site, store declaration, or inject face), not a hook.
@@ -100,7 +91,7 @@ If `test:gui` is red on code you did not touch, neither silently fix nor ignore
 
 Bringing up a new `packages/client/<name>` plugin package (ui-workspace is a complete example; ui-sidebar/ui-user-questions are minimal skeletons):
 
-1. **Package skeleton**: `package.json` (`@deepseek-ai/dsh-client-<name>`, exports `.`/`./invariant`/`./client`/`./src/*`/`./package.json`, `dsh.client` manifest, `files` list, and every browser-side external package under `devDependencies` per the [declaration rules](#dependency-declaration)), `tsconfig.json` (extends `tsconfig.base.client.json`, one `references` entry per workspace dependency plus `runtime-diagnostics/invariants`), `tsdown.config.ts` (`clientBundle(id, ['lib/types/index.js', 'lib/types/invariant.js'])`), `src/index.ts` (empty node-half apply), `src/invariant.ts` (companion with a real reason), `src/css-modules.d.ts` when using CSS Modules, `README.md` with the Model Experience section.
+1. **Package skeleton**: `package.json` (`@deepseek-ai/dsh-client-<name>`, exports `.`/`./invariant`/`./client`/`./src/*`/`./package.json`, `dsh.client` manifest, `files` list), `tsconfig.json` (extends `tsconfig.base.client.json`, one `references` entry per workspace dependency plus `runtime-diagnostics/invariants`), `tsdown.config.ts` (`clientBundle(id, ['lib/types/index.js', 'lib/types/invariant.js'])`), `src/index.ts` (empty node-half apply), `src/invariant.ts` (companion with a real reason), `src/css-modules.d.ts` when using CSS Modules, `README.md` with the Model Experience section.
 2. **Three registration surfaces, all required** (missing any one fails at a different, later point): the `tsconfig.client.json` aggregate `references` entry; a `dsh.client` row in `packages/bundle/web-app/cordis.patch.yml`; a `packages/bundle/web-app/package.json` dependency (profile boots resolve bare row names through the healed `$DSH_HOME/profiles/node_modules` fallback, which mirrors the app's and each bundle's declared dependencies — a row whose package no manifest declares fails to import). `pnpm-workspace.yaml` already globs `packages/*/*`.
 3. **dsh.client manifest semantics**: `platform: 'web'` always; `immediately: true` only for stage-one-prefetch infrastructure rows. `inject` lists package-name dependency edges — they are **informational only** (preflight display, HMR diffing); they do not sequence entry activation or apply order. Activation order is cordis fiber inject waiting on *services*, nothing else.
 4. **Registering into another package's slot**: apply order is unconstrained, and a business service is not a declaration barrier. Use `ctx.slots.inject(name, () => ctx.slots.register(...))`; it waits on the actual declaration, removes the contribution when that declaration collapses, reruns after redeclaration, and leaves with the caller's plugin fiber. Return a generator yielding each registration when several contributions must install and roll back atomically. A bare `slots.register` into an undeclared slot remains an error; keep service edges only for services the contribution actually reads.

+ 2 - 1
packages/client/locale/package.json

@@ -50,7 +50,8 @@
     "@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
     "@deepseek-ai/dsh-client-ui-settings": "workspace:^",
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
-    "@deepseek-ai/dsh-invariants": "workspace:^"
+    "@deepseek-ai/dsh-invariants": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/cordis": "workspace:^",

+ 5 - 5
packages/client/runtime/package.json

@@ -53,7 +53,10 @@
     "@deepseek-ai/dsh-session": "workspace:^",
     "@deepseek-ai/dsh-session-projection": "workspace:^",
     "@deepseek-ai/dsh-session-title": "workspace:^",
-    "@deepseek-ai/dsh-tools": "workspace:^"
+    "@deepseek-ai/dsh-tools": "workspace:^",
+    "immer": "^10.1.1",
+    "react": "^18.2.0",
+    "zustand": "~4.4.7"
   },
   "peerDependencies": {
     "@deepseek-ai/cordis": "workspace:^",
@@ -69,10 +72,7 @@
     "@deepseek-ai/dsh-timeout": "workspace:^",
     "@deepseek-ai/dsh-typert-protocol": "workspace:^",
     "@deepseek-ai/dsh-typert-registry": "workspace:^",
-    "@types/react": "~18.3.1",
-    "immer": "^10.1.1",
-    "react": "^18.2.0",
-    "zustand": "~4.4.7"
+    "@types/react": "~18.3.1"
   },
   "files": [
     "lib/index.js",

+ 2 - 1
packages/client/ui-agent-preset/package.json

@@ -58,7 +58,8 @@
     "@deepseek-ai/dsh-client-ui-settings": "workspace:^",
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-client-web-react": "workspace:^",
-    "@deepseek-ai/dsh-invariants": "workspace:^"
+    "@deepseek-ai/dsh-invariants": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/dsh-api-remotes": "workspace:^",

+ 5 - 5
packages/client/ui-attachment/package.json

@@ -28,16 +28,16 @@
   "license": "MIT",
   "dependencies": {
     "@deepseek-ai/dsh-attachment": "workspace:^",
-    "@deepseek-ai/dsh-client-ui-primitives": "workspace:^"
+    "@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
+    "clsx": "^2.0.0",
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/cordis": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
     "@types/react": "~18.3.1",
-    "@types/react-dom": "~18.3.0",
-    "clsx": "^2.0.0",
-    "react": "^18.2.0",
-    "react-dom": "^18.2.0"
+    "@types/react-dom": "~18.3.0"
   },
   "files": [
     "lib/index.js",

+ 6 - 3
packages/client/ui-commands/package.json

@@ -46,6 +46,9 @@
     "watch": "tsdown --watch"
   },
   "license": "MIT",
+  "dependencies": {
+    "clsx": "^2.0.0"
+  },
   "peerDependencies": {
     "@deepseek-ai/dsh-api-remotes": "workspace:^",
     "@deepseek-ai/dsh-client-locale": "workspace:^",
@@ -56,7 +59,8 @@
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-commands": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
-    "@deepseek-ai/cordis": "workspace:^"
+    "@deepseek-ai/cordis": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/dsh-api-remotes": "workspace:^",
@@ -72,8 +76,7 @@
     "@deepseek-ai/dsh-invariants": "workspace:^",
     "@types/react": "~18.3.1",
     "@deepseek-ai/cordis": "workspace:^",
-    "react": "^18.2.0",
-    "clsx": "^2.0.0"
+    "react": "^18.2.0"
   },
   "files": [
     "lib/index.js",

+ 4 - 3
packages/client/ui-conversation/package.json

@@ -49,6 +49,7 @@
   "license": "MIT",
   "dependencies": {
     "@deepseek-ai/dsh-settings": "workspace:^",
+    "clsx": "^2.0.0",
     "@deepseek-ai/schemastery": "workspace:^"
   },
   "peerDependencies": {
@@ -71,7 +72,8 @@
     "@deepseek-ai/dsh-llm-retry": "workspace:^",
     "@deepseek-ai/dsh-session-stats": "workspace:^",
     "@deepseek-ai/dsh-token-meter": "workspace:^",
-    "@deepseek-ai/dsh-tools": "workspace:^"
+    "@deepseek-ai/dsh-tools": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/cordis": "workspace:^",
@@ -102,8 +104,7 @@
     "@deepseek-ai/dsh-tool-todo": "workspace:^",
     "@deepseek-ai/dsh-tools": "workspace:^",
     "@types/react": "~18.3.1",
-    "react": "^18.2.0",
-    "clsx": "^2.0.0"
+    "react": "^18.2.0"
   },
   "files": [
     "lib/index.js",

+ 4 - 2
packages/client/ui-deliverables/package.json

@@ -45,6 +45,9 @@
     "watch": "tsdown --watch"
   },
   "license": "MIT",
+  "dependencies": {
+    "react": "^18.2.0"
+  },
   "peerDependencies": {
     "@deepseek-ai/dsh-client-connection": "workspace:^",
     "@deepseek-ai/dsh-client-locale": "workspace:^",
@@ -65,8 +68,7 @@
     "@deepseek-ai/dsh-invariants": "workspace:^",
     "@deepseek-ai/dsh-system-prompt": "workspace:^",
     "@types/react": "~18.3.1",
-    "@deepseek-ai/cordis": "workspace:^",
-    "react": "^18.2.0"
+    "@deepseek-ai/cordis": "workspace:^"
   },
   "files": [
     "lib/index.js",

+ 6 - 3
packages/client/ui-directory-picker-browse/package.json

@@ -44,6 +44,9 @@
     "watch": "tsdown --watch"
   },
   "license": "MIT",
+  "dependencies": {
+    "clsx": "^2.0.0"
+  },
   "peerDependencies": {
     "@deepseek-ai/dsh-client-locale": "workspace:^",
     "@deepseek-ai/dsh-client-runtime": "workspace:^",
@@ -51,7 +54,8 @@
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-client-ui-workspace": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
-    "@deepseek-ai/cordis": "workspace:^"
+    "@deepseek-ai/cordis": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/dsh-client-locale": "workspace:^",
@@ -65,8 +69,7 @@
     "@types/react": "~18.3.1",
     "@deepseek-ai/cordis": "workspace:^",
     "react": "^18.2.0",
-    "react-dom": "^18.2.0",
-    "clsx": "^2.0.0"
+    "react-dom": "^18.2.0"
   },
   "files": [
     "lib/index.js",

+ 2 - 1
packages/client/ui-directory-picker-native/package.json

@@ -48,7 +48,8 @@
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-client-ui-workspace": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
-    "@deepseek-ai/cordis": "workspace:^"
+    "@deepseek-ai/cordis": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/dsh-client-runtime": "workspace:^",

+ 2 - 1
packages/client/ui-goal/package.json

@@ -55,7 +55,8 @@
     "@deepseek-ai/dsh-commands": "workspace:^",
     "@deepseek-ai/dsh-goal": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
-    "@deepseek-ai/cordis": "workspace:^"
+    "@deepseek-ai/cordis": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/dsh-client-locale": "workspace:^",

+ 6 - 3
packages/client/ui-input-trigger/package.json

@@ -43,13 +43,17 @@
     "watch": "tsdown --watch"
   },
   "license": "MIT",
+  "dependencies": {
+    "clsx": "^2.0.0"
+  },
   "peerDependencies": {
     "@deepseek-ai/dsh-client-locale": "workspace:^",
     "@deepseek-ai/dsh-client-runtime": "workspace:^",
     "@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
-    "@deepseek-ai/cordis": "workspace:^"
+    "@deepseek-ai/cordis": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/dsh-client-locale": "workspace:^",
@@ -60,8 +64,7 @@
     "@deepseek-ai/dsh-invariants": "workspace:^",
     "@types/react": "~18.3.1",
     "@deepseek-ai/cordis": "workspace:^",
-    "react": "^18.2.0",
-    "clsx": "^2.0.0"
+    "react": "^18.2.0"
   },
   "files": [
     "lib/index.js",

+ 4 - 2
packages/client/ui-jobs/package.json

@@ -45,6 +45,9 @@
   "publishConfig": {
     "access": "public"
   },
+  "dependencies": {
+    "react": "^18.2.0"
+  },
   "peerDependencies": {
     "@deepseek-ai/dsh-client-locale": "workspace:^",
     "@deepseek-ai/dsh-client-runtime": "workspace:^",
@@ -63,8 +66,7 @@
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
     "@types/react": "~18.3.1",
-    "@deepseek-ai/cordis": "workspace:^",
-    "react": "^18.2.0"
+    "@deepseek-ai/cordis": "workspace:^"
   },
   "files": [
     "lib/index.js",

+ 2 - 1
packages/client/ui-layout/package.json

@@ -48,7 +48,8 @@
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-client-ui-theme": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
-    "@deepseek-ai/cordis": "workspace:^"
+    "@deepseek-ai/cordis": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/dsh-client-locale": "workspace:^",

+ 2 - 1
packages/client/ui-message-feedback/package.json

@@ -56,7 +56,8 @@
     "@deepseek-ai/dsh-invariants": "workspace:^",
     "@deepseek-ai/dsh-message-feedback": "workspace:^",
     "@deepseek-ai/dsh-typert-protocol": "workspace:^",
-    "@deepseek-ai/cordis": "workspace:^"
+    "@deepseek-ai/cordis": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/dsh-api-remotes": "workspace:^",

+ 3 - 1
packages/client/ui-model-selection/package.json

@@ -56,7 +56,9 @@
     "@deepseek-ai/dsh-client-ui-input-trigger": "workspace:^",
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
-    "@deepseek-ai/cordis": "workspace:^"
+    "clsx": "^2.1.1",
+    "@deepseek-ai/cordis": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/dsh-api-remotes": "workspace:^",

+ 2 - 1
packages/client/ui-permission-presets/package.json

@@ -60,7 +60,8 @@
     "@deepseek-ai/dsh-client-ui-input-trigger": "workspace:^",
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
-    "@deepseek-ai/dsh-permission-presets": "workspace:^"
+    "@deepseek-ai/dsh-permission-presets": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/cordis": "workspace:^",

+ 2 - 1
packages/client/ui-plan/package.json

@@ -53,7 +53,8 @@
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
     "@deepseek-ai/dsh-plan-mode": "workspace:^",
-    "@deepseek-ai/cordis": "workspace:^"
+    "@deepseek-ai/cordis": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/dsh-api-remotes": "workspace:^",

+ 7 - 5
packages/client/ui-primitives/package.json

@@ -26,11 +26,7 @@
     "./package.json": "./package.json"
   },
   "license": "MIT",
-  "devDependencies": {
-    "@deepseek-ai/dsh-invariants": "workspace:^",
-    "@types/react": "~18.3.1",
-    "@types/react-dom": "~18.3.0",
-    "@deepseek-ai/cordis": "workspace:^",
+  "dependencies": {
     "@shikijs/langs": "^4.3.1",
     "@types/mdast": "^4.0.4",
     "anser": "^2.3.5",
@@ -52,6 +48,12 @@
     "react-dom": "^18.2.0",
     "shiki": "^4.3.1"
   },
+  "devDependencies": {
+    "@deepseek-ai/dsh-invariants": "workspace:^",
+    "@types/react": "~18.3.1",
+    "@types/react-dom": "~18.3.0",
+    "@deepseek-ai/cordis": "workspace:^"
+  },
   "files": [
     "lib/index.js",
     "lib/invariant.js",

+ 5 - 4
packages/client/ui-settings-general/package.json

@@ -49,7 +49,8 @@
   "license": "MIT",
   "dependencies": {
     "@deepseek-ai/dsh-settings": "workspace:^",
-    "@deepseek-ai/schemastery": "workspace:^"
+    "@deepseek-ai/schemastery": "workspace:^",
+    "clsx": "^2.0.0"
   },
   "peerDependencies": {
     "@deepseek-ai/dsh-api-remotes": "workspace:^",
@@ -62,7 +63,8 @@
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-client-web-react": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
-    "@deepseek-ai/cordis": "workspace:^"
+    "@deepseek-ai/cordis": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/dsh-api-remotes": "workspace:^",
@@ -78,8 +80,7 @@
     "@deepseek-ai/dsh-invariants": "workspace:^",
     "@deepseek-ai/cordis": "workspace:^",
     "@types/react": "~18.3.1",
-    "react": "^18.2.0",
-    "clsx": "^2.0.0"
+    "react": "^18.2.0"
   },
   "files": [
     "lib/index.js",

+ 2 - 1
packages/client/ui-settings-models/package.json

@@ -54,7 +54,8 @@
     "@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-client-web-react": "workspace:^",
-    "@deepseek-ai/dsh-invariants": "workspace:^"
+    "@deepseek-ai/dsh-invariants": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/dsh-api-remotes": "workspace:^",

+ 2 - 1
packages/client/ui-settings-plugin-inventory/package.json

@@ -53,7 +53,8 @@
     "@deepseek-ai/dsh-client-ui-settings": "workspace:^",
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
-    "@deepseek-ai/cordis": "workspace:^"
+    "@deepseek-ai/cordis": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/dsh-api-remotes": "workspace:^",

+ 6 - 3
packages/client/ui-settings-plugins/package.json

@@ -56,7 +56,8 @@
     "@deepseek-ai/dsh-client-ui-settings": "workspace:^",
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-client-web-react": "workspace:^",
-    "@deepseek-ai/dsh-invariants": "workspace:^"
+    "@deepseek-ai/dsh-invariants": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/cordis": "workspace:^",
@@ -71,7 +72,6 @@
     "@deepseek-ai/dsh-client-web-react": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
     "@types/react": "~18.3.1",
-    "clsx": "^2.0.0",
     "react": "^18.2.0"
   },
   "files": [
@@ -79,5 +79,8 @@
     "lib/invariant.js",
     "lib/client.js",
     "lib/types/**/*.d.ts"
-  ]
+  ],
+  "dependencies": {
+    "clsx": "^2.0.0"
+  }
 }

+ 2 - 1
packages/client/ui-settings/package.json

@@ -52,7 +52,8 @@
     "@deepseek-ai/dsh-client-schema-form": "workspace:^",
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
-    "@deepseek-ai/dsh-settings": "workspace:^"
+    "@deepseek-ai/dsh-settings": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/cordis": "workspace:^",

+ 6 - 3
packages/client/ui-sidebar/package.json

@@ -44,13 +44,17 @@
     "watch": "tsdown --watch"
   },
   "license": "MIT",
+  "dependencies": {
+    "clsx": "^2.0.0"
+  },
   "peerDependencies": {
     "@deepseek-ai/dsh-client-locale": "workspace:^",
     "@deepseek-ai/dsh-client-runtime": "workspace:^",
     "@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
-    "@deepseek-ai/cordis": "workspace:^"
+    "@deepseek-ai/cordis": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/dsh-client-locale": "workspace:^",
@@ -62,8 +66,7 @@
     "@deepseek-ai/dsh-invariants": "workspace:^",
     "@types/react": "~18.3.1",
     "@deepseek-ai/cordis": "workspace:^",
-    "react": "^18.2.0",
-    "clsx": "^2.0.0"
+    "react": "^18.2.0"
   },
   "files": [
     "lib/index.js",

+ 2 - 1
packages/client/ui-skill/package.json

@@ -56,7 +56,8 @@
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-client-ui-tool": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
-    "@deepseek-ai/cordis": "workspace:^"
+    "@deepseek-ai/cordis": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/dsh-api-remotes": "workspace:^",

+ 4 - 2
packages/client/ui-subagent/package.json

@@ -46,6 +46,9 @@
     "watch": "tsdown --watch"
   },
   "license": "MIT",
+  "dependencies": {
+    "react": "^18.2.0"
+  },
   "peerDependencies": {
     "@deepseek-ai/dsh-client-locale": "workspace:^",
     "@deepseek-ai/dsh-client-runtime": "workspace:^",
@@ -70,8 +73,7 @@
     "@deepseek-ai/dsh-subagent": "workspace:^",
     "@deepseek-ai/dsh-token-meter": "workspace:^",
     "@types/react": "~18.3.1",
-    "@deepseek-ai/cordis": "workspace:^",
-    "react": "^18.2.0"
+    "@deepseek-ai/cordis": "workspace:^"
   },
   "files": [
     "lib/index.js",

+ 3 - 2
packages/client/ui-theme/package.json

@@ -54,7 +54,8 @@
     "@deepseek-ai/dsh-client-ui-settings": "workspace:^",
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-host-webserver": "workspace:^",
-    "@deepseek-ai/dsh-invariants": "workspace:^"
+    "@deepseek-ai/dsh-invariants": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/cordis": "workspace:^",
@@ -68,7 +69,6 @@
     "@deepseek-ai/dsh-host-webserver": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
     "@types/react": "~18.3.1",
-    "clsx": "^2.0.0",
     "react": "^18.2.0"
   },
   "files": [
@@ -84,6 +84,7 @@
   },
   "dependencies": {
     "@deepseek-ai/dsh-settings": "workspace:^",
+    "clsx": "^2.0.0",
     "@deepseek-ai/schemastery": "workspace:^"
   }
 }

+ 5 - 2
packages/client/ui-tool/package.json

@@ -44,6 +44,9 @@
     "watch": "tsdown --watch"
   },
   "license": "MIT",
+  "dependencies": {
+    "clsx": "^2.0.0"
+  },
   "peerDependencies": {
     "@deepseek-ai/cordis": "workspace:^",
     "@deepseek-ai/dsh-api-remotes": "workspace:^",
@@ -52,7 +55,8 @@
     "@deepseek-ai/dsh-client-ui-conversation": "workspace:^",
     "@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
-    "@deepseek-ai/dsh-invariants": "workspace:^"
+    "@deepseek-ai/dsh-invariants": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/cordis": "workspace:^",
@@ -68,7 +72,6 @@
     "@deepseek-ai/dsh-invariants": "workspace:^",
     "@testing-library/react": "^16.1.0",
     "@types/react": "~18.3.1",
-    "clsx": "^2.0.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0"
   },

+ 8 - 4
packages/client/ui-trajectory/package.json

@@ -44,6 +44,10 @@
     "watch": "tsdown --watch"
   },
   "license": "MIT",
+  "dependencies": {
+    "@tanstack/react-virtual": "^3.14.9",
+    "diff": "^9.0.0"
+  },
   "peerDependencies": {
     "@deepseek-ai/dsh-agent": "workspace:^",
     "@deepseek-ai/dsh-client-locale": "workspace:^",
@@ -52,7 +56,9 @@
     "@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
     "@deepseek-ai/cordis": "workspace:^",
     "@deepseek-ai/dsh-compaction": "workspace:^",
-    "@deepseek-ai/dsh-tools": "workspace:^"
+    "@deepseek-ai/dsh-tools": "workspace:^",
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/dsh-agent": "workspace:^",
@@ -69,9 +75,7 @@
     "@types/react-dom": "~18.3.0",
     "@deepseek-ai/cordis": "workspace:^",
     "react": "^18.2.0",
-    "react-dom": "^18.2.0",
-    "@tanstack/react-virtual": "^3.14.9",
-    "diff": "^9.0.0"
+    "react-dom": "^18.2.0"
   },
   "files": [
     "lib/index.js",

+ 4 - 4
packages/client/ui-user-questions/package.json

@@ -48,7 +48,9 @@
     "@deepseek-ai/dsh-client-runtime": "workspace:^",
     "@deepseek-ai/dsh-client-ui-conversation": "workspace:^",
     "@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
-    "@deepseek-ai/dsh-client-ui-slots": "workspace:^"
+    "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
+    "clsx": "^2.0.0",
+    "react": "^18.2.0"
   },
   "peerDependencies": {
     "@deepseek-ai/cordis": "workspace:^",
@@ -65,9 +67,7 @@
     "@deepseek-ai/dsh-system-prompt": "workspace:^",
     "@deepseek-ai/dsh-tools": "workspace:^",
     "@deepseek-ai/dsh-user-questions": "workspace:^",
-    "@types/react": "~18.3.1",
-    "clsx": "^2.0.0",
-    "react": "^18.2.0"
+    "@types/react": "~18.3.1"
   },
   "files": [
     "lib/index.js",

+ 4 - 2
packages/client/ui-workflow-run/package.json

@@ -50,6 +50,9 @@
     "lib/types/**/*.d.ts"
   ],
   "license": "MIT",
+  "dependencies": {
+    "react": "^18.2.0"
+  },
   "peerDependencies": {
     "@deepseek-ai/dsh-client-locale": "workspace:^",
     "@deepseek-ai/dsh-client-runtime": "workspace:^",
@@ -74,7 +77,6 @@
     "@deepseek-ai/dsh-tool-workflow": "workspace:^",
     "@deepseek-ai/dsh-workflow": "workspace:^",
     "@types/react": "~18.3.1",
-    "@deepseek-ai/cordis": "workspace:^",
-    "react": "^18.2.0"
+    "@deepseek-ai/cordis": "workspace:^"
   }
 }

+ 6 - 3
packages/client/ui-workspace/package.json

@@ -45,13 +45,17 @@
     "watch": "tsdown --watch"
   },
   "license": "MIT",
+  "dependencies": {
+    "clsx": "^2.0.0"
+  },
   "peerDependencies": {
     "@deepseek-ai/dsh-client-locale": "workspace:^",
     "@deepseek-ai/dsh-client-runtime": "workspace:^",
     "@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
-    "@deepseek-ai/cordis": "workspace:^"
+    "@deepseek-ai/cordis": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/dsh-client-locale": "workspace:^",
@@ -64,8 +68,7 @@
     "@deepseek-ai/dsh-invariants": "workspace:^",
     "@types/react": "~18.3.1",
     "@deepseek-ai/cordis": "workspace:^",
-    "react": "^18.2.0",
-    "clsx": "^2.0.0"
+    "react": "^18.2.0"
   },
   "files": [
     "lib/index.js",

+ 4 - 4
packages/client/web-react/package.json

@@ -27,7 +27,9 @@
   },
   "license": "MIT",
   "dependencies": {
-    "@deepseek-ai/dsh-client-ui-slots": "workspace:^"
+    "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
+    "react": "^18.2.0",
+    "use-sync-external-store": "1.2.0"
   },
   "peerDependencies": {
     "@deepseek-ai/dsh-invariants": "workspace:^",
@@ -36,9 +38,7 @@
   "devDependencies": {
     "@deepseek-ai/dsh-invariants": "workspace:^",
     "@types/react": "~18.3.1",
-    "@deepseek-ai/cordis": "workspace:^",
-    "react": "^18.2.0",
-    "use-sync-external-store": "1.2.0"
+    "@deepseek-ai/cordis": "workspace:^"
   },
   "files": [
     "lib/index.js",

+ 4 - 4
packages/client/web/package.json

@@ -33,7 +33,9 @@
     "@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-client-ui-theme": "workspace:^",
-    "@deepseek-ai/dsh-client-web-react": "workspace:^"
+    "@deepseek-ai/dsh-client-web-react": "workspace:^",
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/cordis-plugin-loader": "workspace:^",
@@ -43,9 +45,7 @@
     "@types/react": "~18.3.1",
     "@types/react-dom": "~18.3.0",
     "@deepseek-ai/cordis": "workspace:^",
-    "typescript": "^6.0.3",
-    "react": "^18.2.0",
-    "react-dom": "^18.2.0"
+    "typescript": "^6.0.3"
   },
   "peerDependencies": {
     "@deepseek-ai/cordis-plugin-loader": "workspace:^",

+ 2 - 1
packages/extensions/cordis-client-runner/package.json

@@ -54,7 +54,8 @@
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-client-ui-theme": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
-    "@deepseek-ai/cordis": "workspace:^"
+    "@deepseek-ai/cordis": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/cordis-plugin-loader": "workspace:^",

+ 2 - 1
packages/extensions/ui-cordis/package.json

@@ -61,7 +61,8 @@
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-client-ui-tool": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
-    "@deepseek-ai/cordis": "workspace:^"
+    "@deepseek-ai/cordis": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/dsh-api-remotes": "workspace:^",

+ 8 - 26
packages/session-query/session-log-export/package.json

@@ -2,9 +2,7 @@
   "name": "@deepseek-ai/dsh-session-log-export",
   "description": "Web Session-log export command and shared download dialog",
   "version": "0.1.0-rc.6",
-  "publishConfig": {
-    "access": "public"
-  },
+  "publishConfig": { "access": "public" },
   "repository": {
     "type": "git",
     "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
@@ -14,31 +12,14 @@
   "main": "lib/index.js",
   "types": "lib/types/index.d.ts",
   "exports": {
-    ".": {
-      "types": "./lib/types/index.d.ts",
-      "default": "./lib/index.js"
-    },
-    "./invariant": {
-      "types": "./lib/types/invariant.d.ts",
-      "default": "./lib/invariant.js"
-    },
-    "./client": {
-      "types": "./lib/types/client/index.d.ts",
-      "default": "./lib/client.js"
-    },
+    ".": { "types": "./lib/types/index.d.ts", "default": "./lib/index.js" },
+    "./invariant": { "types": "./lib/types/invariant.d.ts", "default": "./lib/invariant.js" },
+    "./client": { "types": "./lib/types/client/index.d.ts", "default": "./lib/client.js" },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
-  "files": [
-    "lib/index.js",
-    "lib/invariant.js",
-    "lib/client.js",
-    "lib/types/**/*.d.ts"
-  ],
-  "scripts": {
-    "bundle": "tsdown",
-    "watch": "tsdown --watch"
-  },
+  "files": ["lib/index.js", "lib/invariant.js", "lib/client.js", "lib/types/**/*.d.ts"],
+  "scripts": { "bundle": "tsdown", "watch": "tsdown --watch" },
   "license": "MIT",
   "peerDependencies": {
     "@deepseek-ai/cordis": "workspace:^",
@@ -49,7 +30,8 @@
     "@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
     "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
     "@deepseek-ai/dsh-commands": "workspace:^",
-    "@deepseek-ai/dsh-invariants": "workspace:^"
+    "@deepseek-ai/dsh-invariants": "workspace:^",
+    "react": "^18.2.0"
   },
   "devDependencies": {
     "@deepseek-ai/cordis": "workspace:^",

+ 114 - 101
pnpm-lock.yaml

@@ -358,6 +358,12 @@ importers:
       '@deepseek-ai/dsh-client-web':
         specifier: workspace:^
         version: link:../../packages/client/web
+      react:
+        specifier: ^18.2.0
+        version: 18.3.1
+      react-dom:
+        specifier: ^18.2.0
+        version: 18.3.1(react@18.3.1)
     devDependencies:
       '@deepseek-ai/cordis-plugin-group':
         specifier: workspace:^
@@ -398,12 +404,6 @@ importers:
       playwright:
         specifier: ^1.49.0
         version: 1.61.1
-      react:
-        specifier: ^18.2.0
-        version: 18.3.1
-      react-dom:
-        specifier: ^18.2.0
-        version: 18.3.1(react@18.3.1)
       typescript:
         specifier: ^6.0.3
         version: 6.0.3
@@ -1607,6 +1607,15 @@ importers:
       '@deepseek-ai/dsh-tools':
         specifier: workspace:^
         version: link:../../core/tools
+      immer:
+        specifier: ^10.1.1
+        version: 10.2.0
+      react:
+        specifier: ^18.2.0
+        version: 18.3.1
+      zustand:
+        specifier: ~4.4.7
+        version: 4.4.7(@types/react@18.3.31)(immer@10.2.0)(react@18.3.1)
     devDependencies:
       '@deepseek-ai/cordis':
         specifier: workspace:^
@@ -1629,15 +1638,6 @@ importers:
       '@types/react':
         specifier: ~18.3.1
         version: 18.3.31
-      immer:
-        specifier: ^10.1.1
-        version: 10.2.0
-      react:
-        specifier: ^18.2.0
-        version: 18.3.1
-      zustand:
-        specifier: ~4.4.7
-        version: 4.4.7(@types/react@18.3.31)(immer@10.2.0)(react@18.3.1)
 
   packages/client/schema-form:
     dependencies:
@@ -1705,6 +1705,15 @@ importers:
       '@deepseek-ai/dsh-client-ui-primitives':
         specifier: workspace:^
         version: link:../ui-primitives
+      clsx:
+        specifier: ^2.0.0
+        version: 2.1.1
+      react:
+        specifier: ^18.2.0
+        version: 18.3.1
+      react-dom:
+        specifier: ^18.2.0
+        version: 18.3.1(react@18.3.1)
     devDependencies:
       '@deepseek-ai/cordis':
         specifier: workspace:^
@@ -1718,17 +1727,12 @@ importers:
       '@types/react-dom':
         specifier: ~18.3.0
         version: 18.3.7(@types/react@18.3.31)
+
+  packages/client/ui-commands:
+    dependencies:
       clsx:
         specifier: ^2.0.0
         version: 2.1.1
-      react:
-        specifier: ^18.2.0
-        version: 18.3.1
-      react-dom:
-        specifier: ^18.2.0
-        version: 18.3.1(react@18.3.1)
-
-  packages/client/ui-commands:
     devDependencies:
       '@deepseek-ai/cordis':
         specifier: workspace:^
@@ -1769,9 +1773,6 @@ importers:
       '@types/react':
         specifier: ~18.3.1
         version: 18.3.31
-      clsx:
-        specifier: ^2.0.0
-        version: 2.1.1
       react:
         specifier: ^18.2.0
         version: 18.3.1
@@ -1784,6 +1785,9 @@ importers:
       '@deepseek-ai/schemastery':
         specifier: link:../../../vendor/schemastery
         version: link:../../../vendor/schemastery
+      clsx:
+        specifier: ^2.0.0
+        version: 2.1.1
     devDependencies:
       '@deepseek-ai/cordis':
         specifier: workspace:^
@@ -1869,14 +1873,15 @@ importers:
       '@types/react':
         specifier: ~18.3.1
         version: 18.3.31
-      clsx:
-        specifier: ^2.0.0
-        version: 2.1.1
       react:
         specifier: ^18.2.0
         version: 18.3.1
 
   packages/client/ui-deliverables:
+    dependencies:
+      react:
+        specifier: ^18.2.0
+        version: 18.3.1
     devDependencies:
       '@deepseek-ai/cordis':
         specifier: workspace:^
@@ -1908,11 +1913,12 @@ importers:
       '@types/react':
         specifier: ~18.3.1
         version: 18.3.31
-      react:
-        specifier: ^18.2.0
-        version: 18.3.1
 
   packages/client/ui-directory-picker-browse:
+    dependencies:
+      clsx:
+        specifier: ^2.0.0
+        version: 2.1.1
     devDependencies:
       '@deepseek-ai/cordis':
         specifier: workspace:^
@@ -1944,9 +1950,6 @@ importers:
       '@types/react':
         specifier: ~18.3.1
         version: 18.3.31
-      clsx:
-        specifier: ^2.0.0
-        version: 2.1.1
       react:
         specifier: ^18.2.0
         version: 18.3.1
@@ -2033,6 +2036,10 @@ importers:
         version: 18.3.1(react@18.3.1)
 
   packages/client/ui-input-trigger:
+    dependencies:
+      clsx:
+        specifier: ^2.0.0
+        version: 2.1.1
     devDependencies:
       '@deepseek-ai/cordis':
         specifier: workspace:^
@@ -2058,14 +2065,15 @@ importers:
       '@types/react':
         specifier: ~18.3.1
         version: 18.3.31
-      clsx:
-        specifier: ^2.0.0
-        version: 2.1.1
       react:
         specifier: ^18.2.0
         version: 18.3.1
 
   packages/client/ui-jobs:
+    dependencies:
+      react:
+        specifier: ^18.2.0
+        version: 18.3.1
     devDependencies:
       '@deepseek-ai/cordis':
         specifier: workspace:^
@@ -2094,9 +2102,6 @@ importers:
       '@types/react':
         specifier: ~18.3.1
         version: 18.3.31
-      react:
-        specifier: ^18.2.0
-        version: 18.3.1
 
   packages/client/ui-layout:
     devDependencies:
@@ -2321,25 +2326,13 @@ importers:
         version: 18.3.1
 
   packages/client/ui-primitives:
-    devDependencies:
-      '@deepseek-ai/cordis':
-        specifier: workspace:^
-        version: link:../../../vendor/cordis
-      '@deepseek-ai/dsh-invariants':
-        specifier: workspace:^
-        version: link:../../runtime-diagnostics/invariants
+    dependencies:
       '@shikijs/langs':
         specifier: ^4.3.1
         version: 4.3.1
       '@types/mdast':
         specifier: ^4.0.4
         version: 4.0.4
-      '@types/react':
-        specifier: ~18.3.1
-        version: 18.3.31
-      '@types/react-dom':
-        specifier: ~18.3.0
-        version: 18.3.7(@types/react@18.3.31)
       anser:
         specifier: ^2.3.5
         version: 2.3.5
@@ -2394,6 +2387,19 @@ importers:
       shiki:
         specifier: ^4.3.1
         version: 4.3.1
+    devDependencies:
+      '@deepseek-ai/cordis':
+        specifier: workspace:^
+        version: link:../../../vendor/cordis
+      '@deepseek-ai/dsh-invariants':
+        specifier: workspace:^
+        version: link:../../runtime-diagnostics/invariants
+      '@types/react':
+        specifier: ~18.3.1
+        version: 18.3.31
+      '@types/react-dom':
+        specifier: ~18.3.0
+        version: 18.3.7(@types/react@18.3.31)
 
   packages/client/ui-settings:
     dependencies:
@@ -2440,6 +2446,9 @@ importers:
       '@deepseek-ai/schemastery':
         specifier: link:../../../vendor/schemastery
         version: link:../../../vendor/schemastery
+      clsx:
+        specifier: ^2.0.0
+        version: 2.1.1
     devDependencies:
       '@deepseek-ai/cordis':
         specifier: workspace:^
@@ -2480,9 +2489,6 @@ importers:
       '@types/react':
         specifier: ~18.3.1
         version: 18.3.31
-      clsx:
-        specifier: ^2.0.0
-        version: 2.1.1
       react:
         specifier: ^18.2.0
         version: 18.3.1
@@ -2575,6 +2581,10 @@ importers:
         version: 18.3.1(react@18.3.1)
 
   packages/client/ui-settings-plugins:
+    dependencies:
+      clsx:
+        specifier: ^2.0.0
+        version: 2.1.1
     devDependencies:
       '@deepseek-ai/cordis':
         specifier: workspace:^
@@ -2612,14 +2622,15 @@ importers:
       '@types/react':
         specifier: ~18.3.1
         version: 18.3.31
-      clsx:
-        specifier: ^2.0.0
-        version: 2.1.1
       react:
         specifier: ^18.2.0
         version: 18.3.1
 
   packages/client/ui-sidebar:
+    dependencies:
+      clsx:
+        specifier: ^2.0.0
+        version: 2.1.1
     devDependencies:
       '@deepseek-ai/cordis':
         specifier: workspace:^
@@ -2648,9 +2659,6 @@ importers:
       '@types/react':
         specifier: ~18.3.1
         version: 18.3.31
-      clsx:
-        specifier: ^2.0.0
-        version: 2.1.1
       react:
         specifier: ^18.2.0
         version: 18.3.1
@@ -2716,6 +2724,10 @@ importers:
         version: 18.3.31
 
   packages/client/ui-subagent:
+    dependencies:
+      react:
+        specifier: ^18.2.0
+        version: 18.3.1
     devDependencies:
       '@deepseek-ai/cordis':
         specifier: workspace:^
@@ -2753,9 +2765,6 @@ importers:
       '@types/react':
         specifier: ~18.3.1
         version: 18.3.31
-      react:
-        specifier: ^18.2.0
-        version: 18.3.1
 
   packages/client/ui-theme:
     dependencies:
@@ -2768,6 +2777,9 @@ importers:
       '@deepseek-ai/schemastery':
         specifier: link:../../../vendor/schemastery
         version: link:../../../vendor/schemastery
+      clsx:
+        specifier: ^2.0.0
+        version: 2.1.1
     devDependencies:
       '@deepseek-ai/cordis':
         specifier: workspace:^
@@ -2802,14 +2814,15 @@ importers:
       '@types/react':
         specifier: ~18.3.1
         version: 18.3.31
-      clsx:
-        specifier: ^2.0.0
-        version: 2.1.1
       react:
         specifier: ^18.2.0
         version: 18.3.1
 
   packages/client/ui-tool:
+    dependencies:
+      clsx:
+        specifier: ^2.0.0
+        version: 2.1.1
     devDependencies:
       '@deepseek-ai/cordis':
         specifier: workspace:^
@@ -2850,9 +2863,6 @@ importers:
       '@types/react':
         specifier: ~18.3.1
         version: 18.3.31
-      clsx:
-        specifier: ^2.0.0
-        version: 2.1.1
       react:
         specifier: ^18.2.0
         version: 18.3.1
@@ -2861,6 +2871,13 @@ importers:
         version: 18.3.1(react@18.3.1)
 
   packages/client/ui-trajectory:
+    dependencies:
+      '@tanstack/react-virtual':
+        specifier: ^3.14.9
+        version: 3.14.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      diff:
+        specifier: ^9.0.0
+        version: 9.0.0
     devDependencies:
       '@deepseek-ai/cordis':
         specifier: workspace:^
@@ -2895,18 +2912,12 @@ importers:
       '@deepseek-ai/dsh-tools':
         specifier: workspace:^
         version: link:../../core/tools
-      '@tanstack/react-virtual':
-        specifier: ^3.14.9
-        version: 3.14.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
       '@types/react':
         specifier: ~18.3.1
         version: 18.3.31
       '@types/react-dom':
         specifier: ~18.3.0
         version: 18.3.7(@types/react@18.3.31)
-      diff:
-        specifier: ^9.0.0
-        version: 9.0.0
       react:
         specifier: ^18.2.0
         version: 18.3.1
@@ -2931,6 +2942,12 @@ importers:
       '@deepseek-ai/dsh-client-ui-slots':
         specifier: workspace:^
         version: link:../ui-slots
+      clsx:
+        specifier: ^2.0.0
+        version: 2.1.1
+      react:
+        specifier: ^18.2.0
+        version: 18.3.1
     devDependencies:
       '@deepseek-ai/cordis':
         specifier: workspace:^
@@ -2959,14 +2976,12 @@ importers:
       '@types/react':
         specifier: ~18.3.1
         version: 18.3.31
-      clsx:
-        specifier: ^2.0.0
-        version: 2.1.1
+
+  packages/client/ui-workflow-run:
+    dependencies:
       react:
         specifier: ^18.2.0
         version: 18.3.1
-
-  packages/client/ui-workflow-run:
     devDependencies:
       '@deepseek-ai/cordis':
         specifier: workspace:^
@@ -3004,11 +3019,12 @@ importers:
       '@types/react':
         specifier: ~18.3.1
         version: 18.3.31
-      react:
-        specifier: ^18.2.0
-        version: 18.3.1
 
   packages/client/ui-workspace:
+    dependencies:
+      clsx:
+        specifier: ^2.0.0
+        version: 2.1.1
     devDependencies:
       '@deepseek-ai/cordis':
         specifier: workspace:^
@@ -3040,9 +3056,6 @@ importers:
       '@types/react':
         specifier: ~18.3.1
         version: 18.3.31
-      clsx:
-        specifier: ^2.0.0
-        version: 2.1.1
       react:
         specifier: ^18.2.0
         version: 18.3.1
@@ -3070,6 +3083,12 @@ importers:
       '@deepseek-ai/dsh-client-web-react':
         specifier: workspace:^
         version: link:../web-react
+      react:
+        specifier: ^18.2.0
+        version: 18.3.1
+      react-dom:
+        specifier: ^18.2.0
+        version: 18.3.1(react@18.3.1)
     devDependencies:
       '@deepseek-ai/cordis':
         specifier: workspace:^
@@ -3092,12 +3111,6 @@ importers:
       '@types/react-dom':
         specifier: ~18.3.0
         version: 18.3.7(@types/react@18.3.31)
-      react:
-        specifier: ^18.2.0
-        version: 18.3.1
-      react-dom:
-        specifier: ^18.2.0
-        version: 18.3.1(react@18.3.1)
       typescript:
         specifier: ^6.0.3
         version: 6.0.3
@@ -3107,6 +3120,12 @@ importers:
       '@deepseek-ai/dsh-client-ui-slots':
         specifier: workspace:^
         version: link:../ui-slots
+      react:
+        specifier: ^18.2.0
+        version: 18.3.1
+      use-sync-external-store:
+        specifier: 1.2.0
+        version: 1.2.0(react@18.3.1)
     devDependencies:
       '@deepseek-ai/cordis':
         specifier: workspace:^
@@ -3117,12 +3136,6 @@ importers:
       '@types/react':
         specifier: ~18.3.1
         version: 18.3.31
-      react:
-        specifier: ^18.2.0
-        version: 18.3.1
-      use-sync-external-store:
-        specifier: 1.2.0
-        version: 1.2.0(react@18.3.1)
 
   packages/code-runtime/code-runtime:
     devDependencies:

+ 0 - 230
scripts/browser-bundled-externals.ts

@@ -1,230 +0,0 @@
-/**
- * The external packages a published browser artifact carries a copy of.
- *
- * Read from the real build configurations rather than declared by hand: each
- * `lib/client.js` plugin bundle is driven through its own `tsdown.config.ts`, and
- * the shell `dist` through `apps/web`'s Vite config. A recording plugin resolves
- * every bare specifier as external and notes it, so the pass walks our own source
- * and stops at the package boundary — which is both fast (about two seconds for
- * the whole repository) and exactly the direct-dependency granularity
- * THIRD_PARTY_NOTICES.md discloses. Erased type imports never appear, because the
- * transform drops them before resolution.
- *
- * Workspace names are followed only on the Vite side, where the shell's aliases
- * map them to source: that is how a browser-only library's own third-party
- * imports — katex and shiki through `ui-primitives`, for one — become visible. A
- * plugin bundle keeps them external, matching the frozen module table it is built
- * against; the wire layers it inlines are host packages that declare their own
- * dependencies, so nothing goes undisclosed.
- *
- * A specifier is recorded only once the host resolves it to a file inside a
- * package. A bundler's own virtual module has no package behind it —
- * `vite/modulepreload-polyfill` is generated by a Vite plugin rather than shipped
- * as a file, so the polyfill in the published `dist` is build glue in the same
- * category as an emitted TypeScript helper, not a redistributed copy of Vite.
- *
- * The pass runs on a clean tree, as a static gate must. The shell's Vite config
- * aliases a few workspace packages to source; every other workspace name would
- * resolve through `node_modules` to a `lib/` entry the real build has emitted but
- * a clean checkout has not, so this module resolves those names to their own
- * source instead. `lib/` is compiled from `src/`, so the third-party edges the
- * pass records are the same either way.
- *
- * rolldown is resolved through tsdown deliberately: the dry run must use the
- * exact bundler the real build uses, which a separate root pin could drift from.
- */
-
-import { existsSync, globSync, readFileSync } from 'node:fs'
-import { createRequire } from 'node:module'
-import { dirname, join } from 'node:path'
-
-/** The plugin-context member the recorder needs to resolve before recording. */
-interface ResolveContext {
-  resolve: (
-    source: string,
-    importer: string,
-    options: { skipSelf: boolean },
-  ) => Promise<{ id: string } | null>
-}
-
-/** A rolldown/Vite plugin shape, narrowed to what the recorder needs. */
-interface RecorderPlugin {
-  name: string
-  enforce?: 'pre'
-  resolveId: (
-    this: ResolveContext,
-    source: string,
-    importer: string | undefined,
-  ) => Promise<{ id: string; external: true } | null>
-}
-
-/**
- * The package a resolved module file belongs to.
- * @param file - absolute path of a resolved module.
- * @returns the package name, or undefined when the file is not inside a package.
- */
-function packageOfFile(file: string): string | undefined {
-  const marker = file.lastIndexOf('node_modules/')
-  if (marker < 0) return undefined
-  const rest = file.slice(marker + 'node_modules/'.length)
-  const parts = rest.split('/')
-  return rest.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0]
-}
-
-/**
- * Source aliases for the workspace packages the shell does not already alias.
- *
- * A clean checkout has no `lib/`, so a workspace name would otherwise resolve
- * through `node_modules` to an entry that does not exist yet. Aliases are the
- * right seam rather than a plugin hook, because Vite resolves a stylesheet
- * `@import` through them too — the theme package publishes its stylesheets from
- * `lib/styles/`. `lib/` is compiled from `src/`, so the third-party edges the
- * pass records are the same either way.
- * @param root - repository root.
- * @param existing - the shell's own alias patterns, whose entry choices win.
- * @returns alias entries mapping each remaining workspace name to its source.
- */
-function workspaceSourceAliases(root: string, existing: readonly string[]): { find: RegExp | string; replacement: string }[] {
-  const aliases: { find: RegExp | string; replacement: string }[] = []
-  for (const pattern of ['packages/*/*/package.json', 'vendor/*/package.json']) {
-    for (const relative of globSync(pattern, { cwd: root })) {
-      const dir = join(root, dirname(relative))
-      const manifest = JSON.parse(readFileSync(join(root, relative), 'utf8')) as Manifest & { name?: string }
-      const name = manifest.name
-      if (name === undefined || !existsSync(join(dir, 'src'))) continue
-      if (existing.some(find => find.includes(name))) continue
-      const root_ = manifest.exports?.['.']
-      const target = typeof root_ === 'string' ? root_ : root_?.default
-      const stem = (target ?? './lib/index.js')
-        .replace(/^\.\/lib\/types\//, '').replace(/^\.\/lib\//, '').replace(/\.js$/, '')
-      const entry = [`${stem}.ts`, `${stem}.tsx`, `${stem}/index.ts`, `${stem}/index.tsx`]
-        .map(candidate => join(dir, 'src', candidate))
-        .find(candidate => existsSync(candidate))
-      // The subpath prefix carries `./client`, `./types`, and `./styles/*` alike:
-      // each published subpath mirrors a path under `src/`.
-      aliases.push({ find: `${name}/`, replacement: `${join(dir, 'src')}/` })
-      if (entry !== undefined) aliases.push({ find: new RegExp(`^${name.replaceAll('/', '\\/')}$`), replacement: entry })
-    }
-  }
-  return aliases
-}
-
-/**
- * Build the plugin that records bare specifiers and stops the walk at them.
- * @param seen - set the recorder adds package names to.
- * @returns the recording plugin.
- */
-function recorder(seen: Set<string>): RecorderPlugin {
-  return {
-    name: 'dsh-record-direct-externals',
-    enforce: 'pre',
-    async resolveId(source, importer) {
-      if (importer === undefined) return null // the entry itself
-      if (source.startsWith('.') || source.startsWith('/') || source.startsWith('\0')) return null
-      if (source.startsWith('virtual:') || source.includes('?')) return null
-      // A workspace name that reaches here is one no alias mapped to source, so
-      // nothing of ours is left to walk; it is never a third-party disclosure.
-      if (source.startsWith('@deepseek-ai/')) return { id: source, external: true }
-      if (source.startsWith('node:')) return { id: source, external: true }
-      if (!source.startsWith('@deepseek-ai/')) {
-        const resolved = await this.resolve(source, importer, { skipSelf: true })
-        const name = resolved === null ? undefined : packageOfFile(resolved.id)
-        if (name !== undefined) seen.add(name)
-      }
-      return { id: source, external: true }
-    },
-  }
-}
-
-interface Manifest {
-  exports?: Record<string, { default?: string } | string | null>
-  files?: string[]
-}
-
-/** Read one workspace manifest. */
-function manifestOf(dir: string): Manifest {
-  return JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as Manifest
-}
-
-/** Whether a manifest publishes a tsdown browser bundle at `lib/client.js`. */
-function publishesClientBundle(manifest: Manifest): boolean {
-  const target = manifest.exports?.['./client']
-  return typeof target === 'object' && target !== null && target.default === './lib/client.js'
-}
-
-/**
- * Record every external package the plugin client bundles carry.
- * @param root - repository root.
- * @param seen - set the recorder adds package names to.
- */
-async function collectFromClientBundles(root: string, seen: Set<string>): Promise<void> {
-  const requireFromTsdown = createRequire(createRequire(import.meta.url).resolve('tsdown'))
-  const { rolldown } = await import(requireFromTsdown.resolve('rolldown')) as {
-    rolldown: (options: Record<string, unknown>) => Promise<{
-      generate: (output: Record<string, unknown>) => Promise<unknown>
-      close: () => Promise<void>
-    }>
-  }
-
-  for (const relative of globSync('packages/*/*/tsdown.config.ts', { cwd: root }).sort()) {
-    const dir = join(root, dirname(relative))
-    if (!publishesClientBundle(manifestOf(dir))) continue
-    const loaded = await import(join(root, relative)) as { default: unknown }
-    const factory = loaded.default
-    const configs = (typeof factory === 'function'
-      ? (factory as (inline: { env: Record<string, string> }) => unknown[])({ env: {} })
-      : [factory]) as { name?: string; entry?: unknown; plugins?: unknown[] }[]
-    // The `/client` config is the browser bundle; its siblings emit the node half.
-    const client = configs.find(config => config.name?.endsWith('/client') === true)
-    if (client === undefined) continue
-    const bundle = await rolldown({
-      cwd: dir,
-      input: client.entry,
-      plugins: [recorder(seen), ...(client.plugins ?? [])],
-      platform: 'browser',
-    })
-    await bundle.generate({ format: 'cjs', minify: false, sourcemap: false })
-    await bundle.close()
-  }
-}
-
-/**
- * Record every external package the prebuilt shell bundle carries.
- * @param root - repository root.
- * @param seen - set the recorder adds package names to.
- */
-async function collectFromShellBundle(root: string, seen: Set<string>): Promise<void> {
-  for (const relative of globSync('apps/*/vite.config.ts', { cwd: root }).sort()) {
-    const dir = join(root, dirname(relative))
-    // Vite belongs to the app that builds with it, so it resolves from there.
-    const { build, resolveConfig } = await import(createRequire(join(dir, 'package.json')).resolve('vite')) as {
-      build: (options: Record<string, unknown>) => Promise<unknown>
-      resolveConfig: (options: Record<string, unknown>, command: string) => Promise<{
-        resolve: { alias: { find: string | RegExp }[] }
-      }>
-    }
-    // The shell already aliases some workspace names to source, and its entry
-    // choices win: a stylesheet `@import` resolves through aliases rather than a
-    // plugin hook, so only the names it leaves out get one from here.
-    const resolved = await resolveConfig({ root: dir, logLevel: 'error' }, 'build')
-    await build({
-      root: dir,
-      logLevel: 'error',
-      plugins: [recorder(seen)],
-      resolve: { alias: workspaceSourceAliases(root, resolved.resolve.alias.map(entry => String(entry.find))) },
-      build: { write: false, minify: false, sourcemap: false, reportCompressedSize: false },
-    })
-  }
-}
-
-/**
- * The external packages a published browser artifact carries a copy of.
- * @param root - repository root.
- * @returns package names, workspace names excluded.
- */
-export async function browserBundledExternals(root: string): Promise<Set<string>> {
-  const seen = new Set<string>()
-  await collectFromClientBundles(root, seen)
-  await collectFromShellBundle(root, seen)
-  return seen
-}

+ 3 - 21
scripts/gen-third-party-notices.spec.ts

@@ -24,12 +24,11 @@ describe('THIRD_PARTY_NOTICES.md', () => {
   // already runs in the test lane, so the check costs no extra CI process.
   // Pre-commit regenerates the file whenever a manifest is staged, so reaching
   // this assertion means the notices were committed without that hook.
-  it('matches what the generator produces from the current manifests', async () => {
-    const generated = await render()
+  it('matches what the generator produces from the current manifests', () => {
+    const generated = render()
     expect(generated).toContain('It depends on the third-party software listed below.')
     expect(readFileSync(resolve(root, 'THIRD_PARTY_NOTICES.md'), 'utf8'), 'stale notices — run `pnpm run gen-third-party-notices`').toBe(generated)
-    // Driving the two real bundlers to learn what ships costs a few seconds.
-  }, 60_000)
+  })
 })
 
 /** Build the (manifests, names) pair `tierExternalDeps` consumes. */
@@ -68,23 +67,6 @@ describe('tierExternalDeps', () => {
     ]))
   })
 
-  it('keeps a devDependency runtime when a published browser artifact carries it', () => {
-    const { manifests, names } = workspace({
-      // The client build inlines these, so a copy ships even though no manifest
-      // resolves the specifier at run time.
-      'packages/client/ui-primitives/package.json': {
-        name: '@deepseek-ai/dsh-client-ui-primitives',
-        devDependencies: { katex: '^0.16', 'test-only-helper': '^1' },
-      },
-    })
-
-    expect(tierExternalDeps(manifests, names, new Set(['katex']))).toEqual(new Map([
-      ['tsx', true],
-      ['katex', true],
-      ['test-only-helper', false],
-    ]))
-  })
-
   it('keeps a package runtime when any shipping area declares it, and excludes workspace links', () => {
     const { manifests, names } = workspace({
       'package.json': { devDependencies: { shared: '^1' } },

+ 16 - 31
scripts/gen-third-party-notices.ts

@@ -13,7 +13,6 @@ import { resolve } from 'node:path'
 import * as yaml from 'js-yaml'
 import { parse as parseToml, type TomlTableWithoutBigInt, type TomlValueWithoutBigInt } from 'smol-toml'
 import parseSpdx from 'spdx-expression-parse'
-import { browserBundledExternals } from './browser-bundled-externals.ts'
 
 const root = resolve(import.meta.dirname, '..')
 const OUT = 'THIRD_PARTY_NOTICES.md'
@@ -348,17 +347,15 @@ function normalizeRepo(raw: string | undefined): string | undefined {
 }
 
 /**
- * External npm dependencies, tiered by what reaches a user: a package is runtime
- * when any manifest outside `DEV_ONLY_AREAS` names it in
- * `dependencies`/`optionalDependencies`, or when a published browser artifact
- * carries a copy of it. A package declared only by tooling, test infrastructure,
- * the website, or the demo leaves — whatever the declaring section is called, and
- * with no shipped artifact carrying it — is development-only.
- * @returns every external dependency with its tier and metadata.
+ * External npm dependencies, tiered by which workspace area declares them at
+ * runtime: a package is runtime when any manifest outside `DEV_ONLY_AREAS`
+ * names it in `dependencies`/`optionalDependencies`. A package declared only
+ * by tooling, test infrastructure, the website, or the demo leaves — whatever
+ * the declaring section is called — is development-only.
  */
-async function collectNpmDeps(): Promise<ExternalDep[]> {
+function collectNpmDeps(): ExternalDep[] {
   const { manifests, names } = loadWorkspaceManifests()
-  return [...tierExternalDeps(manifests, names, await browserBundledExternals(root))]
+  return [...tierExternalDeps(manifests, names)]
     .filter(([name]) => !FIRST_PARTY.has(name))
     .sort(([a], [b]) => a.localeCompare(b))
     .map(([name, runtime]) => ({ name, ...installedMetadata(name), runtime }))
@@ -366,23 +363,11 @@ async function collectNpmDeps(): Promise<ExternalDep[]> {
 
 /**
  * Tier every external dependency the workspace declares.
- *
- * A package a published browser artifact carries is runtime whatever section
- * declares it: the client build inlines its code, or the shell `dist` answers it
- * from the frozen module table, so a copy is redistributed even though nothing on
- * a user's machine resolves the specifier. Those packages are declared as
- * `devDependencies` — `verify-client-runtime-deps` owns that rule — and tiering
- * them by section alone would understate the notice.
  * @param manifests - workspace manifests keyed by repository-relative path.
  * @param names - every workspace package name, which never counts as external.
- * @param bundled - external packages a published browser artifact carries.
  * @returns each external package mapped to whether it is a runtime dependency.
  */
-export function tierExternalDeps(
-  manifests: Map<string, Manifest>,
-  names: Set<string>,
-  bundled: ReadonlySet<string> = new Set(),
-): Map<string, boolean> {
+export function tierExternalDeps(manifests: Map<string, Manifest>, names: Set<string>): Map<string, boolean> {
   const tiers = new Map<string, boolean>()
   // `tsx` is runtime by fiat: the root source-run scripts execute through its ESM hook.
   tiers.set('tsx', true)
@@ -391,7 +376,7 @@ export function tierExternalDeps(
     for (const kind of ALL_KINDS) {
       for (const [dep, range] of Object.entries(manifest[kind] ?? {})) {
         if (names.has(dep) || range.startsWith('workspace:')) continue
-        const runtime = bundled.has(dep) || (!devOnly && (RUNTIME_KINDS as readonly string[]).includes(kind))
+        const runtime = !devOnly && (RUNTIME_KINDS as readonly string[]).includes(kind)
         tiers.set(dep, (tiers.get(dep) ?? false) || runtime)
       }
     }
@@ -675,9 +660,9 @@ ${rows.join('\n')}
  * Render the complete notices document.
  * @returns the exact bytes `THIRD_PARTY_NOTICES.md` must hold.
  */
-export async function render(): Promise<string> {
+export function render(): string {
   verifyBuildTimePins()
-  const npm = await collectNpmDeps()
+  const npm = collectNpmDeps()
   const runtimeDeps = npm.filter(dep => dep.runtime)
   const devDeps = npm.filter(dep => !dep.runtime)
   const vendored = collectVendored()
@@ -722,7 +707,7 @@ ${vendored.map(row => `| \`${row.npmName}\` | \`${row.upstreamName}\` | [${row.u
 
 ## Runtime npm dependencies
 
-External packages that reach a user: a workspace package resolves them at runtime, or a published browser artifact carries a copy of their code. The tier covers every plugin a user can mount from \`cordis.yml\` — not only what the \`dsh\` CLI, Web UI, and Python SDK runtime load by default — and the packages the client build inlines into a plugin bundle or the shell \`dist\`, which are declared as \`devDependencies\` because nothing on a user's machine resolves their specifiers.
+External packages that a workspace package resolves at runtime. The tier covers every plugin a user can mount from \`cordis.yml\` — not only what the \`dsh\` CLI, Web UI, and Python SDK runtime load by default.
 
 ${renderNpmTable(runtimeDeps)}
 
@@ -733,7 +718,7 @@ ${renderClaudeDistribution(claudeDistribution)}
 
 ## Development-only npm dependencies
 
-External packages **directly declared** only by repository tooling, test infrastructure, the documentation site, the demo leaves, or the native launcher's build workspace, and carried by no published artifact. No shipped surface names them itself. A package here may still be pulled in transitively by a runtime dependency — \`pnpm-lock.yaml\` is the authority on the full closure — so this tier records who declares a package, not what a build ultimately bundles.
+External packages **directly declared** only by repository tooling, test infrastructure, the documentation site, the demo leaves, or the native launcher's build workspace. No shipped surface names them itself. A package here may still be pulled in transitively by a runtime dependency — \`pnpm-lock.yaml\` is the authority on the full closure — so this tier records who declares a package, not what a build ultimately bundles.
 
 ${renderNpmTable(devDeps)}
 ${renderNonPermissiveNote(nonPermissiveDev)}
@@ -761,8 +746,8 @@ ${BUILD_TIME_TOOLS.map(tool => `| [\`${tool.name}\`](${tool.repo}) | ${tool.lice
 /** CLI entry: default writes the notices, `--check` fails if the committed copy
  * is stale. Guarded behind an entry-point check so importing this module for
  * tests neither regenerates the committed file nor calls process.exit. */
-async function main(): Promise<void> {
-  const content = await render()
+function main(): void {
+  const content = render()
   if (process.argv.includes('--check')) {
     let committed: string | null = null
     try {
@@ -786,5 +771,5 @@ async function main(): Promise<void> {
 
 // Run only when invoked as a script, not when imported by a test.
 if (process.argv[1] !== undefined && import.meta.filename === resolve(process.argv[1])) {
-  await main()
+  main()
 }

+ 0 - 368
scripts/verify-client-runtime-deps.ts

@@ -1,368 +0,0 @@
-/**
- * Keep browser-only external packages out of installed dependency sections.
- *
- * A browser artifact resolves nothing on the user's machine: tsdown inlines
- * every non-platform specifier into `lib/client.js`, the shell `dist` answers
- * `PLATFORM_MODULES` from its frozen module table, and Vite inlines the shell's
- * own imports into that published `dist`. A specifier only browser source
- * reaches is therefore a build-time input and belongs in `devDependencies`,
- * because npm installs `dependencies` and non-optional `peerDependencies` for
- * every consumer of the published package.
- *
- * Each face is walked from the entries the manifest publishes, not by a
- * directory rule, so a module under `src/` that only the browser entry reaches
- * counts as browser source:
- *
- *   `./client` is `lib/client.js`   host: the other export targets; browser: the bundle
- *   `packages/client/*` with no     browser-only library: host is `src/invariant.ts`,
- *     `./client` export               the companion the host mounts; `.` is browser code
- *   no `.` export, ships a `dist`   prebuilt browser bundle: no host face at all
- *
- * Only external packages are subject: they are what an install downloads. A
- * workspace name stays where its manifest puts it, because that declaration also
- * states which package supplies an injected service or a mounted Remote
- * contribution, and the app installs it either way. A reference from the host
- * face, an erased type import included, likewise keeps a declaration in place.
- *
- * Run: pnpm exec tsx scripts/verify-client-runtime-deps.ts [--json]
- */
-
-import { existsSync, globSync, readFileSync } from 'node:fs'
-import { dirname, join, resolve } from 'node:path'
-import ts from 'typescript'
-import { TypeScriptProject, type CompilerFace } from './ts-project.ts'
-
-const root = resolve(import.meta.dirname, '..')
-
-/**
- * `@deepseek-ai/cordis` placement belongs to check-workspace-constraints, which
- * requires it as a peerDependency plus devDependency of every harness package
- * regardless of face.
- */
-const PLACEMENT_OWNED_ELSEWHERE = new Set(['@deepseek-ai/cordis'])
-
-/** Dependency sections npm installs for a consumer of the published package. */
-const INSTALLED_SECTIONS = ['dependencies', 'peerDependencies'] as const
-
-type Section = (typeof INSTALLED_SECTIONS)[number]
-
-interface Manifest {
-  name?: string
-  files?: string[]
-  exports?: Record<string, unknown>
-  dependencies?: Record<string, string>
-  peerDependencies?: Record<string, string>
-  peerDependenciesMeta?: Record<string, { optional?: boolean }>
-}
-
-/** How a package reaches the browser, which fixes the entries Node can load. */
-type Kind = 'bundle-half' | 'browser-library' | 'prebuilt-dist'
-
-/** What settles an external specifier as build-time only. */
-type Reached = 'browser' | 'nothing'
-
-interface Violation {
-  readonly section: Section
-  readonly dep: string
-  readonly reached: Reached
-  /** Whether the browser face names it, which decides dev-move versus deletion. */
-  readonly browserReferenced: boolean
-}
-
-interface Offender {
-  readonly name: string
-  readonly dir: string
-  readonly kind: Kind
-  readonly violations: Violation[]
-}
-
-/** Why each class needs no install, for the failure report. */
-const REASON: Record<Reached, string> = {
-  browser: 'only a browser artifact reaches it, and that resolves nothing on the user machine',
-  nothing: 'no reference names it at all',
-}
-
-/**
- * Classify a package by the browser artifact it produces.
- * @param dir - repository-relative package directory.
- * @param manifest - the package manifest.
- * @returns the package kind, or undefined when the package has no browser face.
- */
-function kindOf(dir: string, manifest: Manifest): Kind | undefined {
-  if (manifest.exports?.['./client'] !== undefined) return 'bundle-half'
-  if (dir.startsWith('packages/client/')) return 'browser-library'
-  const shipsDist = (manifest.files ?? []).some(entry => entry === 'dist' || entry.startsWith('dist/'))
-  if (shipsDist && manifest.exports?.['.'] === undefined) return 'prebuilt-dist'
-  return undefined
-}
-
-/** The bare package name a specifier names, keeping a leading scope. */
-function packageOf(specifier: string): string {
-  const parts = specifier.split('/')
-  return specifier.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0] ?? specifier
-}
-
-/** One compiler face's bound program plus its module resolution state. */
-interface Face {
-  readonly project: TypeScriptProject
-  readonly host: ts.CompilerHost
-  readonly cache: ts.ModuleResolutionCache
-}
-
-const faces = new Map<CompilerFace, Face>()
-for (const face of ['host', 'client'] as const) {
-  const project = new TypeScriptProject(root, face)
-  const options = project.program.getCompilerOptions()
-  faces.set(face, {
-    project,
-    host: ts.createCompilerHost(options, false),
-    cache: ts.createModuleResolutionCache(root, fileName => fileName, options),
-  })
-}
-
-/** Which face's program bound each workspace module, keyed by absolute path. */
-const boundIn = new Map<string, CompilerFace>()
-for (const [face, { project }] of faces) {
-  for (const sourceFile of project.sourceFiles()) {
-    if (sourceFile.isDeclarationFile) continue
-    if (!boundIn.has(sourceFile.fileName)) boundIn.set(sourceFile.fileName, face)
-  }
-}
-
-/**
- * Read every module specifier one source file names.
- *
- * An import clause is not the only way to reach a package: `require`,
- * `require.resolve`, and a dynamic `import()` on a literal each name one, and a
- * type-only import still names a package the build must resolve.
- * @param sourceFile - a bound source file.
- * @returns every specifier, relative ones included.
- */
-function specifiersOf(sourceFile: ts.SourceFile): string[] {
-  const specifiers: string[] = []
-  const visit = (node: ts.Node): void => {
-    if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node) || ts.isImportEqualsDeclaration(node)) {
-      const specifier = ts.isImportEqualsDeclaration(node)
-        ? (ts.isExternalModuleReference(node.moduleReference) ? node.moduleReference.expression : undefined)
-        : node.moduleSpecifier
-      if (specifier !== undefined && ts.isStringLiteralLike(specifier)) specifiers.push(specifier.text)
-    } else if (ts.isCallExpression(node)) {
-      const target = node.expression
-      const isRequire = ts.isIdentifier(target) && target.text === 'require'
-      const isRequireResolve = ts.isPropertyAccessExpression(target)
-        && ts.isIdentifier(target.expression) && target.expression.text === 'require'
-        && target.name.text === 'resolve'
-      const argument = node.arguments[0]
-      if ((isRequire || isRequireResolve || target.kind === ts.SyntaxKind.ImportKeyword)
-        && argument !== undefined && ts.isStringLiteralLike(argument)) {
-        specifiers.push(argument.text)
-      }
-    }
-    ts.forEachChild(node, visit)
-  }
-  visit(sourceFile)
-  return specifiers
-}
-
-/**
- * Walk one face from its entries and collect the packages it names.
- * @param entries - absolute entry module paths.
- * @param packageDir - absolute package directory; the walk stops at its edge.
- * @returns package names the walk reaches.
- */
-function walk(entries: readonly string[], packageDir: string): Set<string> {
-  const found = new Set<string>()
-  const seen = new Set<string>()
-  const queue = entries.filter(entry => boundIn.has(entry))
-  while (queue.length > 0) {
-    const file = queue.pop()
-    if (file === undefined || seen.has(file)) continue
-    seen.add(file)
-    const faceName = boundIn.get(file)
-    const face = faceName === undefined ? undefined : faces.get(faceName)
-    const sourceFile = face?.project.program.getSourceFile(file)
-    if (face === undefined || sourceFile === undefined) continue
-
-    for (const specifier of specifiersOf(sourceFile)) {
-      if (!specifier.startsWith('.')) {
-        if (!specifier.startsWith('node:')) found.add(packageOf(specifier))
-        continue
-      }
-      const resolved = ts.resolveModuleName(
-        specifier, file, face.project.program.getCompilerOptions(), face.host, face.cache,
-      ).resolvedModule?.resolvedFileName
-      // A relative specifier resolving outside the package is a packaging error
-      // verify-package-paths owns; either way it is not this package's own module.
-      if (resolved !== undefined && resolved.startsWith(`${packageDir}/`)) queue.push(resolved)
-    }
-  }
-  return found
-}
-
-/**
- * The source module behind one published JavaScript export target.
- *
- * `lib/` holds the tsdown bundles and `lib/types/` the tsc emit, so both
- * prefixes lead back to one `src` module.
- * @param dir - absolute package directory.
- * @param emitted - the export target, as written in the manifest.
- * @returns the absolute source path, or undefined when nothing in `src` emits it.
- */
-function sourceBehind(dir: string, emitted: string): string | undefined {
-  const stem = emitted.replace(/^\.\/lib\/types\//, '').replace(/^\.\/lib\//, '').replace(/\.js$/, '')
-  return [`src/${stem}.ts`, `src/${stem}.tsx`, `src/${stem}/index.ts`, `src/${stem}/index.tsx`]
-    .map(candidate => join(dir, candidate))
-    .find(candidate => existsSync(candidate))
-}
-
-interface Entries {
-  readonly host: string[]
-  readonly browser: string[]
-  /**
-   * Published JavaScript entries no `src` module emits — a generated artifact
-   * such as `lib/typert.host.js`, whose own runtime imports are invisible here.
-   */
-  readonly generated: string[]
-}
-
-/**
- * The entry modules of each face, derived from what the manifest publishes.
- * @param dir - absolute package directory.
- * @param manifest - the package manifest.
- * @param kind - the package kind.
- * @returns absolute entry module paths per face, plus unmapped published entries.
- */
-function faceEntries(dir: string, manifest: Manifest, kind: Kind): Entries {
-  if (kind === 'prebuilt-dist') return { host: [], browser: [], generated: [] }
-  if (kind === 'browser-library') {
-    return { host: [join(dir, 'src/invariant.ts')], browser: [join(dir, 'src/index.ts')], generated: [] }
-  }
-  const host = [join(dir, 'src/index.ts'), join(dir, 'src/invariant.ts')]
-  const browser: string[] = []
-  const generated: string[] = []
-  for (const [key, target] of Object.entries(manifest.exports ?? {})) {
-    if (key === '.' || key === './package.json' || key.includes('*')) continue
-    const emitted = typeof target === 'string' ? target : (target as { default?: unknown }).default
-    if (typeof emitted !== 'string' || !emitted.endsWith('.js')) continue
-    // Keyed on the artifact path, not the subpath name: `./client` is the tsdown
-    // browser bundle only when it resolves to lib/client.js, while other packages
-    // publish a plain browser-shared module under the same subpath.
-    const source = emitted === './lib/client.js'
-      ? sourceBehind(dir, './lib/client/index.js')
-      : sourceBehind(dir, emitted)
-    if (source === undefined) generated.push(`${key} -> ${emitted}`)
-    else if (key === './client') browser.push(source)
-    else host.push(source)
-  }
-  return { host, browser, generated }
-}
-
-/** Every installed dependency of a manifest, paired with its section. */
-function installedDeps(manifest: Manifest): { section: Section; dep: string }[] {
-  const deps: { section: Section; dep: string }[] = []
-  for (const section of INSTALLED_SECTIONS) {
-    for (const dep of Object.keys(manifest[section] ?? {})) {
-      if (section === 'peerDependencies' && manifest.peerDependenciesMeta?.[dep]?.optional === true) continue
-      if (PLACEMENT_OWNED_ELSEWHERE.has(dep)) continue
-      deps.push({ section, dep })
-    }
-  }
-  return deps
-}
-
-/**
- * Test whether a Loader config names a package as a whole word.
- * @param text - raw config text.
- * @param dep - package name to look for.
- * @returns true when the name appears outside a longer specifier.
- */
-function namesPackage(text: string, dep: string): boolean {
-  const escaped = dep.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
-  return new RegExp(`(^|[^\\w@/.-])${escaped}(?![\\w.-])`).test(text)
-}
-
-interface Candidate {
-  readonly name: string
-  readonly relativeDir: string
-  readonly manifest: Manifest
-  readonly kind: Kind
-}
-
-const candidates: Candidate[] = []
-for (const path of [
-  ...globSync('packages/*/*/package.json', { cwd: root }),
-  ...globSync('apps/*/package.json', { cwd: root }),
-].sort()) {
-  const relativeDir = dirname(path)
-  const manifest = JSON.parse(readFileSync(join(root, path), 'utf8')) as Manifest
-  if (manifest.name === undefined) continue
-  const kind = kindOf(relativeDir, manifest)
-  if (kind !== undefined) candidates.push({ name: manifest.name, relativeDir, manifest, kind })
-}
-
-const offenders: Offender[] = []
-const unchecked: string[] = []
-for (const { name, relativeDir, manifest, kind } of candidates) {
-  const dir = join(root, relativeDir)
-  const entries = faceEntries(dir, manifest, kind)
-  // A generated Node entry carries runtime imports of its own that no source
-  // states, so this package's declarations cannot be judged from `src` alone.
-  if (entries.generated.length > 0) {
-    unchecked.push(`${name}: generated entry ${entries.generated.join(', ')}`)
-    continue
-  }
-  const host = walk(entries.host, dir)
-  const browser = walk(entries.browser, dir)
-  // A Loader row names its plugin package instead of importing it, so a config
-  // the package owns is part of its host face. YAML keys carry no quotes, so
-  // these are matched as whole names against the raw text.
-  const configs = globSync('cordis*.yml', { cwd: dir }).map(config => readFileSync(join(dir, config), 'utf8'))
-
-  const violations = installedDeps(manifest)
-    // A workspace name stays where the manifest puts it. Such a declaration also
-    // states which package supplies an injected service, which Remote contribution
-    // an assembly mounts, or which Loader row must resolve; the app installs the
-    // package regardless, so moving one saves no download while deleting what
-    // verify-runtime-closure and the Loader read. External packages are the
-    // download, and this gate is about the download.
-    .filter(({ dep }) => !dep.startsWith('@deepseek-ai/'))
-    .filter(({ dep }) => !host.has(dep) && !configs.some(text => namesPackage(text, dep)))
-    .map(({ section, dep }) => ({
-      section,
-      dep,
-      browserReferenced: browser.has(dep),
-      // A prebuilt bundle publishes no Node entry, so everything it declares is
-      // build-time by construction, named in its Vite graph rather than in src.
-      reached: kind === 'prebuilt-dist' || browser.has(dep) ? 'browser' as const : 'nothing' as const,
-    }))
-  if (violations.length > 0) offenders.push({ name, dir: relativeDir, kind, violations })
-}
-
-if (process.argv.includes('--json')) {
-  console.log(JSON.stringify(offenders, null, 2))
-  process.exit(0)
-}
-
-if (unchecked.length > 0) {
-  console.log(`verify-client-runtime-deps: ${String(unchecked.length)} package(s) not checked, no source states their entry's imports:`)
-  for (const entry of unchecked) console.log(`  ${entry}`)
-}
-
-if (offenders.length > 0) {
-  const all = offenders.flatMap(offender => offender.violations)
-  console.error(`verify-client-runtime-deps: ${String(all.length)} build-time specifier(s) in installed sections:`)
-  for (const { name, dir, kind, violations } of offenders) {
-    console.error(`  ${name} (${dir}, ${kind})`)
-    for (const { section, dep, reached } of violations) {
-      console.error(`      ${section}.${dep}  ->  devDependencies [${reached}]`)
-    }
-  }
-  console.error('')
-  for (const reached of ['browser', 'nothing'] as const) {
-    const count = all.filter(violation => violation.reached === reached).length
-    if (count > 0) console.error(`  ${String(count).padStart(4)} ${reached}: ${REASON[reached]}`)
-  }
-  console.error('\nDeclaration rules: packages/client/AGENTS.md.')
-  process.exit(1)
-}
-console.log(`verify-client-runtime-deps: browser-face specifiers are dev-only across ${String(candidates.length)} browser-facing packages.`)

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