fix(skill): address shared runtime review findings

This commit is contained in:
fz committed 2026-09-18 11:52:04 +08:00
1 parent 3ebd70020c
commit 4d34cd87dd
33 files changed
+122 -78

No files matched your search

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-17-shared-office-runtime.md
2026-09-17-shared-office-runtime.md: 28daa6d3809705098722c41847fab9e3ee255bfe
2026-09-17-shared-office-runtime.zh.md: 09cc54e254546aa2787c37ab695c922ee86f0d66
2026-09-17-shared-office-runtime.md: 5925974ce96a91afcb97f89af58f87a3b2d8ff93
2026-09-17-shared-office-runtime.zh.md: 61be67508f285cb3e06305c95eed617e0d5f4218
@@ -14,6 +14,8 @@ SDK deployments need the same Office authoring libraries as Desktop while keepin
The [shared build entry](../../../../scripts/primary-runtime/prepare.ts) owns the download lock, extraction and native smoke checks for Desktop targets and GNU/Linux x64. Desktop supplies output paths and its release version, requires Node.js and pnpm, and applies its signing-specific subprocess environment. Other carriers choose their output directory and may build a Python-only payload. The existing `desktopVersion` manifest field retains its name and records the carrier release; `payloadDigest` distinguishes locked inputs and component selection.
The [Desktop primary-runtime decision](../feature/2026-09-14-desktop-primary-runtime.md) continues to own Desktop installation and platform signing. This decision replaces only its Desktop-only builder and query placement; both records remain active.
## Alternatives considered
**Keep implementation in Desktop.** This leaves SDK and container builders depending on application packaging and signing code despite needing only interpreters, Office resources and a query.
@@ -14,6 +14,8 @@ SDK 部署需要与 Desktop 相同的 Office 创作库,同时将解释器 payl
[共享构建入口](../../../../scripts/primary-runtime/prepare.ts) 持有 Desktop 目标与 GNU/Linux x64 的下载锁、解压和本机冒烟检查。Desktop 提供输出路径和发布版本,要求包含 Node.js 与 pnpm,并应用签名专用子进程环境。其他载体选择自己的输出目录,也可构建 Python-only payload。现有 `desktopVersion` 清单字段保留名字并记录载体发布版本;`payloadDigest` 区分锁定输入与组件选择。
[Desktop 第一方运行时决策](../feature/2026-09-14-desktop-primary-runtime.zh.md)继续持有 Desktop 安装与平台签名规则。本决策仅替代其中由 Desktop 独占构建器和查询实现的部分;两份记录均保持有效。
## 考虑过的替代方案
**将实现保留在 Desktop。** SDK 和容器构建器只需要解释器、Office 资源和查询,却仍会依赖应用打包及签名代码。
@@ -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/feature/2026-09-14-desktop-primary-runtime.md
2026-09-14-desktop-primary-runtime.md: 51bbaac89c5c2ec9a3a016030e7b320d2b5566b8
2026-09-14-desktop-primary-runtime.zh.md: 4f27d61b5e713f66e2f478894dbd547cd7ecfff4
2026-09-14-desktop-primary-runtime.md: de535fd7125c08bf788ddf7680224c6e0df81a4a
2026-09-14-desktop-primary-runtime.zh.md: 508c574e4970b4cc53ef3bdaee8524a867673d34
@@ -14,7 +14,7 @@ Desktop ships Python, Node.js, pnpm, data-processing libraries and Office author
The application version, component versions, Python distribution versions and locked-input digest live in `runtime.json`, not the directory name. The digest covers the selected target's interpreter and wheel archives, shared wheel inputs, Python distribution versions, pnpm version and assembly format; other targets do not invalidate it. Key order within the selected target, wheel records and distribution map, plus wheel-entry order, participates in this identity; top-level lock key order does not. Extraction or assembly changes that alter payload bytes without changing locked inputs require an explicit format bump. Installation publishes a completed staged copy and retains the previous directory until replacement succeeds. Matching payloads reuse installed files; replacements also replace user-added Python dependencies inside the managed tree. Older manifests without a digest remain readable and differ from the current payload. Distribution names use PEP 503 normalization; duplicate normalized names are rejected, and present numpy/pandas distribution versions must agree with their component versions. The Desktop single-instance owner and the tool's shared installation promise serialize normal installation requests.
Node downloads and hash-verifies the complete locked wheel set and unpacks library files into site-packages. This avoids build-host Python and pip version selection without implementing dependency resolution or general wheel installation. Auxiliary scripts, including XlsxWriter's VBA extraction script, remain under the wheel's `.data/scripts` directory; command-line entry-point wrappers are outside this library payload. Other `.data` installation schemes are rejected. Native smoke executes the final payload after temporary files are removed, checking the exact locked distribution set plus bundled pip, Python and pinned wheel versions, dependency completeness and editable Office document round trips, so interpreter links must survive relocation. Smoke checks disable bytecode writes to keep validation artifacts out of the shipped payload.
The shared Node build entry downloads and hash-verifies the complete locked wheel set and unpacks library files into site-packages. This avoids build-host Python and pip version selection without implementing dependency resolution or general wheel installation. Auxiliary scripts, including XlsxWriter's VBA extraction script, remain under the wheel's `.data/scripts` directory; command-line entry-point wrappers are outside this library payload. Other `.data` installation schemes are rejected. Native smoke executes the final payload after temporary files are removed, checking the exact locked distribution set plus bundled pip, Python and pinned wheel versions, dependency completeness and editable Office document round trips, so interpreter links must survive relocation. Smoke checks disable bytecode writes to keep validation artifacts out of the shipped payload.
macOS grants `com.apple.security.cs.allow-jit` only to the standalone Node executable. Hardened-runtime signing without that entitlement prevents V8 from allocating its code region. Interpreter and library smoke checks run after signing as well as after staging cleanup; a valid signature alone does not establish executable behavior.
@@ -22,6 +22,8 @@ Windows signed packaging separates materialization from execution with supervise
Desktop ZIP extraction pins `extract-zip` to `yauzl` 3.4.0 through a scoped dependency override. The 2.x reader can leave large deflate entries unfinished on Node 26 ([upstream issue](https://github.com/thejoshwolfe/yauzl/issues/176)); retaining the existing extractor preserves its path validation and wheel-entry checks. The development launcher uses top-level await so unfinished preparation cannot exit successfully. A large compressed wheel regression checks the complete extracted bytes.
The [shared-runtime decision](../architecture/2026-09-17-shared-office-runtime.md) owns builder, lock and query-package placement for Desktop and SDK carriers, including GNU/Linux x64 and Python-only payloads. Desktop retains the installation and signing behavior described here and requires the full Python, Node.js and pnpm payload.
## Alternatives considered
**System interpreters only.** They do not provide predictable availability or preinstalled numpy and pandas.
@@ -14,7 +14,7 @@ Desktop 将 Python、Node.js、pnpm、数据处理库和 Office 创作库作为
应用版本、组件版本、Python 分发包版本和锁定输入摘要记录在 `runtime.json` 中,不放在目录名里。摘要涵盖所选目标的解释器与 wheel 压缩包、共享 wheel 输入、Python 分发包版本、pnpm 版本和组装格式;其他目标不会使其失效。所选目标、wheel 记录及分发包映射内部的键顺序,以及 wheel 条目顺序参与该身份计算;锁文件顶层键的顺序不参与。解压或组装逻辑在锁定输入不变时改变产物字节,必须显式提升格式版本。安装发布完整的暂存副本,并在替换成功前保留之前的目录。相同产物复用已安装文件;替换也会移除受管目录内用户添加的 Python 依赖。不含摘要的旧清单仍可读取,并与当前产物区分。分发包名称按 PEP 503 归一化,归一化后重复的名称会被拒绝;清单包含 numpy/pandas 分发包版本时,必须与相应组件版本一致。Desktop 单实例所有者和工具共享的安装 Promise 串行处理正常安装请求。
Node 下载并校验完整锁定 wheel 集的哈希,将库文件解压到 site-packages。这避免选择构建主机的 Python 和 pip 版本,也无需实现依赖解析或通用 wheel 安装。XlsxWriter 的 VBA 提取脚本等辅助脚本保留在 wheel 的 `.data/scripts` 目录中;命令行入口包装器不属于该库产物。其他 `.data` 安装方案会被拒绝。本机 smoke 在临时文件删除后执行最终产物,检查精确锁定的分发包集合与内置 pip、Python 与固定 wheel 版本、依赖完整性及可编辑 Office 文档的写入和读取,因此解释器链接必须在迁移后仍有效。Smoke 检查禁用字节码写入,避免把验证产物纳入分发内容。
共享 Node 构建入口下载并校验完整锁定 wheel 集的哈希,将库文件解压到 site-packages。这避免选择构建主机的 Python 和 pip 版本,也无需实现依赖解析或通用 wheel 安装。XlsxWriter 的 VBA 提取脚本等辅助脚本保留在 wheel 的 `.data/scripts` 目录中;命令行入口包装器不属于该库产物。其他 `.data` 安装方案会被拒绝。本机 smoke 在临时文件删除后执行最终产物,检查精确锁定的分发包集合与内置 pip、Python 与固定 wheel 版本、依赖完整性及可编辑 Office 文档的写入和读取,因此解释器链接必须在迁移后仍有效。Smoke 检查禁用字节码写入,避免把验证产物纳入分发内容。
macOS 仅向独立 Node 可执行文件授予 `com.apple.security.cs.allow-jit`。缺少此权限的强化运行时签名会阻止 V8 分配代码区域。解释器和库的 smoke 检查在签名后以及暂存清理后执行;签名有效本身不能证明程序可运行。
@@ -22,6 +22,8 @@ Windows 签名打包通过受监督的签名阶段覆盖第一方运行时和应
Desktop ZIP 解压通过定向依赖覆盖为 `extract-zip` 固定 `yauzl` 3.4.0。2.x 读取器在 Node 26 上可能无法完成较大 deflate 条目的读取([上游问题](https://github.com/thejoshwolfe/yauzl/issues/176));保留现有解压器可保留其路径校验和 wheel 条目检查。开发启动器使用顶层 await,避免准备未完成却成功退出。大压缩 wheel 回归测试检查完整的解压字节。
[共享运行时决策](../architecture/2026-09-17-shared-office-runtime.zh.md)持有 Desktop 与 SDK 载体的构建器、锁文件和查询包归属,覆盖 GNU/Linux x64 与 Python-only payload。Desktop 保留本文的安装与签名行为,并要求完整的 Python、Node.js 和 pnpm payload。
## Alternatives considered
**只使用系统解释器。** 无法保证可用性或预装 numpy 和 pandas。
@@ -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: c4458fac7de4d9e9b63b23855f95aaf69eb89f64
2026-07-30-generated-third-party-notices.zh.md: f26d0d001ed06dd5bea00bbc5e3400b31b254081
2026-07-30-generated-third-party-notices.md: 71f1fd0a5b650d53c5ef21438e17e0a6e162bdf5
2026-07-30-generated-third-party-notices.zh.md: e3adb43e4b407945a8cca88105de689407b5908b
@@ -12,13 +12,13 @@ A hand-written inventory answers none of those durably. Roughly a hundred rows o
## Decision
[`THIRD_PARTY_NOTICES.md`](../../../../THIRD_PARTY_NOTICES.md) is generated by [`scripts/gen-third-party-notices.ts`](../../../../scripts/gen-third-party-notices.ts) from the workspace manifests, `vendor/README.md`, the `pyproject.toml` files, `pnpm-workspace.yaml`, and the [Desktop runtime lock](../../../../scripts/primary-runtime/lock.json). The root README pair links the file from its License section.
[`THIRD_PARTY_NOTICES.md`](../../../../THIRD_PARTY_NOTICES.md) is generated by [`scripts/gen-third-party-notices.ts`](../../../../scripts/gen-third-party-notices.ts) from the workspace manifests, `vendor/README.md`, the `pyproject.toml` files, `pnpm-workspace.yaml`, and the [shared runtime lock](../../../../scripts/primary-runtime/lock.json). The root README pair links the file from its License section.
**Freshness is maintained, not merely enforced.** A pre-commit job regenerates the file and stages it whenever a generator input is staged — any manifest, a workspace declaration, the root or Desktop runtime lock file, `vendor/README.md`, a `pyproject.toml`, the generator itself, or the script holding the build-time pin — so an unrelated dependency edit never has to come back and rerun a generator. The committed bytes are then asserted inside [`scripts/gen-third-party-notices.spec.ts`](../../../../scripts/gen-third-party-notices.spec.ts), which the test lane already runs — the check adds no gate process, no scheduler slot, and no separate CI step. `pnpm run verify-third-party-notices` remains available for a standalone check.
**Freshness is maintained, not merely enforced.** A pre-commit job regenerates the file and stages it whenever a generator input is staged — any manifest, a workspace declaration, the root or shared runtime lock file, `vendor/README.md`, a `pyproject.toml`, the generator itself, or the script holding the build-time pin — so an unrelated dependency edit never has to come back and rerun a generator. The committed bytes are then asserted inside [`scripts/gen-third-party-notices.spec.ts`](../../../../scripts/gen-third-party-notices.spec.ts), which the test lane already runs — the check adds no gate process, no scheduler slot, and no separate CI step. `pnpm run verify-third-party-notices` remains available for a standalone check.
One trigger gap is accepted rather than worked around: lefthook inspects only files present on disk, so **deleting** a manifest runs no job, and removing a package reaches the assertion in the test lane instead. Reconstructing the staged file list to include deletions does not work — lefthook filters the list against the working tree either way. The assertion is the backstop for exactly this case.
The file discloses **direct** dependencies by default; complete npm and Python SDK closures remain in their lock files. Two separately distributed payload sets receive explicit entries: the official Claude executable packages declared by `@anthropic-ai/claude-agent-sdk`, and every distribution in the Desktop runtime lock's `pythonPackages`. Desktop entries include normalized names, exact versions, and recorded license metadata; missing metadata, duplicate normalized names and conflicting normalized versions fail generation. The Desktop wheel set is independent of the `python/` manifests, so scanning only those manifests would omit the packaged Office libraries. Desktop distributions also pass the runtime license check, including the permissive `MIT-CMU` and `PSF-2.0` identifiers declared by Pillow and typing-extensions.
The file discloses **direct** dependencies by default; complete npm and Python SDK closures remain in their lock files. Two separately distributed payload sets receive explicit entries: the official Claude executable packages declared by `@anthropic-ai/claude-agent-sdk`, and every distribution in the shared runtime lock's `pythonPackages`. Bundled entries include normalized names, exact versions, and recorded license metadata; missing metadata, duplicate normalized names and conflicting normalized versions fail generation. The shared wheel set is independent of the `python/` manifests, so scanning only those manifests would omit the packaged Office libraries. Bundled distributions also pass the runtime license check, including the permissive `MIT-CMU` and `PSF-2.0` identifiers declared by Pillow and typing-extensions.
**Tiering follows distribution, not manifest section.** Installed runtime libraries are identified by `dependencies` or `optionalDependencies` outside `DEV_ONLY_AREAS` — the root manifest, `packages/test-support/`, `packages/test-support/client-runtime/`, `website/`, `native/`. Browser inputs resolved by the shipping tsdown and Vite configurations also count as runtime, even in `devDependencies`; [browser third-party build inputs](2026-09-08-browser-third-party-build-inputs.md) owns that classification. Test-support dependencies do not ship merely because their manifest says `dependencies`, and the generator explicitly discloses `tsx` because source launches execute through its ESM hook.
@@ -12,13 +12,13 @@ Status: implemented
## 决策
[`THIRD_PARTY_NOTICES.md`](../../../../THIRD_PARTY_NOTICES.md) 由 [`scripts/gen-third-party-notices.ts`](../../../../scripts/gen-third-party-notices.ts) 依据各工作区 manifest、`vendor/README.md`、`pyproject.toml`、`pnpm-workspace.yaml` 与 [Desktop 运行时锁文件](../../../../scripts/primary-runtime/lock.json)生成。根 README 双语两侧都从「许可证」一节链到该文件。
[`THIRD_PARTY_NOTICES.md`](../../../../THIRD_PARTY_NOTICES.md) 由 [`scripts/gen-third-party-notices.ts`](../../../../scripts/gen-third-party-notices.ts) 依据各工作区 manifest、`vendor/README.md`、`pyproject.toml`、`pnpm-workspace.yaml` 与 [共享运行时锁文件](../../../../scripts/primary-runtime/lock.json)生成。根 README 双语两侧都从「许可证」一节链到该文件。
**新鲜度会得到维护,而非仅靠校验。** 只要暂存了生成器的任一输入——任何 manifest、工作区声明、根锁文件或 Desktop 运行时锁文件、`vendor/README.md`、某个 `pyproject.toml`、生成器自身,或持有构建期 pin 的脚本——pre-commit 任务就会重新生成该文件并将其暂存,改依赖的人不必事后再折返跑一次生成器。已提交的字节随后由 [`scripts/gen-third-party-notices.spec.ts`](../../../../scripts/gen-third-party-notices.spec.ts) 断言,而测试 lane 本就会跑这个文件——这项校验不增加门禁进程、不占调度位、也不新增 CI 步骤。需要单独校验时,`pnpm run verify-third-party-notices` 仍然可用。
**新鲜度会得到维护,而非仅靠校验。** 只要暂存了生成器的任一输入——任何 manifest、工作区声明、根锁文件或共享运行时锁文件、`vendor/README.md`、某个 `pyproject.toml`、生成器自身,或持有构建期 pin 的脚本——pre-commit 任务就会重新生成该文件并将其暂存,改依赖的人不必事后再折返跑一次生成器。已提交的字节随后由 [`scripts/gen-third-party-notices.spec.ts`](../../../../scripts/gen-third-party-notices.spec.ts) 断言,而测试 lane 本就会跑这个文件——这项校验不增加门禁进程、不占调度位、也不新增 CI 步骤。需要单独校验时,`pnpm run verify-third-party-notices` 仍然可用。
有一处触发缺口是接受而非绕过的:lefthook 只检视磁盘上存在的文件,因此**删除** manifest 不会触发任何任务,移除一个包会落到测试 lane 的断言上。重构暂存文件列表以纳入删除的做法不成立——无论怎么给列表,lefthook 都会拿工作树过滤一遍。这个场景正由断言兜底。
文件默认只披露**直接**依赖;完整的 npm 与 Python SDK 闭包保留在各自锁文件中。两组独立分发的载荷会明确列出:`@anthropic-ai/claude-agent-sdk` 声明的官方 Claude 可执行包,以及 Desktop 运行时锁文件 `pythonPackages` 中的每个分发包。Desktop 条目包含归一化名称、精确版本和已记录的许可证元数据;元数据缺失、归一化后名称重复或版本冲突都会使生成失败。Desktop wheel 集合独立于 `python/` 下的 manifest,仅扫描后者会遗漏已打包的 Office 库。Desktop 分发包同样通过运行时许可证检查,其中包括 Pillow 与 typing-extensions 声明的宽松许可证标识 `MIT-CMU` 和 `PSF-2.0`。
文件默认只披露**直接**依赖;完整的 npm 与 Python SDK 闭包保留在各自锁文件中。两组独立分发的载荷会明确列出:`@anthropic-ai/claude-agent-sdk` 声明的官方 Claude 可执行包,以及共享运行时锁文件 `pythonPackages` 中的每个分发包。内置条目包含归一化名称、精确版本和已记录的许可证元数据;元数据缺失、归一化后名称重复或版本冲突都会使生成失败。共享 wheel 集合独立于 `python/` 下的 manifest,仅扫描后者会遗漏已打包的 Office 库。内置分发包同样通过运行时许可证检查,其中包括 Pillow 与 typing-extensions 声明的宽松许可证标识 `MIT-CMU` 和 `PSF-2.0`。
**分层依据是分发内容,而非 manifest 字段名。** 安装的运行时库由 `DEV_ONLY_AREAS` 之外的 `dependencies` 或 `optionalDependencies` 识别;排除区域为根 manifest、`packages/test-support/`、`packages/test-support/client-runtime/`、`website/`、`native/`。发布所用的 tsdown 与 Vite 配置解析到的浏览器输入也属于运行时,即使它们位于 `devDependencies`;[浏览器第三方构建输入](2026-09-08-browser-third-party-build-inputs.zh.md)拥有这项分类。测试支撑依赖不会仅因字段写成 `dependencies` 就被交付,而生成器显式披露 `tsx`,因为源码启动通过其 ESM 钩子执行。
+3 -3
View File
@@ -5,7 +5,7 @@
DeepSeek Harness is licensed under [MIT](LICENSE). It depends on the third-party software listed below. Each project remains under its own license; nothing in this file changes those terms.
This file lists **direct** dependencies declared by the workspace, the explicitly disclosed official Claude Code platform payload closure, and the Desktop bundled Python distributions. It is generated by `scripts/gen-third-party-notices.ts`: a pre-commit hook regenerates it whenever a staged file changes one of its inputs, and `scripts/gen-third-party-notices.spec.ts` asserts in the test lane that the committed bytes match. Deleting a manifest runs no hook, so that case is caught by the assertion instead. Run `pnpm run verify-third-party-notices` for the standalone check.
This file lists **direct** dependencies declared by the workspace, the explicitly disclosed official Claude Code platform payload closure, and the Bundled Python distributions. It is generated by `scripts/gen-third-party-notices.ts`: a pre-commit hook regenerates it whenever a staged file changes one of its inputs, and `scripts/gen-third-party-notices.spec.ts` asserts in the test lane that the committed bytes match. Deleting a manifest runs no hook, so that case is caught by the assertion instead. Run `pnpm run verify-third-party-notices` for the standalone check.
The complete npm transitive closure, including the Landlock launcher workspace, is recorded with exact pinned versions in [`pnpm-lock.yaml`](pnpm-lock.yaml) — inspect it with `pnpm licenses list`. The Python SDK closure is recorded separately in [`python/sdk/uv.lock`](python/sdk/uv.lock).
@@ -244,9 +244,9 @@ Direct dependencies of the `pyproject.toml` manifests, plus `uv` as the developm
| [`pytest`](https://github.com/pytest-dev/pytest) | MIT | test-only |
| [`uv`](https://github.com/astral-sh/uv) | MIT / Apache-2.0 | development workflow tool |
## Desktop bundled Python distributions
## Bundled Python distributions
The [Desktop runtime lock](scripts/primary-runtime/lock.json) records each distribution version and the wheel download hashes. The table includes every entry in `pythonPackages`, including transitive dependencies. Wheel extraction preserves distribution metadata and the license and notice files supplied by each archive. Project licenses below do not enumerate the separate licenses of native libraries bundled inside wheels.
The [shared runtime lock](scripts/primary-runtime/lock.json) records each distribution version and the wheel download hashes. The table includes every entry in `pythonPackages`, including transitive dependencies. Wheel extraction preserves distribution metadata and the license and notice files supplied by each archive. Project licenses below do not enumerate the separate licenses of native libraries bundled inside wheels.
| Distribution | Locked version | Project license |
| --- | --- | --- |
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write apps/desktop/README.md
README.md: 123e27d1d03bf218835af310cb529365cdc47b94
README.zh.md: aee40ffa4ceae673a931f076d668debfdf83de2d
README.md: 91e093e17fe7b748beda76a39c2002732b892902
README.zh.md: 484788eaaa5c7ce604f784c0f8b89688ecc5c815
+1 -1
View File
@@ -28,7 +28,7 @@ The payload follows the Desktop release. `runtime.json` records the Desktop vers
The private Desktop `runtime/bin` directory is added only to package-installation processes, not the Host PATH inherited by PTC and agent shells. This tool does not change PATH, environment variables or user package-manager configuration. pnpm retains its own defaults and user settings for global packages, executable entries and its store, including native errors when the environment does not support global installation. There is no separate dependency updater. [The primary-runtime decision](../../.agents/notes/implemented/feature/2026-09-14-desktop-primary-runtime.md) records these choices.
Node prepares the bundled interpreters and Python libraries without a system Python or pip. [The download lock](../../scripts/primary-runtime/lock.json) pins interpreter archives, Python distribution versions and target-specific wheel URLs and hashes; pnpm follows the Desktop build dependency lock. Wheel filenames and distribution versions must agree for every target. Preserve key order within the selected target, wheel records and distribution map, plus wheel-entry order; these affect payload identity, while top-level lock key order does not. Library wheels unpack into site-packages, retaining auxiliary files under each wheel's `.data/scripts` directory without generating command wrappers. Other installation schemes are rejected. Native-target checks verify the locked wheel set and versions, permit the interpreter's bundled pip, and check the Python version, Office document read/write operations and dependency completeness without writing bytecode after staging cleanup and again after macOS signing. The standalone Node executable receives the JIT entitlement required by V8. Cross-target execution and signed installation require the target release host. Both `dev:desktop` and `start:desktop` prepare `.desktop-build/targets/<target>/runtime/primary-runtime` before launching Electron; first use may download locked dependencies. An unfinished preparation cannot report a successful launcher exit.
Node prepares the bundled interpreters and Python libraries without a system Python or pip. [The download lock](../../scripts/primary-runtime/lock.json) pins interpreter archives, Python distribution versions and target-specific wheel URLs and hashes; the shared builder resolves pnpm from the root development dependency pin. The root package-manager version and the Desktop pin are checked for agreement. Wheel filenames and distribution versions must agree for every target. Preserve key order within the selected target, wheel records and distribution map, plus wheel-entry order; these affect payload identity, while top-level lock key order does not. Library wheels unpack into site-packages, retaining auxiliary files under each wheel's `.data/scripts` directory without generating command wrappers. Other installation schemes are rejected. Native-target checks verify the locked wheel set and versions, permit the interpreter's bundled pip, and check the Python version, Office document read/write operations and dependency completeness without writing bytecode after staging cleanup and again after macOS signing. The standalone Node executable receives the JIT entitlement required by V8. Cross-target execution and signed installation require the target release host. Both `dev:desktop` and `start:desktop` prepare `.desktop-build/targets/<target>/runtime/primary-runtime` before launching Electron; first use may download locked dependencies. An unfinished preparation cannot report a successful launcher exit.
| Decision | Why | Direct consequence |
|---|---|---|
+1 -1
View File
@@ -28,7 +28,7 @@ Desktop 默认注册 `office-docx`、`office-pptx` 和 `office-xlsx`。这些技
Desktop 私有的 `runtime/bin` 目录仅添加到包安装进程,不进入 PTC 和 agent shell 从 Host 继承的 PATH。该工具不修改 PATH、环境变量或用户包管理器配置。pnpm 的全局包、命令入口和 store 保留自身默认值及用户设置,包括环境不支持全局安装时的原生错误。不提供独立依赖更新器。[第一方 Runtime 决策](../../.agents/notes/implemented/feature/2026-09-14-desktop-primary-runtime.zh.md)记录这些选择。
Node 准备内置解释器和 Python 库,无需系统 Python 或 pip。[下载锁](../../scripts/primary-runtime/lock.json)固定解释器压缩包、Python 分发包版本及目标平台 wheel 的 URL 和哈希;pnpm 使用 Desktop 构建依赖锁。每个目标的 wheel 文件名必须与分发包版本一致。所选目标、wheel 记录及分发包映射内部的键顺序,以及 wheel 条目顺序都会影响产物身份,编辑时须保留;锁文件顶层键的顺序不影响该身份。库 wheel 解压到 site-packages,各 wheel 的 `.data/scripts` 目录保留辅助文件,不生成命令行包装器。其他安装方案会被拒绝。本机目标检查在清理暂存目录后以及 macOS 签名后验证锁定 wheel 的集合与版本,允许解释器自带的 pip,并检查 Python 版本、Office 文档读写和依赖完整性,不写入字节码。独立 Node 可执行文件获得 V8 所需的 JIT 权限。跨目标执行和签名安装需要对应的发布主机。`dev:desktop` 和 `start:desktop` 都会在启动 Electron 前准备 `.desktop-build/targets/<target>/runtime/primary-runtime`;首次准备可能需要下载锁定的依赖。准备未完成时,启动命令不能报告成功退出。
Node 准备内置解释器和 Python 库,无需系统 Python 或 pip。[下载锁](../../scripts/primary-runtime/lock.json)固定解释器压缩包、Python 分发包版本及目标平台 wheel 的 URL 和哈希;共享构建器从根开发依赖中解析 pnpm 固定版本。测试检查根 package-manager 版本和 Desktop 固定版本保持一致。每个目标的 wheel 文件名必须与分发包版本一致。所选目标、wheel 记录及分发包映射内部的键顺序,以及 wheel 条目顺序都会影响产物身份,编辑时须保留;锁文件顶层键的顺序不影响该身份。库 wheel 解压到 site-packages,各 wheel 的 `.data/scripts` 目录保留辅助文件,不生成命令行包装器。其他安装方案会被拒绝。本机目标检查在清理暂存目录后以及 macOS 签名后验证锁定 wheel 的集合与版本,允许解释器自带的 pip,并检查 Python 版本、Office 文档读写和依赖完整性,不写入字节码。独立 Node 可执行文件获得 V8 所需的 JIT 权限。跨目标执行和签名安装需要对应的发布主机。`dev:desktop` 和 `start:desktop` 都会在启动 Electron 前准备 `.desktop-build/targets/<target>/runtime/primary-runtime`;首次准备可能需要下载锁定的依赖。准备未完成时,启动命令不能报告成功退出。
| 决策 | 原因 | 直接结果 |
|---|---|---|
-2
View File
@@ -50,10 +50,8 @@
"electron": "^44.0.0",
"electron-builder": "^26.15.3",
"extract-zip": "^2.0.1",
"fflate": "^0.8.2",
"js-yaml": "^4.2.0",
"pnpm": "11.7.0",
"tar": "^7.5.0",
"typescript": "^6.0.3"
}
}
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/config-catalog.md
config-catalog.md: 64ef9c99d2cc98690097d848ab7cfb899c6bb474
config-catalog.zh.md: 76faa711e53011ba34d599f59f616280a4660ad0
config-catalog.md: 2efbd1bda5d4445fe53c1dd8cc1ad27cd3cfcead
config-catalog.zh.md: bb95afa5cb2b7825e45d9ec5315ea1a3ab79de12
+1 -1
View File
@@ -3482,7 +3482,7 @@ export interface Config {
}
```
Source: [`packages/skill/tool-workspace-dependencies/src/index.ts:14`](../packages/skill/tool-workspace-dependencies/src/index.ts)
Source: [`packages/skill/tool-workspace-dependencies/src/index.ts:15`](../packages/skill/tool-workspace-dependencies/src/index.ts)
<a id="deepseek-aidsh-tools"></a>
+1 -1
View File
@@ -3484,7 +3484,7 @@ export interface Config {
}
```
来源:[`packages/skill/tool-workspace-dependencies/src/index.ts:14`](../packages/skill/tool-workspace-dependencies/src/index.ts)
来源:[`packages/skill/tool-workspace-dependencies/src/index.ts:15`](../packages/skill/tool-workspace-dependencies/src/index.ts)
<a id="deepseek-aidsh-tools"></a>
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/tool-catalog.md
tool-catalog.md: bdc3a5af4b1bf723879c423fe201576ca3772e64
tool-catalog.zh.md: bc445ea8d6b0a5778e4bd191693ff01bd6781d73
tool-catalog.md: b3854acff78dc3478bd64c87c2b2c8a89e3cdb15
tool-catalog.zh.md: 1cf65d6e71012260347416e864117c78c528e890
+1 -1
View File
@@ -2445,7 +2445,7 @@ Source: [`packages/workflow/tool-workflow/src/index.ts`](../packages/workflow/to
### `load_workspace_dependencies`
Get absolute paths to bundled Python, Node.js, pnpm, and library directories, plus bundled Python distribution versions. Python includes numpy, pandas, python-docx, python-pptx, openpyxl, Pillow, lxml, and XlsxWriter. Use these libraries for Office files unless the user or workspace instructions select another environment. Run pnpm with the returned Node executable and pnpm script path. This does not change PATH or package-manager settings.
Get absolute paths to bundled Python and library directories, plus bundled Python distribution versions. Node.js and pnpm paths are included when the payload provides them. Python includes numpy, pandas, python-docx, python-pptx, openpyxl, Pillow, lxml, and XlsxWriter. Use these libraries for Office files unless the user or workspace instructions select another environment. When Node.js and pnpm paths are returned, run pnpm with that Node executable and pnpm script path. This does not change PATH or package-manager settings.
```json
{
+1 -1
View File
@@ -2453,7 +2453,7 @@ todo_write 是会话所有的状态;UI 将最新的 todo/write 事件渲染为
### `load_workspace_dependencies`
获取随包附带的 Python、Node.js、pnpm 和库目录的绝对路径,以及随包 Python 发行版的版本。Python 含 numpy、pandas、python-docx、python-pptx、openpyxl、Pillow、lxml 与 XlsxWriter。除非用户或工作区指令选择了别的环境,Office 文件请使用这些库。用返回的 Node 可执行文件和 pnpm 脚本路径运行 pnpm。本工具不改 PATH,也不改包管理器设置。
获取随包附带的 Python 和库目录的绝对路径,以及随包 Python 发行版的版本。payload 提供 Node.js 和 pnpm 时才返回对应路径。Python 含 numpy、pandas、python-docx、python-pptx、openpyxl、Pillow、lxml 与 XlsxWriter。除非用户或工作区指令选择了别的环境,Office 文件请使用这些库。返回 Node.js 和 pnpm 路径时,用该 Node 可执行文件和 pnpm 脚本路径运行 pnpm。本工具不改 PATH,也不改包管理器设置。
```json
{
-1
View File
@@ -90,7 +90,6 @@
"check:ci:windows-complete": "tsx scripts/run-gates.ts ci-windows-complete",
"check:ci:windows-observational": "tsx scripts/run-gates.ts ci-windows-observational",
"check:node-compat": "tsx scripts/run-gates.ts node-compat",
"pnpm": "11.7.0",
"publint": "tsx scripts/publint-all.ts",
"doc-typecheck": "npm run build:lib:host && npm run doc-typecheck:contracts-ready",
"doc-typecheck:contracts-ready": "tsx scripts/doc-typecheck.ts",
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/skill/tool-workspace-dependencies/README.md
README.md: 1464e231980bc24559d5dfc2635d51e57bcfc116
README.zh.md: 48520369dde7cb0a5fcfb8d53af687ce651c47d5
README.md: 77020b8ca21224b9c2aeed440c7837a1fc610e7c
README.zh.md: 62ca9c088a3c6b69343285699864b2cd3418d683
@@ -25,7 +25,7 @@ Deployments that ship their own script runtimes (Desktop's primary runtime, or a
<a id="use-this-package"></a>
## Use this package
Mount the plugin beside the tool registry with the payload directory. The bundled Office skills (`@deepseek-ai/dsh-skill-office`) reference this tool by name for their default interpreter.
Mount the plugin beside the tool registry with the payload directory. Configuration validation requires a nonempty `source` and rejects empty `root` values before activation; both paths must be absolute. The bundled Office skills (`@deepseek-ai/dsh-skill-office`) reference this tool by name for their default interpreter.
### Minimal configuration
@@ -25,7 +25,7 @@ kind: "package-reference"
<a id="use-this-package"></a>
## 使用本包
与工具注册表一起挂载,给出 payload 目录。内置 Office skills(`@deepseek-ai/dsh-skill-office`)按名字引用本工具取默认解释器。
与工具注册表一起挂载,给出 payload 目录。配置校验在激活前要求非空 `source` 并拒绝空 `root`;两条路径都必须为绝对路径。内置 Office skills(`@deepseek-ai/dsh-skill-office`)按名字引用本工具取默认解释器。
### 最小配置
@@ -1,7 +1,7 @@
{
"name": "@deepseek-ai/dsh-tool-workspace-dependencies",
"description": "The load_workspace_dependencies tool: absolute paths into a bundled Python, Node.js, and pnpm payload",
"version": "0.1.6-alpha.1",
"version": "0.1.6-alpha.2",
"publishConfig": {
"access": "public"
},
@@ -37,5 +37,8 @@
"@deepseek-ai/dsh-agent": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^",
"@deepseek-ai/dsh-system-prompt": "workspace:^"
},
"dependencies": {
"@deepseek-ai/schemastery": "workspace:^"
}
}
@@ -3,6 +3,7 @@
import { cp, lstat, mkdir, mkdtemp, readFile, rename, rm, stat } from 'node:fs/promises'
import { dirname, isAbsolute, join } from 'node:path'
import type { Context } from '@deepseek-ai/cordis'
import z from '@deepseek-ai/schemastery'
import { defineTool } from '@deepseek-ai/dsh-tools'
/** Cordis plugin identity. */
@@ -22,6 +23,12 @@ export interface Config {
readonly root?: string
}
/** Require a named payload source before the Loader activates the tool. */
export const Config: z<Config> = z.object({
source: z.string().min(1).required(),
root: z.string().min(1),
})
/** Versions recorded by the payload build, independent of user-installed packages. */
export interface PrimaryRuntimeManifest {
readonly desktopVersion: string
@@ -160,9 +167,9 @@ export async function installPrimaryRuntime(source: string, root: string): Promi
const manifest = await compatibleManifest(source)
await mkdir(dirname(root), { recursive: true })
const previous = `${root}.previous`
await exists(previous)
await exists(root)
if (!await exists(root) && await exists(previous)) await rename(previous, root)
const previousExists = await exists(previous)
const rootExists = await exists(root)
if (!rootExists && previousExists) await rename(previous, root)
if (await exists(join(root, 'runtime.json')) && JSON.stringify(await readPrimaryRuntime(root)) === JSON.stringify(manifest)) {
const paths = workspaceDependencyPaths(root, manifest)
for (const path of payloadEntries(paths)) await stat(path)
@@ -203,7 +210,7 @@ export function apply(ctx: Context, config: Config): void {
})
ctx.tools.register(defineTool({
name: 'load_workspace_dependencies',
description: 'Get absolute paths to bundled Python, Node.js, pnpm, and library directories, plus bundled Python distribution versions. Python includes numpy, pandas, python-docx, python-pptx, openpyxl, Pillow, lxml, and XlsxWriter. Use these libraries for Office files unless the user or workspace instructions select another environment. Run pnpm with the returned Node executable and pnpm script path. This does not change PATH or package-manager settings.',
description: 'Get absolute paths to bundled Python and library directories, plus bundled Python distribution versions. Node.js and pnpm paths are included when the payload provides them. Python includes numpy, pandas, python-docx, python-pptx, openpyxl, Pillow, lxml, and XlsxWriter. Use these libraries for Office files unless the user or workspace instructions select another environment. When Node.js and pnpm paths are returned, run pnpm with that Node executable and pnpm script path. This does not change PATH or package-manager settings.',
parameters: {},
output: {
schema: {
@@ -345,3 +345,18 @@ it('disposes an unused tool without preparing the payload', async () => {
it.each([{ source: 'relative' }, { source: process.cwd(), root: 'relative' }])('rejects relative runtime configuration at plugin load: %j', async (config) => {
await expect(tool(config)).rejects.toThrow('must be absolute paths')
})
it.each([{}, { source: '' }, { source: 1 }])('rejects invalid payload configuration with a named source error: %j', (config) => {
expect(() => workspaceDependencies.Config(config as unknown as workspaceDependencies.Config)).toThrow('source')
})
it('validates the previous directory even when a current installation exists', async () => {
const { source, root, directory } = await fixture()
await installPrimaryRuntime(source, root)
const outside = join(directory, 'outside-previous')
await mkdir(outside)
await writeFile(join(outside, 'keep'), 'untouched')
await symlink(outside, `${root}.previous`, process.platform === 'win32' ? 'junction' : 'dir')
await expect(installPrimaryRuntime(source, root)).rejects.toThrow('filesystem link')
expect(await readFile(join(outside, 'keep'), 'utf8')).toBe('untouched')
})
@@ -14,6 +14,9 @@
{
"path": "../../../vendor/cordis"
},
{
"path": "../../../vendor/schemastery"
},
{
"path": "../../core/tools"
}
+4 -6
View File
@@ -593,18 +593,12 @@ importers:
extract-zip:
specifier: ^2.0.1
version: 2.0.1([email protected])
fflate:
specifier: ^0.8.2
version: 0.8.3
js-yaml:
specifier: ^4.2.0
version: 4.3.1
pnpm:
specifier: 11.7.0
version: 11.7.0
tar:
specifier: ^7.5.0
version: 7.5.22
typescript:
specifier: ^6.0.3
version: 6.0.3
@@ -10375,6 +10369,10 @@ importers:
version: link:../../core/tools
packages/skill/tool-workspace-dependencies:
dependencies:
'@deepseek-ai/schemastery':
specifier: link:../../../vendor/schemastery
version: link:../../../vendor/schemastery
devDependencies:
'@deepseek-ai/cordis':
specifier: workspace:^
+15 -15
View File
@@ -2,13 +2,13 @@ import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSyn
import { join, resolve } from 'node:path'
import { tmpdir } from 'node:os'
import { describe, expect, it } from 'vitest'
import desktopRuntimeLock from './primary-runtime/lock.json' with { type: 'json' }
import primaryRuntimeLock from './primary-runtime/lock.json' with { type: 'json' }
import {
CLAUDE_AGENT_SDK_PACKAGE,
assertRuntimeLicenses,
claudeDistributionFromManifest,
collectPythonDependencies,
collectDesktopPythonDependencies,
collectBundledPythonDependencies,
isOwnerAuthorizedRuntime,
isPermissive,
type Manifest,
@@ -33,7 +33,7 @@ describe('THIRD_PARTY_NOTICES.md', () => {
}, async () => {
const generated = await render()
expect(generated).toContain('It depends on the third-party software listed below.')
expect(generated).toContain(`| [\`numpy\`](https://github.com/numpy/numpy) | ${desktopRuntimeLock.pythonPackages.numpy} | BSD-3-Clause |`)
expect(generated).toContain(`| [\`numpy\`](https://github.com/numpy/numpy) | ${primaryRuntimeLock.pythonPackages.numpy} | BSD-3-Clause |`)
expect(generated).toContain('## LibreOffice conversion kit')
expect(generated).toContain('Recipients must have access to those corresponding sources and notices.')
expect(readFileSync(resolve(root, 'THIRD_PARTY_NOTICES.md'), 'utf8'), 'stale notices — run `pnpm run gen-third-party-notices`').toBe(generated)
@@ -323,22 +323,22 @@ describe('collectPythonDependencies', () => {
})
})
describe('collectDesktopPythonDependencies', () => {
it('discloses the committed Desktop closure with its exact locked versions', () => {
const dependencies = collectDesktopPythonDependencies(desktopRuntimeLock.pythonPackages)
expect(dependencies).toHaveLength(Object.keys(desktopRuntimeLock.pythonPackages).length)
describe('collectBundledPythonDependencies', () => {
it('discloses the committed bundled Python closure with its exact locked versions', () => {
const dependencies = collectBundledPythonDependencies(primaryRuntimeLock.pythonPackages)
expect(dependencies).toHaveLength(Object.keys(primaryRuntimeLock.pythonPackages).length)
expect(dependencies).toContainEqual({
name: 'pillow', version: desktopRuntimeLock.pythonPackages.Pillow,
name: 'pillow', version: primaryRuntimeLock.pythonPackages.Pillow,
license: 'MIT-CMU', repo: 'https://github.com/python-pillow/Pillow',
})
expect(dependencies).toContainEqual({
name: 'typing-extensions', version: desktopRuntimeLock.pythonPackages.typing_extensions,
name: 'typing-extensions', version: primaryRuntimeLock.pythonPackages.typing_extensions,
license: 'PSF-2.0', repo: 'https://github.com/python/typing_extensions',
})
})
it('normalizes names while preserving pinned version strings', () => {
expect(collectDesktopPythonDependencies({ 'typing_extensions': '4.16.0', 'Pillow': '12.3.0' }))
expect(collectBundledPythonDependencies({ 'typing_extensions': '4.16.0', 'Pillow': '12.3.0' }))
.toEqual([
{ name: 'pillow', version: '12.3.0', license: 'MIT-CMU', repo: 'https://github.com/python-pillow/Pillow' },
{ name: 'typing-extensions', version: '4.16.0', license: 'PSF-2.0', repo: 'https://github.com/python/typing_extensions' },
@@ -346,18 +346,18 @@ describe('collectDesktopPythonDependencies', () => {
})
it('rejects duplicate normalized distribution names with the same locked version', () => {
expect(() => collectDesktopPythonDependencies({ typing_extensions: '4.16.0', 'typing.extensions': '4.16.0' }))
expect(() => collectBundledPythonDependencies({ typing_extensions: '4.16.0', 'typing.extensions': '4.16.0' }))
.toThrow('duplicate normalized names')
})
it('rejects missing distribution metadata and conflicting normalized versions', () => {
expect(() => collectDesktopPythonDependencies({ missing: '1.0' })).toThrow('missing from PYTHON_METADATA')
expect(() => collectDesktopPythonDependencies({ Pillow: '12.3.0', pillow: '12.4.0' })).toThrow('conflicting locked versions')
expect(() => collectBundledPythonDependencies({ missing: '1.0' })).toThrow('missing from PYTHON_METADATA')
expect(() => collectBundledPythonDependencies({ Pillow: '12.3.0', pillow: '12.4.0' })).toThrow('conflicting locked versions')
})
it('applies the runtime license check to bundled Python distributions', () => {
expect(() => { assertRuntimeLicenses(collectDesktopPythonDependencies(desktopRuntimeLock.pythonPackages)) }).not.toThrow()
const dependencies = collectDesktopPythonDependencies({ 'copyleft-wheel': '1.0' }, {
expect(() => { assertRuntimeLicenses(collectBundledPythonDependencies(primaryRuntimeLock.pythonPackages)) }).not.toThrow()
const dependencies = collectBundledPythonDependencies({ 'copyleft-wheel': '1.0' }, {
'copyleft-wheel': { license: 'GPL-3.0-only', repo: 'https://example.com/project' },
})
expect(() => { assertRuntimeLicenses(dependencies) }).toThrow('copyleft-wheel (GPL-3.0-only)')
+15 -15
View File
@@ -1,7 +1,7 @@
/**
* Generate `THIRD_PARTY_NOTICES.md` from the workspace manifests: every
* external dependency named by a workspace `package.json`, the vendored-package
* manifest in `vendor/README.md`, the Python `pyproject.toml` files, the Desktop
* manifest in `vendor/README.md`, the Python `pyproject.toml` files, the shared
* Python distribution lock, and the pnpm patch list. npm metadata comes from the installed
* store, so the tree must be installed. `--check` verifies the committed
* artifact. Tier policy and ownership live in
@@ -13,7 +13,7 @@ import { dirname, 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 desktopRuntimeLock from './primary-runtime/lock.json' with { type: 'json' }
import primaryRuntimeLock from './primary-runtime/lock.json' with { type: 'json' }
import { browserBundledExternals } from './browser-bundled-externals.ts'
const root = resolve(import.meta.dirname, '..')
@@ -93,7 +93,7 @@ const OVERRIDES: Record<string, { license?: string; repo?: string }> = {
/**
* Python metadata is recorded from the distributions' license and project
* fields; generation does not require installing their wheels. Both Python
* manifests and the Desktop lock reject names absent from this map.
* manifests and the shared runtime lock reject names absent from this map.
*/
const PYTHON_METADATA: Record<string, { license: string; repo: string; role?: string }> = {
pydantic: { license: 'MIT', repo: 'https://github.com/pydantic/pydantic', role: 'runtime dependency of `deepseek-harness-sdk`' },
@@ -616,12 +616,12 @@ function collectPython(): { name: string; license: string; repo: string; role: s
}
/**
* Disclose every Desktop wheel distribution using its locked version, rejecting duplicate normalized names.
* @param packages - Distribution names and exact versions from the Desktop runtime lock.
* Disclose every bundled wheel distribution using its locked version, rejecting duplicate normalized names.
* @param packages - Distribution names and exact versions from the shared runtime lock.
* @param metadata - License and source metadata for every locked distribution.
* @returns Normalized, sorted distribution identities with versions and licenses.
*/
export function collectDesktopPythonDependencies(
export function collectBundledPythonDependencies(
packages: Readonly<Record<string, string>>,
metadata: PythonMetadata = PYTHON_METADATA,
): { name: string; version: string; license: string; repo: string }[] {
@@ -630,14 +630,14 @@ export function collectDesktopPythonDependencies(
const name = normalizePythonDistributionName(distribution)
const previous = normalized.get(name)
if (previous !== undefined && previous !== version) {
throw new Error(`gen-third-party-notices: Desktop python distribution ${name} has conflicting locked versions.`)
throw new Error(`gen-third-party-notices: bundled Python distribution ${name} has conflicting locked versions.`)
}
if (previous !== undefined) throw new Error(`gen-third-party-notices: Desktop python distribution ${name} has duplicate normalized names.`)
if (previous !== undefined) throw new Error(`gen-third-party-notices: bundled Python distribution ${name} has duplicate normalized names.`)
normalized.set(name, version)
}
return [...normalized].sort(([a], [b]) => a.localeCompare(b)).map(([name, version]) => {
const entry = metadata[name]
if (entry === undefined) throw new Error(`gen-third-party-notices: Desktop python distribution ${name} is missing from PYTHON_METADATA.`)
if (entry === undefined) throw new Error(`gen-third-party-notices: bundled Python distribution ${name} is missing from PYTHON_METADATA.`)
return { name, version, license: entry.license, repo: entry.repo }
})
}
@@ -753,7 +753,7 @@ export async function render(): Promise<string> {
const kitRuntime = runtimeDeps.some(dep => dep.name === LIBREOFFICE_KIT_PACKAGE)
const vendored = collectVendored()
const python = collectPython()
const desktopPython = collectDesktopPythonDependencies(desktopRuntimeLock.pythonPackages)
const bundledPython = collectBundledPythonDependencies(primaryRuntimeLock.pythonPackages)
const patched = collectPatched()
const claudeDistribution = runtimeDeps.some(
dep => dep.name === CLAUDE_AGENT_SDK_PACKAGE,
@@ -762,7 +762,7 @@ export async function render(): Promise<string> {
: undefined
const nonPermissiveDev = devDeps.filter(dep => !isPermissive(dep.license))
assertRuntimeLicenses(runtimeDeps)
assertRuntimeLicenses(desktopPython)
assertRuntimeLicenses(bundledPython)
const patchedLines = patched.map(({ spec, patch }) => `- \`${spec}\` — [\`${patch}\`](${patch})`)
return `<!-- Generated by scripts/gen-third-party-notices.ts — do not edit by hand.
@@ -772,7 +772,7 @@ export async function render(): Promise<string> {
DeepSeek Harness is licensed under [MIT](LICENSE). It depends on the third-party software listed below. Each project remains under its own license; nothing in this file changes those terms.
This file lists **direct** dependencies declared by the workspace, the explicitly disclosed official Claude Code platform payload closure, and the Desktop bundled Python distributions. It is generated by \`scripts/gen-third-party-notices.ts\`: a pre-commit hook regenerates it whenever a staged file changes one of its inputs, and \`scripts/gen-third-party-notices.spec.ts\` asserts in the test lane that the committed bytes match. Deleting a manifest runs no hook, so that case is caught by the assertion instead. Run \`pnpm run verify-third-party-notices\` for the standalone check.
This file lists **direct** dependencies declared by the workspace, the explicitly disclosed official Claude Code platform payload closure, and the Bundled Python distributions. It is generated by \`scripts/gen-third-party-notices.ts\`: a pre-commit hook regenerates it whenever a staged file changes one of its inputs, and \`scripts/gen-third-party-notices.spec.ts\` asserts in the test lane that the committed bytes match. Deleting a manifest runs no hook, so that case is caught by the assertion instead. Run \`pnpm run verify-third-party-notices\` for the standalone check.
The complete npm transitive closure, including the Landlock launcher workspace, is recorded with exact pinned versions in [\`pnpm-lock.yaml\`](pnpm-lock.yaml) — inspect it with \`pnpm licenses list\`. The Python SDK closure is recorded separately in [\`python/sdk/uv.lock\`](python/sdk/uv.lock).
@@ -817,13 +817,13 @@ Direct dependencies of the \`pyproject.toml\` manifests, plus \`uv\` as the deve
${python.map(dep => `| [\`${dep.name}\`](${dep.repo}) | ${dep.license} | ${dep.role} |`).join('\n')}
| [\`uv\`](https://github.com/astral-sh/uv) | MIT / Apache-2.0 | development workflow tool |
## Desktop bundled Python distributions
## Bundled Python distributions
The [Desktop runtime lock](scripts/primary-runtime/lock.json) records each distribution version and the wheel download hashes. The table includes every entry in \`pythonPackages\`, including transitive dependencies. Wheel extraction preserves distribution metadata and the license and notice files supplied by each archive. Project licenses below do not enumerate the separate licenses of native libraries bundled inside wheels.
The [shared runtime lock](scripts/primary-runtime/lock.json) records each distribution version and the wheel download hashes. The table includes every entry in \`pythonPackages\`, including transitive dependencies. Wheel extraction preserves distribution metadata and the license and notice files supplied by each archive. Project licenses below do not enumerate the separate licenses of native libraries bundled inside wheels.
| Distribution | Locked version | Project license |
| --- | --- | --- |
${desktopPython.map(dep => `| [\`${dep.name}\`](${dep.repo}) | ${dep.version} | ${dep.license} |`).join('\n')}
${bundledPython.map(dep => `| [\`${dep.name}\`](${dep.repo}) | ${dep.version} | ${dep.license} |`).join('\n')}
## First-party native packages
+13
View File
@@ -1,3 +1,4 @@
import { readFileSync } from 'node:fs'
import { createHash } from 'node:crypto'
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
@@ -133,3 +134,15 @@ it('copies complete Office resources outside the application archive and removes
it('gives Python-only payloads a distinct identity', () => {
expect(primaryRuntimePayloadDigest('linux-x64', lock, undefined)).not.toBe(primaryRuntimePayloadDigest('linux-x64', lock, '11.7.0'))
})
it('keeps carrier pnpm versions aligned with the shared payload build', () => {
const root = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8')) as {
packageManager: string
devDependencies: { pnpm: string }
}
const desktop = JSON.parse(readFileSync(new URL('../../apps/desktop/package.json', import.meta.url), 'utf8')) as {
devDependencies: { pnpm: string }
}
expect(desktop.devDependencies.pnpm).toBe(root.devDependencies.pnpm)
expect(root.packageManager).toBe(`pnpm@${root.devDependencies.pnpm}`)
})
@@ -217,7 +217,7 @@
},
{
"name": "load_workspace_dependencies",
"description": "Get absolute paths to bundled Python, Node.js, pnpm, and library directories, plus bundled Python distribution versions. Python includes numpy, pandas, python-docx, python-pptx, openpyxl, Pillow, lxml, and XlsxWriter. Use these libraries for Office files unless the user or workspace instructions select another environment. Run pnpm with the returned Node executable and pnpm script path. This does not change PATH or package-manager settings.",
"description": "Get absolute paths to bundled Python and library directories, plus bundled Python distribution versions. Node.js and pnpm paths are included when the payload provides them. Python includes numpy, pandas, python-docx, python-pptx, openpyxl, Pillow, lxml, and XlsxWriter. Use these libraries for Office files unless the user or workspace instructions select another environment. When Node.js and pnpm paths are returned, run pnpm with that Node executable and pnpm script path. This does not change PATH or package-manager settings.",
"parameters": {
"type": "object",
"properties": {}