Merge origin/master into feat/cua-permission-onboarding
This commit is contained in:
commit
a928f614bc
548 files changed
+13534
-1837
No files matched your search
@@ -2077,6 +2077,9 @@
|
||||
"process/2026-07-06-generated-config-catalog.i18n.yaml": "sha256:48e64ccfc7622559a9b964e2741ad82a9fa37243a0d5c472f0482d9c748d7739",
|
||||
"process/2026-07-06-generated-config-catalog.md": "sha256:c8654a4e084bfbf4f7d8eb78239dc1d5d62e1384b2aed309fa2526a4d6de0f04",
|
||||
"process/2026-07-06-generated-config-catalog.zh.md": "sha256:5ab8d964f12e0cf9a04251e988f0b2a114d7ae8ea7ed337c00f625213cb1dc94",
|
||||
"process/2026-07-06-node-engine-floor.i18n.yaml": "sha256:65d73bf7a6c2fb7609ff3a8f18a435e83bb488e005365d065d184942958bfb35",
|
||||
"process/2026-07-06-node-engine-floor.md": "sha256:b62c8e8dd008b0ab7874b5d57c20c629ffb71e4b4a62922afaf6a1039d25898e",
|
||||
"process/2026-07-06-node-engine-floor.zh.md": "sha256:3cd685e59f913ef700b3e2ed3b941c2266bb2de706810cbe570f4832a26a5034",
|
||||
"process/2026-07-06-parallel-github-ci-gates.i18n.yaml": "sha256:0f6ece268d9a51bc20cb8eb929f26d8838761603a64eb08dc24521198f10da36",
|
||||
"process/2026-07-06-parallel-github-ci-gates.md": "sha256:6249bd7396ae7f2d0dc671879ce21cefab33a47ace6ef17a25a70e8650b815af",
|
||||
"process/2026-07-06-parallel-github-ci-gates.zh.md": "sha256:cf7edb9bcf97ab1d4e452330c0df0b127a664509ec3f11597ace3eabeb663a5b",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Frozen bilingual archive consistency record: Git blob hashes after archive metadata insertion.
|
||||
2026-07-06-node-engine-floor.md: 4e399b2a71bb4a76a28bc4b643a761ce65c55865
|
||||
2026-07-06-node-engine-floor.zh.md: abd4913688cc4b3af02fa2f0b06d4b8206652d13
|
||||
+1
@@ -1,6 +1,7 @@
|
||||
# Agent Note: Raise the Node LTS engine floor to 22.19
|
||||
|
||||
Status: implemented
|
||||
Archived: 2026-10-15
|
||||
|
||||
English | [中文](2026-07-06-node-engine-floor.zh.md)
|
||||
|
||||
+1
@@ -1,6 +1,7 @@
|
||||
# Agent Note: 将 Node LTS 引擎下限提升至 22.19
|
||||
|
||||
Status: implemented
|
||||
Archived: 2026-10-15
|
||||
|
||||
[English](2026-07-06-node-engine-floor.md) | 中文
|
||||
|
||||
+4
-4
@@ -21,11 +21,11 @@
|
||||
en: 985e0eb63f3d1fd6
|
||||
zh: 337cc44fadc6227e
|
||||
/agent-note-single-file-executable-sdk-runtime-distribution-single-exe/decision/build-pipeline-and-artifacts:
|
||||
en: 921085cb9d894688
|
||||
zh: 12d99fe153d36738
|
||||
en: 9d885e8d58bc8bbb
|
||||
zh: 41a9779fbdea8a1a
|
||||
/agent-note-single-file-executable-sdk-runtime-distribution-single-exe/decision/python-sdk-distribution-two-carriers-exe-for-production-node-for-development:
|
||||
en: 02e6b36f9f92c83a
|
||||
zh: edd8b2db7a787483
|
||||
en: b2a740a1ab7a5002
|
||||
zh: a48be6ca6fc65912
|
||||
/agent-note-single-file-executable-sdk-runtime-distribution-single-exe/decision/naming-lineage:
|
||||
en: 31a5aecb8bf828bb
|
||||
zh: 2dffb6fd2e0f8194
|
||||
|
||||
+2
-2
@@ -48,13 +48,13 @@ The [Office kit](2026-09-14-independent-libreoffice-kit.md) and its installed de
|
||||
|
||||
### Build pipeline and artifacts
|
||||
|
||||
[`scripts/build-exe-for-python-sdk.ts`](../../../../scripts/build-exe-for-python-sdk.ts): runtime closure verification → `pnpm run build` → (after clearing) `pnpm --filter dsh-python-runtime-closure deploy --legacy --prod --config.allow-unused-patches=true --config.node-linker=hoisted --config.auto-install-peers=false --config.link-workspace-packages=true --config.hoist-workspace-packages=false` **directly into** `python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/`, including [`python/sdk-runtime/runtime-bootstrap.mjs`](../../../../python/sdk-runtime/runtime-bootstrap.mjs) as the carrier-root `runtime-bootstrap.mjs` → restore direct workspace packages omitted by legacy deploy and reject any remaining manifest gap → replace staged dependency symlinks with their target bytes, remove package-manager `.bin` links, and fail if any symlink remains → verify the deployed bootstrap and inject pkg configuration with that bin plus assets covering dynamic profile, bundle, frontend, preset, native-library, and configuration reads → stage the target `node-pty` addon → invoke `pkg --sea` once per target → write `deepseek-harness-sdk-runtime-<platform>-<arch>` under `dist-exe/` and copy it with its lightweight resource manifest and Office skill assets into the runtime directory. The Python runtime owns that bootstrap. It calls the public CLI export for ordinary launches and dispatches a provider-private selection to the same `@deepseek-ai/dsh-subprocess-local/runner` core without changing CLI grammar or adding another executable; the [native-containment reference](../../../../packages/subprocess/subprocess-local/README.md) owns that private path. Linux CI rebuilds `pty.node` inside the matching manylinux 2.28 container because legacy deploy omits that install side effect. Every target copies its native `@vscode/ripgrep` binary beside the executable as the required `-rg` sidecar; pkg runtimes select that sidecar through `process.pkg`, while ordinary Node execution uses `@vscode/ripgrep` directly. macOS uses its target prebuild and also emits the required `-spawn-helper`. The deploy flags preserve the measured runtime layout: `--legacy` is the mandatory path with inject-workspace-packages off; hoisted supplies package directories that staging makes symlink-free; staging removes nested workspace copies only when pnpm records the same dependency identity at the payload root, preserving singleton services; disabling workspace hoisting excludes unrelated workspace source packages; disabling automatic peer installation prevents undeclared peers from expanding the closure; link-workspace-packages selects direct workspace dependencies. [`pnpm-workspace.yaml`](../../../../pnpm-workspace.yaml) overrides the transitive `@deepseek-ai/cosmokit` and `@deepseek-ai/schemastery` semver requests to the pinned vendor sources so legacy deploy never resolves those unpublished names from a registry. Production deployment permits unused workspace patches because development-only tools can be absent from its closure; included-package patch failures still fail deployment. Workspace installs retain strict unused-patch validation.
|
||||
[`scripts/build-exe-for-python-sdk.ts`](../../../../scripts/build-exe-for-python-sdk.ts): runtime closure verification → `pnpm run build` → (after clearing) `pnpm --filter dsh-python-runtime-closure deploy --legacy --prod --config.allow-unused-patches=true --config.node-linker=hoisted --config.auto-install-peers=false --config.link-workspace-packages=true --config.hoist-workspace-packages=false` **directly into** `python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/`, including [`python/sdk-runtime/runtime-bootstrap.mjs`](../../../../python/sdk-runtime/runtime-bootstrap.mjs) as the carrier-root `runtime-bootstrap.mjs` → restore direct workspace packages omitted by legacy deploy and reject any remaining manifest gap → replace staged dependency symlinks with their target bytes, remove package-manager `.bin` links, and fail if any symlink remains → verify the deployed bootstrap and inject pkg configuration with that bin plus assets covering dynamic profile, bundle, frontend, preset, native-library, and configuration reads → stage the target `node-pty` addon → invoke `pkg --sea --compress Brotli` once per target → write `deepseek-harness-sdk-runtime-<platform>-<arch>` under `dist-exe/` and copy it with its lightweight resource manifest and Office skill assets into the runtime directory. The Python runtime owns that bootstrap. It calls the public CLI export for ordinary launches and dispatches a provider-private selection to the same `@deepseek-ai/dsh-subprocess-local/runner` core without changing CLI grammar or adding another executable; the [native-containment reference](../../../../packages/subprocess/subprocess-local/README.md) owns that private path. Linux CI rebuilds `pty.node` inside the matching manylinux 2.28 container because legacy deploy omits that install side effect. Every target copies its native `@vscode/ripgrep` binary beside the executable as the required `-rg` sidecar; pkg runtimes select that sidecar through `process.pkg`, while ordinary Node execution uses `@vscode/ripgrep` directly. macOS uses its target prebuild and also emits the required `-spawn-helper`. The deploy flags preserve the measured runtime layout: `--legacy` is the mandatory path with inject-workspace-packages off; hoisted supplies package directories that staging makes symlink-free; staging removes nested workspace copies only when pnpm records the same dependency identity at the payload root, preserving singleton services; disabling workspace hoisting excludes unrelated workspace source packages; disabling automatic peer installation prevents undeclared peers from expanding the closure; link-workspace-packages selects direct workspace dependencies. [`pnpm-workspace.yaml`](../../../../pnpm-workspace.yaml) overrides the transitive `@deepseek-ai/cosmokit` and `@deepseek-ai/schemastery` semver requests to the pinned vendor sources so legacy deploy never resolves those unpublished names from a registry. Production deployment permits unused workspace patches because development-only tools can be absent from its closure; included-package patch failures still fail deployment. Workspace installs retain strict unused-patch validation.
|
||||
|
||||
CI: [`.github/workflows/build-exe-for-python-sdk.yml`](../../../../.github/workflows/build-exe-for-python-sdk.yml) runs [installed-wheel validation](../testing/2026-08-23-installed-python-wheel-black-box-ci.md) on Linux/Windows x64 for pull requests and Linux ARM64 plus both macOS architectures for master pushes. The [public publication workflow](../../archived/process/2026-08-11-python-publication-workflow.md) calls it for all five targets; `workflow_dispatch` can still select a subset. Native builds run on linux-x64 / linux-arm64 (`ubuntu-24.04-arm`) / macos-arm64 / macos-x64 (`macos-15-intel`) / win-x64 (`windows-2025`), with `~/.pkg-cache` cached where applicable, and pkg handles macOS ad-hoc signing. Each leg installs the release-shaped SDK and runtime wheels into a clean venv outside the checkout, verifies their package and executable origin, then drives the complete keyless scenario set through the public SDK and direct NDJSON JSON-RPC. Trusted pull requests and master pushes additionally run a real DeepSeek two-turn tool smoke on their selected targets; fork and Dependabot heads receive no key. Linux inspects the executable and native addon's GLIBC requirements and runs an additional manylinux 2.28 smoke, while macOS checks the runtime, ripgrep, and PTY helper architectures and verifies that all three deployment targets fit the wheel tag. A full five-target run retains six artifacts, each containing one release file: the platform-independent SDK wheel and five native runtime wheels; a subset dispatch retains the SDK wheel and selected runtime wheels. Bare executables and source bundles remain intermediate test inputs. [`.gitlab-ci.yml`](../../../../.gitlab-ci.yml) accepts `python-v<repository-version>` tag pipelines whose version matches the root `package.json`, builds one SDK wheel and five native runtime wheels, then a single serialized job checks and publishes all six to the project PyPI registry. The [`python/sdk-runtime` README](../../../../python/sdk-runtime/README.md) owns the Windows target and the explicit exclusion of Windows arm64.
|
||||
|
||||
### Python SDK distribution: two carriers, exe for production, node for development
|
||||
|
||||
The Python SDK lives at [`python/`](../../../../python/README.md): `python/sdk` is the client and `python/sdk-runtime` is the runtime carrier package. The runtime package's data directory holds the build-injected platform executable with its required `-rg` sidecar, lightweight download metadata and Office skills, and optional macOS helper, plus the build-injected `runtime/node/` closure tree for repository development. `resolve_bundled_launch_args()` selects the executable by default; explicit `DSH_RUNTIME_MODE=node` runs `runtime/node/runtime-bootstrap.mjs` on system Node 22.19 or newer. The node carrier never enters wheel distributions, and neither carrier uses a checked-in complete `cordis.yml`.
|
||||
The Python SDK lives at [`python/`](../../../../python/README.md): `python/sdk` is the client and `python/sdk-runtime` is the runtime carrier package. The runtime package's data directory holds the build-injected platform executable with its required `-rg` sidecar, lightweight download metadata and Office skills, and optional macOS helper, plus the build-injected `runtime/node/` closure tree for repository development. `resolve_bundled_launch_args()` selects the executable by default; explicit `DSH_RUNTIME_MODE=node` runs `runtime/node/runtime-bootstrap.mjs` on system Node 24.9 or newer. The node carrier never enters wheel distributions, and neither carrier uses a checked-in complete `cordis.yml`.
|
||||
|
||||
[`scripts/build-python-release.py`](../../../../scripts/build-python-release.py) reads the authoritative `X.Y.Z` or prerelease version from the repository root `package.json`, converts prereleases to their PEP 440 spelling, and stages both packages at that wheel version, with `deepseek-harness-sdk` depending exactly on the matching `deepseek-harness-runtime-bin`. An optional `python-v<repository-version>` release tag is a consistency assertion and is rejected when it differs from the repository version; the source `pyproject.toml` development sentinel never determines a release version. Staging also carries the repository license into both wheels and the third-party notices into the bundled runtime wheel. The SDK is a `py3-none-any` wheel; each wheel-only runtime package contains one exe, lightweight resource metadata and skills, and its architecture-matched ripgrep sidecar, and the macOS wheel also contains its architecture-matched spawn helper. Runtime wheels use `py3-none-manylinux_2_28_x86_64`, `py3-none-manylinux_2_28_aarch64`, `py3-none-macosx_14_0_arm64`, `py3-none-macosx_14_0_x86_64`, or `py3-none-win_amd64`; the Hatch hook rejects sdists, universal tags, mixed-platform payloads, missing or extra sidecars, and unsupported platforms. Both macOS tags deliberately declare a conservative 14.0 installation floor: the packaged Node 24 executables declare macOS 13.5, and the x64 PTY helper declares 10.7, but release validation proves the complete payload only against the 14.0 wheel claim rather than promising each observed component minimum as a supported host. The two macOS wheels remain architecture-specific; no universal2 wheel is published.
|
||||
|
||||
|
||||
+2
-2
@@ -48,13 +48,13 @@ exe 的 VFS 内是**构建产物形态的真实包树**(各包的 `lib/` + 真
|
||||
|
||||
### 构建流水线与产物
|
||||
|
||||
[`scripts/build-exe-for-python-sdk.ts`](../../../../scripts/build-exe-for-python-sdk.ts):运行时闭包校验 → `pnpm run build` →(清空后)`pnpm --filter dsh-python-runtime-closure deploy --legacy --prod --config.allow-unused-patches=true --config.node-linker=hoisted --config.auto-install-peers=false --config.link-workspace-packages=true --config.hoist-workspace-packages=false` **直接写入** `python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/`,其中包含作为载体根目录 `runtime-bootstrap.mjs` 的 [`python/sdk-runtime/runtime-bootstrap.mjs`](../../../../python/sdk-runtime/runtime-bootstrap.mjs) → 恢复 legacy deploy 遗漏的直接工作区包,并拒绝剩余的 manifest 缺口 → 将暂存依赖中的符号链接替换为目标文件内容,删除包管理器的 `.bin` 链接,并在仍有任何符号链接时失败 → 验证已部署的 bootstrap,并注入以该文件为 bin 的 pkg 配置及覆盖动态读取 profile、bundle、前端、preset、原生库与配置文件的 assets → 暂存目标平台的 `node-pty` addon → 每个构建目标调用一次 `pkg --sea` → 将 `deepseek-harness-sdk-runtime-<platform>-<arch>` 写入 `dist-exe/`,并将它与轻量资源清单及 Office skill 资源一同拷回运行时目录。该 bootstrap 由 Python runtime 拥有;普通启动时它调用公开 CLI export,而提供方私有选择会分派到同一个 `@deepseek-ai/dsh-subprocess-local/runner` 核心,不改变 CLI 语法,也不增加另一个可执行文件;[原生 containment 参考](../../../../packages/subprocess/subprocess-local/README.zh.md)负责这条私有路径。Linux CI 会在匹配的 manylinux 2.28 容器中重新构建 `pty.node`,因为 legacy deploy 会遗漏这一安装副作用。每个目标都会把对应的原生 `@vscode/ripgrep` 二进制复制到可执行文件旁,作为必需的 `-rg` 伴随文件;pkg 运行时通过 `process.pkg` 选择该伴随文件,普通 Node 执行则直接使用 `@vscode/ripgrep`。macOS 使用对应目标的预构建产物,并额外生成所需的 `-spawn-helper`。部署标志都有实测依据:未启用 `inject-workspace-packages` 时必须使用 `--legacy`;`hoisted` 提供包目录,再由暂存步骤消除符号链接;只有 pnpm 在载荷根目录记录了相同依赖标识时,暂存步骤才删除对应的嵌套工作区副本,以保留服务单例;关闭工作区提升可排除无关的工作区源码包;关闭对等依赖自动安装可防止未声明的对等依赖扩大闭包;`link-workspace-packages` 选择直接工作区依赖。[`pnpm-workspace.yaml`](../../../../pnpm-workspace.yaml) 将传递的 `@deepseek-ai/cosmokit` 与 `@deepseek-ai/schemastery` semver 请求覆盖到固定的 vendor 源码,使 legacy deploy 不会从注册表解析这些未发布名称。 生产部署允许未使用的工作区补丁,因为开发工具可能不属于运行时闭包;闭包内包的补丁应用失败仍会中止部署。工作区安装继续严格校验未使用的补丁。
|
||||
[`scripts/build-exe-for-python-sdk.ts`](../../../../scripts/build-exe-for-python-sdk.ts):运行时闭包校验 → `pnpm run build` →(清空后)`pnpm --filter dsh-python-runtime-closure deploy --legacy --prod --config.allow-unused-patches=true --config.node-linker=hoisted --config.auto-install-peers=false --config.link-workspace-packages=true --config.hoist-workspace-packages=false` **直接写入** `python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/`,其中包含作为载体根目录 `runtime-bootstrap.mjs` 的 [`python/sdk-runtime/runtime-bootstrap.mjs`](../../../../python/sdk-runtime/runtime-bootstrap.mjs) → 恢复 legacy deploy 遗漏的直接工作区包,并拒绝剩余的 manifest 缺口 → 将暂存依赖中的符号链接替换为目标文件内容,删除包管理器的 `.bin` 链接,并在仍有任何符号链接时失败 → 验证已部署的 bootstrap,并注入以该文件为 bin 的 pkg 配置及覆盖动态读取 profile、bundle、前端、preset、原生库与配置文件的 assets → 暂存目标平台的 `node-pty` addon → 每个构建目标调用一次 `pkg --sea --compress Brotli` → 将 `deepseek-harness-sdk-runtime-<platform>-<arch>` 写入 `dist-exe/`,并将它与轻量资源清单及 Office skill 资源一同拷回运行时目录。该 bootstrap 由 Python runtime 拥有;普通启动时它调用公开 CLI export,而提供方私有选择会分派到同一个 `@deepseek-ai/dsh-subprocess-local/runner` 核心,不改变 CLI 语法,也不增加另一个可执行文件;[原生 containment 参考](../../../../packages/subprocess/subprocess-local/README.zh.md)负责这条私有路径。Linux CI 会在匹配的 manylinux 2.28 容器中重新构建 `pty.node`,因为 legacy deploy 会遗漏这一安装副作用。每个目标都会把对应的原生 `@vscode/ripgrep` 二进制复制到可执行文件旁,作为必需的 `-rg` 伴随文件;pkg 运行时通过 `process.pkg` 选择该伴随文件,普通 Node 执行则直接使用 `@vscode/ripgrep`。macOS 使用对应目标的预构建产物,并额外生成所需的 `-spawn-helper`。部署标志都有实测依据:未启用 `inject-workspace-packages` 时必须使用 `--legacy`;`hoisted` 提供包目录,再由暂存步骤消除符号链接;只有 pnpm 在载荷根目录记录了相同依赖标识时,暂存步骤才删除对应的嵌套工作区副本,以保留服务单例;关闭工作区提升可排除无关的工作区源码包;关闭对等依赖自动安装可防止未声明的对等依赖扩大闭包;`link-workspace-packages` 选择直接工作区依赖。[`pnpm-workspace.yaml`](../../../../pnpm-workspace.yaml) 将传递的 `@deepseek-ai/cosmokit` 与 `@deepseek-ai/schemastery` semver 请求覆盖到固定的 vendor 源码,使 legacy deploy 不会从注册表解析这些未发布名称。 生产部署允许未使用的工作区补丁,因为开发工具可能不属于运行时闭包;闭包内包的补丁应用失败仍会中止部署。工作区安装继续严格校验未使用的补丁。
|
||||
|
||||
CI 使用 [`.github/workflows/build-exe-for-python-sdk.yml`](../../../../.github/workflows/build-exe-for-python-sdk.yml):[安装后 wheel 包验证](../testing/2026-08-23-installed-python-wheel-black-box-ci.zh.md)在拉取请求上运行 Linux/Windows x64,在 master 推送上运行 Linux ARM64 与两种 macOS 架构。[公开发布工作流](../../archived/process/2026-08-11-python-publication-workflow.md)调用它构建全部五个目标;`workflow_dispatch` 仍可选择部分目标。linux-x64、linux-arm64(`ubuntu-24.04-arm`)、macos-arm64、macos-x64(`macos-15-intel`)与 win-x64(`windows-2025`)分别进行原生构建,并在适用平台缓存 `~/.pkg-cache`;macOS 的 ad-hoc 签名由 pkg 处理。每个平台都把发布形态的 SDK wheel 包与运行时 wheel 包安装到 checkout 外的干净 venv,证明包与可执行文件来源,再通过公开 SDK 与直接 NDJSON JSON-RPC 运行完整 keyless 场景。可信拉取请求与 master 推送还会在各自选定的目标上运行真实 DeepSeek 双轮工具冒烟测试;fork 与 Dependabot head 不会获得密钥。Linux 会检查可执行文件和原生 addon 各自的 GLIBC 依赖,并额外运行 manylinux 2.28 冒烟测试;macOS 则检查 runtime、ripgrep 与 PTY helper 的架构,并验证三个载荷的部署目标都符合 wheel 包标签。完整构建五个目标时保留 6 个产物,每个产物只含一个发布文件:平台无关的 SDK wheel 包与 5 个原生运行时 wheel 包;手动选择部分目标时保留 SDK wheel 与所选运行时 wheel。裸 exe 与源码包只作为测试中间输入。[`.gitlab-ci.yml`](../../../../.gitlab-ci.yml) 只接受版本与根目录 `package.json` 匹配的 `python-v<repository-version>` 标签流水线,构建一个 SDK wheel 包和 5 个原生运行时 wheel 包,再由单个串行任务校验并将这 6 个文件发布到项目的 PyPI 注册表。[`python/sdk-runtime` README](../../../../python/sdk-runtime/README.zh.md)负责 Windows 目标及对 Windows arm64 的明确排除。
|
||||
|
||||
### Python SDK 分发:双载体,exe 用于生产,`node` 用于开发
|
||||
|
||||
Python SDK 位于 [`python/`](../../../../python/README.zh.md):`python/sdk` 是客户端,`python/sdk-runtime` 是运行时载体包。运行时包的数据目录包含构建注入的平台可执行文件及其必需的 `-rg` 伴随文件、轻量下载元数据与 Office skills 和可选的 macOS helper,以及供仓库开发使用的构建注入 `runtime/node/` 闭包树。`resolve_bundled_launch_args()` 默认选择可执行文件;显式设置 `DSH_RUNTIME_MODE=node` 会在系统 Node 22.19 或更高版本上运行 `runtime/node/runtime-bootstrap.mjs`。node 载体从不进入 wheel 分发,两种载体都不使用检入的完整 `cordis.yml`。
|
||||
Python SDK 位于 [`python/`](../../../../python/README.zh.md):`python/sdk` 是客户端,`python/sdk-runtime` 是运行时载体包。运行时包的数据目录包含构建注入的平台可执行文件及其必需的 `-rg` 伴随文件、轻量下载元数据与 Office skills 和可选的 macOS helper,以及供仓库开发使用的构建注入 `runtime/node/` 闭包树。`resolve_bundled_launch_args()` 默认选择可执行文件;显式设置 `DSH_RUNTIME_MODE=node` 会在系统 Node 24.9 或更高版本上运行 `runtime/node/runtime-bootstrap.mjs`。node 载体从不进入 wheel 分发,两种载体都不使用检入的完整 `cordis.yml`。
|
||||
|
||||
[`scripts/build-python-release.py`](../../../../scripts/build-python-release.py) 从仓库根目录的 `package.json` 读取权威的 `X.Y.Z` 或预发布版本,把预发布版本转换为 PEP 440 写法,并以该 wheel 包版本暂存两个包,让 `deepseek-harness-sdk` 精确依赖匹配版本的 `deepseek-harness-runtime-bin`。可选的 `python-v<repository-version>` 发布标签只是一项一致性断言,与仓库版本不同时会被拒绝;源码 `pyproject.toml` 中的开发占位版本从不决定发布版本。暂存过程还会把仓库许可证放入两个 wheel 包,并把第三方声明放入内置运行时 wheel 包。SDK 是 `py3-none-any` wheel 包;每个只提供 wheel 包的运行时包都包含一个 exe、轻量资源元数据与 skills 及架构匹配的 ripgrep 伴随文件,macOS wheel 包还包含与其架构匹配的 spawn helper。运行时 wheel 包使用 `py3-none-manylinux_2_28_x86_64`、`py3-none-manylinux_2_28_aarch64`、`py3-none-macosx_14_0_arm64`、`py3-none-macosx_14_0_x86_64` 或 `py3-none-win_amd64`;Hatch 钩子拒绝 sdist、通用标签、混合平台载荷、伴随文件缺失或多余,以及不支持的平台。两个 macOS 标签都会特意声明保守的 14.0 安装下限:打包后的 Node 24 可执行文件声明 macOS 13.5,x64 PTY helper 声明 10.7,但发布验证只按照 14.0 wheel 声明证明完整载荷,不会把各组件实测的最低版本承诺为受支持宿主。两个 macOS wheel 包仍按架构分别发布,不发布 universal2 wheel 包。
|
||||
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@
|
||||
en: 55846ac57e07eeda
|
||||
zh: 3de03730d4b28863
|
||||
/agent-note-dsh-source-launch-through-the-tsx-esm-hook/problem:
|
||||
en: 68385f7e47d3ac1f
|
||||
zh: 285ca290832e2939
|
||||
en: d13aa56d01c1181d
|
||||
zh: 837427ed6963c39d
|
||||
/agent-note-dsh-source-launch-through-the-tsx-esm-hook/decision:
|
||||
en: 12adf8ed56b4d5c3
|
||||
zh: fbbb9add935730c0
|
||||
|
||||
@@ -8,7 +8,7 @@ English | [中文](2026-07-29-dsh-source-launch-tsx-esm.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The [archived native source-launch decision](../../archived/architecture/2026-07-28-dsh-native-typescript-source-launch.md) ran `apps/cli/src/bin.ts` under `node --experimental-transform-types` with a resolve-only paths loader, so Node owned TypeScript transformation. Node 26.0.0 removed `--experimental-transform-types` (the process rejects the flag with `bad option`), keeping only strip mode, and strip mode rejects syntax this source graph requires: vendored Cordis parameter properties (`constructor(private ctx: Context)`), the `@Inject` decorators in `vendor/hmr`, and runtime enums/namespaces throughout `vendor/` and `packages/workflow`. The repository's engines range (`^22.19.0 || >=24.0.0`) includes Node 26, so the native launch chain could not start at all there — and no CI job executed the real launch vector, so the incompatibility shipped silently.
|
||||
The [archived native source-launch decision](../../archived/architecture/2026-07-28-dsh-native-typescript-source-launch.md) ran `apps/cli/src/bin.ts` under `node --experimental-transform-types` with a resolve-only paths loader, so Node owned TypeScript transformation. Node 26.0.0 removed `--experimental-transform-types` (the process rejects the flag with `bad option`), keeping only strip mode, and strip mode rejects syntax this source graph requires: vendored Cordis parameter properties (`constructor(private ctx: Context)`), the `@Inject` decorators in `vendor/hmr`, and runtime enums/namespaces throughout `vendor/` and `packages/workflow`. The supported engines range includes Node 26, so the native launch chain could not start at all there — and no CI job executed the real launch vector, so the incompatibility shipped silently.
|
||||
|
||||
Startup latency also mattered: the off-thread `module.register()` hooks worker serialized every resolution across threads (~440ms of `makeSyncRequest` wait during TUI boot), and the full tsx default (`--import tsx`) pays ~0.4s in its CJS hook's resolution amplification.
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
[已归档的原生源码启动决策](../../archived/architecture/2026-07-28-dsh-native-typescript-source-launch.md)让 `apps/cli/src/bin.ts` 在 `node --experimental-transform-types` 下运行,配合一个只做解析的 paths loader,由 Node 负责 TypeScript 转换。Node 26.0.0 移除了 `--experimental-transform-types`(进程以 `bad option` 拒绝该 flag),只保留 strip 模式,而 strip 模式无法接受这个源码图必需的语法:vendor Cordis 中的参数属性(`constructor(private ctx: Context)`)、`vendor/hmr` 中的 `@Inject` 装饰器,以及遍布 `vendor/` 与 `packages/workflow` 的运行时 enum/namespace。仓库的 engines 范围(`^22.19.0 || >=24.0.0`)包含 Node 26,因此原生启动链在其上完全无法启动——且没有任何 CI 任务执行过真实启动向量,这一不兼容悄然发布。
|
||||
[已归档的原生源码启动决策](../../archived/architecture/2026-07-28-dsh-native-typescript-source-launch.md)让 `apps/cli/src/bin.ts` 在 `node --experimental-transform-types` 下运行,配合一个只做解析的 paths loader,由 Node 负责 TypeScript 转换。Node 26.0.0 移除了 `--experimental-transform-types`(进程以 `bad option` 拒绝该 flag),只保留 strip 模式,而 strip 模式无法接受这个源码图必需的语法:vendor Cordis 中的参数属性(`constructor(private ctx: Context)`)、`vendor/hmr` 中的 `@Inject` 装饰器,以及遍布 `vendor/` 与 `packages/workflow` 的运行时 enum/namespace。受支持的 engines 范围包含 Node 26,因此原生启动链在其上完全无法启动——且没有任何 CI 任务执行过真实启动向量,这一不兼容悄然发布。
|
||||
|
||||
启动延迟同样是问题:off-thread 的 `module.register()` 钩子工作线程把每次解析都跨线程序列化(TUI 启动期间约 440ms 的 `makeSyncRequest` 等待),而完整的 tsx 默认形态(`--import tsx`)会因其 CJS 钩子放大解析开销而多花约 0.4s。
|
||||
|
||||
|
||||
+2
-2
@@ -33,8 +33,8 @@
|
||||
en: a244f6a88fdabf3d
|
||||
zh: e4feedb7297f4865
|
||||
/agent-note-typert-gateway-targeted-method-calls/three-artifact-kinds-and-two-typescript-programs:
|
||||
en: c5bcd6eb76bf3d42
|
||||
zh: 9df2fbb364643186
|
||||
en: 4479d41466723100
|
||||
zh: 3a7f398e0d0070a5
|
||||
/agent-note-typert-gateway-targeted-method-calls/the-remote-package-entry:
|
||||
en: 75e977fe0fb71d59
|
||||
zh: 00760c79da173cb7
|
||||
|
||||
@@ -207,17 +207,9 @@ Client Program
|
||||
|
||||
`remote-client` is the Host Program's second emitter, not a third Program or the Client's local face. It contains no Host Cordis merge, Service class, Context class, or implementation code, and it does not enter the Host-local reflection registry.
|
||||
|
||||
The Host lib build performs strict Host analysis and emits both the Host-local and Remote consumer artifacts. The Client lib then consumes the Remote DTS. The complete order is:
|
||||
Source-only Host analysis writes the Host-local and Remote consumer artifacts before package compilation. [The generation-order decision](../process/2026-10-18-source-first-metadata-builds.md) owns placement and its tradeoffs; [Development](../../../../docs/development.md#typescript-project-layout) owns the command sequence.
|
||||
|
||||
```text
|
||||
Host lib build
|
||||
→ 生成 typert.host.{js,d.ts}
|
||||
→ 生成各业务包 lib/typert.remote-client.{js,d.ts,d.ts.map}
|
||||
→ 完成 Client lib 和 typert.client 产物
|
||||
→ Vite 构建 Web
|
||||
```
|
||||
|
||||
The top-level `build` runs the step graph in `scripts/build.ts`, which completes the Host bundle and the Remote artifacts before starting Client TypeScript compilation. `build:lib` remains the complete serial lib build for `build:bench`, and its Host half `build:lib:host` serves the vendor release workflows. `typecheck` writes the Remote artifacts through `scripts/generate-typert.ts`, beside Host compilation, before Client TypeScript compilation. A clean build must not depend on stale `.d.ts` files from an earlier build.
|
||||
The top-level `build`, declaration-only `typecheck`, and serial `build:lib` share metadata preparation before package compilation. `build:lib` remains the complete serial lib build for `build:bench`; its Host half `build:lib:host` serves the vendor release workflows and retains aggregate checking. A clean build must not depend on stale `.d.ts` files from an earlier build.
|
||||
|
||||
Compiler-backed repository gates that resolve the consumer surface have the same prerequisite even when their primary inputs are source files. The public `typecheck`, `lint`, and `doc-typecheck` commands run the Host contract pass first. The gate scheduler may use their `*:contracts-ready` variants only after an explicit Typert-contract or complete-build dependency, so parallel lanes neither read missing declarations nor run concurrent generators against the same outputs.
|
||||
|
||||
|
||||
@@ -207,17 +207,9 @@ Client Program
|
||||
|
||||
`remote-client` 是 Host Program 的第二个 emitter,不是第三个 Program,也不是 Client 本地 face。它不包含 Host Cordis merge、Service class、Context class 或实现代码,不进入 Host 本地 reflection registry。
|
||||
|
||||
Host lib 构建负责完成严格 Host 分析并产出 Host 本地 artifact 与 Remote 消费端 artifact;Client lib 随后消费 Remote DTS。完整顺序为:
|
||||
仅源码的 Host 分析在包编译之前写出 Host 本地 artifact 与 Remote 消费端 artifact。[生成顺序决策](../process/2026-10-18-source-first-metadata-builds.zh.md)负责生成位置及其取舍;[开发指南](../../../../docs/development.zh.md#typescript-project-layout)负责命令序列。
|
||||
|
||||
```text
|
||||
Host lib build
|
||||
→ 生成 typert.host.{js,d.ts}
|
||||
→ 生成各业务包 lib/typert.remote-client.{js,d.ts,d.ts.map}
|
||||
→ 完成 Client lib 和 typert.client 产物
|
||||
→ Vite 构建 Web
|
||||
```
|
||||
|
||||
顶层 `build` 运行 `scripts/build.ts` 中的步骤图,先完成 Host bundle 与 Remote artifact,再启动 Client TypeScript 编译。`build:lib` 仍是 `build:bench` 使用的完整串行 lib 构建,其 Host 部分 `build:lib:host` 供 vendor 发布 workflow 使用。`typecheck` 与 Host 编译并行,经 `scripts/generate-typert.ts` 写出 Remote artifact,再启动 Client TypeScript 编译。一次干净构建不能依赖上次残留的 `.d.ts`。
|
||||
顶层 `build`、仅生成声明的 `typecheck` 与串行 `build:lib` 都在包编译之前准备元数据。`build:lib` 仍是 `build:bench` 使用的完整串行 lib 构建;其 Host 部分 `build:lib:host` 供 vendor 发布 workflow 使用,并保留 aggregate 检查。一次干净构建不能依赖上次残留的 `.d.ts`。
|
||||
|
||||
即使主要输入是源文件,需要通过编译器解析消费方 surface 的仓库门禁也有相同的前置条件。公共 `typecheck`、`lint` 和 `doc-typecheck` 命令会先执行 Host 约定 pass。门禁调度器仅可在显式的 Typert 约定依赖或完整构建依赖完成后使用对应的 `*:contracts-ready` 变体,使并行 lane 既不会读取缺失的声明,也不会针对同一输出并发运行多个生成器。
|
||||
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
en: b19652a0e1cc1826
|
||||
zh: 4d3511e4f0db5b9d
|
||||
/agent-note-one-outbound-proxy-policy-installed-before-anything-can-request/problem:
|
||||
en: 70e5f3ed33010c98
|
||||
zh: 5437d943953f5a40
|
||||
en: 6379f0a2522f4c46
|
||||
zh: c45ffeb5089be274
|
||||
/agent-note-one-outbound-proxy-policy-installed-before-anything-can-request/decision:
|
||||
en: efe23161a54f70c5
|
||||
zh: e24ee097918b2724
|
||||
en: 92526818ff4870c2
|
||||
zh: 8c54ff3f4941d5ad
|
||||
/agent-note-one-outbound-proxy-policy-installed-before-anything-can-request/alternatives-considered:
|
||||
en: df44eb15557b95fe
|
||||
zh: b757e53da4c0a99f
|
||||
en: df6c078f239a11f5
|
||||
zh: 914da26836aa373b
|
||||
/agent-note-one-outbound-proxy-policy-installed-before-anything-can-request/consequences:
|
||||
en: 6a4acbab915f1bd1
|
||||
zh: 2c8ffd62ba74ff96
|
||||
|
||||
@@ -10,7 +10,7 @@ Node's built-in `fetch` ignores `HTTP_PROXY` and `HTTPS_PROXY`. Every other tool
|
||||
|
||||
The repository had briefly had an answer and lost it without noticing. PR #971 set `NODE_USE_ENV_PROXY=1` in `bin/dsh`; eleven days later the “cleanup: remove managed source installer” change deleted that launcher wholesale, taking the flag with it. What survived was one sentence in `apps/cli/reference/README.md` telling the reader to set a variable that nothing consumed any more.
|
||||
|
||||
That sentence could not have worked anyway, for three measured reasons. `NODE_USE_ENV_PROXY` samples the environment at process start, while `loadLayeredEnv()` merges the `.env` layers afterwards, so a proxy declared in `$DSH_HOME/.env` is invisible to it. It reaches Node 24.0+ and, on the 22 line, only 22.21+ — while `engines` admits `^22.19.0`, where the variable does not exist and setting it warns about nothing. And it does not reach `web-fetch-http` at all: that provider passes its own `dispatcher` to `fetch`, and an explicit dispatcher overrides the global one whatever the flag says.
|
||||
That sentence could not have worked anyway, for three measured reasons. `NODE_USE_ENV_PROXY` samples the environment at process start, while `loadLayeredEnv()` merges the `.env` layers afterwards, so a proxy declared in `$DSH_HOME/.env` is invisible to it. It reaches Node 24.0+ and, on the 22 line, only 22.21+ — the runtime floor at adoption was Node 22.19, where the variable did not exist and setting it warned about nothing. And it does not reach `web-fetch-http` at all: that provider passes its own `dispatcher` to `fetch`, and an explicit dispatcher overrides the global one whatever the flag says.
|
||||
|
||||
## Decision
|
||||
|
||||
@@ -48,7 +48,7 @@ This accepts a documented seam. Such a context matches bypass entries by Node's
|
||||
|
||||
**SDK transports need independent verification.** The OTLP exporter selects `http-exporter-transport` on Node, which posts through `node:http` and bypasses the global fetch dispatcher; a `globalThis.fetch` reference in its browser implementation does not prove Node routing. The [E2B removal](../simplification/2026-09-11-remove-e2b-providers.md) retires a second SDK transport integration without changing this requirement.
|
||||
|
||||
**Telemetry stays direct on purpose.** Routing it needs one of two things, and both cost more than the channel is worth. An `http.Agent` reads the environment through `proxyEnv`, which arrived in Node 22.21 and 24.5 — inside the engines range, so 22.19, 22.20, and 24.0–24.4 would stay direct regardless, and the proxy package would have to keep a `createNodeHttpAgent` export for a path that works on some runtimes. Replacing the transport with the SDK's `fetch` delegate covers every runtime, but that delegate has no compression, and the shipped `base` bundle enables gzip: a realistic OTLP batch measures 6.4x smaller with it. An attempt that refused `exporter.compression` instead broke every test that boots the shipped bundle, and one that gzipped at the serializer worked but put transport code in a telemetry plugin to keep it working.
|
||||
**Telemetry stays direct on purpose.** Routing it needs one of two things, and both cost more than the channel is worth. An `http.Agent` reads the environment through `proxyEnv`, which arrived in Node 22.21 and 24.5 — above the runtime floor at adoption, so Node 22.19, 22.20, and 24.0–24.4 stayed direct regardless, and the proxy package would have to keep a `createNodeHttpAgent` export for a path that worked on some runtimes. The Node 24.9 floor removes this API gap; routing still needs independent transport integration and must preserve compression. Replacing the transport with the SDK's `fetch` delegate covers every runtime, but that delegate has no compression, and the shipped `base` bundle enables gzip: a realistic OTLP batch measures 6.4x smaller with it. An attempt that refused `exporter.compression` instead broke every test that boots the shipped bundle, and one that gzipped at the serializer worked but put transport code in a telemetry plugin to keep it working.
|
||||
|
||||
Weighed against that, telemetry is the one outbound channel whose loss costs the user nothing: no tool, no model request, and no session depends on it, and an export that cannot connect is already dropped silently. A user behind a mandatory proxy is left exactly where they were before this change rather than regressed. `egress.spec.ts` now asserts the exclusion — an SDK upgrade that moved the exporter onto `fetch` would start routing telemetry through a proxy silently, and that case is what makes it visible.
|
||||
|
||||
@@ -58,7 +58,7 @@ Weighed against that, telemetry is the one outbound channel whose loss costs the
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Document `NODE_USE_ENV_PROXY=1` and stop.** Rejected on three measurements, above: invisible to `$DSH_HOME/.env`, absent on the lowest supported Node, and bypassed by `web-fetch-http` regardless. It is also what the repository already claimed to do.
|
||||
**Document `NODE_USE_ENV_PROXY=1` and stop.** Rejected at adoption on the three measurements above: invisible to `$DSH_HOME/.env`, absent on the Node 22.19 floor, and bypassed by `web-fetch-http` regardless. The environment-snapshot and explicit-dispatcher limitations remain on the current runtime floor.
|
||||
|
||||
**Thread a policy value to every call site.** DeepSeek-Reasonix does this across 98 sites, buying a per-provider opt-out. Rejected: that opt-out exists for a need this harness does not have, and nine sites changed by hand means the tenth is forgotten — Pi's changelog records OAuth and Bedrock as two separate after-the-fact fixes of exactly that kind. The isolation argument for it is real, and is answered instead by handling worker threads explicitly and by proving disposal restores the previous dispatcher.
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ Node 内置的 `fetch` 会忽略 `HTTP_PROXY` 与 `HTTPS_PROXY`。开发者运
|
||||
|
||||
仓库曾短暂拥有过答案,又在无人察觉时弄丢了。PR #971 在 `bin/dsh` 里设置了 `NODE_USE_ENV_PROXY=1`;十一天后的“cleanup: remove managed source installer”改动整体删除了那个启动器,把该标志一并带走。留下的只有 `apps/cli/reference/README.md` 里的一句话,让读者去设置一个已经无人消费的变量。
|
||||
|
||||
即便照做,那句话也不可能生效,原因有三条且都经过实测。`NODE_USE_ENV_PROXY` 在进程启动时对环境取快照,而 `loadLayeredEnv()` 是在之后才合并 `.env` 层,因此写在 `$DSH_HOME/.env` 中的代理对它不可见。它只覆盖 Node 24.0+,在 22 线上只覆盖 22.21+——而 `engines` 允许 `^22.19.0`,那里根本没有这个变量,设置了也不会有任何警告。它也完全触及不到 `web-fetch-http`:该提供方向 `fetch` 传入自己的 `dispatcher`,而显式 dispatcher 无论标志如何都会覆盖全局的那个。
|
||||
即便照做,那句话也不可能生效,原因有三条且都经过实测。`NODE_USE_ENV_PROXY` 在进程启动时对环境取快照,而 `loadLayeredEnv()` 是在之后才合并 `.env` 层,因此写在 `$DSH_HOME/.env` 中的代理对它不可见。它只覆盖 Node 24.0+,在 22 线上只覆盖 22.21+——采用此决策时的运行时下限为 Node 22.19,那里根本没有这个变量,设置了也不会有任何警告。它也完全触及不到 `web-fetch-http`:该提供方向 `fetch` 传入自己的 `dispatcher`,而显式 dispatcher 无论标志如何都会覆盖全局的那个。
|
||||
|
||||
## Decision
|
||||
|
||||
@@ -48,7 +48,7 @@ URL 层策略未受影响:仅 `http(s)`、禁止内嵌凭据、长度上限与
|
||||
|
||||
**SDK 传输需要独立验证。** OTLP 导出器在 Node 上选择 `http-exporter-transport`,通过 `node:http` 投递并绕过全局 fetch dispatcher;其浏览器实现中的 `globalThis.fetch` 引用不能证明 Node 路由。[E2B 移除决策](../simplification/2026-09-11-remove-e2b-providers.zh.md)撤下另一项 SDK 传输集成,但不改变这一要求。
|
||||
|
||||
**遥测的直连是有意为之。** 要让它走代理只有两条路,代价都超过这条通道本身的价值。`http.Agent` 通过 `proxyEnv` 读取环境,而该选项自 Node 22.21 与 24.5 才有——落在 engines 范围之内,因此 22.19、22.20 与 24.0–24.4 无论如何仍是直连,而代理包还得为一条只在部分运行时生效的路径保留 `createNodeHttpAgent` 导出。改用 SDK 的 `fetch` delegate 替换传输可以覆盖所有运行时,但该 delegate 没有压缩能力,而随附的 `base` bundle 启用了 gzip:实测一批真实规模的 OTLP 数据启用后体积只有 1/6.4。曾有一版转而在加载期拒绝 `exporter.compression`,结果凡是启动随附 bundle 的测试全部失败;另一版在 serializer 处 gzip 确实能跑通,但代价是把传输层代码塞进了遥测插件。
|
||||
**遥测的直连是有意为之。** 要让它走代理只有两条路,代价都超过这条通道本身的价值。`http.Agent` 通过 `proxyEnv` 读取环境,而该选项自 Node 22.21 与 24.5 才有——高于采用此决策时的运行时下限,因此 Node 22.19、22.20 与 24.0–24.4 无论如何仍是直连,而代理包还得为一条只在当时部分运行时生效的路径保留 `createNodeHttpAgent` 导出。Node 24.9 下限消除了这一 API 缺口;代理路由仍需要独立集成传输,并保留压缩。改用 SDK 的 `fetch` delegate 替换传输可以覆盖所有运行时,但该 delegate 没有压缩能力,而随附的 `base` bundle 启用了 gzip:实测一批真实规模的 OTLP 数据启用后体积只有 1/6.4。曾有一版转而在加载期拒绝 `exporter.compression`,结果凡是启动随附 bundle 的测试全部失败;另一版在 serializer 处 gzip 确实能跑通,但代价是把传输层代码塞进了遥测插件。
|
||||
|
||||
与之相比,遥测是唯一一条丢失了对用户毫无代价的出网通道:没有任何工具、模型请求或会话依赖它,而连不上的导出本就被静默丢弃。处在强制代理后的用户,只是停留在本次改动之前的状态,而不是被弄坏。`egress.spec.ts` 现在断言这一排除——若某次 SDK 升级把导出器挪到 `fetch` 上,遥测就会开始静默走代理,而该用例正是让这件事暴露出来的东西。
|
||||
|
||||
@@ -58,7 +58,7 @@ URL 层策略未受影响:仅 `http(s)`、禁止内嵌凭据、长度上限与
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**只写文档,让用户设 `NODE_USE_ENV_PROXY=1`。** 基于上文三条实测被否决:对 `$DSH_HOME/.env` 不可见、在最低支持的 Node 上不存在、且无论如何被 `web-fetch-http` 绕过。而这恰恰是仓库此前声称的做法。
|
||||
**只写文档,让用户设 `NODE_USE_ENV_PROXY=1`。** 采用此决策时基于上文三条实测被否决:对 `$DSH_HOME/.env` 不可见、在 Node 22.19 下限上不存在、且无论如何被 `web-fetch-http` 绕过。环境快照和显式 dispatcher 的限制在当前运行时下限上仍然存在。
|
||||
|
||||
**把策略值传递到每一个调用点。** DeepSeek-Reasonix 在 98 处这样做,换来每提供方的 opt-out。被否决:该能力服务于本 Harness 并不具备的需求,而手工改九处意味着第十处会被遗忘——Pi 的变更日志正记录了 OAuth 与 Bedrock 两次事后补漏。它关于隔离性的论点确实成立,本方案改为显式处理 worker 线程、并以「dispose 后还原前一个 dispatcher」的断言来回应。
|
||||
|
||||
|
||||
@@ -72,8 +72,8 @@
|
||||
en: 607a1afd89892d15
|
||||
zh: a0aa85124340458c
|
||||
/agent-note-remote-duplex-stream-one-kind-of-stream-an-uplink-channel-and-an-invocation-context/decision/security-and-validation:
|
||||
en: 46a0c0b3ce1e5671
|
||||
zh: 7743707de42b1d1d
|
||||
en: 2426665452a6a37b
|
||||
zh: 282572d66e17a237
|
||||
/agent-note-remote-duplex-stream-one-kind-of-stream-an-uplink-channel-and-an-invocation-context/decision/in-process-and-worker-carriers:
|
||||
en: df4ae60d8403cdbb
|
||||
zh: 70c6024a2a9a1e84
|
||||
@@ -102,8 +102,8 @@
|
||||
en: 26dcd8bebdc8d974
|
||||
zh: e74bcef7c549e70c
|
||||
/agent-note-remote-duplex-stream-one-kind-of-stream-an-uplink-channel-and-an-invocation-context/placement:
|
||||
en: ecfaff2b9b2ef021
|
||||
zh: 85b392d79364e0c3
|
||||
en: 40c838816d0941ed
|
||||
zh: 4f5428a040b33b74
|
||||
/agent-note-remote-duplex-stream-one-kind-of-stream-an-uplink-channel-and-an-invocation-context/key-sequences:
|
||||
en: 130e4f9ec7296a10
|
||||
zh: 2a5aa1302a8db61a
|
||||
|
||||
@@ -309,6 +309,7 @@ Credit frames wait until a consumer that needs sustained high-volume uplink appe
|
||||
| Peer | Admitted at upgrade as one `PeerScope`; `invocation.peer` of every stream on that socket is that Peer |
|
||||
| Item validation | With an uplink codec, every item is decoded strictly, and a failure fails the whole stream with the existing `gateway/input-invalid` (details `{ endpoint, field: 'uplink' }`); without a codec, only JSON-safety validation applies |
|
||||
| Frame size | A single frame keeps the `maxPayload` of `ws`; the inbox limit bounds the accumulated total |
|
||||
| Frame compression | Each connection negotiates `permessage-deflate` when the Gateway config `websocketCompression` is `'deflate'` (the default); frames below 1,024 characters are sent uncompressed. See [the compression decision](2026-10-18-websocket-permessage-deflate.md) |
|
||||
|
||||
### In-process and worker carriers
|
||||
|
||||
@@ -392,6 +393,8 @@ export type RemoteStreamOpener = (
|
||||
// index.ts Config addition
|
||||
/** Uplink inbox limit per logical stream, in UTF-8 bytes of frames (default 262144). */
|
||||
readonly streamInboxBytes?: number
|
||||
/** Per-message WebSocket compression; 'none' disables the extension (default 'deflate'). */
|
||||
readonly websocketCompression?: 'none' | 'deflate'
|
||||
```
|
||||
|
||||
`prepareInvocation`:
|
||||
@@ -453,7 +456,7 @@ cancel : missing → ignore; otherwise control.abort(new Error('Remote stream ca
|
||||
| `dsh-typert-registry` | Load-time validation that `uplink.codec` is a valid strict codec |
|
||||
| `dsh-typert-generator` (`model.ts`, `analyzer.ts`, `emitter.ts`) | `remoteResultType` recognizes `RemoteStream<Out, In>` and generates the `uplink` boundary; the descriptor emits `uplink: { codec }`; the generated Client signature returns `RemoteStreamHandle<Out, In>`; the parameter loop accepts only business parameters and a final `signal` |
|
||||
| `dsh-client-connection` (`peer.ts`, `rpc.ts`, `rpc-host.ts`, `index.ts`) | The `Peer` class; `connection.browserPeer` and `admit`; the `/api` and upgrade routes obtain the Peer through `admit`; the optional `uplink` of `rpc.open` |
|
||||
| `dsh-api-gateway` Host (`stream-protocol.ts`, `stream-server.ts`, `types.ts`, `index.ts`) | `item` / `end` frames; `UplinkInbox`; `Config.streamInboxBytes`; `GatewayInvocation` and `extend({ invocation })`; `UplinkDecoder`; `handleUpgrade(req, socket, head, peer)` |
|
||||
| `dsh-api-gateway` Host (`stream-protocol.ts`, `stream-server.ts`, `types.ts`, `index.ts`) | `item` / `end` frames; `UplinkInbox`; `Config.streamInboxBytes` and `Config.websocketCompression`; `GatewayInvocation` and `extend({ invocation })`; `UplinkDecoder`; `handleUpgrade(req, socket, head, peer)` |
|
||||
| `dsh-api-gateway` Client (`client/index.ts`, `client/stream-client.ts`) | `invokeStream` returns the handle; the uplink pump is driven by the handle's queue |
|
||||
| `dsh-webworker-runtime`, `dsh-remote-mock` | The worker tunnel's two uplink frames and the `serveStream` assembly; `StreamHandle.uplink`; the direct proxy returns the real handle |
|
||||
| Documentation | The READMEs of gateway, the api group, connection, protocol, and generator; `docs/api-gateway`; the type-equiv blocks of `docs/subsystems/typert`; the config and Cordis catalogs |
|
||||
|
||||
@@ -309,6 +309,7 @@ credit 帧留待需要持续大流量上行的消费者出现时再加;inbox
|
||||
| Peer | 升级时接纳为一个 `PeerScope`,该 socket 上所有流的 `invocation.peer` 都是它 |
|
||||
| 项校验 | 有 uplink codec 时逐项严格解码,失败即整条流以既有的 `gateway/input-invalid` 失败(details `{ endpoint, field: 'uplink' }`);无 codec 时只做 JSON 安全校验 |
|
||||
| 帧大小 | 单帧沿用 `ws` 的 `maxPayload`;inbox 上限限制累计量 |
|
||||
| 帧压缩 | Gateway 配置 `websocketCompression` 为 `'deflate'`(默认)时,每条连接协商 `permessage-deflate`;少于 1024 个字符的帧不压缩。见[压缩决定](2026-10-18-websocket-permessage-deflate.zh.md) |
|
||||
|
||||
### 进程内与 worker 载体
|
||||
|
||||
@@ -392,6 +393,8 @@ export type RemoteStreamOpener = (
|
||||
// index.ts Config addition
|
||||
/** Uplink inbox limit per logical stream, in UTF-8 bytes of frames (default 262144). */
|
||||
readonly streamInboxBytes?: number
|
||||
/** Per-message WebSocket compression; 'none' disables the extension (default 'deflate'). */
|
||||
readonly websocketCompression?: 'none' | 'deflate'
|
||||
```
|
||||
|
||||
`prepareInvocation`:
|
||||
@@ -453,7 +456,7 @@ cancel : missing → ignore; otherwise control.abort(new Error('Remote stream ca
|
||||
| `dsh-typert-registry` | 加载校验 `uplink.codec` 为有效的严格 codec |
|
||||
| `dsh-typert-generator`(`model.ts`、`analyzer.ts`、`emitter.ts`) | `remoteResultType` 识别 `RemoteStream<Out, In>` 并生成 `uplink` boundary;描述符输出 `uplink: { codec }`;生成的 Client 签名返回 `RemoteStreamHandle<Out, In>`;参数循环只认业务参数与末位 `signal` |
|
||||
| `dsh-client-connection`(`peer.ts`、`rpc.ts`、`rpc-host.ts`、`index.ts`) | `Peer` 类;`connection.browserPeer` 与 `admit`;`/api` 与升级路由经 `admit` 取 Peer;`rpc.open` 的可选 `uplink` |
|
||||
| `dsh-api-gateway` Host(`stream-protocol.ts`、`stream-server.ts`、`types.ts`、`index.ts`) | `item` / `end` 帧;`UplinkInbox`;`Config.streamInboxBytes`;`GatewayInvocation` 与 `extend({ invocation })`;`UplinkDecoder`;`handleUpgrade(req, socket, head, peer)` |
|
||||
| `dsh-api-gateway` Host(`stream-protocol.ts`、`stream-server.ts`、`types.ts`、`index.ts`) | `item` / `end` 帧;`UplinkInbox`;`Config.streamInboxBytes` 与 `Config.websocketCompression`;`GatewayInvocation` 与 `extend({ invocation })`;`UplinkDecoder`;`handleUpgrade(req, socket, head, peer)` |
|
||||
| `dsh-api-gateway` Client(`client/index.ts`、`client/stream-client.ts`) | `invokeStream` 返回句柄;上行泵由句柄的队列驱动 |
|
||||
| `dsh-webworker-runtime`、`dsh-remote-mock` | worker 隧道的两种上行帧与 `serveStream` 组装;`StreamHandle.uplink`;直接代理返回真句柄 |
|
||||
| 文档 | gateway、api 组、connection、protocol、generator 的 README;`docs/api-gateway`;`docs/subsystems/typert` 的 type-equiv 块;config 与 Cordis 目录 |
|
||||
|
||||
+2
-2
@@ -33,5 +33,5 @@
|
||||
en: 939cedceed200cb2
|
||||
zh: c7bb02d0da400c8d
|
||||
/agent-note-refresh-runtime-resolution-after-package-operations-and-expire-hmr-package-configuration/consequences:
|
||||
en: 1b9e31b4e773fac5
|
||||
zh: c140ce07fc64bd55
|
||||
en: 5a0120a6a0d48912
|
||||
zh: 649e7bc118bda1a2
|
||||
+1
-1
@@ -113,5 +113,5 @@ Tests need no API key and make no model calls.
|
||||
|
||||
- With HMR, supported package operations publish the updated private dependencies and remove unused mappings after plugins stop. Without HMR, a pending startup-bundle disablement defers later publications until restart, preserving running plugins' mappings.
|
||||
- In the supported thread, package.json configuration reads use the changed fields; JSON modules retain their own reload behavior. The separate module, loader-thread, and Worker limitations above still apply.
|
||||
- Package configuration below expired directories is parsed in JavaScript and must keep the native reader's field and error semantics; tests compare it with the native reader on Node 22, 24, and 26.
|
||||
- Package configuration below expired directories is parsed in JavaScript and must keep the native reader's field and error semantics; tests compare it with the native reader on Node 24 and 26.
|
||||
- The implementation depends on several Node internal interfaces, and these tests must run again for Node upgrades.
|
||||
+1
-1
@@ -113,5 +113,5 @@ binding 的 `getNearestParentPackageJSON` 只被 `package_json_reader` 自己调
|
||||
|
||||
- 有 HMR 时,支持的包操作发布更新后的私有依赖,并在插件停止后移除不再需要的映射。没有 HMR 时,待生效的启动 bundle 停用会将后续发布延迟到重启,保留运行中插件的映射。
|
||||
- 在支持的线程内,package.json 配置读取使用更新后的字段;JSON 模块保留自己的重载行为。上述模块、loader 线程及 Worker 的独立限制仍然适用。
|
||||
- 包配置读取改由 JS 解析 package.json,需要与原生读取保持字段和错误语义一致;测试在 Node 22、24、26 上与原生读取逐项比较。
|
||||
- 包配置读取改由 JS 解析 package.json,需要与原生读取保持字段和错误语义一致;测试在 Node 24、26 上与原生读取逐项比较。
|
||||
- 依赖多个 Node internal 接口,Node 升级时需要重跑这些测试。
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-10-16-separate-instruction-chain-budgets.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-16-separate-instruction-chain-budgets.md
|
||||
/agent-note-separate-byte-budgets-for-the-user-global-and-project-instruction-chains:
|
||||
en: 8ad05eb6bd0511b9
|
||||
zh: bd0658fa58edc70d
|
||||
/agent-note-separate-byte-budgets-for-the-user-global-and-project-instruction-chains/problem:
|
||||
en: 77f3bb05053ce59f
|
||||
zh: 4a1759900b71cc44
|
||||
/agent-note-separate-byte-budgets-for-the-user-global-and-project-instruction-chains/decision:
|
||||
en: 5dd811039ee2fef3
|
||||
zh: c9c1dc27890ff204
|
||||
/agent-note-separate-byte-budgets-for-the-user-global-and-project-instruction-chains/alternatives-considered:
|
||||
en: 157837c9fbc377cb
|
||||
zh: 32fb2d8427529772
|
||||
/agent-note-separate-byte-budgets-for-the-user-global-and-project-instruction-chains/consequences:
|
||||
en: 186092d64376de0c
|
||||
zh: 92fe504ff77cba71
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
# Agent Note: Separate byte budgets for the user-global and project instruction chains
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-16-separate-instruction-chain-budgets.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
`dsh-agent-instructions` rendered the user-global and project files as one chain under one `maxBytes`, the rule of the [original workspace-context decision](../../archived/feature/2026-06-24-workspace-context.md). Rendering omits the broadest files first, and user-global files are the broadest, so a large project chain removed them: under the shipped 65,536-byte budget, 20 KiB user-global, 30 KiB project root, and 20 KiB nested files rendered without the user-global file.
|
||||
|
||||
## Decision
|
||||
|
||||
The renderer splits the loaded files into the user-global and project chains, keeping their order, and renders each under the same `maxBytes`: omit the broadest files, truncate the last file, then fall back to the compact intro, to the notice with an empty heading, and to the notice alone. The project chain keeps the result it has when rendered alone; user-global files are omitted or truncated only beyond their own budget, except in the fallback below.
|
||||
|
||||
When all files fit `maxBytes` together, or only one chain has files, the output is unchanged byte for byte. Otherwise one `<system-reminder>` frame of at most twice `maxBytes` holds the merged notice when a file was omitted or truncated, one intro when a chain kept its frame, and the rendered sections in loaded order, with user-global files first in a baseline. If a chain renders without a frame and the merged frame would exceed that bound, all files render as one chain under one `maxBytes`, which can omit a user-global file that fit its own budget; this needs a budget of a few hundred bytes or a notice that nearly fills or exceeds `maxBytes`. Reconciliation batches apply the same split.
|
||||
|
||||
The `maxBytes` key and the baseline identity are unchanged, so a resumed session keeps its visible baseline and receives a user-global file that baseline omitted as one additional-instructions transition.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**One budget that gives user-global files priority.** A large user-global file then truncates or omits the project root file, and bytes near the limit decide which chain loses content.
|
||||
|
||||
**One budget with a user-global share.** The project chain still shrinks, and no measurement supports a share size.
|
||||
|
||||
**A configuration field for the second chain.** No deployment needs different sizes, and the field would join the baseline identity, so resumed sessions would resend their baselines.
|
||||
|
||||
**The Codex layout: an unbounded user-global file and a root-first project chain.** Output has no bound, and the project chain keeps its broadest files, not its most specific ones.
|
||||
|
||||
## Consequences
|
||||
|
||||
- A baseline or batch holding both chains can reach twice `maxBytes`; deployments that sized `maxBytes` as a total budget halve it, per the [upgrade guide](../../../../docs/upgrade-guide/v0.2.1-rc.1/agent-instructions-chain-budgets/guide.md).
|
||||
- User-global instructions survive a large project chain.
|
||||
- The `agent-instructions-budget` snapshot shows a truncated user-global file beside the whole project file; `agent-instructions-budget-chains` shows two chains that fit only separately.
|
||||
- The package README, `Config.maxBytes` JSDoc, and configuration catalog state the per-chain bound.
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
# Agent Note: 用户全局指令链与项目指令链分别使用字节预算
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-16-separate-instruction-chain-budgets.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
`dsh-agent-instructions` 曾把用户全局文件与项目文件作为一条指令链,在一个 `maxBytes` 内渲染,这是[最初的工作区上下文决策](../../archived/feature/2026-06-24-workspace-context.md)定下的规则。渲染会最先省略最宽泛的文件,而用户全局文件最宽泛,因此较大的项目指令链会移除它们:在默认的 65,536 字节预算下,20 KiB 的用户全局文件、30 KiB 的项目根文件与 20 KiB 的嵌套文件一起渲染时,用户全局文件被省略。
|
||||
|
||||
## 决策
|
||||
|
||||
渲染器把已加载的文件分成用户全局指令链与项目指令链,各自保持原有顺序,并分别在同一个 `maxBytes` 内渲染:先省略最宽泛的文件,再截断最后一个文件,然后依次退回到精简引言、带空标题的通知和仅含通知的文本。项目指令链的结果与它单独渲染时相同;用户全局文件只有超出自身预算时才会被省略或截断,下文的回退情形除外。
|
||||
|
||||
所有文件合起来能放进 `maxBytes`,或只有一条链有文件时,输出逐字节不变。其他情况下,一个至多为 `maxBytes` 两倍的 `<system-reminder>` 框架包含合并后的通知(仅当有文件被省略或截断时)、一段引言(仅当某条链保留了框架时),以及按加载顺序排列的渲染段落,因此基线中用户全局文件在前。若某条链在没有框架的情况下渲染,且合并后的框架会超过该上限,则所有文件作为一条链在一个 `maxBytes` 内渲染,这可能省略一个本可放进自身预算的用户全局文件;这只会在预算只有几百字节,或通知接近或超过 `maxBytes` 时发生。对账批次采用同样的拆分。
|
||||
|
||||
`maxBytes` 配置键与基线标识都不变,因此恢复的会话保留其可见基线;该基线曾省略的用户全局文件会以一次附加指令变更的形式加入。
|
||||
|
||||
## 备选方案
|
||||
|
||||
**一个预算,并优先保留用户全局文件。** 较大的用户全局文件会截断或省略项目根文件,而且临界值附近的字节差异就决定哪条链丢失内容。
|
||||
|
||||
**一个预算,并为用户全局文件划出份额。** 项目指令链仍会缩小,而且没有测量数据支撑份额的大小。
|
||||
|
||||
**为第二条链增加配置字段。** 没有部署需要为两条链设定不同大小,而且该字段会进入基线标识,使恢复的会话重新发送基线。
|
||||
|
||||
**采用 Codex 的布局:不设上限的用户全局文件,加上从根目录开始保留的项目指令链。** 输出没有上限,项目指令链会保留最宽泛的文件,而不是最具体的文件。
|
||||
|
||||
## 影响
|
||||
|
||||
- 同时包含两条链的基线或批次最多可达 `maxBytes` 的两倍;把 `maxBytes` 当作总预算设定的部署应将其减半,见[升级指南](../../../../docs/upgrade-guide/v0.2.1-rc.1/agent-instructions-chain-budgets/guide.zh.md)。
|
||||
- 用户全局指令不会因项目指令链较大而被移除。
|
||||
- `agent-instructions-budget` snapshot 显示被截断的用户全局文件与完整的项目文件;`agent-instructions-budget-chains` 显示两条只能各自放下的链。
|
||||
- 包 README、`Config.maxBytes` 的 JSDoc 与配置目录说明了按链计算的上限。
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
# Bilingual-pair consistency record for 2026-10-16-steering-batch-start-in-definition-state.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-16-steering-batch-start-in-definition-state.md
|
||||
/agent-note-precompute-the-steering-batch-start-in-conversation-node-definition-state:
|
||||
en: 6f27c4f6133966ba
|
||||
zh: 36b1e3bf1b809f09
|
||||
/agent-note-precompute-the-steering-batch-start-in-conversation-node-definition-state/problem:
|
||||
en: 6901df0549909883
|
||||
zh: 3e7f9698a26fe568
|
||||
/agent-note-precompute-the-steering-batch-start-in-conversation-node-definition-state/decision:
|
||||
en: 581f8768c22fda63
|
||||
zh: 1db7d743dbe65e96
|
||||
/agent-note-precompute-the-steering-batch-start-in-conversation-node-definition-state/alternatives-considered:
|
||||
en: 479dc0bcce2c7674
|
||||
zh: a9fa5ed6eaa40919
|
||||
/agent-note-precompute-the-steering-batch-start-in-conversation-node-definition-state/consequences:
|
||||
en: 66b21a77e1fa35f1
|
||||
zh: 8a3e13e94a27a3bf
|
||||
/agent-note-precompute-the-steering-batch-start-in-conversation-node-definition-state/testing:
|
||||
en: 4b0da9e7eeb3906a
|
||||
zh: 35ea5569f39939d6
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# Agent Note: Precompute the steering batch start in Conversation Node Definition state
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-16-steering-batch-start-in-definition-state.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Chat keeps a 44px slot, the height of the running status row, above the first steering row of each claim that lands after the running Turn's process output, so admitting steering swaps the status and the bubble without moving the bubble ([conversation-nodes README](../../../../packages/client/ui-chat/src/client/conversation-nodes/README.md)). Deciding that a steering row starts such a batch needs two facts its own `user/message` event does not carry: whether it is the first human message of its `next-step` claim, and whether the same Turn already has durable visible output before it, which places the row after the Turn's process control. The answer must hold for live appends, replay, and loading earlier history, and must not scan the event window or the Chat Nodes.
|
||||
|
||||
## Decision
|
||||
|
||||
The batch start is a fact in Conversation Node State, computed by Definitions through the existing [`reader.previous`](2026-08-09-client-conversation-node-assembly.md) lookup:
|
||||
|
||||
- [`inbox.ts`](../../../../packages/client/ui-chat/src/client/conversation-nodes/inbox.ts) records `firstClaimedHuman`, the first human message removed by each `next-step` claim, in the Client Inbox State.
|
||||
- [`turn-process.ts`](../../../../packages/client/ui-chat/src/client/conversation-nodes/turn-process.ts) registers `processEvidenceDefinition` (kind `process-evidence`, `publication: () => 'none'`): one Context per Step with durable visible output, id `turn:step`, matched from `assistant/message`, `tool/call`, `tool/result`, and `llm/retry` through the existing `processEvidence` test. Streaming chunks never reach it. Its State holds the Turn number; the Context's matches accumulate that Step's evidence events, and nothing reads them.
|
||||
- [`message.ts`](../../../../packages/client/ui-chat/src/client/conversation-nodes/message.ts) calls `reader.previous('process-evidence')` in `start`, only for the claim's first human message. When the nearest earlier record belongs to the same Turn, the steering State carries `batchStart: true`. Later appends have larger seqs and cannot change the answer; loading earlier history replays the dependency through the assembler's tracking of `reader.previous`, as Message already does for Inbox.
|
||||
- The steering renderer reads only `data.batchStart` and writes `data-chat-steering-batch="start"` on its bubble root; CSS gives the containing row the slot.
|
||||
|
||||
The lookup costs O(log N) per claim-leading human message, where N is the number of Steps with output in the loaded window. The change adds no framework API and no persisted or wire data.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Compute it in the renderer from props.** The renderer would need neighbouring rows and the Turn's process state, which puts a steering branch into the business-free node list and Seat, and the row would not re-render when only a neighbour changes.
|
||||
|
||||
**Compute it in the Chat snapshot builder.** A projector over the ordered entries would put a steering-specific rule and an extra pass into the generic build layer.
|
||||
|
||||
**Add tracked Turn-data reads to the framework (`reader.turnData`).** It changes the `ConversationNodeBuildReader` contract and the assembler's dependency tracking for one consumer, while `reader.previous` already gives the needed ordering, dependency, and replay.
|
||||
|
||||
## Consequences
|
||||
|
||||
The decision lives in node data, follows replay and earlier-history loading through existing dependency tracking, and leaves the renderer, Seat, node list, and ui-conversation framework unchanged. It costs one more Definition and one Context per Step with output; each State is one number, and each Context holds references to its Step's evidence events. The slot follows the claim's first human message rather than its first rendered row, so a runtime-context notice logged before that message in the same claim sits above the slot; this follows from the README's rule that the claim's first human message keeps the slot.
|
||||
|
||||
## Testing
|
||||
|
||||
- [conversation-node-definitions.client.spec.ts](../../../../packages/client/ui-chat/tests/conversation-node-definitions.client.spec.ts): `records durable process evidence once per Step and ignores live chunks and invisible replies` pins evidence matching; `marks each claim lead and starts a batch only after the Turn control`, run for replay, live appends, and prepends, pins batch starts for the first and a later claim, no batch start for the second row of a claim, an idle claim that opens a Turn, or a claim after a Step without output.
|
||||
- [chat-view.client.spec.tsx](../../../../packages/client/ui-chat/tests/chat-view.client.spec.tsx): `marks the steering row whose data carries a batch start, and no other input` pins the rendered attribute; [turn-tail-spacing.client.spec.ts](../../../../packages/client/ui-chat/tests/turn-tail-spacing.client.spec.ts) pins the 44px slot rule.
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# Agent Note: 在 Conversation Node Definition 的 State 中预计算插话批首
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-16-steering-batch-start-in-definition-state.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
Chat 在每次 claim 的第一条插话行上方保留 44px 的位置(等于运行状态行的高度),条件是这批插话落在运行中 Turn 的过程输出之后;这样插话被领取时,运行状态与气泡交换位置,气泡不动([conversation-nodes README](../../../../packages/client/ui-chat/src/client/conversation-nodes/README.zh.md))。判断一条插话是否是批首,需要两个它自己的 `user/message` 事件里没有的事实:它是否是所在 `next-step` claim 的第一条人类消息;同一 Turn 在它之前是否已有持久可见的输出,也就是这一行是否排在 Turn 过程控件之后。结论在实时追加、回放和加载更早历史时都必须成立,并且不能扫描事件窗口或 Chat Node。
|
||||
|
||||
## 决策
|
||||
|
||||
批首是 Conversation Node State 中的事实,由 Definition 通过现有的 [`reader.previous`](2026-08-09-client-conversation-node-assembly.zh.md) 查询计算:
|
||||
|
||||
- [`inbox.ts`](../../../../packages/client/ui-chat/src/client/conversation-nodes/inbox.ts) 在 Client Inbox State 中记录 `firstClaimedHuman`,即每次 `next-step` claim 取走的第一条人类消息。
|
||||
- [`turn-process.ts`](../../../../packages/client/ui-chat/src/client/conversation-nodes/turn-process.ts) 注册 `processEvidenceDefinition`(kind `process-evidence`,`publication: () => 'none'`):每个有持久可见输出的 Step 一个 Context,id 为 `turn:step`,通过现有的 `processEvidence` 判断从 `assistant/message`、`tool/call`、`tool/result`、`llm/retry` 匹配。流式 chunk 不会进入它。它的 State 只保存 Turn 编号;Context 的 matches 累积该 Step 的证据事件,没有任何代码读取它们。
|
||||
- [`message.ts`](../../../../packages/client/ui-chat/src/client/conversation-nodes/message.ts) 只对 claim 的第一条人类消息,在 `start` 中调用 `reader.previous('process-evidence')`。最近的一条更早记录属于同一 Turn 时,插话 State 带上 `batchStart: true`。之后追加的事件 seq 都更大,不会改变结论;加载更早历史时,组装器对 `reader.previous` 的依赖追踪会重放这个依赖,与 Message 读取 Inbox 的方式相同。
|
||||
- 插话渲染器只读取 `data.batchStart`,在气泡根元素上写 `data-chat-steering-batch="start"`;CSS 给所在行保留这个位置。
|
||||
|
||||
每条领头的人类消息做一次 O(log N) 查询,N 为已加载窗口中有输出的 Step 数。这一改动不新增框架 API,也不涉及持久化或传输数据。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**在渲染器里根据 props 计算。** 渲染器需要相邻行和 Turn 的过程状态,这会把插话分支带进本应不含业务的节点列表和 Seat;而且只有相邻行变化时,这一行不会重新渲染。
|
||||
|
||||
**在 Chat 快照构建层计算。** 对有序条目做一次投影,会把插话专用的规则和额外一轮遍历放进通用构建层。
|
||||
|
||||
**给框架增加带追踪的 Turn 数据读取(`reader.turnData`)。** 为了一个使用方修改 `ConversationNodeBuildReader` 的约定和组装器的依赖追踪;而 `reader.previous` 已经提供所需的顺序、依赖与重放。
|
||||
|
||||
## 后果
|
||||
|
||||
结论保存在节点数据中,借助现有依赖追踪在回放和加载更早历史时保持正确;渲染器、Seat、节点列表和 ui-conversation 框架都不变。代价是多一个 Definition,每个有输出的 Step 多一个 Context:每个 State 只有一个数字,每个 Context 持有该 Step 证据事件的引用。保留的位置跟随 claim 的第一条人类消息,而不是该批第一条渲染出来的行;因此同一次 claim 中记录在它之前的 runtime-context 通知会排在这个位置上方;这由 README 中“claim 的第一条人类消息保留该位置”的规则推出。
|
||||
|
||||
## 测试
|
||||
|
||||
- [conversation-node-definitions.client.spec.ts](../../../../packages/client/ui-chat/tests/conversation-node-definitions.client.spec.ts):`records durable process evidence once per Step and ignores live chunks and invisible replies` 固定证据匹配;`marks each claim lead and starts a batch only after the Turn control` 分别以回放、实时追加和加载更早历史三种方式运行,固定第一次和之后一次 claim 的批首,以及同一 claim 的第二行、开启 Turn 的空闲 claim、无输出 Step 之后的 claim 都不是批首。
|
||||
- [chat-view.client.spec.tsx](../../../../packages/client/ui-chat/tests/chat-view.client.spec.tsx):`marks the steering row whose data carries a batch start, and no other input` 固定渲染出的属性;[turn-tail-spacing.client.spec.ts](../../../../packages/client/ui-chat/tests/turn-tail-spacing.client.spec.ts) 固定 44px 位置的规则。
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Bilingual-pair consistency record for 2026-10-18-websocket-permessage-deflate.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-18-websocket-permessage-deflate.md
|
||||
/agent-note-compress-the-remote-stream-websocket-with-permessage-deflate:
|
||||
en: 0bc2e23413bf72c1
|
||||
zh: 5bb252767d0a9fac
|
||||
/agent-note-compress-the-remote-stream-websocket-with-permessage-deflate/problem:
|
||||
en: 4b2b62b888cf4735
|
||||
zh: 9d5c1ffce7c253b5
|
||||
/agent-note-compress-the-remote-stream-websocket-with-permessage-deflate/decision:
|
||||
en: f0d660d42cd9cfbf
|
||||
zh: a1fff91323e6b213
|
||||
/agent-note-compress-the-remote-stream-websocket-with-permessage-deflate/decision/negotiation-and-degradation:
|
||||
en: efd339349731b4eb
|
||||
zh: d4fafd884b3cfc75
|
||||
/agent-note-compress-the-remote-stream-websocket-with-permessage-deflate/decision/failure-and-capacity:
|
||||
en: 5f666978c80f0098
|
||||
zh: 69afb0f62622c5cb
|
||||
/agent-note-compress-the-remote-stream-websocket-with-permessage-deflate/alternatives-considered:
|
||||
en: 1df91aee779c7c51
|
||||
zh: b8db98186b21143d
|
||||
/agent-note-compress-the-remote-stream-websocket-with-permessage-deflate/consequences:
|
||||
en: b6e6e5fb4dc0f38d
|
||||
zh: fbaa18b8c4b53940
|
||||
@@ -0,0 +1,72 @@
|
||||
# Agent Note: Compress the Remote stream WebSocket with permessage-deflate
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-18-websocket-permessage-deflate.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The Web GUI carries every live Remote stream over one physical WebSocket, `/api/remote.mux`: model token deltas, session events, tool output, terminal output, and the `$events` fan-out. The mux itself is the implemented [Remote duplex stream](2026-09-19-remote-duplex-stream.md) design; this note adds one transport option to it. The Host's HTTP surface already exposes `compression: 'none' | 'gzip'`; before this option the WebSocket had no equivalent, so the link stayed uncompressed.
|
||||
|
||||
Over a tunnel, a reverse proxy, or an admitted public URL, the large JSON frames — session replay, long tool output, terminal scrollback — spend bandwidth the protocol can already reclaim. Browsers offer `permessage-deflate` in the upgrade handshake without asking, so the missing half was the server's.
|
||||
|
||||
## Decision
|
||||
|
||||
The Gateway Config adds `websocketCompression: 'none' | 'deflate'`, defaulting to `'deflate'`, and passes the resolved boolean into the mux. The mux constructs `WebSocketServer` with these transport options and decides compression per outbound frame:
|
||||
|
||||
| Option | Value | Why |
|
||||
|---|---|---|
|
||||
| Frame compression threshold | `1024` characters of the JSON text, decided in the mux send path | `ws` ignores its `threshold` option while context takeover is enabled ([upstream 41ae5631](https://github.com/websockets/ws/commit/41ae5631)), so the mux passes `compress` per frame; frames below 1,024 characters (most token-level `item` frames) stay uncompressed and out of the zlib pool |
|
||||
| `perMessageDeflate.zlibDeflateOptions.level` | `1` | Interactive frames value latency over ratio; the HTTP gzip default in this repository is also `1` |
|
||||
| Context takeover | `ws` default (enabled both ways) | Best ratio for repetitive JSON; no per-message dictionary rebuild |
|
||||
|
||||
The mux constructor takes one options object carrying `heartbeatIntervalMs`, `streamInboxBytes`, and `compression`, because the Gateway resolves the config vocabulary and `stream-server.ts` does not learn `'none' | 'deflate'`.
|
||||
|
||||
Nothing on the client changes: the browser API offers the extension already, the Node carrier uses `ws` with `perMessageDeflate: true` by default, and a client that offers nothing gets an uncompressed upgrade.
|
||||
|
||||
### Negotiation and degradation
|
||||
|
||||
| Peer | Result |
|
||||
|---|---|
|
||||
| Browser (`client/connection` opens a native `WebSocket`) | Negotiates the extension; both directions compressed |
|
||||
| Node `ws` carrier (tests, authenticated Desktop callers) | Negotiates the extension; both directions compressed |
|
||||
| Client that does not offer the extension | Upgrade succeeds without `Sec-WebSocket-Extensions`; frames identical to an uncompressed connection |
|
||||
| New Host with an old Client, or the reverse | No protocol change; the extension is per connection and optional |
|
||||
| `websocketCompression: 'none'` | No extension is negotiated; frames are uncompressed |
|
||||
|
||||
### Failure and capacity
|
||||
|
||||
| Condition | Behavior |
|
||||
|---|---|
|
||||
| Buffered uplink exceeds `streamInboxBytes` | Still fails the logical stream with `gateway/uplink-overflow`; the budget counts decompressed frame bytes, so its meaning does not change |
|
||||
| Ping/Pong heartbeat | Ping cadence is unaffected: control frames are never compressed, and the activity rule's Pong and inbound-frame arms are unchanged |
|
||||
| Downlink write liveness | A sub-threshold write stays synchronous, so it counts as peer activity only when the local send buffer was already full; a frame whose JSON text reaches 1,024 characters compresses asynchronously, so its completed write counts as buffered activity |
|
||||
| zlib saturation | Compression runs on the libuv thread pool behind `ws`'s global limiter; the event loop is not blocked, but thread-pool capacity is shared with file I/O |
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Compress frames with gzip.** Rejected because WebSocket has no gzip frame format; RFC 7692 defines `permessage-deflate` as the only registered per-message compression extension.
|
||||
|
||||
**Let the client choose.** Rejected because the browser `WebSocket` API exposes no extension parameters and no way to disable negotiation; the decision can only live on the server.
|
||||
|
||||
**Mirror the HTTP surface with `compressionLevel` and `compressionThresholdBytes`.** Deferred, not rejected: no measurement justifies exposing tuning knobs yet, and the repository forbids hiding a real tunable in a constant while also forbidding speculative configuration. Add the fields when a deployment needs a different trade-off.
|
||||
|
||||
**Enable compression unconditionally.** Rejected because the choice varies by deployment: a loopback deployment pays CPU for no bandwidth gain and must be able to turn it off, which is what the Config field is for.
|
||||
|
||||
**Compress only the downlink.** Rejected because the extension is negotiated per connection and applies to both directions; a one-way accept is not expressible, and the uplink frames are small anyway.
|
||||
|
||||
## Consequences
|
||||
|
||||
Compression reclaims the bandwidth of large JSON frames over a tunnel, a reverse proxy, or an admitted public URL. Frames below 1,024 characters stay out of the zlib pool, and context takeover keeps the dictionary across messages, so repetitive JSON compresses well without per-message rebuilds.
|
||||
|
||||
**Thread-pool contention.** zlib shares the libuv pool with file I/O. The 1,024-character threshold keeps small streaming frames out of it; a saturated pool delays compressed frames rather than blocking the loop.
|
||||
|
||||
**Per-connection memory.** Each negotiated connection holds a deflate and an inflate context for its lifetime. One socket per browser tab keeps this proportional to open tabs; an implementation that expects many idle tabs should measure before enabling by default.
|
||||
|
||||
**No benefit over loopback.** The common local deployment gains nothing but CPU, which is why the field exists; the default is chosen for remote and tunneled use.
|
||||
|
||||
**Unmeasured default.** The `'deflate'` default rests on the HTTP gzip precedent and on remote deployments, not on an on/off byte-and-latency comparison. Record that comparison when one is run and revisit the default if local overhead proves visible.
|
||||
|
||||
**Weakened write liveness.** A compressed downlink write completes when `ws` has deflated and written it, not when the TCP peer acknowledges data, so continuous compressed downlink traffic counts as buffered activity and delays missed-heartbeat termination. The heartbeat rule and its code are unchanged.
|
||||
|
||||
The Gateway unit tests pin default negotiation, no-extension behavior, a client that offers nothing, the per-frame threshold, and the compressed-write liveness consequence; the config default and the generated config catalog are updated in the same change.
|
||||
@@ -0,0 +1,72 @@
|
||||
# Agent Note: 用 permessage-deflate 压缩 Remote 流 WebSocket
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-18-websocket-permessage-deflate.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
Web GUI 的所有实时 Remote 流共用一条物理 WebSocket `/api/remote.mux`:模型 token 增量、session 事件、工具输出、终端输出,以及 `$events` 广播。这条 mux 本身是已实现的 [Remote 双工流](2026-09-19-remote-duplex-stream.zh.md)设计,本 Note 只给它加一个传输选项。Host 的 HTTP 面已经暴露 `compression: 'none' | 'gzip'`;在此选项之前 WebSocket 没有对应能力,链路因此从不压缩。
|
||||
|
||||
走隧道、反向代理或被接纳的公网地址时,大 JSON 帧——会话回放、长工具输出、终端滚动历史——占掉的带宽协议本来就能省下来。浏览器在升级握手时本来就会主动 offer `permessage-deflate`,缺的只是服务端这一半。
|
||||
|
||||
## 决定
|
||||
|
||||
Gateway Config 增加一个字段 `websocketCompression: 'none' | 'deflate'`,默认 `'deflate'`,把解析后的布尔值交给 mux。mux 用以下传输选项构造 `WebSocketServer`,并在发送时逐帧决定是否压缩:
|
||||
|
||||
| 选项 | 取值 | 理由 |
|
||||
|---|---|---|
|
||||
| 帧压缩阈值 | `1024` 个 JSON 文本字符,由 mux 在发送路径判定 | `ws` 在开启上下文接管时会忽略自己的 `threshold` 选项([上游 41ae5631](https://github.com/websockets/ws/commit/41ae5631)),因此 mux 逐帧传入 `compress`;少于 1024 个字符的帧(多数 token 级 `item` 帧)不压缩,也不进入 zlib 线程池 |
|
||||
| `perMessageDeflate.zlibDeflateOptions.level` | `1` | 交互帧优先延迟而非压缩率;本仓库 HTTP gzip 默认也是 `1` |
|
||||
| 上下文接管 | `ws` 默认(双向开启) | 重复 JSON 压缩率最好,不必逐帧重建字典 |
|
||||
|
||||
mux 构造函数接收一个 options 对象,携带 `heartbeatIntervalMs`、`streamInboxBytes` 与 `compression`,因为配置词汇由 Gateway 解析,`stream-server.ts` 不认识 `'none' | 'deflate'`。
|
||||
|
||||
客户端零改动:浏览器 API 本来就会 offer 该扩展,Node 载体用的 `ws` 默认 `perMessageDeflate: true`,完全不 offer 的客户端拿到的就是不压缩的升级响应。
|
||||
|
||||
### 协商与降级
|
||||
|
||||
| 对端 | 结果 |
|
||||
|---|---|
|
||||
| 浏览器(`client/connection` 打开原生 `WebSocket`) | 协商成功,双向压缩 |
|
||||
| Node `ws` 载体(测试、认证的 Desktop 调用方) | 协商成功,双向压缩 |
|
||||
| 不 offer 该扩展的客户端 | 升级成功但不带 `Sec-WebSocket-Extensions`,帧与不压缩连接一致 |
|
||||
| 新 Host 配旧 Client,或反过来 | 没有协议变更;扩展按连接、可选协商 |
|
||||
| `websocketCompression: 'none'` | 不协商扩展;帧不压缩 |
|
||||
|
||||
### 失败与容量
|
||||
|
||||
| 情形 | 行为 |
|
||||
|---|---|
|
||||
| 缓冲的上行超过 `streamInboxBytes` | 仍以 `gateway/uplink-overflow` 失败该逻辑流;预算统计的是解压后的帧字节,语义不变 |
|
||||
| Ping/Pong 心跳 | Ping 周期不受影响:控制帧从不压缩,活动判据中 Pong 与收到帧这两支不变 |
|
||||
| 下行写入活性 | 阈值以下的写入保持同步,只有本机发送缓冲区已满时才计为对端活动;JSON 文本达到 1024 个字符的帧异步压缩,其写入完成会计为缓冲活动 |
|
||||
| zlib 打满 | 压缩跑在 libuv 线程池、受 `ws` 全局并发限制约束;事件循环不被阻塞,但线程池容量与文件 IO 共享 |
|
||||
|
||||
## 备选方案
|
||||
|
||||
**用 gzip 压缩帧。** 否决:WebSocket 没有 gzip 帧格式,RFC 7692 只注册了 `permessage-deflate` 这一种逐消息压缩扩展。
|
||||
|
||||
**让客户端决定。** 否决:浏览器 `WebSocket` API 不暴露扩展参数,也没有关闭协商的手段,这个决定只能落在服务端。
|
||||
|
||||
**照 HTTP 面配齐 `compressionLevel` 与 `compressionThresholdBytes`。** 暂缓而非否决:目前没有测量依据支持暴露调优旋钮,而仓库既禁止把真正的可调项藏进常量,也禁止投机式配置。等某个部署需要不同取舍时再加字段。
|
||||
|
||||
**无条件开启压缩。** 否决:这个选择随部署变化——本地 loopback 部署付出 CPU 却换不到带宽,必须能关掉,这正是 Config 字段的用途。
|
||||
|
||||
**只压下行。** 否决:该扩展按连接协商并同时作用于两个方向,单向接受不可表达,何况上行帧本来就小。
|
||||
|
||||
## 后果
|
||||
|
||||
压缩为走隧道、反向代理或被接纳公网地址的大 JSON 帧省下带宽。少于 1024 个字符的帧不进入 zlib 线程池,上下文接管跨消息保留字典,因此重复 JSON 的压缩率好且不必逐帧重建。
|
||||
|
||||
**线程池争用。** zlib 与文件 IO 共用 libuv 线程池。1024 字符阈值把小的流式帧挡在外面;线程池打满时被延迟的是压缩帧,事件循环仍不被阻塞。
|
||||
|
||||
**每连接内存。** 每条协商成功的连接在整个生命周期内持有 deflate 与 inflate 一对上下文。每个浏览器标签页一条 socket 时,内存与打开的标签页成正比;如果实现预期大量空闲标签页,应先测量再默认开启。
|
||||
|
||||
**loopback 无收益。** 最常见的本地部署只多付 CPU,所以才有这个字段;默认值是为远端与隧道场景选的。
|
||||
|
||||
**默认值未经测量。** `'deflate'` 这个默认值依据的是 HTTP gzip 的先例与远端场景,不是开/关两态的字节与延迟对比。做过对比后要记录结论,本地开销若明显则重新考虑默认值。
|
||||
|
||||
**写入活性被削弱。** 压缩后的下行写入在 `ws` 完成 deflate 并写出时即完成,而不是等到 TCP 对端确认数据,因此持续压缩下行会把写入计为缓冲活动并推迟心跳超时终止。心跳规则及其代码未改动。
|
||||
|
||||
Gateway 单测钉住默认协商、不协商、不支持扩展的客户端、逐帧阈值与压缩写入的活性后果;配置默认值与生成的 config catalog 在同一次改动中更新。
|
||||
+6
-6
@@ -9,14 +9,14 @@
|
||||
en: 3238dfb7db7dfdd8
|
||||
zh: ec3bdd9a9da0a321
|
||||
/agent-note-user-authorized-subagent-model-routes/decision:
|
||||
en: 0166e452a774ade5
|
||||
zh: 747061b9424b3610
|
||||
en: 9eeb42f10be1bd28
|
||||
zh: 24e6cca2db77444a
|
||||
/agent-note-user-authorized-subagent-model-routes/alternatives-considered:
|
||||
en: e41c17226190ea8b
|
||||
zh: d3b1395fafe75f01
|
||||
/agent-note-user-authorized-subagent-model-routes/consequences:
|
||||
en: f1927d31ffc1dee9
|
||||
zh: b0fed04b80043c71
|
||||
en: 26244ba796f6ed70
|
||||
zh: 47e9744719b05f0b
|
||||
/agent-note-user-authorized-subagent-model-routes/related-decisions:
|
||||
en: 595a9485d91592c6
|
||||
zh: 7b32611ea380498c
|
||||
en: 0eead87ec1172d81
|
||||
zh: 4e776a583f5a00a5
|
||||
+7
-7
@@ -12,11 +12,11 @@ Registering an LLM adapter makes its routes reachable, but does not authorize an
|
||||
|
||||
The Host-owned `subagent-model-selection` settings section stores an explicit `enabled` switch and `allowedModels`, an array of exact `{ provider, model }` routes. Enabling requires at least one route; disabling may retain the selected routes for later reuse. The Plugins settings card reads the live adapter directory through `session/modelCatalog`, lets the user stage the switch and routes, and saves both fields in one revision-fenced settings mutation. It stores no adapter-owned display names, descriptions, or reasoning-effort metadata. A stored or staged route absent from the current directory remains visible as unavailable and removable; a provider-local catalog failure does not block other providers or erase saved authorization or an unsaved selection. A connection reset discards the draft because namespace revisions are comparable only within one Host process.
|
||||
|
||||
A newly composed top-level Session snapshots the route list in `subagent/model-selection-policy` when the setting is enabled, before its model-selectable definitions can reach a request. Event presence means selection was enabled; the event does not store the global switch. Child Sessions inherit that exact list from their live parent, and resumed Sessions use the recorded event instead of current settings. Settings changes therefore affect only subsequently composed top-level Sessions, while a legacy Session without the event remains disabled, including an explicitly empty restored Session.
|
||||
The Host settings service snapshots the route list for each fresh top-level Session in `subagent/model-selection-policy` when the setting is enabled, before its first model request. Event presence means selection was enabled; the event does not store the global switch. Child Sessions inherit that exact list from their live parent, and resumed Sessions use the recorded event instead of current settings. Settings changes therefore affect only subsequently created top-level Sessions. Without the event, including an explicitly empty restored Session, explicit selection remains restricted to the current parent route.
|
||||
|
||||
The fixed `list_subagent_models` schema does not enumerate the policy. At call time, provider and model listings are the intersection of the Session route list and the adapter's live advertised directory. An exact provider/model lookup first requires authorization, then resolves the adapter-owned model metadata and all advertised reasoning efforts. The delegation executor independently rejects any explicit provider, model, or effort selection whose effective provider/model route is outside the Session list before `resolveCallConfig()` validates adapter availability and effort support. A call that supplies no selection field retains configured or inherited routing because the model made no route choice.
|
||||
The fixed `list_subagent_models` schema does not enumerate the policy. At call time, it lists authorized registered providers and intersects their advertised model catalogs with the recorded route list. Exact lookup requires authorization before resolving model metadata and all supported reasoning efforts, including for ids absent from the catalog. Without a recorded policy, discovery resolves only the parent Agent’s current effective route, even if the catalog omits it. The delegation executor independently checks any explicit provider, model, or effort selection against the same authorization before adapter preflight. A call with no selection fields retains configured or inherited routing.
|
||||
|
||||
Model selection has no unrestricted static mode. The default-off Host setting is the only authority, and an enabled Session always carries an exact allowlist. The primary spawn tool reads that setting; the shipped fork tool still exposes no route selection so inherited conversation prefixes remain eligible for provider-side KV Cache reuse.
|
||||
Model selection has no unrestricted static mode. The default-off Host setting authorizes alternative child routes; without it, the parent route and its supported efforts remain selectable. Each delegation tool’s `modelSelection` option controls only its route fields. One delegation instance per scope enables `modelDiscovery` to own discovery; the setting, discovery, and delegation read the same package-local authorization projection. Web and Desktop presets mount discovery by default, enable spawn route fields, and leave fork route fields disabled to preserve inherited-prefix reuse; custom fork compositions may enable them under the same Session authorization. Tool installation does not sample or expand authorization.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -33,11 +33,11 @@ Model selection has no unrestricted static mode. The default-off Host setting is
|
||||
## Consequences
|
||||
|
||||
- New adapter registrations and newly advertised models do not expand user authorization.
|
||||
- Adapter removals or catalog failures can reduce what discovery currently lists without deleting the saved route decision; an exact authorized route remains usable when its adapter accepts it even if the advisory catalog omits it.
|
||||
- The allowlist itself consumes no parent-request tokens. Only a `list_subagent_models` result enters the transcript.
|
||||
- Adapter removal or catalog lookup failure can make provider discovery unavailable without deleting the saved route decision; an allowed model remains usable when its adapter accepts it even if the advisory catalog omits it.
|
||||
- The allowlist itself consumes no parent-request tokens. Only a `list_subagent_models` result enters the transcript. The `modelSelection` option adds three route fields; `modelDiscovery` adds a fixed discovery schema. Both options default off; shipped headless and SDK profiles carry neither.
|
||||
- The policy event is log-only and is appended while an Agent is composed, before either SDK begins its run subscription. Shipped SDK profiles do not enable this Web-owned preference, so the event changes neither SDK's expected notifications or persisted-session output; package restore tests own its durable projection instead of fabricating an SDK composition solely to emit it.
|
||||
- Unit coverage pins settings validation, malformed durable values, Session sampling and inheritance, discovery intersection, executor denial, live UI catalog invalidation, staged-route retention, connection-generation invalidation, staged whole-array writes, stale-revision rejection, and retry after scoped installation failure. The assembled Web scenario pins the real settings document and Plugins card flow.
|
||||
- Unit coverage pins settings validation, malformed durable values, Session sampling and inheritance, shared definitions, parent-route defaults, discovery restrictions, executor denial, and the settings UI’s staged writes and revision checks. The assembled Web scenario pins the real settings document and Plugins card flow.
|
||||
|
||||
## Related decisions
|
||||
|
||||
The route arguments, adapter preflight, discovery tool, and fork cache restriction remain owned by [model-selected subagent routes](../../../../packages/subagent/tool-subagent/README.md).
|
||||
The route arguments, adapter preflight, discovery tool, and fork restriction remain owned by [model-selected subagent routes](../../../../packages/subagent/tool-subagent/README.md).
|
||||
+7
-7
@@ -12,11 +12,11 @@ Status: implemented
|
||||
|
||||
Host 自有的 `subagent-model-selection` 设置 section 保存显式 `enabled` 开关与 `allowedModels`,后者是由精确 `{ provider, model }` 路由组成的数组。启用时必须至少有一条路由;关闭时可以保留已选路由,供以后重新启用。Plugins 设置卡通过 `session/modelCatalog` 读取实时适配器目录,让用户暂存开关与路由,再在一次带 revision 限制的设置 mutation 中保存两个字段。它不保存适配器自有的显示名称、描述或推理强度元数据。当前目录中缺失的已存或暂存路由仍显示为不可用并允许移除;某个提供方的目录失败不会阻塞其他提供方,也不会清除已存授权或未保存选择。连接重置会丢弃草稿,因为 namespace revision 只能在同一个 Host 进程内比较。
|
||||
|
||||
设置启用时,新组合的顶层 Session 会在模型可选定义进入请求之前,把路由列表快照记录为 `subagent/model-selection-policy`。事件存在就表示模型选择已启用;事件不保存全局开关。子 Session 从在线父级继承同一份精确列表,恢复的 Session 使用已记录事件而不是当前设置。因此,设置修改只影响之后组合的顶层 Session,而没有该事件的旧 Session 仍保持禁用,包括显式为空的恢复 Session。
|
||||
设置启用时,Host 设置服务为每个新顶层 Session 在首个模型请求前将路由列表记录为 `subagent/model-selection-policy`。事件存在表示选择已启用;事件不保存全局开关。子 Session 从在线父级继承同一精确列表,恢复的 Session 使用已记录事件而非当前设置。因此设置变更只影响之后创建的顶层 Session。没有该事件时,包括显式为空的恢复 Session,显式选择仍仅限当前父级路由。
|
||||
|
||||
固定的 `list_subagent_models` schema 不会枚举该策略。调用时,提供方和模型列表是 Session 路由列表与适配器实时公布目录的交集。精确 provider/model 查询先要求授权,再解析适配器自有的模型元数据和全部已公布推理强度。委派执行器还会独立拒绝任何生效 provider/model 路由不在 Session 列表内的显式提供方、模型或强度选择,然后才由 `resolveCallConfig()` 校验适配器可用性与强度支持。完全没有选择字段的调用保留配置或继承路由,因为模型没有作出路由选择。
|
||||
固定的 `list_subagent_models` schema 不枚举策略。调用时,它列出已授权且已注册的提供方,并取其公布模型目录与已记录路由列表的交集。精确查询先要求授权,再解析模型元数据及全部支持的推理强度,也允许目录未列出的 ID。没有已记录策略时,发现只解析父 Agent 当前生效的路由,即使目录未列出它。委派执行器会在适配器预检前独立校验显式提供方、模型或强度选择是否符合相同授权。没有选择字段的调用保留配置或继承的路由。
|
||||
|
||||
模型选择不再有无限制的静态模式。默认关闭的 Host 设置是唯一授权来源,启用的 Session 始终携带精确允许列表。主 spawn 工具读取该设置;随附 fork 工具仍不公开路由选择,使继承的对话前缀继续符合提供方侧 KV Cache 复用条件。
|
||||
模型选择没有无限制的静态模式。默认关闭的 Host 设置授权其他子级路由;没有该授权时,仍可选择父级路由及其支持的强度。各委派工具的 `modelSelection` 选项只控制自身路由字段。每个作用域的一个委派实例启用 `modelDiscovery` 并负责发现;设置、发现和委派读取包内同一个授权投影。Web 与 Desktop preset 默认挂载发现工具,启用 spawn 路由字段,并为 fork 保持关闭,以保留对继承前缀的复用;自定义 fork 组合可以在相同 Session 授权下启用这些字段。工具安装不采样或扩大授权。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -33,11 +33,11 @@ Host 自有的 `subagent-model-selection` 设置 section 保存显式 `enabled`
|
||||
## Consequences
|
||||
|
||||
- 新适配器注册和新公布模型不会扩大用户授权。
|
||||
- 适配器移除或目录失败可以减少发现当前列出的内容,但不会删除已存路由决定;即使建议性目录省略某条精确已授权路由,只要适配器接受它,该路由仍然可用。
|
||||
- 允许列表本身不消耗父级请求 token。只有 `list_subagent_models` 结果进入 transcript。
|
||||
- 适配器移除或目录查询失败可能使提供方发现不可用,但不会删除已存路由决定;只要适配器接受某个获准模型,即使建议性目录省略它,该模型仍可使用。
|
||||
- 允许列表本身不消耗父级请求 token。只有 `list_subagent_models` 结果进入 transcript。`modelSelection` 选项增加三个路由字段;`modelDiscovery` 增加固定发现 schema。两个选项都默认关闭;随附 headless 与 SDK profile 两者都不携带。
|
||||
- 策略事件仅存在于日志,并在 Agent 组合期间、两套 SDK 开始订阅运行前追加。随附 SDK profile 不启用这项 Web 自有偏好,因此该事件不会改变任一 SDK 的预期通知或持久 Session 输出;其持久投影由包级恢复测试负责,不会为了发出该事件而虚构 SDK 组合。
|
||||
- 单元覆盖固定设置校验、异常持久值、Session 取样与继承、发现交集、执行器拒绝、UI 实时目录失效、暂存路由保留、连接换代失效、暂存后的整数组写入、陈旧 revision 拒绝,以及作用域安装失败后的重试。组装 Web 场景固定真实设置文档与 Plugins 设置卡流程。
|
||||
- 单元覆盖固定设置校验、异常持久值、Session 取样与继承、共享定义、父级路由默认值、发现限制、执行器拒绝,以及设置 UI 的暂存写入与 revision 检查。组装 Web 场景固定真实设置文档与 Plugins 设置卡流程。
|
||||
|
||||
## Related decisions
|
||||
|
||||
路由参数、适配器预检、发现工具与 fork 缓存限制仍由[模型选择的 subagent 路由](../../../../packages/subagent/tool-subagent/README.zh.md)负责。
|
||||
路由参数、适配器预检、发现工具与 fork 限制仍由[模型选择的 subagent 路由](../../../../packages/subagent/tool-subagent/README.zh.md)负责。
|
||||
@@ -9,23 +9,23 @@
|
||||
en: 492ba2355360d81a
|
||||
zh: 825c0e7ab1cf4beb
|
||||
/agent-note-experimental-auto-review-before-each-tool-call/decision:
|
||||
en: 3868c5c4b087bdba
|
||||
zh: c3dff98fc5f05f19
|
||||
en: d31f010f9c22c452
|
||||
zh: 5c3966a953227998
|
||||
/agent-note-experimental-auto-review-before-each-tool-call/decision/effects-and-authority:
|
||||
en: 55a79d8572fb4332
|
||||
zh: f63d03d0f3bb5811
|
||||
en: db0a2deb9193a00d
|
||||
zh: dab5a65b88957464
|
||||
/agent-note-experimental-auto-review-before-each-tool-call/decision/one-complete-reviewer-request:
|
||||
en: d93bd9b8af6b0bfc
|
||||
zh: 9514c2e5554a721e
|
||||
en: 3a07a59b6c5ec26c
|
||||
zh: e4f3290dc6d26368
|
||||
/agent-note-experimental-auto-review-before-each-tool-call/decision/result-and-cancellation:
|
||||
en: 237018634b5f9dcf
|
||||
zh: cbc49d9c1b794065
|
||||
en: 647fe68c52b907e0
|
||||
zh: 267a739bad44ad65
|
||||
/agent-note-experimental-auto-review-before-each-tool-call/decision/process-catalog-and-children:
|
||||
en: 0042c645360c8361
|
||||
zh: 5dba39466d376559
|
||||
/agent-note-experimental-auto-review-before-each-tool-call/alternatives-considered:
|
||||
en: fe97128ed05b9e43
|
||||
zh: 4aaa344933fa89b4
|
||||
en: a7e7f25cf98215bc
|
||||
zh: 1ddcabec2bba3539
|
||||
/agent-note-experimental-auto-review-before-each-tool-call/consequences:
|
||||
en: 1aca35a6d0784f67
|
||||
zh: 51cfa397db101600
|
||||
@@ -10,7 +10,7 @@ Full access lets useful project work proceed without repeated approvals, but it
|
||||
|
||||
## Decision
|
||||
|
||||
[`dsh-experimental-auto-review`](../../../../packages/experimental/auto-review/README.md) is an explicitly installed experimental Web layer, published under the [experimental package publication policy](../../../../packages/experimental/README.md). Default Web retains Read Only, Workspace Write, and Full access. The layer contributes current-session `auto`, whose only durable identity is `permission/preset:auto`; it uses Full access's `danger-full-access` sandbox and tool definitions with the approval policy chosen by [the user-approval fallback decision](2026-09-24-auto-review-user-approval-fallback.md). Headless, General settings, and new-session defaults exclude the integration.
|
||||
[`dsh-experimental-auto-review`](../../../../packages/experimental/auto-review/README.md) is an explicitly installed experimental profile layer, published under the [experimental package publication policy](../../../../packages/experimental/README.md). Default Web retains Read Only, Workspace Write, and Full access. The layer contributes current-session `auto`, whose only durable identity is `permission/preset:auto`; it uses Full access's `danger-full-access` sandbox and tool definitions with the approval policy chosen by [the user-approval fallback decision](2026-09-24-auto-review-user-approval-fallback.md). Default Headless, General settings, and new-session defaults exclude the integration. [Headless caller admission](2026-10-18-headless-auto-review.md) defines explicit headless opt-in and transport-independent user-source authority.
|
||||
|
||||
Every native call and started PTC `tools.*` inner call receives one review before its body. The outer `run_code` transport and direct Node effects in a PTC program remain outside this guarantee. There are no tool-name exemptions, cached grants, retries, configurable policy, or second authorization check; [the user-approval fallback decision](2026-09-24-auto-review-user-approval-fallback.md) owns what follows a denial. A repeated call receives a fresh review.
|
||||
|
||||
@@ -21,12 +21,12 @@ The fixed policy follows the allow/soft-deny/hard-deny distinction and classifie
|
||||
| Risk | Examples | Decision |
|
||||
| --- | --- | --- |
|
||||
| `low` | Ordinary project-local reads/writes, analysis, format/lint/test/build, non-destructive Git, and exact cleanup of an object whose retained calls establish creation in this Session | Allow |
|
||||
| `medium` | Irreversible deletion of pre-existing state, force push/history rewrite, production reads/writes/deployment, non-sensitive external writes/sends, and permission/security/system changes | Allow only with explicit current human or direct-parent authorization naming action, exact target, and necessary scope |
|
||||
| `medium` | Irreversible deletion of pre-existing state, force push/history rewrite, production reads/writes/deployment, non-sensitive external writes/sends, and permission/security/system changes | Allow only with explicit current user or direct-parent authorization naming action, exact target, and necessary scope |
|
||||
| `high` | Sensitive data exfiltration across the current trust boundary and equivalent hard-deny effects | Deny, including an explicitly requested action |
|
||||
|
||||
Actual effects that are ambiguous or broader than established scope fail closed. A later instruction clears an earlier conflict only by explicitly revoking or replacing it; direct-parent instructions cannot override human restrictions. History can establish a session-created object but cannot authorize medium work or remove a high-risk prohibition.
|
||||
Actual effects that are ambiguous or broader than established scope fail closed. A later instruction clears an earlier conflict only by explicitly revoking or replacing it; direct-parent instructions cannot override user restrictions. History can establish a session-created object but cannot authorize medium work or remove a high-risk prohibition.
|
||||
|
||||
The reviewer derives authority from existing Session facts. A shipped Web human instruction has `source.kind === 'user'` and the `rpcId` written at browser prompt admission. A child's initial direct-parent prompt is identified after its own existing creation descriptor; a later `agent-message` is direct-parent instruction only when `senderSessionId` matches `SessionHeader.parentSession`. Human text defines or replaces the task and restrictions. Direct-parent text defines the child task within those restrictions. Project instructions only constrain; checkpoints restore lossy context; images, attachment metadata, and historical calls provide facts. Compaction does not promote a checkpoint into the human authority of text that left the surface.
|
||||
The reviewer derives authority from existing Session facts. Text with `source.kind === 'user'` defines or replaces the task and restrictions, independent of its input transport. A child's initial direct-parent prompt is identified after its own existing creation descriptor; a later `agent-message` is direct-parent instruction only when `senderSessionId` matches `SessionHeader.parentSession`. Inherited and unverified child creation prompts remain facts. [User-source admission](2026-10-18-headless-auto-review.md) defines caller and restored-history semantics. Direct-parent text defines the child task within user restrictions. Project instructions only constrain; checkpoints restore lossy context; images, attachment metadata, and historical calls provide facts. Compaction does not promote a checkpoint into the instruction authority of text that left the surface.
|
||||
|
||||
### One complete reviewer request
|
||||
|
||||
@@ -37,7 +37,7 @@ The integration uses only the latest `request/header.config` provider/model and
|
||||
| `REVIEW_POLICY` | Fixed classification, source authority, and strict result rules; allow executes immediately with Full access and no later confirmation |
|
||||
| `ENVIRONMENT` | Existing Session header `cwd` only |
|
||||
| `PROJECT_INSTRUCTIONS` | Visible project instructions with their original sources and constraint role |
|
||||
| `FILTERED_HISTORY` | Current compaction surface's sourced human/direct-parent messages, checkpoints, image/attachment facts, and historical call names with logged arguments |
|
||||
| `FILTERED_HISTORY` | Current compaction surface's sourced user/direct-parent messages, checkpoints, image/attachment facts, and historical call names with logged arguments |
|
||||
| `PENDING_ACTION` | Tool name, description, parameter schema, and parsed arguments |
|
||||
|
||||
The reviewer builds those two action sections from the Session's complete action history: an authorization is the earlier call the `REVIEW_POLICY` source rules make eligible, and a duplicate or conflicting identity has to be visible anywhere in the log rather than inside a recent window, so neither the Session projections nor a bounded read serves the decision. The read is the deprecated synchronous `snapshotEvents()` under a line-scoped `typescript/no-deprecated` waiver.
|
||||
@@ -50,7 +50,7 @@ The reviewer may emit reasoning blocks followed by exactly one JSON text block a
|
||||
|
||||
A final denial, which [the user-approval fallback decision](2026-09-24-auto-review-user-approval-fallback.md) limits to the `never` approval policy, gives native results and PTC settle events the same structured `AutoReviewDeniedError` / `AUTO_REVIEW_DENIED` and optional raw reason. For a final denial the main agent receives only `Auto review rejected tool "<name>"; its body was not executed` through ordinary failure rendering. PTC retains the existing program exception/catch behavior; catching a denial does not elevate it to an outer failure. The generic Web tool card supplies the denial identity for the collapsed row and one not-executed output line for the expanded row, with no input body. Only that display trims and collapses line separators or supplies the localized empty-reason fallback; persistence and both SDKs preserve the complete raw reason, without a new length or redaction rule.
|
||||
|
||||
Admission and active-review enrollment occur synchronously before the first await. The integration owns one lifecycle controller and one set of active operations. Unload closes new selection/review admission, changes live Auto Sessions to Full access through the existing preset writer, which writes the `never` approval policy without changing the sandbox value or closing terminals, then aborts and drains reviews before removing listener and contribution. After provider settlement, a lifecycle abort always produces canonical pre-dispatch cancellation, including late allow, deny, or failure. Caller cancellation retains ToolRuntime's priority: late allow cancels before dispatch; a late failure or final denial retains that outcome, and a late denial under `ask` produces a cancelled approval and canonical pre-dispatch cancellation. No cancelled review starts a tool body.
|
||||
Admission and active-review enrollment occur synchronously before the first await. The integration owns one lifecycle controller and one set of active operations. Unload closes new selection/review admission, then aborts and drains reviews before removing listener and contribution. Only unload while the root context remains active changes live Auto Sessions to Full access through the existing preset writer, which writes the `never` approval policy without changing the sandbox value or closing terminals. Root shutdown preserves selection: stopping the application does not revoke its installed integration, and its child disposers can race Session persistence cleanup. The root fiber enters `UNLOADING` before child cleanup starts, so that lifecycle state distinguishes shutdown without a new shutdown service or timing heuristic. After provider settlement, a lifecycle abort always produces canonical pre-dispatch cancellation, including late allow, deny, or failure. Caller cancellation retains ToolRuntime's priority: late allow cancels before dispatch; a late failure or final denial retains that outcome, and a late denial under `ask` produces a cancelled approval and canonical pre-dispatch cancellation. No cancelled review starts a tool body.
|
||||
|
||||
A persisted Auto Session cannot publish when the complete integration is absent or failed. Its log is not rewritten, and no background retry runs. Reinstallation allows a user to reopen it; live Sessions migrated to Full access remain there until explicitly switched.
|
||||
|
||||
@@ -64,7 +64,7 @@ The [delegation-time policy capture](../../../../packages/subagent/subagent/READ
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Default or released integration** would make an experimental model approval policy part of every deployment. A private source Web patch keeps opt-in explicit while using the existing plugin installer.
|
||||
**Default or released integration** would make an experimental model approval policy part of every deployment. An explicit profile patch keeps opt-in explicit while using the existing plugin installer.
|
||||
|
||||
**A new sandbox or approval-policy value** would couple review to enforcement and create combinations without a current consumer. An explicit preset identity preserves the unchanged Full access execution behavior.
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ Full access 让有用的项目工作无需反复审批即可继续,但也允
|
||||
|
||||
## 决策
|
||||
|
||||
[`dsh-experimental-auto-review`](../../../../packages/experimental/auto-review/README.zh.md)是显式安装的实验性 Web 层,按[实验包发布策略](../../../../packages/experimental/README.zh.md)参与发布。默认 Web 保持 Read Only、Workspace Write 与 Full access。此层贡献仅限当前会话的 `auto`,唯一持久身份为 `permission/preset:auto`;它使用 Full access 的 `danger-full-access` 沙箱与工具定义,审批策略由[用户审批兜底决策](2026-09-24-auto-review-user-approval-fallback.zh.md)决定。Headless、通用设置与新会话默认值都排除此 integration。
|
||||
[`dsh-experimental-auto-review`](../../../../packages/experimental/auto-review/README.zh.md)是显式安装的实验性 profile 层,按[实验包发布策略](../../../../packages/experimental/README.zh.md)参与发布。默认 Web 保持 Read Only、Workspace Write 与 Full access。此层贡献仅限当前会话的 `auto`,唯一持久身份为 `permission/preset:auto`;它使用 Full access 的 `danger-full-access` 沙箱与工具定义,审批策略由[用户审批兜底决策](2026-09-24-auto-review-user-approval-fallback.zh.md)决定。默认 Headless、通用设置与新会话默认值都排除此 integration。[Headless 调用者入口](2026-10-18-headless-auto-review.zh.md)定义显式 headless 启用方式与不依赖传输的 user-source 权威。
|
||||
|
||||
每个原生调用与已开始的 PTC `tools.*` inner call 都在 body 前接受一次审查。外层 `run_code` transport 与 PTC 程序内直接 Node 效果不在保证范围内。不提供按工具名豁免、缓存 grant、重试、可配置策略或第二授权检查;拒绝之后的行为由[用户审批兜底决策](2026-09-24-auto-review-user-approval-fallback.zh.md)负责。重复调用也重新审查。
|
||||
|
||||
@@ -21,12 +21,12 @@ Full access 让有用的项目工作无需反复审批即可继续,但也允
|
||||
| 风险 | 示例 | 决定 |
|
||||
| --- | --- | --- |
|
||||
| `low` | 普通项目内读写、分析、format/lint/test/build、非破坏性 Git,以及保留调用能证明在本 Session 创建的对象的精确清理 | 允许 |
|
||||
| `medium` | 不可逆删除既有状态、force push/历史改写、生产读写/部署、非敏感外部写入/发送,以及权限/安全/系统变更 | 只有当前 human 或直接父级明确授权动作、准确目标与必要范围时才允许 |
|
||||
| `medium` | 不可逆删除既有状态、force push/历史改写、生产读写/部署、非敏感外部写入/发送,以及权限/安全/系统变更 | 只有当前用户或直接父级明确授权动作、准确目标与必要范围时才允许 |
|
||||
| `high` | 敏感数据跨越当前信任边界外泄及等价 hard-deny 效果 | 拒绝,包括显式请求的动作 |
|
||||
|
||||
实际效果不明确或超出已确立范围时按拒绝处理。后续指令只有明确撤销或替换旧指令才能消除冲突;直接父级指令不能覆盖 human 限制。历史可以证明对象由本会话创建,但不能授权 medium 操作或解除 high 禁令。
|
||||
实际效果不明确或超出已确立范围时按拒绝处理。后续指令只有明确撤销或替换旧指令才能消除冲突;直接父级指令不能覆盖用户限制。历史可以证明对象由本会话创建,但不能授权 medium 操作或解除 high 禁令。
|
||||
|
||||
Reviewer 从既有 Session 事实派生权威。Shipped Web human 指令具有 `source.kind === 'user'` 和浏览器 prompt admission 写入的 `rpcId`。Child 初始直接父级 prompt 在其自身既有创建 descriptor 之后识别;后续 `agent-message` 只有 `senderSessionId` 与 `SessionHeader.parentSession` 匹配时才属于直接父级指令。Human 文本定义或替换任务与限制。直接父级文本在这些限制内定义 child 任务。项目指令只施加约束;checkpoint 恢复有损语境;图片、附件元数据和历史调用提供事实。Compaction 不会把 checkpoint 提升为已经离开 surface 的原文本所拥有的 human 权威。
|
||||
Reviewer 从既有 Session 事实派生权威。`source.kind === 'user'` 的文本定义或替换任务与限制,不区分输入传输。Child 初始直接父级 prompt 在其自身既有创建 descriptor 之后识别;后续 `agent-message` 只有 `senderSessionId` 与 `SessionHeader.parentSession` 匹配时才属于直接父级指令。继承的 child 创建 prompt 与无法验证的 child 创建 prompt 仍是事实。[User-source 接纳](2026-10-18-headless-auto-review.zh.md)定义调用者与恢复历史的语义。直接父级文本在用户限制内定义 child 任务。项目指令只施加约束;checkpoint 恢复有损语境;图片、附件元数据和历史调用提供事实。Compaction 不会把 checkpoint 提升为已经离开 surface 的原文本所拥有的指令权威。
|
||||
|
||||
### 一次完整 reviewer 请求
|
||||
|
||||
@@ -37,7 +37,7 @@ Integration 只使用最新 `request/header.config` 的 provider/model 与 shi
|
||||
| `REVIEW_POLICY` | 固定分类、来源权威与严格结果规则;allow 后立即以 Full access 执行,没有后续确认 |
|
||||
| `ENVIRONMENT` | 仅既有 Session header `cwd` |
|
||||
| `PROJECT_INSTRUCTIONS` | 可见项目指令,保留原始来源并赋予约束角色 |
|
||||
| `FILTERED_HISTORY` | 当前 compaction surface 中带来源的 human/直接父级消息、checkpoint、图片/附件事实,以及历史调用名称与日志参数 |
|
||||
| `FILTERED_HISTORY` | 当前 compaction surface 中带来源的用户/直接父级消息、checkpoint、图片/附件事实,以及历史调用名称与日志参数 |
|
||||
| `PENDING_ACTION` | 工具名称、描述、参数 schema 与解析后的 arguments |
|
||||
|
||||
reviewer 从 Session 的完整动作历史构建这两个动作分节:授权是符合 `REVIEW_POLICY` 来源规则的那个更早调用,而重复或冲突的身份必须在整份日志的任何位置都可见,而不是只在近期窗口内可见,因此 Session 投影与有界读取都无法支撑该判定。这次读取使用已废弃的同步 `snapshotEvents()`,并带有行级 `typescript/no-deprecated` 豁免。
|
||||
@@ -50,7 +50,7 @@ Reviewer 可以输出 reasoning blocks,随后恰好一个 JSON text block 和
|
||||
|
||||
最终拒绝([用户审批兜底决策](2026-09-24-auto-review-user-approval-fallback.zh.md)将其限定在 `never` 审批策略下)让原生结果与 PTC 结算事件携带同形结构化 `AutoReviewDeniedError`/`AUTO_REVIEW_DENIED` 及可选原始理由。对最终拒绝,主 agent 通过普通失败渲染只收到 `Auto review rejected tool "<name>"; its body was not executed`。PTC 保留既有程序异常/catch 行为;捕获拒绝不会将其提升为外层失败。通用 Web 工具卡片为折叠行提供拒绝身份,为展开行提供一行未执行输出,不提供输入正文。只有该显示过程会 trim、折叠行分隔符,或提供本地化空理由 fallback;持久化与两套 SDK 保留完整原始理由,不增加长度或脱敏规则。
|
||||
|
||||
准入与在途 review 登记在首次 await 前同步完成。Integration 拥有一个生命周期 controller 和一个在途操作集合。卸载先关闭新选择/review admission,经由既有 preset writer 把存活 Auto Session 切为 Full access,该 writer 写入 `never` 审批策略,不改变沙箱值、不关闭终端,然后中止并等待 review 结清,最后移除 listener 与 contribution。Provider 结算后,lifecycle abort 始终形成规范的 dispatch 前取消,包括晚到 allow、deny 或 failure。Caller 取消保留 ToolRuntime 优先级:晚到 allow 在 dispatch 前取消;晚到 failure 或最终拒绝保留原结果,`ask` 下晚到的拒绝产生已取消的审批与规范的 dispatch 前取消。被取消的 review 不启动工具 body。
|
||||
准入与在途 review 登记在首次 await 前同步完成。Integration 拥有一个生命周期 controller 和一个在途操作集合。卸载先关闭新选择/review admission,然后中止并等待 review 结清,最后移除 listener 与 contribution。只有在根 context 仍活跃时卸载插件,才经由既有 preset writer 把存活 Auto Session 切为 Full access;该 writer 写入 `never` 审批策略,不改变沙箱值、不关闭终端。根关闭保留选择:停止应用不等于撤销已安装的 integration,且子级 disposer 可能与 Session 持久化清理并发。根 fiber 在子级清理开始前进入 `UNLOADING`,因此可用该生命周期状态区分关闭,无需新建关闭服务或依赖时序推测。Provider 结算后,lifecycle abort 始终形成规范的 dispatch 前取消,包括晚到 allow、deny 或 failure。Caller 取消保留 ToolRuntime 优先级:晚到 allow 在 dispatch 前取消;晚到 failure 或最终拒绝保留原结果,`ask` 下晚到的拒绝产生已取消的审批与规范的 dispatch 前取消。被取消的 review 不启动工具 body。
|
||||
|
||||
完整 integration 缺失或失败时,持久 Auto Session 不能发布。日志不改写,也不后台重试。重装后用户可以重新打开它;已经迁移为 Full access 的存活 Session 保持原状,直到显式切换。
|
||||
|
||||
@@ -64,7 +64,7 @@ Auto 在选择器选项中带 `EXP` 标记;选中后的 composer 控件不显
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**默认或正式发布 integration** 会把实验模型审批策略带入每个部署。私有源码 Web patch 在复用既有插件安装器的同时保持显式 opt-in。
|
||||
**默认或正式发布 integration** 会把实验模型审批策略带入每个部署。显式 profile patch 在复用既有插件安装器的同时保持显式 opt-in。
|
||||
|
||||
**新增 sandbox 或 approval-policy 值**会把 review 与执行耦合,产生没有当前消费者的组合。显式 preset 身份保留 Full access 未改变的执行行为。
|
||||
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
en: ed725dff16cd2339
|
||||
zh: 555d4ba70af96790
|
||||
/agent-note-auto-review-asks-the-user-after-a-denial/decision:
|
||||
en: f5ec55bcfe5e9a9a
|
||||
zh: 1b7462f8f06320e9
|
||||
en: 6bd32636c775b690
|
||||
zh: 5579111a3dbb6bf9
|
||||
/agent-note-auto-review-asks-the-user-after-a-denial/alternatives-considered:
|
||||
en: 01f71dd10849f925
|
||||
zh: 8b79a7e149a9a60c
|
||||
|
||||
@@ -14,7 +14,7 @@ A reviewer denial returns the tools pipeline's existing `ask` decision when the
|
||||
|
||||
Selecting Auto writes `danger-full-access` with the `ask` approval policy. A recorded Auto selection also matches the `never` policy. A [delegated in-process child pins `never`](../../../../packages/subagent/subagent/README.md), so a child Auto keeps the final `AutoReviewDeniedError` denial and its reason, and a Session recorded under the earlier `never` bundle still resolves to Auto instead of Full access. Selecting Auto again writes `ask`.
|
||||
|
||||
A reviewer failure denies the call with `Auto review of tool "<name>" failed; its body was not executed: <error>` and no structured error info. A provider failure's message carries the finish kind, failure code, and provider message, so the model and the generic tool card show the actual cause.
|
||||
A reviewer failure denies the call with `Auto review of tool "<name>" failed; its body was not executed: <error>` and structured `AutoReviewFailedError` / `AUTO_REVIEW_FAILED` info. [Headless callers](2026-10-18-headless-auto-review.md) use that identity to distinguish failure from denial. A provider failure's message carries the finish kind, failure code, and provider message, so the model and the generic tool card show the actual cause.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ Status: implemented
|
||||
|
||||
选择 Auto 会写入 `danger-full-access` 与 `ask` 审批策略。已记录的 Auto 选择也匹配 `never` 策略。[进程内委派 child 固定 `never`](../../../../packages/subagent/subagent/README.zh.md),因此 child 的 Auto 保留最终的 `AutoReviewDeniedError` 拒绝及其理由;按先前 `never` 组合记录的 Session 仍解析为 Auto,而不是 Full access。再次选择 Auto 会写入 `ask`。
|
||||
|
||||
Reviewer 失败以 `Auto review of tool "<name>" failed; its body was not executed: <error>` 拒绝调用,不带结构化错误信息。Provider 失败消息包含 finish 类型、失败代码与 provider 消息,因此模型与通用工具卡片显示实际原因。
|
||||
Reviewer 失败以 `Auto review of tool "<name>" failed; its body was not executed: <error>` 拒绝调用,带结构化 `AutoReviewFailedError` / `AUTO_REVIEW_FAILED` 信息。[Headless 调用者](2026-10-18-headless-auto-review.zh.md)用该身份区分失败与拒绝。Provider 失败消息包含 finish 类型、失败代码与 provider 消息,因此模型与通用工具卡片显示实际原因。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-10-18-headless-auto-review.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-10-18-headless-auto-review.md
|
||||
/agent-note-headless-caller-authority-for-auto-review:
|
||||
en: 9be347ac387545f7
|
||||
zh: b360764b7d22d1d6
|
||||
/agent-note-headless-caller-authority-for-auto-review/problem:
|
||||
en: 01dcd32fe547f90c
|
||||
zh: f9f577c4d72ebdbe
|
||||
/agent-note-headless-caller-authority-for-auto-review/decision:
|
||||
en: 372c020d57c6186a
|
||||
zh: 31c00bb5175930b1
|
||||
/agent-note-headless-caller-authority-for-auto-review/alternatives-considered:
|
||||
en: 7e7aefd55c59837c
|
||||
zh: e119a25a2171d376
|
||||
/agent-note-headless-caller-authority-for-auto-review/consequences:
|
||||
en: e3c7afb422c73478
|
||||
zh: 2acb65eaa03ad716
|
||||
@@ -0,0 +1,33 @@
|
||||
# Agent Note: Headless caller authority for Auto review
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-18-headless-auto-review.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Headless, SDK, and ACP admit tasks with `source.kind: 'user'`. Auto must recognize those tasks as instructions without requiring a browser RPC identity. The message role alone is insufficient: injected context also uses the user role but names its own source.
|
||||
|
||||
## Decision
|
||||
|
||||
The [experimental Auto layer](../../../../packages/experimental/auto-review/README.md#headless-supervision) supports explicit headless selection. Default profiles remain unchanged. This extends the Web-only scope of [per-tool Auto review](2026-08-28-auto-review.md); its risk classes, full-access execution, PTC exclusions, and lifecycle remain authoritative.
|
||||
|
||||
The reviewer assigns user-sourced text the `user-instruction` role, including existing history and tasks admitted before Auto was selected. The user may be a human or a supervising caller. Both can define the task and authorize an exact medium-risk action, target, and scope. The current child's creation prompt and direct-parent messages keep their subordinate role; inherited and unverified creation prompts remain facts. Files, constraints, checkpoints, images, and tool results do not acquire instruction authority. Input producers own the source attribution; task text never selects its source. The message types and persisted fields remain unchanged.
|
||||
|
||||
Headless applies an explicit `--approval-policy` after selecting the permission preset. Saved policy continues when both options are omitted on resume. Pass `--permission-mode auto --approval-policy never` to select unattended Auto explicitly. This retains the structured denial and avoids dependence on a human answerer. `ask` still follows [the approval fallback decision](2026-09-24-auto-review-user-approval-fallback.md); it does not add a CLI approval transport. Application shutdown preserves the saved Auto selection. Restoring it requires the integration even when selecting another preset; removing the plugin while the application remains active still migrates live Sessions to Full access.
|
||||
|
||||
The JSON projection includes existing durable error fields under its normal output bounds. A final denial carries `AutoReviewDeniedError` / `AUTO_REVIEW_DENIED` and optional reviewer detail; a technical or parsing failure carries `AutoReviewFailedError` / `AUTO_REVIEW_FAILED`. Neither executes the tool body. Completed agent execution does not imply that every requested action succeeded.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Reuse a browser RPC id.** A headless invocation is not a browser request. Faking that identity would couple authorization to a different transport and misdescribe the persisted source.
|
||||
|
||||
**Add a headless admission marker.** The existing user source already identifies task input. A transport-specific marker adds persisted metadata and reader rules without changing the required authority decision.
|
||||
|
||||
**Promote every user-role message.** Plugins also supply user-role context. The existing source discriminator distinguishes admitted user input from injected content.
|
||||
|
||||
**A live caller-approval channel.** Headless consumes stdin as one task. Approving a suspended call needs a bidirectional protocol with correlation, disconnect and cancellation handling, and an answerer lifecycle. A later invocation can instead supply scoped authorization and receive a fresh review, without claiming to approve the earlier call.
|
||||
|
||||
## Consequences
|
||||
|
||||
A supervisor can use Auto for implementation and gates, inspect structured denials, and resume with revision instructions. Native tools keep all supported tool bodies under review; direct Node effects inside PTC remain outside it. The reviewer remains a fallible model with full-access consequences, and the integration remains experimental and opt-in. Custom producers that use the user source are treated as the user; background context must use its own source.
|
||||
@@ -0,0 +1,33 @@
|
||||
# Agent Note: Headless 调用者对 Auto review 的授权
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-18-headless-auto-review.md) | 中文
|
||||
|
||||
## Problem
|
||||
|
||||
Headless、SDK 和 ACP 以 `source.kind: 'user'` 接纳任务。Auto 必须将这些任务识别为指令,而不要求浏览器 RPC 身份。仅有消息角色不足以判断:注入上下文也使用 user role,但声明自己的 source。
|
||||
|
||||
## Decision
|
||||
|
||||
[实验性 Auto 层](../../../../packages/experimental/auto-review/README.zh.md#headless-supervision)支持显式 headless 选择。默认 profile 保持不变。这扩展了[逐工具 Auto review](2026-08-28-auto-review.zh.md)仅限 Web 的范围;其风险分类、完全访问执行、PTC 排除项和生命周期仍具权威性。
|
||||
|
||||
Reviewer 将 user-source 文本归为 `user-instruction`,包括已有历史和选择 Auto 前接纳的任务。用户可以是人或监督调用者;两者都可定义任务,并明确授权 medium 风险动作、准确目标和范围。当前 child 的创建 prompt 和直接父级消息保留从属角色;继承的创建 prompt 与无法验证的创建 prompt 仍是事实。文件、约束、checkpoint、图片和工具结果不会获得指令权威。输入生产者负责来源归属;任务文本不能选择自己的 source。消息类型和持久字段保持不变。
|
||||
|
||||
Headless 在选择权限预设后应用显式 `--approval-policy`。恢复时省略两个选项会延续已保存的策略。传入 `--permission-mode auto --approval-policy never` 可显式选择无人值守 Auto。这保留结构化拒绝,并避免依赖人工应答者。`ask` 仍遵循[审批兜底决策](2026-09-24-auto-review-user-approval-fallback.zh.md);它不添加 CLI 审批传输。应用关闭保留已保存的 Auto 选择。恢复时需要该 integration,即使要选择其他预设也是如此;应用仍运行时移除插件则仍将存活 Session 迁移到 Full access。
|
||||
|
||||
JSON 投影在正常输出限制内包含已有的持久错误字段。最终拒绝携带 `AutoReviewDeniedError` / `AUTO_REVIEW_DENIED` 和可选 reviewer 详情;技术或解析失败携带 `AutoReviewFailedError` / `AUTO_REVIEW_FAILED`。两者都不执行工具 body。Agent 执行完成不代表每个请求动作都成功。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**复用浏览器 RPC id。** Headless 调用不是浏览器请求。伪造该身份会将授权耦合到另一种传输,并错误描述持久来源。
|
||||
|
||||
**添加 headless 入口标记。** 已有 user source 已标识任务输入。传输专属标记增加持久元数据和读取规则,却不改变所需的权威判断。
|
||||
|
||||
**提升每条 user-role 消息。** 插件也提供 user-role 上下文。已有 source 判别字段区分接纳的用户输入与注入内容。
|
||||
|
||||
**实时调用者审批通道。** Headless 将 stdin 作为一个任务消费。审批暂停的调用需要双向协议、请求关联、断连与取消处理,以及应答者生命周期。后续调用可以改为提供限定范围的授权并接受新审查,不声称批准了先前调用。
|
||||
|
||||
## Consequences
|
||||
|
||||
监督者可以使用 Auto 实现修改和运行检查、查看结构化拒绝,并通过修订指令恢复。原生工具让所有支持的工具 body 都经过审查;PTC 内的直接 Node 效果仍不在范围内。Reviewer 仍是可能出错的模型,执行具有完全访问权限的后果,integration 仍保持实验性与显式启用。使用 user source 的自定义生产者被视为用户;后台上下文必须使用自己的 source。
|
||||
@@ -1,19 +0,0 @@
|
||||
# Bilingual-pair consistency record for 2026-07-06-node-engine-floor.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-06-node-engine-floor.md
|
||||
/agent-note-raise-the-node-lts-engine-floor-to-22-19:
|
||||
en: 01f43f2b4c6bd4fa
|
||||
zh: 7f34e5b3c1c55419
|
||||
/agent-note-raise-the-node-lts-engine-floor-to-22-19/problem:
|
||||
en: d7bf1b1699ce0dc8
|
||||
zh: f10dfd0e4787bb44
|
||||
/agent-note-raise-the-node-lts-engine-floor-to-22-19/decision:
|
||||
en: 0051c0d8c1948f63
|
||||
zh: cbab7aa0f53f7200
|
||||
/agent-note-raise-the-node-lts-engine-floor-to-22-19/consequences:
|
||||
en: 0b9fbe932d9a8b1f
|
||||
zh: 642e19d06caecef2
|
||||
/agent-note-raise-the-node-lts-engine-floor-to-22-19/alternatives-considered:
|
||||
en: c433389b2064eaf9
|
||||
zh: 57de2af812def1c4
|
||||
@@ -9,8 +9,8 @@
|
||||
en: 3ecedc74cceced3b
|
||||
zh: f4708f1887d42c61
|
||||
/agent-note-ci-failover-runbook-hosted-pools-in-house-pool/decision:
|
||||
en: fbc8d8ef03350ac9
|
||||
zh: 7fc81bf75eaab0b7
|
||||
en: 4a76615718dbc8f0
|
||||
zh: f46b885a11d5dd1d
|
||||
/agent-note-ci-failover-runbook-hosted-pools-in-house-pool/decision/release-rehearsals-share-the-linux-switch:
|
||||
en: 01e74068e0a96375
|
||||
zh: aa811861ce30cb47
|
||||
@@ -24,8 +24,8 @@
|
||||
en: 2e3829fd72d6b1b0
|
||||
zh: 563f0abade7974f9
|
||||
/agent-note-ci-failover-runbook-hosted-pools-in-house-pool/capacity-during-failover:
|
||||
en: 22611ed4316adc7c
|
||||
zh: 29acd02b4a07313d
|
||||
en: 3dffe12ecf2356c2
|
||||
zh: 7e2385fcabab1398
|
||||
/agent-note-ci-failover-runbook-hosted-pools-in-house-pool/capacity-during-failover/switch-back:
|
||||
en: 63cf911019db8d2f
|
||||
zh: 5eb3903b0ceb30db
|
||||
|
||||
@@ -10,7 +10,7 @@ The three required Linux worker jobs in [CI](../../../../.github/workflows/ci.ym
|
||||
|
||||
## Decision
|
||||
|
||||
The three primary Linux jobs (`node-24`, `node-24-coverage`, `node-24-consumers`), the three `node-compat` matrix entries, and `all-checks-passed` resolve through `DSH_CI_FAILOVER_LINUX`; the native Windows jobs resolve through `DSH_CI_FAILOVER_WINDOWS`. A platform switch does not redirect the other platform. Set to `selfhosted` by a repository writer, the applicable trusted jobs select `vm-backup` or `dsh-win-ci`; the `blacksmith` value routes the participating jobs per the [blacksmith failover leg note](2026-09-09-blacksmith-failover-leg.md); unset or any other value retains the workflow-defined hosted fallbacks. Node compatibility jobs require a same-repository, non-fork head and a non-Dependabot author, use isolated runtime setup, and retain the `ubuntu-latest` fallback under unset and non-special values; the blacksmith branch carries none of those predicates. Under the `selfhosted` value, Linux failover bounds snapshot concurrency and skips hosted package-cache restores. The verdict follows its workers so it does not remain queued on an unavailable hosted pool. Each switch is writer-manageable repository state, not a merge, so it works while checks are red. The `serial / linux (self-hosted standby)` and `serial / windows (self-hosted standby)` lanes re-prove the complete unsharded aggregates on master pushes.
|
||||
The three primary Linux jobs (`node-24`, `node-24-coverage`, `node-24-consumers`), the two `node-compat` matrix entries, and `all-checks-passed` resolve through `DSH_CI_FAILOVER_LINUX`; the native Windows jobs resolve through `DSH_CI_FAILOVER_WINDOWS`. A platform switch does not redirect the other platform. Set to `selfhosted` by a repository writer, the applicable trusted jobs select `vm-backup` or `dsh-win-ci`; the `blacksmith` value routes the participating jobs per the [blacksmith failover leg note](2026-09-09-blacksmith-failover-leg.md); unset or any other value retains the workflow-defined hosted fallbacks. Node compatibility jobs require a same-repository, non-fork head and a non-Dependabot author, use isolated runtime setup, and retain the `ubuntu-latest` fallback under unset and non-special values; the blacksmith branch carries none of those predicates. Under the `selfhosted` value, Linux failover bounds snapshot concurrency and skips hosted package-cache restores. The verdict follows its workers so it does not remain queued on an unavailable hosted pool. Each switch is writer-manageable repository state, not a merge, so it works while checks are red. The `serial / linux (self-hosted standby)` and `serial / windows (self-hosted standby)` lanes re-prove the complete unsharded aggregates on master pushes.
|
||||
|
||||
The [superseded-CI cancellation policy](../../../../.github/workflows/ci.yml) governs master pushes and manual runs in the same workflow/ref group, including standby drills. Rapid master updates can starve a drill before it reaches a verdict. Use the latest completed standby verdict and check its age and commit before treating it as readiness evidence; a cancelled or merely scheduled run is not proof of readiness.
|
||||
|
||||
@@ -40,7 +40,7 @@ The two switches are independent: flip only the one whose platform is degraded.
|
||||
|
||||
## Capacity during failover
|
||||
|
||||
Capacity includes the master standby, main-CI jobs, and three release-rehearsal jobs for each eligible PR or master push while the Linux switch is set. Each trusted PR also adds three Node compatibility jobs at gate concurrency one, including the build-backed Node 22 leg and cold temporary runtime downloads. The release rehearsal workflows cancel superseded runs within each workflow/ref group under the [cancellation policy](../../../../.github/workflows/ci.yml); different refs can still add concurrent build, pack, and install load. Check current CPU, memory, disk, and queue pressure before extending self-hosted operation; extra registrations on this VM add scheduling slots, not machine resources. Do not infer spare capacity from the standby alone. When host resources permit extra registrations, use an org registration token (org Settings → Actions → Runners → New runner). Clone an existing runner directory **excluding its identity files** — `rsync -a --exclude '.runner*' --exclude '.credentials*' --exclude '_diag' --exclude '_work' <src>/ <dst>/` (the globs also catch `.runner_migrated`/`.credentials_migrated`, which GitHub writes on migrated runners and which equally trigger the already-configured refusal) — then run `config.sh` (copying `.runner`/`.credentials` verbatim makes it refuse with "already configured"), and **start the listener**: `sudo ./svc.sh install ubuntu && sudo ./svc.sh start`. Registration alone leaves the runner offline; a started service adds a scheduling slot, not CPU or memory.
|
||||
Capacity includes the master standby, main-CI jobs, and three release-rehearsal jobs for each eligible PR or master push while the Linux switch is set. Each trusted PR also adds two Node compatibility jobs at gate concurrency one, including the build-backed Node 24.9 leg and cold temporary runtime downloads. The release rehearsal workflows cancel superseded runs within each workflow/ref group under the [cancellation policy](../../../../.github/workflows/ci.yml); different refs can still add concurrent build, pack, and install load. Check current CPU, memory, disk, and queue pressure before extending self-hosted operation; extra registrations on this VM add scheduling slots, not machine resources. Do not infer spare capacity from the standby alone. When host resources permit extra registrations, use an org registration token (org Settings → Actions → Runners → New runner). Clone an existing runner directory **excluding its identity files** — `rsync -a --exclude '.runner*' --exclude '.credentials*' --exclude '_diag' --exclude '_work' <src>/ <dst>/` (the globs also catch `.runner_migrated`/`.credentials_migrated`, which GitHub writes on migrated runners and which equally trigger the already-configured refusal) — then run `config.sh` (copying `.runner`/`.credentials` verbatim makes it refuse with "already configured"), and **start the listener**: `sudo ./svc.sh install ubuntu && sudo ./svc.sh start`. Registration alone leaves the runner offline; a started service adds a scheduling slot, not CPU or memory.
|
||||
|
||||
|
||||
### Switch back
|
||||
|
||||
@@ -10,7 +10,7 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
三个主要 Linux 作业(`node-24`、`node-24-coverage`、`node-24-consumers`)、三个 `node-compat` 矩阵条目和 `all-checks-passed` 通过 `DSH_CI_FAILOVER_LINUX` 解析;原生 Windows 作业通过 `DSH_CI_FAILOVER_WINDOWS` 解析。一个平台的开关不会重定向另一个平台。仓库写者将变量设为 `selfhosted` 时,适用的可信作业选择 `vm-backup` 或 `dsh-win-ci`;`blacksmith` 取值按 [blacksmith 故障切换支路笔记](2026-09-09-blacksmith-failover-leg.zh.md) 路由参与切换的作业;未设置或任何其它值保留工作流定义的托管回退。Node 兼容性作业要求同仓库且非 fork 的头部以及非 Dependabot 作者,使用隔离运行时设置,并在未设置与非特殊值下保留 `ubuntu-latest` 回退;blacksmith 分支不带上述任何条件。在 `selfhosted` 取值下,Linux 故障切换会限制快照并发,并跳过托管软件包缓存恢复。判定作业跟随工作作业,避免继续在不可用的托管池排队。每个开关都是写者可管理的仓库状态而非一次合并,因此在检查失败时仍然有效。`serial / linux (self-hosted standby)` 与 `serial / windows (self-hosted standby)` 通道在 master 推送上重新验证完整的未分片聚合流程。
|
||||
三个主要 Linux 作业(`node-24`、`node-24-coverage`、`node-24-consumers`)、两个 `node-compat` 矩阵条目和 `all-checks-passed` 通过 `DSH_CI_FAILOVER_LINUX` 解析;原生 Windows 作业通过 `DSH_CI_FAILOVER_WINDOWS` 解析。一个平台的开关不会重定向另一个平台。仓库写者将变量设为 `selfhosted` 时,适用的可信作业选择 `vm-backup` 或 `dsh-win-ci`;`blacksmith` 取值按 [blacksmith 故障切换支路笔记](2026-09-09-blacksmith-failover-leg.zh.md) 路由参与切换的作业;未设置或任何其它值保留工作流定义的托管回退。Node 兼容性作业要求同仓库且非 fork 的头部以及非 Dependabot 作者,使用隔离运行时设置,并在未设置与非特殊值下保留 `ubuntu-latest` 回退;blacksmith 分支不带上述任何条件。在 `selfhosted` 取值下,Linux 故障切换会限制快照并发,并跳过托管软件包缓存恢复。判定作业跟随工作作业,避免继续在不可用的托管池排队。每个开关都是写者可管理的仓库状态而非一次合并,因此在检查失败时仍然有效。`serial / linux (self-hosted standby)` 与 `serial / windows (self-hosted standby)` 通道在 master 推送上重新验证完整的未分片聚合流程。
|
||||
|
||||
[被取代 CI 的取消策略](../../../../.github/workflows/ci.yml) 管理同一工作流/引用组内的 master 推送和手动运行,包括热备演练。master 快速更新可能让演练因反复被取消而始终无法得出结论。判断就绪状态时,使用最近一次已完成的热备结论,并核对其时间和提交;已取消或仅被调度的运行不构成就绪证据。
|
||||
|
||||
@@ -40,7 +40,7 @@ Status: implemented
|
||||
|
||||
## 切换期间的容量
|
||||
|
||||
Linux 开关启用期间,容量需覆盖 master 热备、主 CI 作业,以及每个符合条件的 PR 或 master 推送的三个发布演练作业。每个可信 PR 还会增加三个门禁并发度为一的 Node 兼容性作业,包括需要构建的 Node 22 条目和冷临时运行时下载。发布演练工作流依据[取消策略](../../../../.github/workflows/ci.yml)取消各工作流/引用组内被取代的运行;不同引用仍可能增加并发构建、打包和安装负载。延长自托管运行前,检查当前 CPU、内存、磁盘和队列压力;同一虚拟机上新增注册只增加调度槽位,不增加机器资源。不能只依据热备负载推断空闲容量。主机资源允许增加注册实例时,使用组织级注册 token(组织 Settings → Actions → Runners → New runner)。复制现有 runner 目录时**必须排除身份文件**——`rsync -a --exclude '.runner*' --exclude '.credentials*' --exclude '_diag' --exclude '_work' <src>/ <dst>/`(通配同时排除 `.runner_migrated`/`.credentials_migrated`——GitHub 会在迁移过的运行器上写入这些文件,它们同样会触发 already-configured 拒绝)——再跑 `config.sh`(原样拷贝 `.runner`/`.credentials` 会使其以 "already configured" 拒绝),然后**启动监听器**:`sudo ./svc.sh install ubuntu && sudo ./svc.sh start`。仅注册不会上线;启动服务增加的是调度槽位,而非 CPU 或内存。
|
||||
Linux 开关启用期间,容量需覆盖 master 热备、主 CI 作业,以及每个符合条件的 PR 或 master 推送的三个发布演练作业。每个可信 PR 还会增加两个门禁并发度为一的 Node 兼容性作业,包括需要构建的 Node 24.9 条目和冷临时运行时下载。发布演练工作流依据[取消策略](../../../../.github/workflows/ci.yml)取消各工作流/引用组内被取代的运行;不同引用仍可能增加并发构建、打包和安装负载。延长自托管运行前,检查当前 CPU、内存、磁盘和队列压力;同一虚拟机上新增注册只增加调度槽位,不增加机器资源。不能只依据热备负载推断空闲容量。主机资源允许增加注册实例时,使用组织级注册 token(组织 Settings → Actions → Runners → New runner)。复制现有 runner 目录时**必须排除身份文件**——`rsync -a --exclude '.runner*' --exclude '.credentials*' --exclude '_diag' --exclude '_work' <src>/ <dst>/`(通配同时排除 `.runner_migrated`/`.credentials_migrated`——GitHub 会在迁移过的运行器上写入这些文件,它们同样会触发 already-configured 拒绝)——再跑 `config.sh`(原样拷贝 `.runner`/`.credentials` 会使其以 "already configured" 拒绝),然后**启动监听器**:`sudo ./svc.sh install ubuntu && sudo ./svc.sh start`。仅注册不会上线;启动服务增加的是调度槽位,而非 CPU 或内存。
|
||||
|
||||
|
||||
### 切回
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
en: e5c984fa32cc5e69
|
||||
zh: 25d5742b2106d830
|
||||
/agent-note-pr-artifact-builds-and-aggregate-typescript-checks/decision:
|
||||
en: af93d8849bb8831a
|
||||
zh: 53e15e801ab844da
|
||||
en: 26a903fdfeffcea7
|
||||
zh: f6ea094ec32a7b14
|
||||
/agent-note-pr-artifact-builds-and-aggregate-typescript-checks/alternatives-considered:
|
||||
en: f61da066f9ddcd62
|
||||
zh: 816fd6e0063017cd
|
||||
|
||||
@@ -10,7 +10,7 @@ PR jobs need the same compiled packages for benchmarks, compatibility, packaging
|
||||
|
||||
## Decision
|
||||
|
||||
Required Linux consumer and native Windows build jobs retain normal builds, including both complete aggregate programs. Public `build` and `typecheck` keep the same checks. [Node version ownership](2026-07-06-node-engine-floor.md) and [native Windows ownership](2026-08-08-native-windows-pull-request-ci.md) remain in force.
|
||||
Required Linux consumer and native Windows build jobs retain normal builds, including both complete aggregate programs. Public `build` and `typecheck` keep the same checks. [Node version ownership](2026-10-15-node-24-runtime-floor.md) and [native Windows ownership](2026-08-08-native-windows-pull-request-ci.md) remain in force.
|
||||
|
||||
Checked PR artifact builds compile every existing project reference from each compiler face with one serial `tsc -b` process, then run the ordinary Typert, bundling, Desktop, and Web stages. The helper reads the existing Host and Client aggregates rather than maintaining another package list. It omits only each root's additional test and script program; package diagnostics, emitted outputs, and Host-before-Client ordering remain required. Non-PR release and real-API workflow builds retain their normal checks. That artifact build is `pnpm run build:artifacts`; [the build split](2026-10-11-build-artifacts-check-split.md) owns its steps and scheduling.
|
||||
|
||||
@@ -20,9 +20,9 @@ The Python executable builder selects `build:artifacts` artifact compilation wit
|
||||
|
||||
PR artifact builds in CI invoke the root manifest's `build:artifacts`; a PR that edits these root manifest scripts activates the separate native-addon platform matrix, a trade-off [the build split](2026-10-11-build-artifacts-check-split.md) records. The vendor rehearsal and benchmark lanes invoke the internal build helpers directly.
|
||||
|
||||
Job inventory, runner selectors, and benchmark sample counts stay fixed; gate and worker ceilings do not increase. The three compatibility cases run one job at a time; the two-target Python runtime matrix starts after the keyless Python SDK job succeeds. Four Linux lanes, the Python SDK, three Windows lanes, and one compatibility job permit at most nine worker jobs initially. Once the SDK job finishes, the remaining seven independent lanes, one compatibility job, and two runtime targets permit at most ten. The runtime plan and wheel stages precede those targets and each use one job. This permits runtime validation to overlap the Node 22 build within the original ten-job ceiling. Coverage partitions reuse compact file-cost metadata within their platform and runner pool. Parallel release packing uses pnpm's existing bounded scheduler; serial publication keeps family order.
|
||||
Job inventory, runner selectors, and benchmark sample counts stay fixed; gate and worker ceilings do not increase. The two compatibility cases run one job at a time; the two-target Python runtime matrix starts after the keyless Python SDK job succeeds. Four Linux lanes, the Python SDK, three Windows lanes, and one compatibility job permit at most nine worker jobs initially. Once the SDK job finishes, the remaining seven independent lanes, one compatibility job, and two runtime targets permit at most ten. The runtime plan and wheel stages precede those targets and each use one job. This permits runtime validation to overlap the Node 24.9 build within the original ten-job ceiling. Coverage partitions reuse compact file-cost metadata within their platform and runner pool. Parallel release packing uses pnpm's existing bounded scheduler; serial publication keeps family order.
|
||||
|
||||
Each Node version runs the same seven source compatibility specs in one serial Vitest invocation. Both file parallelism and the CLI worker limit are fixed to one, and the gate sets `VITEST_MAX_WORKERS=1` because that environment variable overrides Vitest's CLI limit. Files retain fork isolation; the build-backed Node 22 lazy-search smoke keeps its separate build dependency. Three paired local samples preserve all 253 assertion outcomes and reduce startup-only median time from 11.52 to 8.69 seconds, without increasing owned descendant-process peak.
|
||||
Each Node version runs the same eight source compatibility specs in one serial Vitest invocation. Both file parallelism and the CLI worker limit are fixed to one, and the gate sets `VITEST_MAX_WORKERS=1` because that environment variable overrides Vitest's CLI limit. Files retain fork isolation; the build-backed Node 24.9 lazy-search smoke keeps its separate build dependency. The original seven-spec paired samples preserve all 253 assertion outcomes and reduce startup-only median time from 11.52 to 8.69 seconds, without increasing owned descendant-process peak.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ PR job 需要相同的编译包来运行 benchmark、兼容性、打包和运行
|
||||
|
||||
## Decision
|
||||
|
||||
必需的 Linux consumer 与原生 Windows build job 保留普通构建,包括两个完整 aggregate program。公开的 `build` 和 `typecheck` 保留相同检查。[Node 版本职责](2026-07-06-node-engine-floor.zh.md)与[原生 Windows 职责](2026-08-08-native-windows-pull-request-ci.zh.md)仍然有效。
|
||||
必需的 Linux consumer 与原生 Windows build job 保留普通构建,包括两个完整 aggregate program。公开的 `build` 和 `typecheck` 保留相同检查。[Node 版本职责](2026-10-15-node-24-runtime-floor.zh.md)与[原生 Windows 职责](2026-08-08-native-windows-pull-request-ci.zh.md)仍然有效。
|
||||
|
||||
带检查的 PR 产物构建通过一个串行 `tsc -b` 进程编译各 compiler face 的全部现有 project reference,然后运行普通 Typert、bundling、Desktop 和 Web 阶段。helper 读取现有 Host 与 Client aggregate,而不维护另一份包清单。它只省略各 root 附加的测试和脚本 program;包诊断、emit 输出及 Host 先于 Client 的顺序仍然必需。非 PR 的 release 和真实 API 工作流构建保留普通检查。该产物构建即 `pnpm run build:artifacts`,其步骤与调度见[构建拆分](2026-10-11-build-artifacts-check-split.zh.md)。
|
||||
|
||||
@@ -20,9 +20,9 @@ Python executable builder 在验证 runtime closure 后,通过 `--build-artifa
|
||||
|
||||
CI 的 PR 产物构建调用 root manifest 中的 `build:artifacts`;修改 root manifest 中这些 script 的 PR 会激活独立的 native-addon 平台矩阵,[构建拆分](2026-10-11-build-artifacts-check-split.zh.md)记录这一取舍。vendor 演练与 benchmark lane 直接调用内部 build helper。
|
||||
|
||||
job 清单、runner 选择和 benchmark sample 数量保持固定;gate 与 worker 上限不提高。三个 compatibility case 每次运行一个 job;双目标 Python runtime 矩阵在 keyless Python SDK job 成功后启动。四个 Linux lane、Python SDK、三个 Windows lane 和一个 compatibility job,初始最多占用九个 worker job。SDK job 完成后,剩余七个独立 lane、一个 compatibility job 和两个 runtime target,最多占用十个 job。runtime plan 与 wheel 阶段先于这些 target,各占一个 job。这允许 runtime 验证与 Node 22 构建重叠,同时保持原有十个 job 的上限。覆盖率分区在各自的平台和 runner pool 内复用精简的文件成本元数据。并行 release 打包使用 pnpm 现有的有界调度器;串行发布保持 family 顺序。
|
||||
job 清单、runner 选择和 benchmark sample 数量保持固定;gate 与 worker 上限不提高。两个 compatibility case 每次运行一个 job;双目标 Python runtime 矩阵在 keyless Python SDK job 成功后启动。四个 Linux lane、Python SDK、三个 Windows lane 和一个 compatibility job,初始最多占用九个 worker job。SDK job 完成后,剩余七个独立 lane、一个 compatibility job 和两个 runtime target,最多占用十个 job。runtime plan 与 wheel 阶段先于这些 target,各占一个 job。这允许 runtime 验证与 Node 24.9 构建重叠,同时保持原有十个 job 的上限。覆盖率分区在各自的平台和 runner pool 内复用精简的文件成本元数据。并行 release 打包使用 pnpm 现有的有界调度器;串行发布保持 family 顺序。
|
||||
|
||||
每个 Node 版本在一次串行 Vitest 调用中运行相同的七个 source compatibility spec。文件并行和 CLI worker 上限均固定为一,gate 还设置 `VITEST_MAX_WORKERS=1`,因为该环境变量会覆盖 Vitest 的 CLI 上限。文件保持 fork 隔离;依赖构建的 Node 22 lazy-search smoke 保留独立的 build 依赖。三对本地样本保持全部 253 个 assertion outcome,将启动开销比较的 median 从 11.52 秒降至 8.69 秒,且不增加所拥有 descendant process 的峰值。
|
||||
每个 Node 版本在一次串行 Vitest 调用中运行相同的八个 source compatibility spec。文件并行和 CLI worker 上限均固定为一,gate 还设置 `VITEST_MAX_WORKERS=1`,因为该环境变量会覆盖 Vitest 的 CLI 上限。文件保持 fork 隔离;依赖构建的 Node 24.9 lazy-search smoke 保留独立的 build 依赖。原来七个 spec 的成对样本保持全部 253 个 assertion outcome,将启动开销比较的 median 从 11.52 秒降至 8.69 秒,且不增加所拥有 descendant process 的峰值。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
+12
-12
@@ -12,23 +12,23 @@
|
||||
en: a15c7c13f167e5ac
|
||||
zh: 63bcc2ef60c2f344
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/commands:
|
||||
en: 174241330463841d
|
||||
zh: 7fe361c07e634053
|
||||
en: cc797cf688f8de64
|
||||
zh: 5d997b686a435cc5
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/steps-and-dependencies:
|
||||
en: 43e78e255306cd43
|
||||
zh: a741d5ef1c7379f3
|
||||
en: c6c77bb1c7acf5e3
|
||||
zh: d53c2358bef42502
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/scheduling-failure-and-output:
|
||||
en: 4b324f92307d3691
|
||||
zh: e0cf4411a747f7e5
|
||||
en: 6a1b4ed0d9910133
|
||||
zh: f32c6882208ded71
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/callers:
|
||||
en: 55c55eea07fdb459
|
||||
zh: ab78cdfa4d04a5e7
|
||||
en: 895968956b86a9d2
|
||||
zh: 5218d9ab8c60a53a
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/measurements:
|
||||
en: b3a753ac2aab6073
|
||||
zh: 19cbd0e32751896f
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/completeness-and-lossless-evidence:
|
||||
en: 3cd40e0db9a2c89a
|
||||
zh: 3f708be239779d0a
|
||||
en: bed9a43f84716794
|
||||
zh: 300ba712a7812a99
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/decision/intentional-trade-offs-not-lossless:
|
||||
en: 14370bc5b54e1278
|
||||
zh: 7ac933092d3e9954
|
||||
@@ -36,5 +36,5 @@
|
||||
en: 54905b912d17c74e
|
||||
zh: 86a298004c8024e3
|
||||
/agent-note-split-build-artifacts-and-build-check-with-build-running-both-concurrently/consequences:
|
||||
en: 083467f68d0f03ed
|
||||
zh: 93400e9994b8c645
|
||||
en: 48a8cbb9e8fee654
|
||||
zh: ece1aeaee843b48a
|
||||
@@ -18,41 +18,25 @@ Each command does only its own job.
|
||||
|
||||
| Command | Responsibility | Steps | Scheduling | Client build record |
|
||||
|---|---|---|---|---|
|
||||
| `pnpm run build:artifacts` | Produces every artifact of `build`; package projects are fully type-checked as usual, the two aggregate root programs are not checked | native → host-src → host-bundle → desktop-bundle → client-src → client-bundle → web | One linear chain | Deleted at start, written after every step passes |
|
||||
| `pnpm run build:check` | Type-checks only the two aggregate root programs | host-check, client-check | Both steps concurrent, no prerequisite steps | Neither read nor written |
|
||||
| `pnpm run build:artifacts` | Produces every artifact of `build`; package projects are fully type-checked as usual, the Host and Client aggregate programs are not checked | [Artifact order](../../../../docs/development.md#typescript-project-layout) | Dependency order | Deleted at start, written after every step passes |
|
||||
| `pnpm run build:check` | Type-checks only the Host and Client aggregate programs | host-check, client-check | Host and Client checks concurrent, no prerequisite steps | Neither read nor written |
|
||||
| `pnpm run build` | Every step of `build:artifacts` and `build:check` | The dependency graph below | Concurrent through the run-gates scheduler | Deleted at start, written after every step passes |
|
||||
|
||||
`build:check` neither detects, rebuilds, nor validates `build:artifacts` outputs; when prerequisite outputs are missing or stale, the compiler's own diagnostics (for example TS6305 and TS2307) report it, and no command adds prerequisite checks, guards, or fallbacks. `build:artifacts` produces the same artifacts and record as `build` and replaces `--artifacts-only`; that flag and the `libraries` mode of `compile-referenced-projects.ts` are removed, and the Python executable builder uses `--build-artifacts`. `build:official` remains `tsx scripts/build.ts --profile official` and runs the complete `build`.
|
||||
|
||||
### Steps and dependencies
|
||||
|
||||
| Step | Command | `needs` in `build` |
|
||||
|---|---|---|
|
||||
| native | `pnpm run build:native-system` | — |
|
||||
| host-src | `node --max-old-space-size=4096 <tsc> -b <references of tsconfig.host.json>` | native |
|
||||
| host-bundle | `tsdown --config-loader native --env.DSH_BUILD_FACE host` (runs Typert) | host-src |
|
||||
| desktop-bundle | `pnpm --filter @deepseek-ai/dsh-desktop run bundle` | host-bundle |
|
||||
| client-src | `<tsc> -b <references of tsconfig.client.json>` | desktop-bundle |
|
||||
| client-bundle | `tsdown --config-loader native --env.DSH_BUILD_FACE client` | client-src |
|
||||
| web | `pnpm run build:web` | client-bundle |
|
||||
| host-check | `node --max-old-space-size=4096 <tsc> -p tsconfig.host.json` | host-src |
|
||||
| client-check | `<tsc> -p tsconfig.client.json` | client-src |
|
||||
[Source-first ordering](2026-10-18-source-first-metadata-builds.md) prepares metadata before package compilation. [Development](../../../../docs/development.md#typescript-project-layout) and `buildGates()` own the current step graph; each aggregate check starts after its own package declarations exist.
|
||||
|
||||
```text
|
||||
native → host-src ─┬─ host-check ───────────────────────────────────────────────────────┐
|
||||
└─ host-bundle → desktop-bundle → client-src ─┬─ client-check ───────┤ → record
|
||||
└─ client-bundle → web ┘
|
||||
```
|
||||
|
||||
- client-src must follow host-bundle: Client packages import `@deepseek-ai/dsh-*/remote`, whose types `lib/typert.remote-client.d.ts` Typert writes, inside Host tsdown in these scopes and through `scripts/generate-typert.ts` in the typecheck scope. host-check reads no Typert output; clean serial builds have always finished the Host aggregate check before Host tsdown.
|
||||
- The artifact chain keeps the two non-data edges native → host-src and desktop-bundle → client-src; only the two check steps attach to the chain in parallel.
|
||||
- Client compilation reads generated Remote declarations from the source-only metadata pass. Desktop bundling remains before Client compilation; changing that non-data edge requires separate evidence.
|
||||
- The artifact chain keeps the two non-data edges native → host-src and desktop-bundle → client-src; aggregate checks attach after their package compilation; native and metadata generation may run together.
|
||||
- [`compile-referenced-projects.ts`](../../../../scripts/compile-referenced-projects.ts) owns the package-library step list `libraryBuildSteps`, the references argument construction `referencedProjectsCompilerArgs` (relative paths keep the command below Windows' command-line limit), and the aggregate-check arguments `aggregateCheckArgs`. The vendor release rehearsal (`host-libraries`) and CI benchmarks (`--noCheck`) run the same step list serially.
|
||||
- The check steps use project mode (`tsc -p`): it reads the declarations that references already emitted and compiles no reference, writing incremental state to the aggregates' existing `tsconfig.{host,client}.tsbuildinfo`. `build` and `build:check` run the same command, so their diagnostics are identical.
|
||||
|
||||
### Scheduling, failure, and output
|
||||
|
||||
- [`scripts/build.ts`](../../../../scripts/build.ts) owns the step table and the three scopes and executes them through `runAggregate` from [`scripts/run-gates.ts`](../../../../scripts/run-gates.ts): each step is a gate with `needs`, and shell-free spawning, per-step buffered output, fail-fast process-tree termination, Windows `taskkill`, and host signal forwarding come from the run-gates implementation. `build:artifacts` is `tsx scripts/build.ts --scope artifacts`, `build:check` is `--scope check`, and `build` omits `--scope` and runs both as one graph; `--profile` applies only to artifact scopes, and combining it with `--scope check` fails argument parsing.
|
||||
- The concurrency limit is `DSH_GATE_CONCURRENCY`, defaulting to the available CPU count; the dependency graph is at most 3 wide (client-check and client-bundle can start while host-check is still running). Master serial reference lanes set `DSH_GATE_CONCURRENCY=1`, so `build` runs serially in topological order there, consistent with the [serial reference decision](2026-07-21-serial-cross-platform-ci-reference.md) and without workflow edits.
|
||||
- The concurrency limit is `DSH_GATE_CONCURRENCY`, using the repository worker budget by default; aggregate checks may overlap later artifact steps. Master serial reference lanes set `DSH_GATE_CONCURRENCY=1`, so `build` runs serially in topological order there, consistent with the [serial reference decision](2026-07-21-serial-cross-platform-ci-reference.md) and without workflow edits.
|
||||
- By default (locally), every dependent of a failed step is recorded as skipped, while running steps and steps unrelated to the failure run to completion; the executed set depends only on step outcomes, so one run reports every independent type error. With `DSH_GATE_FAIL_FAST=1` (set in every building PR lane), the first failure terminates the process trees of the other running steps and records every unfinished step as skipped.
|
||||
- A failed step prints `== FAILED <step> ==`, its command, the exit reason, and the step's complete buffered output, and the final summary lists failed and skipped steps; a passing step prints only its PASS line, and `DSH_GATE_VERBOSE=1` prints all output. The exit code is 1 when any step failed or was skipped, and the client build record is not written. Every step inherits the public Client environment selected by `clientBuildProcessEnvironment`.
|
||||
- In CI, `build` itself is one gate of an outer run-gates aggregate; outer fail-fast termination propagates to the inner scheduler. `check:node-compat` and `check:ci:lint:contracts-ready` already nest the same way.
|
||||
@@ -63,11 +47,11 @@ native → host-src ─┬─ host-check ─────────────
|
||||
|---|---|---|
|
||||
| `build`, `build:official`, `Makefile` `build`, `scripts/publish-npm-baseline.ts`, `apps/desktop/scripts/package-target.ts`, `scripts/dev-bundle-registry.ts`, `release-publish.yml`, `sandbox.yml`, and the non-PR branches of `e2e.yml` and `release.yml` | `build` / `build:official` | Yes |
|
||||
| `test:web`, `test:web:refresh`, `test:web:perf`, `test:web:stress`, `apps/desktop` `test:updates:local`, `test:bench` / `build:bench` | `build`; `build:bench` runs `build:native-system`, `build:lib`, and the benchmark tsdown | Yes |
|
||||
| `typecheck` (run by the pre-push hook) | `build.ts --scope typecheck`: host-src beside host-typert (the bundle-free, source-only Typert pass `generate-typert.ts`) → client-src, with `tsc -b tsconfig.{host,client}.json` after each face's package compilation; no bundle | Yes |
|
||||
| `typecheck` (run by the pre-push hook) | `build.ts --scope typecheck`: host-typert (the source-only Typert pass `generate-typert.ts`) → package compilation, with `tsc -b tsconfig.{host,client}.json` after each face's package compilation; no bundle | Yes |
|
||||
| `dev:web` ([`scripts/dev-web.ts`](../../../../scripts/dev-web.ts)), `dev:desktop` (`apps/desktop/scripts/dev.ts`) | `build:artifacts` | None before launch; after `dev:web` launches, its `tsc -b tsconfig.client.json --watch` keeps checking the Client aggregate root program |
|
||||
| Prerequisite step of `lint`, `lint:fix`, `doc-typecheck`, and the CI Typert contracts gate | Host package compilation beside the bundle-free Typert pass (`compile-referenced-projects.ts host-contracts`), without any bundle | None |
|
||||
| Prerequisite step of `lint`, `lint:fix`, `doc-typecheck`, and the CI Typert contracts gate | Source-only Typert generation before Host package compilation (`compile-referenced-projects.ts host-contracts`), without any bundle | None |
|
||||
| CI node 24 / snapshots and artifacts (`check:ci:consumers`), windows node 24 / build & observational (`check:ci:windows-blocking`) | `build` | Yes, the two required lanes |
|
||||
| CI node-compat 22.19 | `build:artifacts` (through run-gates) | None |
|
||||
| CI node-compat 24.9 | `build:artifacts` (through run-gates) | None |
|
||||
| CI python runtime | `build-exe-for-python-sdk.ts --build-artifacts` | None |
|
||||
| PR rehearsals in `e2e.yml` and `release.yml` | `pnpm run build:artifacts --profile official` | None |
|
||||
| `build-preview-cloudflare.yml` | `pnpm run build:artifacts` | None |
|
||||
@@ -114,7 +98,7 @@ The serial branch sample overlapped other work on the host (742 s of foreign CPU
|
||||
| Package source type error | host-src / client-src | Not reported (reads existing declarations) | host-src / client-src | Reported | Reported |
|
||||
| Type error only in an aggregate root program's tests, scripts, benchmarks, or website | Not reported | host-check / client-check | host-check / client-check | Reported | consumers, windows-build |
|
||||
| Package tsconfig configuration error | host-src / client-src | Not reported (`tsc -p` does not validate referenced projects' tsconfig options or syntax) | host-src / client-src | Reported | Reported |
|
||||
| Typert analysis error | host-bundle | Not reported | host-bundle | Reported (through host-typert) | Reported |
|
||||
| Typert analysis error | host-typert | Not reported | host-typert | Reported (through host-typert) | Reported |
|
||||
| Missing prerequisite output | — | Compiler diagnostic (TS6305, TS2307) | — | — | — |
|
||||
|
||||
Negative controls: after a type error (TS2322) is added to `scripts/pnpm-invocation.spec.ts`, which only the Host aggregate root program contains, `build:artifacts` exits 0; `build:check` exits 1 with the Host aggregate check failing and the Client aggregate check passing; `build` exits 1 with 8 of 9 steps passing and 1 failing, and writes no client build record. In a scratch fixture whose referenced project's tsconfig has an invalid `target`, a JSON syntax error, or an unknown compiler option, `tsc -p` exits 0, while `tsc -b` reports TS6046, TS5092 with TS1012, and TS5023 respectively and exits 1; when the referenced project lacks `composite`, both report TS6306.
|
||||
@@ -150,8 +134,8 @@ CI evidence: on PR #6005 both required lanes that run the concurrent `pnpm run b
|
||||
|
||||
Development iterations can skip the two aggregate root programs with `build:artifacts` (51.6% faster cold than master's `build`) and complete the checks with `build:check` or `build` before declaring work complete. `build` stays complete: the Host aggregate check is off the critical path, the Client aggregate check runs alongside the Client bundle and web, and a cold build is 46.7% faster than master (including the lossless speed-ups note's changes; medians of 2). The costs are a higher memory peak, a little extra CPU from concurrent contention, less passing output by default, and several development and prerequisite commands that do not check test types (see the trade-offs above).
|
||||
|
||||
Removing or changing `build` in the two required lanes, consumers and windows-build, requires first transferring both aggregate checks to another blocking owner. The typecheck scope and the lint prerequisites generate Typert contracts without bundling ([local gate speed-ups](2026-10-12-local-gate-speedups.md)); the artifact scopes keep generating them inside Host tsdown.
|
||||
Removing or changing `build` in the two required lanes, consumers and windows-build, requires first transferring both aggregate checks to another blocking owner. The typecheck scope and the lint prerequisites generate Typert contracts without bundling ([local gate speed-ups](2026-10-12-local-gate-speedups.md)); artifact scopes use [source-first metadata ordering](2026-10-18-source-first-metadata-builds.md).
|
||||
|
||||
client-src's dependency on host-bundle guarantees the "Host and Remote artifacts complete before Client compilation" ordering that [Typert Remote method calls](../architecture/2026-08-02-typert-remote-method-calls.md) describes.
|
||||
host-src depends on host-typert, so generated Remote declarations precede package compilation. [Typert Remote method calls](../architecture/2026-08-02-typert-remote-method-calls.md) describes the emitted contributions.
|
||||
|
||||
Verification: `scripts/build.spec.ts` covers the type-stripping check, rejection of the removed flag and invalid scopes, the conflict between `--profile` and `--scope check`, scope selection, step dependencies, and the environment; `compile-referenced-projects.spec.ts` covers project mode checking only the aggregate program, the diagnostic for missing declarations, and `tsc -b --dry` accepting the tsbuildinfo that `tsc -p` writes; failure propagation, output buffering, and process-tree termination come from the run-gates scheduler and its tests; `run-gates.spec.ts`, `ci-workflow.spec.ts`, `preview-workflow.spec.ts`, `build-exe-for-python-sdk.spec.ts`, `dev-web.spec.ts`, and `tests/ci-release-selfhosted.spec.ts` pin each caller's command.
|
||||
@@ -18,41 +18,25 @@ Status: implemented
|
||||
|
||||
| 命令 | 职责 | 步骤 | 调度 | client build record |
|
||||
|---|---|---|---|---|
|
||||
| `pnpm run build:artifacts` | 产出 `build` 的全部产物;包 project 照常完整类型检查,不检查两个聚合根程序 | native → host-src → host-bundle → desktop-bundle → client-src → client-bundle → web | 一条线性链 | 开始时删除,全部通过后写入 |
|
||||
| `pnpm run build:check` | 只类型检查两个聚合根程序 | host-check、client-check | 两步并发,无前置步骤 | 不读写 |
|
||||
| `pnpm run build:artifacts` | 产出 `build` 的全部产物;包 project 照常完整类型检查,不检查Host 与 Client 聚合程序 | [产物顺序](../../../../docs/development.zh.md#typescript-project-layout) | 依赖顺序 | 开始时删除,全部通过后写入 |
|
||||
| `pnpm run build:check` | 只类型检查Host 与 Client 聚合程序 | host-check、client-check | Host 与 Client 检查并发,无前置步骤 | 不读写 |
|
||||
| `pnpm run build` | `build:artifacts` 与 `build:check` 的全部步骤 | 下文依赖图 | run-gates 调度器并发执行 | 开始时删除,全部通过后写入 |
|
||||
|
||||
`build:check` 不检测、不重建、也不校验 `build:artifacts` 的产物;前置产物缺失或过期时,由编译器自身的诊断(例如 TS6305、TS2307)报告,任何命令都不增加前置检查、守卫或回退逻辑。`build:artifacts` 产出的产物与 record 和 `build` 相同,并取代 `--artifacts-only`;该参数与 `compile-referenced-projects.ts` 的 `libraries` 模式已删除,Python executable builder 使用 `--build-artifacts`。`build:official` 仍是 `tsx scripts/build.ts --profile official`,执行完整的 `build`。
|
||||
|
||||
### 步骤与依赖
|
||||
|
||||
| 步骤 | 命令 | `build` 中的 `needs` |
|
||||
|---|---|---|
|
||||
| native | `pnpm run build:native-system` | — |
|
||||
| host-src | `node --max-old-space-size=4096 <tsc> -b <tsconfig.host.json 的 references>` | native |
|
||||
| host-bundle | `tsdown --config-loader native --env.DSH_BUILD_FACE host`(运行 Typert) | host-src |
|
||||
| desktop-bundle | `pnpm --filter @deepseek-ai/dsh-desktop run bundle` | host-bundle |
|
||||
| client-src | `<tsc> -b <tsconfig.client.json 的 references>` | desktop-bundle |
|
||||
| client-bundle | `tsdown --config-loader native --env.DSH_BUILD_FACE client` | client-src |
|
||||
| web | `pnpm run build:web` | client-bundle |
|
||||
| host-check | `node --max-old-space-size=4096 <tsc> -p tsconfig.host.json` | host-src |
|
||||
| client-check | `<tsc> -p tsconfig.client.json` | client-src |
|
||||
[先生成顺序](2026-10-18-source-first-metadata-builds.zh.md)在包编译前准备元数据。[开发指南](../../../../docs/development.zh.md#typescript-project-layout)与 `buildGates()` 负责当前步骤图;每个 aggregate 检查在自己的包声明就绪后开始。
|
||||
|
||||
```text
|
||||
native → host-src ─┬─ host-check ───────────────────────────────────────────────────────┐
|
||||
└─ host-bundle → desktop-bundle → client-src ─┬─ client-check ───────┤ → record
|
||||
└─ client-bundle → web ┘
|
||||
```
|
||||
|
||||
- client-src 必须在 host-bundle 之后:Client 包导入 `@deepseek-ai/dsh-*/remote`,其类型 `lib/typert.remote-client.d.ts` 由 Typert 写出:这些 scope 在 Host tsdown 中生成,typecheck scope 经 `scripts/generate-typert.ts` 生成。host-check 不读取 Typert 输出,干净的串行构建一直在 Host tsdown 之前完成 Host 聚合检查。
|
||||
- 产物链保留 native → host-src 与 desktop-bundle → client-src 这两条非数据依赖;只有两个 check 步骤并行挂接到链上。
|
||||
- Client 编译读取仅源码元数据阶段生成的 Remote 声明。Desktop 打包仍在 Client 编译之前;改变该非数据依赖需要单独的证据。
|
||||
- 产物链保留 native → host-src 与 desktop-bundle → client-src 这两条非数据依赖;aggregate 检查在包编译后挂接;native 与元数据生成可以并行。
|
||||
- [`compile-referenced-projects.ts`](../../../../scripts/compile-referenced-projects.ts) 拥有包库步骤列表 `libraryBuildSteps`、references 参数构造 `referencedProjectsCompilerArgs`(相对路径传参,避开 Windows 命令行长度上限)与聚合检查参数 `aggregateCheckArgs`。vendor 发布演练(`host-libraries`)与 CI benchmark(`--noCheck`)串行执行同一步骤列表。
|
||||
- check 步骤使用 project mode(`tsc -p`):它读取 references 已有的声明而不编译任何 reference,incremental 状态写入聚合配置已有的 `tsconfig.{host,client}.tsbuildinfo`。`build` 与 `build:check` 使用同一条命令,因此诊断相同。
|
||||
|
||||
### 调度、失败与输出
|
||||
|
||||
- [`scripts/build.ts`](../../../../scripts/build.ts) 拥有步骤表与三个范围,执行复用 [`scripts/run-gates.ts`](../../../../scripts/run-gates.ts) 的 `runAggregate`:每个步骤是一个带 `needs` 的 gate,shell-free 启动、按步骤缓冲输出、fail-fast 进程树终止、Windows `taskkill` 与宿主信号转发都沿用 run-gates 的实现。`build:artifacts` 为 `tsx scripts/build.ts --scope artifacts`,`build:check` 为 `--scope check`,`build` 不带 `--scope`,把两者作为一张图执行;`--profile` 只适用于产生产物的范围,与 `--scope check` 同用时参数解析失败。
|
||||
- 并发上限取 `DSH_GATE_CONCURRENCY`,未设置时为可用 CPU 数;依赖图宽度最多为 3(host-check 尚未结束时,client-check 与 client-bundle 可能已开始)。master 串行参考 lane 设置 `DSH_GATE_CONCURRENCY=1`,`build` 在那里按拓扑顺序串行执行,符合[串行参考决策](2026-07-21-serial-cross-platform-ci-reference.zh.md),无需修改 workflow。
|
||||
- 并发上限取 `DSH_GATE_CONCURRENCY`,未设置时采用仓库 worker 预算;aggregate 检查可与后续产物步骤重叠。master 串行参考 lane 设置 `DSH_GATE_CONCURRENCY=1`,`build` 在那里按拓扑顺序串行执行,符合[串行参考决策](2026-07-21-serial-cross-platform-ci-reference.zh.md),无需修改 workflow。
|
||||
- 缺省(本地)时,失败步骤的所有依赖方记为 skipped,正在运行的步骤和与失败无关的步骤继续运行到结束;执行集合只取决于各步骤的结果,因此一次运行报告所有相互独立的类型错误。`DSH_GATE_FAIL_FAST=1`(所有构建型 PR lane 已设置)时,第一个失败终止其余正在运行步骤的进程树,所有未完成步骤记为 skipped。
|
||||
- 失败步骤打印 `== FAILED <步骤> ==`、命令、退出原因与该步骤完整的缓冲输出,结尾汇总列出 failed 与 skipped 步骤;成功步骤只打印 PASS 行,`DSH_GATE_VERBOSE=1` 打印全部输出。任一步骤 failed 或 skipped 时退出码为 1,且不写 client build record。所有步骤继承 `clientBuildProcessEnvironment` 选定的公开 Client 环境。
|
||||
- CI 中 `build` 本身是外层 run-gates 的一个 gate;外层 fail-fast 终止会转发到内层调度。`check:node-compat` 与 `check:ci:lint:contracts-ready` 已使用同样的嵌套。
|
||||
@@ -63,11 +47,11 @@ native → host-src ─┬─ host-check ─────────────
|
||||
|---|---|---|
|
||||
| `build`、`build:official`、`Makefile` `build`、`scripts/publish-npm-baseline.ts`、`apps/desktop/scripts/package-target.ts`、`scripts/dev-bundle-registry.ts`、`release-publish.yml`、`sandbox.yml`、`e2e.yml` 与 `release.yml` 的非 PR 分支 | `build` / `build:official` | 有 |
|
||||
| `test:web`、`test:web:refresh`、`test:web:perf`、`test:web:stress`、`apps/desktop` `test:updates:local`、`test:bench` / `build:bench` | `build`;`build:bench` 运行 `build:native-system`、`build:lib` 与 benchmark tsdown | 有 |
|
||||
| `typecheck`(pre-push hook 运行它) | `build.ts --scope typecheck`:host-src 与 host-typert(不打包、只读源码的 Typert 生成 `generate-typert.ts`)并行 → client-src,各 face 包编译后运行 `tsc -b tsconfig.{host,client}.json`;不含任何 bundle | 有 |
|
||||
| `typecheck`(pre-push hook 运行它) | `build.ts --scope typecheck`:host-typert(只读源码的 Typert 生成 `generate-typert.ts`)→ 包编译,各 face 包编译后运行 `tsc -b tsconfig.{host,client}.json`;不含任何 bundle | 有 |
|
||||
| `dev:web`([`scripts/dev-web.ts`](../../../../scripts/dev-web.ts))、`dev:desktop`(`apps/desktop/scripts/dev.ts`) | `build:artifacts` | 启动前无;`dev:web` 启动后的 `tsc -b tsconfig.client.json --watch` 持续检查 Client 聚合根程序 |
|
||||
| `lint`、`lint:fix`、`doc-typecheck` 与 CI 的 Typert contracts gate 的前置步骤 | Host 包编译与不打包的 Typert 生成并行(`compile-referenced-projects.ts host-contracts`),不含任何 bundle | 无 |
|
||||
| `lint`、`lint:fix`、`doc-typecheck` 与 CI 的 Typert contracts gate 的前置步骤 | 仅源码 Typert 生成后进行 Host 包编译(`compile-referenced-projects.ts host-contracts`),不含任何 bundle | 无 |
|
||||
| CI node 24 / snapshots and artifacts(`check:ci:consumers`)、windows node 24 / build & observational(`check:ci:windows-blocking`) | `build` | 有,两个必需 lane |
|
||||
| CI node-compat 22.19 | `build:artifacts`(经 run-gates) | 无 |
|
||||
| CI node-compat 24.9 | `build:artifacts`(经 run-gates) | 无 |
|
||||
| CI python runtime | `build-exe-for-python-sdk.ts --build-artifacts` | 无 |
|
||||
| `e2e.yml`、`release.yml` 的 PR 演练 | `pnpm run build:artifacts --profile official` | 无 |
|
||||
| `build-preview-cloudflare.yml` | `pnpm run build:artifacts` | 无 |
|
||||
@@ -114,7 +98,7 @@ wall 中位数从 249.4 s 降到 172.1 s(−77.3 s,−31%)。各步骤单
|
||||
| 包源码类型错误 | host-src / client-src | 不报告(读取已有声明) | host-src / client-src | 报告 | 报告 |
|
||||
| 只属于聚合根程序的测试、脚本、benchmark、website 类型错误 | 不报告 | host-check / client-check | host-check / client-check | 报告 | consumers、windows-build |
|
||||
| 包 tsconfig 配置错误 | host-src / client-src | 不报告(`tsc -p` 不校验被引用 project 的 tsconfig 选项与语法) | host-src / client-src | 报告 | 报告 |
|
||||
| Typert 分析错误 | host-bundle | 不报告 | host-bundle | 报告(经 host-typert) | 报告 |
|
||||
| Typert 分析错误 | host-typert | 不报告 | host-typert | 报告(经 host-typert) | 报告 |
|
||||
| 前置产物缺失 | — | 编译器诊断(TS6305、TS2307) | — | — | — |
|
||||
|
||||
负对照:在只属于 Host 聚合根程序的 `scripts/pnpm-invocation.spec.ts` 中加入类型错误(TS2322)后,`build:artifacts` 以 0 退出;`build:check` 以 1 退出,其中 Host 聚合检查失败、Client 聚合检查通过;`build` 以 1 退出,9 个步骤中 8 个通过、1 个失败,且不写 client build record。在临时 fixture 中,被引用 project 的 tsconfig 含无效 `target`、JSON 语法错误或未知编译选项时,`tsc -p` 以 0 退出,`tsc -b` 分别报告 TS6046、TS5092 与 TS1012、TS5023 并以 1 退出;被引用 project 缺少 `composite` 时,两者都报告 TS6306。
|
||||
@@ -150,8 +134,8 @@ CI 证据:PR #6005 上,两个运行并发 `pnpm run build` 的必需 lane
|
||||
|
||||
开发迭代可以用 `build:artifacts` 跳过两个聚合根程序(冷启动比 master 的 `build` 快 51.6%),在声明完成前用 `build:check` 或 `build` 补齐检查。`build` 保持完整:Host 聚合检查不在关键路径上,Client 聚合检查与 Client bundle、web 并行,冷启动比 master 快 46.7%(含无损提速 Note 的改动,2 个样本的中位数)。代价是更高的内存峰值、并发争用带来的少量额外 CPU、默认更少的成功输出,以及多个开发与前置命令不检查测试类型(见上文取舍)。
|
||||
|
||||
删除或改变 consumers 与 windows-build 两个必需 lane 的 `build`,必须先把两个聚合检查转交另一处阻塞型 owner。typecheck scope 与 lint 类前置步骤不打包即生成 Typert 约定([本地门禁提速](2026-10-12-local-gate-speedups.zh.md));产物 scope 仍在 Host tsdown 中生成。
|
||||
删除或改变 consumers 与 windows-build 两个必需 lane 的 `build`,必须先把两个聚合检查转交另一处阻塞型 owner。typecheck scope 与 lint 类前置步骤不打包即生成 Typert 约定([本地门禁提速](2026-10-12-local-gate-speedups.zh.md));产物 scope 采用[先生成元数据的顺序](2026-10-18-source-first-metadata-builds.zh.md)。
|
||||
|
||||
[Typert Remote 方法调用](../architecture/2026-08-02-typert-remote-method-calls.zh.md)描述的“Client 编译前先完成 Host 与 Remote 产物”由 client-src 对 host-bundle 的依赖保证。
|
||||
host-src 依赖 host-typert,因此生成的 Remote 声明先于包编译。[Typert Remote 方法调用](../architecture/2026-08-02-typert-remote-method-calls.zh.md)说明生成的贡献。
|
||||
|
||||
验证:`scripts/build.spec.ts` 覆盖类型剥离检查、已删除参数与非法范围的拒绝、`--profile` 与 `--scope check` 的冲突、范围选择、步骤依赖与环境;`compile-referenced-projects.spec.ts` 覆盖 project mode 只检查聚合程序、缺失声明的诊断,以及 `tsc -b --dry` 接受 `tsc -p` 写出的 tsbuildinfo;失败传播、输出缓冲与进程树终止沿用 run-gates 的调度实现及其测试;`run-gates.spec.ts`、`ci-workflow.spec.ts`、`preview-workflow.spec.ts`、`build-exe-for-python-sdk.spec.ts`、`dev-web.spec.ts` 与 `tests/ci-release-selfhosted.spec.ts` 固定各调用方的命令。
|
||||
@@ -27,8 +27,8 @@
|
||||
en: cd6e05c61a7cc495
|
||||
zh: 480d2d85d78d2d7f
|
||||
/agent-note-build-speed-ups-that-keep-outputs-unchanged/decision/5-the-typert-workspace-root-also-requires-packages:
|
||||
en: 8b89436df201141c
|
||||
zh: 2fab82419c8ba704
|
||||
en: b2bea05b59abe4b3
|
||||
zh: 1e3304dec8a77146
|
||||
/agent-note-build-speed-ups-that-keep-outputs-unchanged/decision/6-typert-generates-in-a-worker-thread:
|
||||
en: 0362483a79c07881
|
||||
zh: d84a2390b49c4012
|
||||
@@ -39,8 +39,8 @@
|
||||
en: 80c2ddc5fdff0b5f
|
||||
zh: f5ce309a2e88ff4f
|
||||
/agent-note-build-speed-ups-that-keep-outputs-unchanged/alternatives-considered:
|
||||
en: 4372c82ef0c944dd
|
||||
zh: 5fb70d499baadea7
|
||||
en: 8def70ac09c5250c
|
||||
zh: 5385aa58fc4b0b3b
|
||||
/agent-note-build-speed-ups-that-keep-outputs-unchanged/consequences:
|
||||
en: 93a96d3ed398f348
|
||||
zh: 93b7c7405be981a6
|
||||
@@ -84,7 +84,7 @@ All numbers come from one shared Linux host (256 vCPU, 4 TB memory, load average
|
||||
- **Purpose:** Correct workspace-root resolution. 24 dual-face packages carry their own `tsconfig.host.json` leaf; searching upward for `tsconfig.host.json` alone took each of them as a root, so workspace mode ran one empty discovery per such package per build, and `package` mode silently produced no artifacts for dual-face packages.
|
||||
- **Change:** The workspace root is the nearest ancestor of the bundle output that contains both `tsconfig.host.json` and `packages/`.
|
||||
- **Performance:** Host tsdown 14.82 s → 15.39 s (medians of 3), within noise.
|
||||
- **Why it is lossless:** The repository root [`tsdown.config.ts`](../../../../tsdown.config.ts) uses only workspace mode, and the 743 output files are byte-identical. The test `resolves the workspace root above a dual-face package leaf` pins the rule.
|
||||
- **Why it is lossless:** The workspace-mode comparison found the 743 output files byte-identical; [source-first ordering](2026-10-18-source-first-metadata-builds.md) owns current root-build generation. The test `resolves the workspace root above a dual-face package leaf` pins the rule.
|
||||
- **Behavior difference:** A `package`-mode consumer building a dual-face package receives that package's Typert artifacts; repository builds do not use `package` mode.
|
||||
|
||||
### 6. Typert generates in a Worker Thread
|
||||
@@ -118,7 +118,7 @@ All numbers come from one shared Linux host (256 vCPU, 4 TB memory, load average
|
||||
- **Narrow `paths` to each face's reference closure (item 1):** The 328-entry version that keeps wildcard keys checks in 55.73 s against 59.08 s (one sample), within noise; the version without wildcard keys produces 18 diagnostics.
|
||||
- **Split the Client test aggregate program (item 1):** Every sub-program must inject the 242 files carrying module augmentations to pass, and the visible location count stays at 33–49; splitting into 2, 4, or 8 groups leaves the Check sum nearly flat while user CPU rises by 9%, 30%, and 57%, lowering only wall, and the 4- and 8-group splits each produce 1–2 diagnostics.
|
||||
- **Cache resolution results and make the discovery queue O(1) (Typert):** After items 2–4 only 28 surface walks remain, about 0.18 s, below noise.
|
||||
- **Move Typert into a separate build step in artifact builds (Typert):** After item 6 the worker already overlaps bundling, leaving at most about 1 s. Type checks, which need no bundle, run a source-only generation step ([local gate speed-ups](2026-10-12-local-gate-speedups.md)).
|
||||
- **Move Typert into a separate build step in artifact builds (Typert):** After item 6 the worker already overlaps bundling, leaving at most about 1 s. Type checks, which need no bundle, run a source-only generation step ([local gate speed-ups](2026-10-12-local-gate-speedups.md)). The [source-first ordering decision](2026-10-18-source-first-metadata-builds.md) selects a separate step for compiler dependency ordering and records its no-change build cost; the public worker plugin remains available.
|
||||
- **Filter `transform` by code content (item 7):** rolldown interprets `^` and `\s` with Rust regex semantics, which differ from JavaScript on lone CR, U+2028, and BOM, so a decorated module could be missed.
|
||||
- **Build a process-wide manifest index on `globalThis` (item 8):** The prototype saves about as much time as item 8, but it is process-global state without an owner, violating the shared-state ownership rule in the [package rules](../../../../packages/AGENTS.md).
|
||||
- **Disable tsdown's gzip report (item 8):** `--no-report` lowers Client tsdown by 0.95 s, within the 1.56 s baseline spread.
|
||||
|
||||
@@ -84,7 +84,7 @@ Status: implemented
|
||||
- **目的:** 修正工作区根目录的判定。24 个双 face 包带有自己的 `tsconfig.host.json` 叶子配置;只按 `tsconfig.host.json` 向上查找时,每个这样的包都被当作根目录,workspace 模式每次构建为它们各做一次空发现,`package` 模式下双 face 包得不到产物且没有报错。
|
||||
- **改动:** 工作区根目录是打包输出最近的、同时包含 `tsconfig.host.json` 与 `packages/` 的祖先目录。
|
||||
- **性能:** Host tsdown 14.82 s → 15.39 s(各 3 个样本的中位数),在噪声内。
|
||||
- **无损依据:** 仓库根 [`tsdown.config.ts`](../../../../tsdown.config.ts) 只使用 workspace 模式,743 个产物文件逐字节一致。测试 `resolves the workspace root above a dual-face package leaf` 固定该判定。
|
||||
- **无损依据:** workspace 模式的比较中,743 个产物文件逐字节一致;[先生成顺序](2026-10-18-source-first-metadata-builds.zh.md)负责当前根构建的生成。测试 `resolves the workspace root above a dual-face package leaf` 固定该判定。
|
||||
- **行为差异:** `package` 模式的使用方构建双 face 包时得到该包的 Typert 产物;仓库构建不使用 `package` 模式。
|
||||
|
||||
### 6. Typert 在 Worker Thread 中生成
|
||||
@@ -118,7 +118,7 @@ Status: implemented
|
||||
- **把 `paths` 收窄为各 face 的 reference closure(第 1 项):** 保留通配键的 328 条版本 Check 为 55.73 s 对 59.08 s(1 个样本),在噪声内;去掉通配键的版本产生 18 个诊断。
|
||||
- **拆分 Client 测试聚合程序(第 1 项):** 每个子程序都必须注入 242 个含模块增强的文件才能通过检查,可见位置数仍为 33–49;拆成 2、4、8 组时 Check 总和基本不变,user CPU 分别增加 9%、30%、57%,只降低 wall,且 4 组与 8 组各出现 1–2 个诊断。
|
||||
- **缓存解析结果并把发现队列改为 O(1)(Typert):** 第 2–4 项之后只剩 28 次表面遍历,约 0.18 s,低于噪声。
|
||||
- **在产物构建中把 Typert 移到独立构建步骤(Typert):** 第 6 项之后 worker 已与打包重叠,剩余收益不超过约 1 s。不需要 bundle 的类型检查运行只读源码的生成步骤([本地门禁提速](2026-10-12-local-gate-speedups.zh.md))。
|
||||
- **在产物构建中把 Typert 移到独立构建步骤(Typert):** 第 6 项之后 worker 已与打包重叠,剩余收益不超过约 1 s。不需要 bundle 的类型检查运行只读源码的生成步骤([本地门禁提速](2026-10-12-local-gate-speedups.zh.md))。 [先生成顺序决策](2026-10-18-source-first-metadata-builds.zh.md)为了编译依赖顺序而选择独立步骤,并记录无改动构建的代价;公开 worker 插件仍可使用。
|
||||
- **按代码内容过滤 `transform`(第 7 项):** rolldown 以 Rust 正则语义解释 `^` 与 `\s`,在孤立 CR、U+2028 与 BOM 上与 JavaScript 不同,可能漏掉含装饰器的模块。
|
||||
- **在 `globalThis` 上建立进程级 manifest 索引(第 8 项):** 原型收益与第 8 项相当,但它是没有 owner 的进程全局状态,违反[包规则](../../../../packages/AGENTS.md)中的共享状态归属要求。
|
||||
- **关闭 tsdown 的 gzip 报告(第 8 项):** `--no-report` 使 Client tsdown 降低 0.95 s,在 1.56 s 的基线跨度内。
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
en: 7430353afba96352
|
||||
zh: 6d2a09619a67ecba
|
||||
/agent-note-local-gate-hook-and-test-speed-ups/decision/1-type-checks-generate-typert-declarations-without-bundling:
|
||||
en: 4dc48e24a3323b7d
|
||||
zh: 7c88325160a41d12
|
||||
en: e9c08ebd00049b44
|
||||
zh: 36e90dc485b4acb7
|
||||
/agent-note-local-gate-hook-and-test-speed-ups/decision/2-the-pre-commit-hook-does-not-regenerate-third-party-notices:
|
||||
en: e2e344f184621172
|
||||
zh: 97d8785aaf9105bf
|
||||
|
||||
@@ -39,23 +39,15 @@ All numbers are wall times from one shared Linux container with a 64-CPU quota o
|
||||
### 1. Type checks generate Typert declarations without bundling
|
||||
|
||||
- **Purpose:** Give `typecheck` and the prerequisite of `lint`, `lint:fix`, `doc-typecheck`, and the CI `typert-contracts` gate the declarations they read, package declarations from `tsc` and Typert declarations, without the Host tsdown pass or the Desktop bundle.
|
||||
- **Change:** `typecheck` is `tsx scripts/build.ts --scope typecheck`, which runs the steps below through the run-gates scheduler ([`scripts/build.ts`](../../../../scripts/build.ts)). The scope builds no native addon, tsdown bundle, Desktop bundle, or Web output, writes no client build record, and leaves runnable `lib/` output as the last `build:artifacts` left it; run `build:artifacts` before launching or testing built output. The prerequisite of `lint`, `lint:fix`, `doc-typecheck`, and `typert-contracts` is `compile-referenced-projects.ts host-contracts`, which runs host-src beside host-typert ([`compile-referenced-projects.ts`](../../../../scripts/compile-referenced-projects.ts)). `build`, `build:artifacts`, benchmark builds, and the PR rehearsal in `release-vendor.yml` run Typert inside the Host tsdown pass.
|
||||
- **Change:** `typecheck` is `tsx scripts/build.ts --scope typecheck`, which runs the steps below through the run-gates scheduler ([`scripts/build.ts`](../../../../scripts/build.ts)). The scope builds no native addon, tsdown bundle, Desktop bundle, or Web output, writes no client build record, and leaves runnable `lib/` output as the last `build:artifacts` left it; run `build:artifacts` before launching or testing built output. The prerequisite of `lint`, `lint:fix`, `doc-typecheck`, and `typert-contracts` is `compile-referenced-projects.ts host-contracts`, which runs host-typert before host-src ([`compile-referenced-projects.ts`](../../../../scripts/compile-referenced-projects.ts)). `build`, `build:artifacts`, benchmark builds, and the vendor rehearsal use the same source-only generation before compilation ([ordering decision](2026-10-18-source-first-metadata-builds.md)).
|
||||
|
||||
| Step | Command | `needs` |
|
||||
|---|---|---|
|
||||
| host-src | `node --max-old-space-size=4096 <tsc> -b <references of tsconfig.host.json>` | — |
|
||||
| host-typert | `node --import tsx/esm scripts/generate-typert.ts` | — |
|
||||
| client-src | `<tsc> -b <references of tsconfig.client.json>` | host-src, host-typert |
|
||||
| host-check | `node --max-old-space-size=4096 <tsc> -b tsconfig.host.json` | host-src |
|
||||
| client-check | `<tsc> -b tsconfig.client.json` | client-src |
|
||||
|
||||
- **Shared generation:** [`scripts/generate-typert.ts`](../../../../scripts/generate-typert.ts) calls `WorkspaceTypertGenerator.generateContributors()` for `WORKSPACE_TYPERT_FACES` ([`scripts/typert-faces.ts`](../../../../scripts/typert-faces.ts)), the faces the Host tsdown plugin also generates, and writes the result with `writeWorkspaceArtifacts()` from [`artifact-files.ts`](../../../../packages/typert/generator/src/artifact-files.ts), the writer the plugin uses. Like the plugin, it skips TypeScript diagnostics, which Host package compilation reports; a Typert analysis error fails host-typert as it fails the Host bundle in `build`. host-typert runs under Node with tsx's ESM hook rather than through `pnpm exec`, whose startup would lengthen the path to Client compilation.
|
||||
- **Shared generation:** [`scripts/generate-typert.ts`](../../../../scripts/generate-typert.ts) calls `WorkspaceTypertGenerator.generateContributors()` for `WORKSPACE_TYPERT_FACES` ([`scripts/typert-faces.ts`](../../../../scripts/typert-faces.ts)), the repository generation faces, and writes the result with `writeWorkspaceArtifacts()` from [`artifact-files.ts`](../../../../packages/typert/generator/src/artifact-files.ts), the writer the plugin uses. Like the plugin, it skips TypeScript diagnostics, which Host package compilation reports; a Typert analysis error fails host-typert before dependent compilation. host-typert runs under Node with tsx's ESM hook rather than through `pnpm exec`, whose startup would lengthen the path to Client compilation.
|
||||
- **Aggregate checks in build mode:** host-check and client-check start after their own face's package compilation, so build mode finds every reference up to date, checks only the aggregate root program, and skips a root program whose tsbuildinfo is current.
|
||||
- **Source-only analysis:** host-typert can run beside host-src because its Host program reads no workspace build output. `tsconfig.base.json` maps `@deepseek-ai/dsh-client-file-upload/types` to source; without that alias the specifier resolves through package `exports` to `lib/types/types.d.ts`. With it, the Host program loads 1,289 files, none of them under a workspace package's `lib/`. `generate-typert.ts` enables `rejectBuildOutputs`: [`build-output-guard.ts`](../../../../packages/typert/generator/src/build-output-guard.ts) fails analysis with a `TypertAnalysisError` when a face program loads a file from a workspace package's `lib/` directory, or cannot resolve an import of a workspace package, as on a tree whose packages are not yet compiled, naming each such import specifier and the `paths` entry that maps it to source. Either way a missing alias fails generation whether or not host-src has run. The tsdown plugin leaves the option off because in `build` Typert always runs after Host package compilation. The [Typert generator README](../../../../packages/typert/generator/README.md) documents the writers and the option.
|
||||
- **Source-only analysis:** host-typert can run before host-src because its Host program reads no workspace build output. `tsconfig.base.json` maps `@deepseek-ai/dsh-client-file-upload/types` to source; without that alias the specifier resolves through package `exports` to `lib/types/types.d.ts`. With it, the Host program loads 1,289 files, none of them under a workspace package's `lib/`. `generate-typert.ts` enables `rejectBuildOutputs`: [`build-output-guard.ts`](../../../../packages/typert/generator/src/build-output-guard.ts) fails analysis with a `TypertAnalysisError` when a face program loads a file from a workspace package's `lib/` directory, or cannot resolve an import of a workspace package, as on a tree whose packages are not yet compiled, naming each such import specifier and the `paths` entry that maps it to source. Either way a missing alias fails generation whether or not host-src has run. The public tsdown plugin retains its independent generation policy; root artifact builds use the source-only pass. The [Typert generator README](../../../../packages/typert/generator/README.md) documents the writers and the option.
|
||||
- **Why it is lossless:** No workspace tsconfig sets `allowJs`, every `exports` `types` condition points into `lib/types/` or at `lib/typert.*`, and every tsdown config sets `dts: false`, so no type check reads tsdown output. The 140 Typert files (28 packages × 5 outputs) are byte-identical across the baseline tsdown pass, the current tsdown pass, `generate-typert.ts` after every Typert output is deleted, and `generate-typert.ts` with all 344 `lib/types` directories hidden; with one Remote method temporarily added to `packages/goal/goal`, the tsdown pass and `generate-typert.ts` also write identical files. `typecheck` checks every package project of both faces and both aggregate root programs, the programs that `build` checks.
|
||||
- **Performance:** `typecheck` with nothing to recheck takes 34.1/35.0 → 21.9/20.0 s. When both aggregate root programs must be rechecked, it takes 83.5 → 76.0/72.1 s, and its critical path is the Host aggregate check at about 66–70 s. The prerequisite of `lint` and `doc-typecheck` takes 52.2/29.6 → 12.5/11.3 s.
|
||||
- **Trade-off:** `typecheck`, `lint`, `lint:fix`, `doc-typecheck`, and `typert-contracts` write Typert declarations but no bundle, so runnable `lib/` JavaScript can lag behind those declarations until the next `build:artifacts`.
|
||||
- **Relation to the build:** [The lossless build speed-ups note](2026-10-11-lossless-build-speedups.md) rejects moving Typert out of the Host tsdown pass for builds, where its Worker Thread already overlaps bundling and a separate step saves at most about 1 s. Type-check commands run no bundler, so generating without one removes the whole Host tsdown pass and the Desktop bundle from their critical path. [The build split note](2026-10-11-build-artifacts-check-split.md) lists every caller and its command.
|
||||
- **Relation to the build:** [Source-first metadata ordering](2026-10-18-source-first-metadata-builds.md) owns generation placement and its measured cost. Declaration-only commands still omit bundling. [The build split note](2026-10-11-build-artifacts-check-split.md) owns command responsibilities.
|
||||
- **Known gap:** Incremental `tsc -b` does not record `lib/typert.remote-client.d.ts`, which Client sources reach through package `exports`, as an input of the consuming project; when a Remote signature changes and no Client source does, an incremental check does not recheck the consumers. The gap is independent of which pass writes the declarations; Issue #6022 tracks it.
|
||||
|
||||
### 2. The pre-commit hook does not regenerate third-party notices
|
||||
|
||||
@@ -39,23 +39,15 @@ Status: implemented
|
||||
### 1. 类型检查不打包地生成 Typert 声明
|
||||
|
||||
- **目的:** 让 `typecheck` 以及 `lint`、`lint:fix`、`doc-typecheck` 与 CI `typert-contracts` 门禁的前置步骤得到它们读取的声明,即 `tsc` 产出的包声明与 Typert 声明,而不运行 Host tsdown 阶段或 Desktop bundle。
|
||||
- **改动:** `typecheck` 是 `tsx scripts/build.ts --scope typecheck`,它通过 run-gates 调度器运行下表中的步骤([`scripts/build.ts`](../../../../scripts/build.ts))。该 scope 不构建 native addon、tsdown bundle、Desktop bundle 或 Web 产物,不写 client build record,可运行的 `lib/` 产物保持上一次 `build:artifacts` 的状态;启动或测试构建产物之前先运行 `build:artifacts`。`lint`、`lint:fix`、`doc-typecheck` 与 `typert-contracts` 的前置步骤是 `compile-referenced-projects.ts host-contracts`,它让 host-src 与 host-typert 并行([`compile-referenced-projects.ts`](../../../../scripts/compile-referenced-projects.ts))。`build`、`build:artifacts`、benchmark 构建与 `release-vendor.yml` 的 PR(Pull Request)演练在 Host tsdown 阶段内运行 Typert。
|
||||
- **改动:** `typecheck` 是 `tsx scripts/build.ts --scope typecheck`,它通过 run-gates 调度器运行下表中的步骤([`scripts/build.ts`](../../../../scripts/build.ts))。该 scope 不构建 native addon、tsdown bundle、Desktop bundle 或 Web 产物,不写 client build record,可运行的 `lib/` 产物保持上一次 `build:artifacts` 的状态;启动或测试构建产物之前先运行 `build:artifacts`。`lint`、`lint:fix`、`doc-typecheck` 与 `typert-contracts` 的前置步骤是 `compile-referenced-projects.ts host-contracts`,它在 host-src 之前运行 host-typert([`compile-referenced-projects.ts`](../../../../scripts/compile-referenced-projects.ts))。`build`、`build:artifacts`、benchmark 构建与 vendor 演练都在编译前采用同一仅源码生成([顺序决策](2026-10-18-source-first-metadata-builds.zh.md))。
|
||||
|
||||
| 步骤 | 命令 | `needs` |
|
||||
|---|---|---|
|
||||
| host-src | `node --max-old-space-size=4096 <tsc> -b <references of tsconfig.host.json>` | — |
|
||||
| host-typert | `node --import tsx/esm scripts/generate-typert.ts` | — |
|
||||
| client-src | `<tsc> -b <references of tsconfig.client.json>` | host-src、host-typert |
|
||||
| host-check | `node --max-old-space-size=4096 <tsc> -b tsconfig.host.json` | host-src |
|
||||
| client-check | `<tsc> -b tsconfig.client.json` | client-src |
|
||||
|
||||
- **共用生成:** [`scripts/generate-typert.ts`](../../../../scripts/generate-typert.ts) 对 `WORKSPACE_TYPERT_FACES`([`scripts/typert-faces.ts`](../../../../scripts/typert-faces.ts),即 Host tsdown 插件同样生成的 face)调用 `WorkspaceTypertGenerator.generateContributors()`,并用 [`artifact-files.ts`](../../../../packages/typert/generator/src/artifact-files.ts) 中插件同样使用的 `writeWorkspaceArtifacts()` 写出结果。与插件一样,它跳过 TypeScript 诊断,这些诊断由 Host 包编译报告;Typert 分析错误使 host-typert 失败,正如它在 `build` 中使 Host bundle 失败。host-typert 由 Node 加 tsx 的 ESM 钩子运行,而不经 `pnpm exec`,因为后者的启动开销会拉长通往 Client 编译的路径。
|
||||
- **共用生成:** [`scripts/generate-typert.ts`](../../../../scripts/generate-typert.ts) 对 `WORKSPACE_TYPERT_FACES`([`scripts/typert-faces.ts`](../../../../scripts/typert-faces.ts),即仓库生成的 face)调用 `WorkspaceTypertGenerator.generateContributors()`,并用 [`artifact-files.ts`](../../../../packages/typert/generator/src/artifact-files.ts) 中插件同样使用的 `writeWorkspaceArtifacts()` 写出结果。与插件一样,它跳过 TypeScript 诊断,这些诊断由 Host 包编译报告;Typert 分析错误使 host-typert 失败,依赖它的编译不会启动。host-typert 由 Node 加 tsx 的 ESM 钩子运行,而不经 `pnpm exec`,因为后者的启动开销会拉长通往 Client 编译的路径。
|
||||
- **以 build mode 运行聚合检查:** host-check 与 client-check 在本侧 face 的包编译之后才开始,因此 build mode 发现所有 references 都是最新的,只检查聚合根程序,并在根程序的 tsbuildinfo 为最新时跳过它。
|
||||
- **只读源码的分析:** host-typert 能与 host-src 并行,是因为它的 Host program 不读取任何工作区构建产物。`tsconfig.base.json` 把 `@deepseek-ai/dsh-client-file-upload/types` 映射到源码;没有这条别名时,该说明符经包 `exports` 解析到 `lib/types/types.d.ts`。有了它,Host program 加载 1,289 个文件,其中没有一个位于工作区包的 `lib/` 下。`generate-typert.ts` 开启 `rejectBuildOutputs`:face program 加载工作区包 `lib/` 目录下的文件,或无法解析对工作区包的 import(例如包尚未编译的工作区)时,[`build-output-guard.ts`](../../../../packages/typert/generator/src/build-output-guard.ts) 以 `TypertAnalysisError` 使分析失败,并指出每个这样的 import 说明符,以及把它映射到源码的 `paths` 条目。因此无论 host-src 是否已运行,缺失的别名都会使生成失败。tsdown 插件不开启该选项,因为在 `build` 中 Typert 总在 Host 包编译之后运行。[Typert generator README](../../../../packages/typert/generator/README.zh.md) 记录了这些写入函数与该选项。
|
||||
- **只读源码的分析:** host-typert 能在 host-src 之前运行,是因为它的 Host program 不读取任何工作区构建产物。`tsconfig.base.json` 把 `@deepseek-ai/dsh-client-file-upload/types` 映射到源码;没有这条别名时,该说明符经包 `exports` 解析到 `lib/types/types.d.ts`。有了它,Host program 加载 1,289 个文件,其中没有一个位于工作区包的 `lib/` 下。`generate-typert.ts` 开启 `rejectBuildOutputs`:face program 加载工作区包 `lib/` 目录下的文件,或无法解析对工作区包的 import(例如包尚未编译的工作区)时,[`build-output-guard.ts`](../../../../packages/typert/generator/src/build-output-guard.ts) 以 `TypertAnalysisError` 使分析失败,并指出每个这样的 import 说明符,以及把它映射到源码的 `paths` 条目。因此无论 host-src 是否已运行,缺失的别名都会使生成失败。公开 tsdown 插件保留独立的生成策略;根产物构建使用仅源码阶段。[Typert generator README](../../../../packages/typert/generator/README.zh.md) 记录了这些写入函数与该选项。
|
||||
- **无损依据:** 没有任何工作区 tsconfig 设置 `allowJs`,每个 `exports` 的 `types` 条件都指向 `lib/types/` 内或 `lib/typert.*`,每个 tsdown 配置都设置 `dts: false`,因此没有类型检查读取 tsdown 产物。140 个 Typert 文件(28 个包 × 5 个输出)在以下场景中逐字节一致:基线 tsdown 阶段、当前 tsdown 阶段、删除全部 Typert 输出后运行 `generate-typert.ts`、隐藏全部 344 个 `lib/types` 目录后运行 `generate-typert.ts`;临时给 `packages/goal/goal` 加一个 Remote 方法时,tsdown 阶段与 `generate-typert.ts` 写出的文件也相同。`typecheck` 检查两个 face 的全部包 project 与两个聚合根程序,即 `build` 检查的程序。
|
||||
- **性能:** 无需重查时 `typecheck` 为 34.1/35.0 → 21.9/20.0 s。两个聚合根程序都需要重查时为 83.5 → 76.0/72.1 s,其关键路径是约 66–70 s 的 Host 聚合检查。`lint` 与 `doc-typecheck` 的前置步骤为 52.2/29.6 → 12.5/11.3 s。
|
||||
- **取舍:** `typecheck`、`lint`、`lint:fix`、`doc-typecheck` 与 `typert-contracts` 写出 Typert 声明但不写 bundle,因此在下一次 `build:artifacts` 之前,可运行的 `lib/` JavaScript 可能落后于这些声明。
|
||||
- **与构建的关系:** [无损构建提速 Note](2026-10-11-lossless-build-speedups.zh.md) 否决了在构建中把 Typert 移出 Host tsdown 阶段:构建中它的 Worker Thread 已与打包重叠,独立步骤最多只能省约 1 s。类型检查命令不运行打包器,因此不打包地生成会把整个 Host tsdown 阶段与 Desktop bundle 移出它们的关键路径。[构建拆分 Note](2026-10-11-build-artifacts-check-split.zh.md) 列出每个调用方及其命令。
|
||||
- **与构建的关系:** [先生成元数据的顺序](2026-10-18-source-first-metadata-builds.zh.md)负责生成位置及其测得的代价。仅生成声明的命令仍省略打包。[构建拆分 Note](2026-10-11-build-artifacts-check-split.zh.md)负责命令职责。
|
||||
- **已知缺口:** 增量 `tsc -b` 不把 Client 源码经包 `exports` 访问到的 `lib/typert.remote-client.d.ts` 记为消费方 project 的输入;Remote 签名改变而 Client 源码未变时,增量检查不会重查消费方。这一缺口与由哪一种生成写出声明无关,由 Issue #6022 跟踪。
|
||||
|
||||
### 2. pre-commit 钩子不重新生成第三方声明
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-10-15-node-24-runtime-floor.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-10-15-node-24-runtime-floor.md
|
||||
/agent-note-node-runtime-floor-and-compatibility-coverage:
|
||||
en: 256156264ba84f11
|
||||
zh: 29680265e82fc2c6
|
||||
/agent-note-node-runtime-floor-and-compatibility-coverage/problem:
|
||||
en: 1e45546d7452029f
|
||||
zh: d99966ac42bb193f
|
||||
/agent-note-node-runtime-floor-and-compatibility-coverage/decision:
|
||||
en: 13b84da1ca9bbb1f
|
||||
zh: 5bf45cf6f742cd5a
|
||||
/agent-note-node-runtime-floor-and-compatibility-coverage/consequences:
|
||||
en: 224ce4b13f297f8b
|
||||
zh: 1bb5f8ebdf3243b3
|
||||
/agent-note-node-runtime-floor-and-compatibility-coverage/alternatives-considered:
|
||||
en: 413d1133689bd55c
|
||||
zh: fc77301991e1f425
|
||||
@@ -0,0 +1,33 @@
|
||||
# Agent Note: Node runtime floor and compatibility coverage
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-15-node-24-runtime-floor.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The root `engines.node` range promises that the installed workspace supports each admitted runtime. Its minimum must cover both the runtime APIs DSH uses and the declared requirements of installed dependencies. Otherwise strict installation can reject a supported version before DSH starts.
|
||||
|
||||
## Decision
|
||||
|
||||
Set `engines.node` to `>=24.9.0`. Node 24.9 is the minimum for DSH and its development carriers, including the experimental OAR subagent integration, whose `@botiverse/oar` library requires Node 24. This supersedes the [Node 22.19 floor](../../archived/process/2026-07-06-node-engine-floor.md); its Node 23 exclusion remains satisfied, and the declaration policy below remains narrower than the runtime floor. Migration instructions live in the [upgrade guide](../../../../docs/upgrade-guide/v0.2.1-rc.2/node-24/guide.md).
|
||||
|
||||
The primary Node 24 jobs own complete typechecking and unit coverage. Focused compatibility jobs run on pinned Node 24.9 and Node 26. Node 24.9 supports the existing `import.meta.main` script entrypoints and native TypeScript stripping. Pinning 24.9 exercises this verified minimum and its v1 private module loader; the primary jobs track the latest Node 24 and exercise its v2 loader, introduced in 24.12.0. A floating `24` compatibility job would duplicate the primary loader coverage and stop testing the minimum.
|
||||
|
||||
The Node 24.9 compatibility job also builds artifacts and checks that the shipped Web CLI starts and disposes with full-text Session search disabled. Command-entrypoint, source-worker, Zstandard, source-launch, profile-resolution, and jsdom-storage checks run on both compatibility versions. Real-API e2e jobs stay on Node 24 because they validate provider integration.
|
||||
|
||||
The root, Web, and desktop projects retain `@types/node` on the 22.x line. Their current code needs no Node 24-only declarations. A narrower type API remains valid on the supported runtime and avoids unrelated declaration changes. Raise these declarations when a supported API requires them, and keep their major no newer than the runtime floor. The independently published native addon has its own runtime and type support policy.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Node 22 installations must upgrade before running DSH. Packaged applications with an embedded runtime do not require a system Node upgrade.
|
||||
- Supported runtimes provide `node:sqlite` and native TypeScript stripping without opt-in flags. The source CLI still uses its declared `tsx/esm` launcher.
|
||||
- The compatibility graph retains both private loader interfaces used by supported Node versions.
|
||||
- A future runtime-floor change updates `engines.node`, compatibility jobs, current documentation, this note, and an upgrade guide together.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **Keep Node 22 support.** It excludes the OAR library from the supported installation.
|
||||
- **Use OAR’s declared minimum of Node 24.0.** Existing script entrypoints depend on `import.meta.main`, which Node 24.0 lacks. A dependency minimum alone does not establish the application minimum.
|
||||
- **Test only the latest Node 24.** It does not exercise the minimum or the older private loader interface still used within the supported 24.x line.
|
||||
- **Raise Node declarations ahead of the minimum.** It allows code to compile against APIs that a supported runtime can lack.
|
||||
@@ -0,0 +1,33 @@
|
||||
# Agent Note: Node 运行时下限与兼容性覆盖
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-15-node-24-runtime-floor.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
根 `engines.node` 范围承诺安装后的工作区支持其允许的每一个运行时。其下限必须覆盖 DSH 使用的运行时 API 和所安装依赖声明的要求。否则,严格安装可能会在 DSH 启动前拒绝一个受支持的版本。
|
||||
|
||||
## 决策
|
||||
|
||||
将 `engines.node` 设为 `>=24.9.0`。Node 24.9 是 DSH 及其开发载体的下限,也允许实验性 OAR 子代理集成使用要求 Node 24 的 `@botiverse/oar` 库。此决策取代 [Node 22.19 下限](../../archived/process/2026-07-06-node-engine-floor.md);Node 23 仍被排除,下文的声明策略也仍比运行时下限更窄。迁移说明见[升级指南](../../../../docs/upgrade-guide/v0.2.1-rc.2/node-24/guide.zh.md)。
|
||||
|
||||
主要的 Node 24 任务负责完整的类型检查和单元测试覆盖率。聚焦的兼容性任务在固定的 Node 24.9 和 Node 26 上运行。Node 24.9 支持现有 `import.meta.main` 脚本入口和原生 TypeScript 类型剥离。固定 24.9 能验证这个已验证的下限及其 v1 私有模块加载器;主要任务跟随最新的 Node 24,验证 24.12.0 引入的 v2 加载器。浮动的 `24` 兼容性任务会重复主要任务的加载器覆盖,并停止验证下限。
|
||||
|
||||
Node 24.9 兼容性任务还构建产物,并检查随产品提供的 Web CLI 能在禁用全文 Session 搜索时启动和释放资源。command-entrypoint、source-worker、Zstandard、source-launch、profile-resolution 和 jsdom-storage 检查在两个兼容性版本上运行。真实 API 的 e2e 任务保留在 Node 24 上,因为它们验证的是提供方集成。
|
||||
|
||||
根项目、Web 和桌面项目将 `@types/node` 保留在 22.x 线。它们当前的代码不需要 Node 24 专属声明。较窄的类型 API 仍适用于受支持的运行时,也避免无关的声明变更。当受支持的 API 需要时再提升这些声明,并确保其主版本不高于运行时下限。独立发布的原生 addon 有自己的运行时和类型支持策略。
|
||||
|
||||
## 后果
|
||||
|
||||
- 使用 Node 22 的安装必须先升级,才能运行 DSH。内嵌运行时的打包应用不要求升级系统 Node。
|
||||
- 受支持的运行时无需启用标志即可使用 `node:sqlite` 和原生 TypeScript 类型剥离。源码 CLI 仍使用声明的 `tsx/esm` 启动器。
|
||||
- 兼容性任务图保留受支持 Node 版本使用的两种私有加载器接口。
|
||||
- 后续提升运行时下限时,必须一起更新 `engines.node`、兼容性任务、当前文档、本文和升级指南。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
- **保留 Node 22 支持。** 这会将 OAR 库排除在受支持的安装之外。
|
||||
- **采用 OAR 声明的 Node 24.0 下限。** 现有脚本入口依赖 `import.meta.main`,而 Node 24.0 缺少该功能。仅凭依赖下限无法确定应用下限。
|
||||
- **只验证最新的 Node 24。** 这既不能验证下限,也不能验证受支持 24.x 系列仍使用的旧私有加载器接口。
|
||||
- **将 Node 声明提升至下限之上。** 这会允许代码使用受支持运行时可能缺少的 API 编译通过。
|
||||
@@ -0,0 +1,22 @@
|
||||
# Bilingual-pair consistency record for 2026-10-18-source-first-metadata-builds.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-10-18-source-first-metadata-builds.md
|
||||
/agent-note-generate-metadata-before-package-compilation:
|
||||
en: 734f339ae4f8dc93
|
||||
zh: 5adf12518d4be31d
|
||||
/agent-note-generate-metadata-before-package-compilation/problem:
|
||||
en: 3c40c9bd375290a6
|
||||
zh: dc8d892bdd08c6b4
|
||||
/agent-note-generate-metadata-before-package-compilation/decision:
|
||||
en: c9f896731a173a13
|
||||
zh: 9978e46528caa456
|
||||
/agent-note-generate-metadata-before-package-compilation/measurements:
|
||||
en: 42f488af4497f671
|
||||
zh: 00eeb4cc51799da3
|
||||
/agent-note-generate-metadata-before-package-compilation/alternatives-considered:
|
||||
en: 81fde4514a8b6fc0
|
||||
zh: 4739f5c7978e7db2
|
||||
/agent-note-generate-metadata-before-package-compilation/consequences:
|
||||
en: b12bcb7cc9467706
|
||||
zh: 8896ac7cd011e3c0
|
||||
@@ -0,0 +1,46 @@
|
||||
# Agent Note: Generate metadata before package compilation
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-18-source-first-metadata-builds.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
A Host package can consume generated Remote declarations without becoming a Client package. Generating metadata during Host bundling forces the build to identify those consumers, delay their compilation, and propagate that delay to their callers. TypeScript project references already describe the package dependencies; maintaining another source-import graph duplicates ordering work.
|
||||
|
||||
## Decision
|
||||
|
||||
Artifact and declaration-only builds run source-only Typert generation before package compilation. The existing generator rejects workspace build outputs and unresolved workspace imports, so a metadata producer cannot depend on its own generated declarations through another source package. TypeScript retains compiler-face ownership and rejects project-reference cycles. Generation failure stops dependent compilation and bundling.
|
||||
|
||||
The root bundler uses `decoratorPlugin()` to lower standard TypeScript decorators without generating metadata again. The public `typertPlugin()` retains package/workspace generation and its Worker Thread behavior. [Development](../../../../docs/development.md#typescript-project-layout) owns the current command order.
|
||||
|
||||
This ordering replaces only the generation placement in [local gate speed-ups](2026-10-12-local-gate-speedups.md), [lossless build speed-ups](2026-10-11-lossless-build-speedups.md), [the build/check split](2026-10-11-build-artifacts-check-split.md), and [Remote method generation](../architecture/2026-08-02-typert-remote-method-calls.md). Their other optimizations, failure guarantees, public plugin behavior, and historical measurements remain useful and stay active.
|
||||
|
||||
## Measurements
|
||||
|
||||
The local experiment uses macOS ARM64, Node 24.19.0 and TypeScript 6.0.3. `DSH_GATE_CONCURRENCY=1 pnpm run build:artifacts --profile official` runs baseline/candidate/candidate/baseline. Each run starts after `pnpm run clean`, then repeats without source changes. Installation and cleanup are excluded; native preparation, package compilation, bundles, Desktop, Web and build-record publication are included. Other owned heavy jobs pause; unrelated host load remains possible. Filesystem caches are not flushed.
|
||||
|
||||
| Inputs | Clean baseline / candidate (s) | No-change baseline / candidate (s) | Clean peak RSS baseline / candidate (MiB) |
|
||||
|---|---|---|---|
|
||||
| Without Node Session consumers | 56.67, 53.55 / 56.29, 56.37 | 13.64, 13.74 / 15.32, 15.26 | 3855, 3858 / 3255, 3268 |
|
||||
| Separate prototype: Node Session consumers and delayed-stage removal | 61.18, 61.41 / 56.27, 56.44 | 20.08, 20.09 / 15.98, 16.09 | 4013, 4012 / 3278, 3291 |
|
||||
|
||||
RSS is summed over descendant processes approximately every 100ms; shared pages count more than once, so this is not unique physical memory. Two samples per case are local evidence, not a CI budget. All 150 generated Typert/Remote files are byte-identical in both comparisons. Complete inventories contain 12,246 and 12,352 files respectively; only the changed decorator-plugin module's four compiler outputs differ.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep generation inside Host bundling.** It preserves overlap and the faster standalone no-change build, but consumers of generated declarations require a separate delayed compilation schedule.
|
||||
|
||||
**Hand-maintain delayed packages or change their compiler faces.** Both make package ownership depend on build placement and add an inventory beside project references.
|
||||
|
||||
**Use source-first generation only for type checks.** Artifact builds would still need the consumer scanner, leaving two schedules to maintain.
|
||||
|
||||
**Remove the public generation plugin.** Its package/workspace API and Worker Thread failure ownership remain supported; repository scheduling does not justify deleting that API.
|
||||
|
||||
## Consequences
|
||||
|
||||
The consumer row is evidence from a separate integration prototype. It does not describe the packages or validation delivered by this prerequisite.
|
||||
|
||||
The standalone prerequisite costs 1.60s (+11.7%) in median no-change artifact time because generation no longer overlaps bundling. Its clean difference is +1.22s, smaller than the baseline spread. This is ordering support, not an independent speedup. With delayed stages removed, the measured consumer build improves by 4.94s clean and 4.05s without changes. Peak summed RSS decreases in both cases.
|
||||
|
||||
Source-generation tests reject metadata dependency cycles with absent and preexisting outputs. A fresh Host-consumer fixture compiles against metadata generated in the same build. Decorator tests exercise both plugin entry points, and complete builds verify application artifacts. Wine's separate command sequence prepares metadata explicitly; its source guard rejects missing preparation, while native Windows execution remains CI-owned.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Agent Note: 在包编译前生成元数据
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-18-source-first-metadata-builds.md) | 中文
|
||||
|
||||
## Problem
|
||||
|
||||
Host 包可以消费生成的 Remote 声明,而不必成为 Client 包。如果在 Host 打包期间生成元数据,构建就必须识别这些消费方、延迟其编译,并把延迟传播给调用方。TypeScript project reference 已经描述包依赖;再维护一张源码导入图会重复处理顺序。
|
||||
|
||||
## Decision
|
||||
|
||||
产物构建与仅生成声明的构建都在包编译之前运行仅源码的 Typert 生成。现有生成器拒绝工作区构建产物与无法解析的工作区导入,因此元数据生产者不能经由另一个源码包依赖自身生成的声明。TypeScript 保留 compiler face 的归属,并拒绝 project reference 环。生成失败后不启动依赖它的编译与打包。
|
||||
|
||||
根打包器使用 `decoratorPlugin()` 转换标准 TypeScript 装饰器,不重复生成元数据。公开的 `typertPlugin()` 保留 package/workspace 生成与 Worker Thread 行为。[开发指南](../../../../docs/development.zh.md#typescript-project-layout)负责当前命令顺序。
|
||||
|
||||
该顺序只替代[本地门禁提速](2026-10-12-local-gate-speedups.zh.md)、[无损构建提速](2026-10-11-lossless-build-speedups.zh.md)、[构建/检查拆分](2026-10-11-build-artifacts-check-split.zh.md)与 [Remote 方法生成](../architecture/2026-08-02-typert-remote-method-calls.zh.md)中的生成位置。它们的其他优化、失败保证、公开插件行为与历史测量仍有价值,因此保留为活跃记录。
|
||||
|
||||
## Measurements
|
||||
|
||||
本地实验使用 macOS ARM64、Node 24.19.0 与 TypeScript 6.0.3。`DSH_GATE_CONCURRENCY=1 pnpm run build:artifacts --profile official` 按基线/候选/候选/基线顺序运行。每轮先执行 `pnpm run clean`,再在源码不变的情况下重跑。安装与清理不计时;native 准备、包编译、bundle、Desktop、Web 与构建记录发布计入时间。暂停本任务的其他重负载工作;主机上仍可能有无关负载。不清空文件系统缓存。
|
||||
|
||||
| 输入 | 干净构建基线/候选 (s) | 无改动构建基线/候选 (s) | 干净构建 RSS 峰值基线/候选 (MiB) |
|
||||
|---|---|---|---|
|
||||
| 不含 Node Session 消费方 | 56.67, 53.55 / 56.29, 56.37 | 13.64, 13.74 / 15.32, 15.26 | 3855, 3858 / 3255, 3268 |
|
||||
| 独立原型:Node Session 消费方与后置阶段删除 | 61.18, 61.41 / 56.27, 56.44 | 20.08, 20.09 / 15.98, 16.09 | 4013, 4012 / 3278, 3291 |
|
||||
|
||||
RSS 约每 100ms 汇总所有子孙进程;共享页面会重复计数,因此不代表唯一物理内存。每种情况的两个样本是本地证据,不是 CI 预算。两组比较中的全部 150 个 Typert/Remote 生成文件逐字节相同。完整清单分别包含 12,246 与 12,352 个文件;只有装饰器插件模块的四个编译产物不同。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**保留 Host 打包内的生成。** 它保留重叠执行与更快的独立无改动构建,但生成声明的消费方需要单独的后置编译调度。
|
||||
|
||||
**手工维护后置包,或改变它们的 compiler face。** 两者都让包归属受构建位置影响,并在 project reference 之外增加一份清单。
|
||||
|
||||
**仅在类型检查中采用先生成顺序。** 产物构建仍需要消费方扫描器,留下两套需要维护的调度。
|
||||
|
||||
**删除公开生成插件。** 它的 package/workspace API 与 Worker Thread 失败归属仍受支持;仓库调度改变不能成为删除该 API 的理由。
|
||||
|
||||
## Consequences
|
||||
|
||||
消费方一行是独立集成原型的测量证据,不表示本前置改动交付的包或验证。
|
||||
|
||||
独立前置改动使无改动产物构建的中位数增加 1.60s(+11.7%),因为生成不再与打包重叠。干净构建增加 1.22s,小于基线的样本波动。该改动提供顺序支持,并非独立提速。删除后置阶段后,测得的消费方构建在干净状态下快 4.94s,无改动时快 4.05s。两组的进程树 RSS 峰值都下降。
|
||||
|
||||
源码生成测试在产物缺失和已存在时均拒绝元数据依赖环。干净的 Host 消费方 fixture 使用同一次构建生成的元数据完成编译。装饰器测试覆盖两个插件入口,完整构建验证应用产物。Wine 的独立命令序列显式准备元数据;源码守卫拒绝缺失的准备步骤,原生 Windows 执行仍由 CI 负责。
|
||||
@@ -0,0 +1,22 @@
|
||||
# Bilingual-pair consistency record for 2026-10-18-borrow-auto-review-inputs.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/simplification/2026-10-18-borrow-auto-review-inputs.md
|
||||
/agent-note-borrow-auto-review-inputs-until-serialization:
|
||||
en: 7935a9077525c95c
|
||||
zh: f54640700003b6a3
|
||||
/agent-note-borrow-auto-review-inputs-until-serialization/problem:
|
||||
en: ae55e01074cf34e0
|
||||
zh: c9778fdb8fb157e3
|
||||
/agent-note-borrow-auto-review-inputs-until-serialization/proposal:
|
||||
en: 67b001f13952f165
|
||||
zh: 09f849fad8817aad
|
||||
/agent-note-borrow-auto-review-inputs-until-serialization/alternatives-considered:
|
||||
en: 86495afdbc23ebe1
|
||||
zh: d5c237aea3f14eed
|
||||
/agent-note-borrow-auto-review-inputs-until-serialization/acceptance-criteria:
|
||||
en: b193bda5eb966372
|
||||
zh: e493c784adec4f05
|
||||
/agent-note-borrow-auto-review-inputs-until-serialization/risks:
|
||||
en: df5aebcbd42a13df
|
||||
zh: 5945aa631bd4e07f
|
||||
@@ -0,0 +1,25 @@
|
||||
# Agent Note: Borrow Auto review inputs until serialization
|
||||
|
||||
Status: proposed
|
||||
|
||||
English | [中文](2026-10-18-borrow-auto-review-inputs.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
[`snapshotAutoReview()`](../../../../packages/experimental/auto-review/src/index.ts) reads frozen Session history, request headers, and tool arguments, but also copies `session.surface.nodes` and freezes its private result. Its only caller, `classifyRisk()`, immediately serializes that result without an intervening await or publication.
|
||||
|
||||
## Proposal
|
||||
|
||||
Borrow `session.surface.nodes` and return the readonly intermediate result without another `deepFreeze()`. Preserve all action validation and the final `GenerateOptions` freeze. [Session-owned immutability](../../implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md) and [request-only input freezing](../../implemented/architecture/2026-09-17-persistence-attribution-policy.md) remain unchanged. This removes a private array allocation and freeze traversal without changing the reviewer request.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep the intermediate snapshot.** A future retained or asynchronous reader would need stable inputs. No current consumer retains this object or awaits before rendering.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
Native and PTC requests retain identical text, source filtering, and action checks. Existing request-freezing and cancellation assertions pass. Serialization does not mutate borrowed Session or execution data.
|
||||
|
||||
## Risks
|
||||
|
||||
Future asynchronous work or publication of the intermediate object would require reconsidering ownership. Removing the final request freeze or the source owners' snapshots is outside this proposal.
|
||||
@@ -0,0 +1,25 @@
|
||||
# Agent Note: Auto review 输入在序列化前采用借用
|
||||
|
||||
Status: proposed
|
||||
|
||||
[English](2026-10-18-borrow-auto-review-inputs.md) | 中文
|
||||
|
||||
## Problem
|
||||
|
||||
[`snapshotAutoReview()`](../../../../packages/experimental/auto-review/src/index.ts) 读取已冻结的 Session 历史、请求头和工具参数,但还会复制 `session.surface.nodes` 并冻结私有结果。它唯一的调用方 `classifyRisk()` 会立即序列化该结果,中间没有 await,也不会发布结果。
|
||||
|
||||
## Proposal
|
||||
|
||||
借用 `session.surface.nodes`,返回只读的中间结果,不再调用一次 `deepFreeze()`。保留全部操作校验和最终 `GenerateOptions` 的冻结。[Session 拥有的不可变性](../../implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.zh.md) 和[仅用于请求的输入冻结](../../implemented/architecture/2026-09-17-persistence-attribution-policy.zh.md) 保持不变。这会移除一次私有数组分配和冻结遍历,同时保持审核请求不变。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**保留中间快照。** 未来若有留存数据或异步读取的消费方,就需要稳定输入。当前没有消费方留存该对象,也没有消费方在渲染前执行 await。
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
原生工具和 PTC 请求保留完全相同的文本、来源过滤和操作检查。现有请求冻结与取消断言通过。序列化不修改借用的 Session 或执行数据。
|
||||
|
||||
## Risks
|
||||
|
||||
未来若引入异步工作或发布中间对象,需要重新考虑所有权。移除最终请求的冻结或数据所有者提供的快照不在本提案范围内。
|
||||
@@ -0,0 +1,22 @@
|
||||
# Bilingual-pair consistency record for 2026-10-18-parse-ssh-policy-once.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/simplification/2026-10-18-parse-ssh-policy-once.md
|
||||
/agent-note-parse-ssh-policies-once-at-request-admission:
|
||||
en: 0c215d7a2d291857
|
||||
zh: df3c91383d122974
|
||||
/agent-note-parse-ssh-policies-once-at-request-admission/problem:
|
||||
en: 3cb8bcfb464726d4
|
||||
zh: f87f7cc23e7d161c
|
||||
/agent-note-parse-ssh-policies-once-at-request-admission/proposal:
|
||||
en: 71eb2e823861a3b0
|
||||
zh: 0f6fb08df9b3217f
|
||||
/agent-note-parse-ssh-policies-once-at-request-admission/alternatives-considered:
|
||||
en: 98230674c8b68772
|
||||
zh: 068c70ede9914a05
|
||||
/agent-note-parse-ssh-policies-once-at-request-admission/acceptance-criteria:
|
||||
en: e48ed6ba31628951
|
||||
zh: 240022dd565487bc
|
||||
/agent-note-parse-ssh-policies-once-at-request-admission/risks:
|
||||
en: 0d2e4cd1658f0d1a
|
||||
zh: 6838c9083b629c8b
|
||||
@@ -0,0 +1,25 @@
|
||||
# Agent Note: Parse SSH policies once at request admission
|
||||
|
||||
Status: proposed
|
||||
|
||||
English | [中文](2026-10-18-parse-ssh-policy-once.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The [`sandbox` and `fs.write`/`fs.edit` handlers](../../../../packages/ssh/ssh/src/helper.ts) are the private policy resolver's only callers. They validate requests with `policySchema`; the resolver repeats that parse, including a policy and extra-root array copy.
|
||||
|
||||
## Proposal
|
||||
|
||||
Accept `z.output<typeof policySchema>` in the private resolver and remove its parse. Retain both wire parsers, execution-host path resolution, cancellation, SessionId branding, and explicit omission of undefined optional fields. The [SSH execution decision](../../implemented/architecture/2026-09-11-posix-ssh-runtime.md) continues to own remote policy enforcement.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep the resolver's unknown input.** It protects future unparsed callers, but current callers already decode. A typed parameter states the required precondition without repeated runtime work.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
Both request handlers still reject malformed policies, relative roots, and unexpected fields. Remote physical-root checks and returned policies remain unchanged. Existing helper tests cover absent and supplied roots, read-only policy, and denied writes.
|
||||
|
||||
## Risks
|
||||
|
||||
A future wire caller must validate before invoking the resolver. Preserve optional-field omission because Zod permits explicit `undefined` while the execution-policy type does not.
|
||||
@@ -0,0 +1,25 @@
|
||||
# Agent Note: 在请求接收处只解析一次 SSH 策略
|
||||
|
||||
Status: proposed
|
||||
|
||||
[English](2026-10-18-parse-ssh-policy-once.md) | 中文
|
||||
|
||||
## Problem
|
||||
|
||||
[`sandbox` 和 `fs.write`/`fs.edit` 处理函数](../../../../packages/ssh/ssh/src/helper.ts) 是私有策略解析器仅有的调用方。它们使用 `policySchema` 校验请求;解析器重复该解析,包括复制策略对象和额外根目录数组。
|
||||
|
||||
## Proposal
|
||||
|
||||
让私有解析器接收 `z.output<typeof policySchema>`,移除其中的解析。保留两个线上请求解析器、执行主机上的路径解析、取消、SessionId 品牌类型转换,以及对值为 undefined 的可选字段的显式省略。[SSH 执行决策](../../implemented/architecture/2026-09-11-posix-ssh-runtime.zh.md) 继续规定远程策略执行。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**保留解析器的 unknown 输入。** 它能保护未来未经解析的调用方,但当前调用方已经解码。类型化参数可表达必要前提,无需重复执行运行时工作。
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
两个请求处理函数仍拒绝格式错误的策略、相对根目录和意外字段。远程物理根目录检查和返回策略保持不变。现有辅助进程测试覆盖未提供和已提供根目录、只读策略以及被拒绝的写入。
|
||||
|
||||
## Risks
|
||||
|
||||
未来的线上请求调用方必须先校验,再调用解析器。保留可选字段省略,因为 Zod 允许显式 `undefined`,而执行策略类型不允许。
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
# Bilingual-pair consistency record for 2026-10-18-single-workspace-canonicalization.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/simplification/2026-10-18-single-workspace-canonicalization.md
|
||||
/agent-note-canonicalize-each-sandbox-workspace-once:
|
||||
en: d9beb1e7a988d598
|
||||
zh: 9ff16ea850cb3d57
|
||||
/agent-note-canonicalize-each-sandbox-workspace-once/problem:
|
||||
en: 04219da3d9301a2d
|
||||
zh: 92a399597d315cba
|
||||
/agent-note-canonicalize-each-sandbox-workspace-once/proposal:
|
||||
en: d5017ea68bf366ea
|
||||
zh: 6aa157b402d4d8d0
|
||||
/agent-note-canonicalize-each-sandbox-workspace-once/alternatives-considered:
|
||||
en: f73a3a3a1a90fb16
|
||||
zh: 3ec1d23f24436e26
|
||||
/agent-note-canonicalize-each-sandbox-workspace-once/acceptance-criteria:
|
||||
en: 0a2aa37511fa5574
|
||||
zh: 15258e58b498d944
|
||||
/agent-note-canonicalize-each-sandbox-workspace-once/risks:
|
||||
en: 1fdb53b7c48b1f8d
|
||||
zh: 999debc230df8d39
|
||||
@@ -0,0 +1,25 @@
|
||||
# Agent Note: Canonicalize each sandbox workspace once
|
||||
|
||||
Status: proposed
|
||||
|
||||
English | [中文](2026-10-18-single-workspace-canonicalization.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
[`LocalSandboxProvider.confine()`](../../../../packages/sandbox/sandbox-local/src/index.ts) canonicalizes `workspaceRoot` and copies the policy before dispatch. POSIX profile builders call [`workspaceWritableRoots()`](../../../../packages/sandbox/sandbox/src/roots.ts), which resolves that workspace again. The common preprocessing also runs for POSIX read-only policies that grant no workspace writes.
|
||||
|
||||
## Proposal
|
||||
|
||||
Remove the common policy copy. Keep canonicalization in shared POSIX root derivation; canonicalize once inside `windowsAclRunnerArgv()`, reusing that value for argv, SID derivation, and ACL grants. Preserve per-call extra-root validation and each backend's temporary-directory rules.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep common preprocessing.** It supplies every backend with one physical workspace identity, but POSIX repeats the filesystem work. Explicit Windows normalization preserves the [Windows ACL decision](../../implemented/feature/2026-08-08-windows-acl-restricted-token-sandbox.md).
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
Direct profile builders and `runnerCommand` retain canonical roots. Windows argv, SID, and grant keys agree. Existing alias-rejection and read-only checks pass; a focused trace confirms removal of the duplicate workspace lookup.
|
||||
|
||||
## Risks
|
||||
|
||||
Incomplete relocation can split Windows grant identity. Do not cache extra roots: enforcement must recheck a directory replaced by a symlink.
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Agent Note: 每个沙箱工作区只规范化一次
|
||||
|
||||
Status: proposed
|
||||
|
||||
[English](2026-10-18-single-workspace-canonicalization.md) | 中文
|
||||
|
||||
## Problem
|
||||
|
||||
[`LocalSandboxProvider.confine()`](../../../../packages/sandbox/sandbox-local/src/index.ts) 在分派前规范化 `workspaceRoot` 并复制策略。POSIX 配置构建函数调用 [`workspaceWritableRoots()`](../../../../packages/sandbox/sandbox/src/roots.ts),再次解析同一工作区。对于不授予工作区写权限的 POSIX 只读策略,公共预处理也会执行。
|
||||
|
||||
## Proposal
|
||||
|
||||
移除公共策略复制。保留共享 POSIX 根目录推导中的规范化;在 `windowsAclRunnerArgv()` 内规范化一次,并在 argv、SID 推导和 ACL 授权中复用该值。保留每次调用的额外根目录校验,以及各后端的临时目录规则。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**保留公共预处理。** 它为每个后端提供统一的物理工作区标识,但 POSIX 会重复访问文件系统。显式保留 Windows 规范化可维持 [Windows ACL 决策](../../implemented/feature/2026-08-08-windows-acl-restricted-token-sandbox.zh.md)。
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
直接调用配置构建函数和使用 `runnerCommand` 时仍得到规范化的根目录。Windows argv、SID 与授权键保持一致。现有别名拒绝和只读检查通过;聚焦的跟踪结果确认重复的工作区查找已移除。
|
||||
|
||||
## Risks
|
||||
|
||||
迁移不完整可能使 Windows 授权标识不一致。不得缓存额外根目录:执行权限限制时必须重新检查被符号链接替换的目录。
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
en: 7b7248fc12313b69
|
||||
zh: a55e2c3376c401a7
|
||||
/agent-note-dependency-swaps-rejected-by-the-2026-07-nih-audit/proposal:
|
||||
en: d95debbdb2a7e730
|
||||
zh: 1edbf6ca992a2319
|
||||
en: fcd5edbdfefd224c
|
||||
zh: 0172065e20f9b3ee
|
||||
/agent-note-dependency-swaps-rejected-by-the-2026-07-nih-audit/alternatives-considered:
|
||||
en: 229a0110ca47384e
|
||||
zh: 3f86f858d56190c5
|
||||
+1
-1
@@ -58,7 +58,7 @@ Adopt the following dependency swaps. Rejected — per-item evidence below; a fu
|
||||
|
||||
**SQLite and storage:**
|
||||
|
||||
- **`better-sqlite3` for the three SQLite backends**: all use builtin `node:sqlite`, intentional twice over — it gates the [Node engine floor](../../implemented/process/2026-07-06-node-engine-floor.md) and works inside the single-file executable where a native addon would complicate packaging. No hand-rolled migrations or busy-retry loops exist.
|
||||
- **`better-sqlite3` for the three SQLite backends**: all use builtin `node:sqlite`, intentional twice over — it is available throughout the [supported Node range](../../implemented/process/2026-10-15-node-24-runtime-floor.md) and works inside the single-file executable where a native addon would complicate packaging. No hand-rolled migrations or busy-retry loops exist.
|
||||
|
||||
**Repo tooling:**
|
||||
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ Status: rejected — 下列每一项替换在证据上都未达到净简化门
|
||||
|
||||
**SQLite 与存储:**
|
||||
|
||||
- **以 `better-sqlite3` 承担三个 SQLite 后端**:三者全部使用内置 `node:sqlite`,且是双重有意为之——它是 [Node 引擎下限](../../implemented/process/2026-07-06-node-engine-floor.zh.md)的把关依据,也能在单文件可执行体内工作,原生 addon 反而会让打包复杂化。不存在任何手写的迁移或 busy 重试循环。
|
||||
- **以 `better-sqlite3` 承担三个 SQLite 后端**:三者全部使用内置 `node:sqlite`,且是双重有意为之——它在 [受支持的 Node 范围](../../implemented/process/2026-10-15-node-24-runtime-floor.zh.md)内可用,也能在单文件可执行体内工作,原生 addon 反而会让打包复杂化。不存在任何手写的迁移或 busy 重试循环。
|
||||
|
||||
**仓库工具链:**
|
||||
|
||||
|
||||
@@ -41,9 +41,9 @@ description: Use when reviewing a pull request in the deepseek-harness repo —
|
||||
- **Intent and interface contracts:** trace both sides of every changed interface. Confirm the implementation matches the PR and any Agent Note, including errors, cancellation, ownership, and disposal.
|
||||
- **Error handling:** apply [dsh-error-handling](../dsh-error-handling/SKILL.md) to changed failure paths, including their state guarantees and final user-visible reports. Reject catch-and-continue unless the state owner establishes that subsequent work's preconditions hold, including after recovery.
|
||||
- **Lifecycle and concurrency:** for async setup, callbacks, processes, or teardown, apply [defensive-patterns/general.md](../../../docs/defensive-patterns/general.md). Check races before publication, cancellation during awaits, independent error reporting, callback containment, ownership before reentry, complete detach cleanup, and quiescent disposal.
|
||||
- **Capability and consumer fit:** trace every current consumer, then flag consumer-specific behavior leaking into the interface under [the package rules](../../../packages/AGENTS.md). Flag the inverse too: a new public method on a generic service (registry, session, agent) whose only caller is one internal consumer is an unnecessary API expansion — require a private capability closure handed to that consumer at construction instead.
|
||||
- **Scope, ownership, and necessity:** map each abstraction, state machine, option, defensive copy, and compatibility path to its current contract, production consumer, and owning plugin or service. Challenge unrelated features and speculative generality, then test the PR against [the root rules](../../../AGENTS.md#conventions).
|
||||
- **Configuration and public choices:** ask what current-consumer evidence or prior art supports each default, public operation set, format, or imported external concept. Require an explicit choice or deferral when that evidence is absent.
|
||||
- **Scope, ownership, and necessity:** first identify the required behavior behind each new distinction, field, adapter, state machine, option, defensive copy, and compatibility path. A requirement, check, or Agent Note introduced by the PR is a design choice to assess, not independent evidence of necessity. Compare existing source fields, discriminants, and their consumers before accepting another representation; distinguish deleting a concept from relocating its declaration. Moving an unnecessary concept closer to its owner is not a simplification. Challenge unrelated features and speculative generality against [the root rules](../../../AGENTS.md#conventions).
|
||||
- **Capability and consumer fit:** trace every current consumer, then flag consumer-specific behavior leaking into the interface under [the package rules](../../../packages/AGENTS.md). For required behavior, prefer an existing extension point near its producer or consumer when the local implementation adds little complexity, following [the ownership rule](../../../docs/defensive-patterns/general.md#a-business-capability-owns-itself); compare producer-owned declaration merging with changes to distant core or shared type declarations. Flag the inverse too: a new public method on a generic service (registry, session, agent) whose only caller is one internal consumer is an unnecessary API expansion — require a private capability closure handed to that consumer at construction instead.
|
||||
- **Configuration and public choices:** trace authoritative reads and precedence before accepting another CLI or configuration pass-through; a forwarded value may be shadowed before use. Ask what current-consumer evidence or prior art supports each default, public operation set, format, or imported external concept. Require an explicit choice or deferral when that evidence is absent.
|
||||
- **Model perspective:** inspect the exact prompts, tool schemas, results, and diagnostics the model receives across affected modes. Flag concepts outside the model's task, then verify stable text verbatim and dynamic behavior through snapshots or end-to-end coverage.
|
||||
- **Enforcement:** follow every denial path to the operation that executes it; exercise direct and alternate callers that can bypass schemas, prompts, facades, wrappers, or listener ordering.
|
||||
- **Borrowed and derived state:** determine whether each retained value is borrowed or owned under the package contract, then trace notifications and every cache, prompt, UI echo, replay, and query view to the documented success point and authoritative source.
|
||||
|
||||
@@ -17,14 +17,14 @@ For broad requests, divide independent domains among subagents: loop and persist
|
||||
|
||||
## Search for removable obligations
|
||||
|
||||
Use these questions to guide discovery. Read the relevant section of [historical patterns](references/historical-patterns.md) when a candidate needs calibration; the examples illustrate decisions, not a current deletion inventory.
|
||||
Use these questions to guide discovery. Requirements, checks, and Agent Notes introduced by the code under review are design choices to assess, not independent evidence that its distinctions are necessary. Read the relevant section of [historical patterns](references/historical-patterns.md) when a candidate needs calibration; the examples illustrate decisions, not a current deletion inventory.
|
||||
|
||||
- **Does a declared feature have a complete effect path?** Trace producer, transformations, provider or endpoint support, and observable result. A field copied everywhere may still have no setter, no reader, or only implementations that reject it. Search constructors and discriminant emitters as well as callers.
|
||||
- **Which distinctions change a consumer's action?** Several internal states may require one model-facing status or one usable identifier. Keep internal distinctions that control authorization, residency, durability, or ownership; remove public distinctions that only invite an unusable choice.
|
||||
- **Does a declared feature have a complete effect path?** Trace producer, transformations, provider or endpoint support, and observable result. A field copied everywhere may still have no setter, no reader, or only implementations that reject it. For CLI and configuration pass-throughs, follow authoritative reads and precedence to find forwarded values that are shadowed before use. Search constructors and discriminant emitters as well as callers.
|
||||
- **Which distinctions change a consumer's action?** First ask whether the distinction, field, or adapter is needed at all. Trace existing source fields and discriminants through their emitters and authorization or capability checks before adding a transport-specific marker; a broad envelope category need not have the source field's meaning. Consider deleting a distinction separately from relocating its declaration, and state which inputs or capabilities each alternative admits. Several internal states may require one model-facing status or one usable identifier. Preserve required authorization, residency, durability, and ownership behavior; remove public distinctions that only invite an unusable choice.
|
||||
- **Could a smaller explicit behavior remove the subsystem?** Fixed intervals, caller-supplied time zones, or reported activation failure can eliminate generalized calendars, persisted defaults, or automatic rollback. Name the capability given up and the machinery deleted. A production caller makes this a behavior decision, not an automatic rejection; implementation must remain within the user's authorization.
|
||||
- **Can a consumer read the authoritative value when needed?** Look for copied histories, promotion ledgers, invalidation events followed by reads, and feature caches beside a shared projection stream. Derivation or reuse may delete subscriptions, retained data, stale-response handling, and recovery paths together. Establish the required time of observation first.
|
||||
- **Is composition being mistaken for policy?** Optional service presence, a provider method, or a sibling tool can express availability without deciding deployment behavior. Explicit configuration or a caller-intent operation may remove registries and dependency checks. Preserve distinct operations when their ownership or completion timing differs.
|
||||
- **What owns the complete maintenance cost?** Trace pass-through configuration, duplicate application trees, demo launchers used by products, resolver-only workspaces, and installation helpers with their own upgrade lifecycle. Compare the entire removed system with the replacement, including residual glue; moving complexity or adding a gate to keep duplicate definitions equal does not remove it.
|
||||
- **What owns the complete maintenance cost?** Trace duplicate application trees, demo launchers used by products, resolver-only workspaces, and installation helpers with their own upgrade lifecycle. For required behavior, compare existing producer/consumer extension points, including declaration merging, with changes to distant core or shared type declarations under [the ownership rule](../../../docs/defensive-patterns/general.md#a-business-capability-owns-itself). Compare the entire removed system with the replacement, including residual glue; moving an unnecessary concept closer to its owner or adding a gate to keep duplicate definitions equal does not remove its maintenance cost.
|
||||
|
||||
## Prove reachability and the trade-off
|
||||
|
||||
@@ -32,7 +32,7 @@ Start with `rg`, then read the matches. Search exact symbols, property reads and
|
||||
|
||||
Include `packages/*/*/src`, `apps/`, `python/`, runtime scripts, shipped profiles and overlays, package manifests and exports, loader resolution, generated runtime assets, and installed-package consumers. Classify examples and fixtures by their actual entry point rather than directory name. Follow generated catalogs to their consumers: a catalog used by model-written plugin mounts is a dynamic product API, while a documentation-only listing is not a fixed caller. Public third-party extension paths can be intentional despite zero repository call sites. A static unused-code checker must cover manifest-driven loading, generated outputs, and compiler faces without maintaining a parallel per-workspace exception inventory.
|
||||
|
||||
For each candidate, record the current owner, effective producer/consumer path, what disappears, what remains, and the strongest reason to retain it. Distinguish:
|
||||
For each candidate, record the current owner, effective producer/consumer path, what disappears, what remains, capability and compatibility effects, and the strongest reason to retain it. Distinguish:
|
||||
|
||||
- removal of unreachable or unread behavior;
|
||||
- a narrower public or product behavior with an explicit loss;
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
---
|
||||
name: dsh-pr-description
|
||||
description: Use when creating a deepseek-harness pull request, or when updating its description after later changes or a handled review round. Defines each description section, Issue references, test design, change-specific disclosures, and the Human and Agent Roles section with its levels, stages, grading rules, and update timing.
|
||||
user-invocable: false
|
||||
---
|
||||
|
||||
# Writing PR descriptions
|
||||
|
||||
This skill defines what a deepseek-harness PR description contains, how to write it, and when to update it. The [PR template](../../../.github/pull_request_template.md) supplies only the section headings and placeholders; this skill owns the requirements for each section. Every PR description follows this skill, including PRs created with `gh pr create --body-file`, which skips the template.
|
||||
|
||||
## Scope
|
||||
|
||||
- Write the complete description when you create the PR.
|
||||
- After the PR exists, update the affected sections when the description no longer matches the PR, at times you judge suitable.
|
||||
- Each PR in a stack has its own description, which covers only that layer's changes against its base branch.
|
||||
|
||||
## Description structure
|
||||
|
||||
The description uses four second-level headings, in this order: `## Motivation`, `## Changes`, `## Testing`, and `## Human and Agent Roles`.
|
||||
|
||||
Write all headings and labels, such as **Interface changes**, **Behavior changes**, **Test design**, and **Test entries**, in English. Write the rest of the description in Chinese unless a human asked for another language for this PR.
|
||||
|
||||
### Motivation
|
||||
|
||||
- State the problem in one sentence.
|
||||
- Reference at least one same-repository Issue: use `Fixes #N`, `Closes #N`, or `Resolves #N` when the PR resolves it, and `Refs #N` when it only gives context. PR numbers do not count as Issue references, and neither do references inside HTML comments, code fences, or inline code. [Issue management](../../../.github/issue-management/README.md) owns the exact rules and the Priority matching.
|
||||
|
||||
### Changes
|
||||
|
||||
- Interface changes: summarize changes to commands (including repository scripts), flags, configuration keys, environment variables, public exports, plugin registration values, protocols, or persisted formats; write None when there are none.
|
||||
- Behavior changes: summarize results that users, models, developers, or the system can observe, including defaults, performance, timing, and output; write None when there are none. Changes to documentation and agent instructions, such as READMEs, AGENTS.md, and skills, are behavior changes for the readers they guide.
|
||||
- State only behavior that the diff guarantees and the tests cover.
|
||||
|
||||
### Testing
|
||||
|
||||
Testing starts with the test design, followed by the test entries.
|
||||
|
||||
**Test design**: summarize how the tests cover each item in Changes and why that coverage is complete. Choose the form by change type:
|
||||
|
||||
| Change type | What the test design shows |
|
||||
|---|---|
|
||||
| Behavior change | A behavior matrix of scenario × behavior before × behavior after × covering test, showing which scenarios failed or differed before the change |
|
||||
| Performance change | Before and after values per stage or scenario, with the measurement conditions: machine, run count, and how the value is taken |
|
||||
| Refactoring or migration without behavior change | How the unchanged behavior is shown: byte-for-byte comparison, unchanged snapshots, or old-versus-new comparison tests |
|
||||
| Documentation only | How the content is checked: links, bilingual pairing, example compilation, or similar |
|
||||
|
||||
When the PR mixes change types, give one form for each type present. Items that Changes lists as None need no test design.
|
||||
|
||||
**Test entries**:
|
||||
|
||||
- Add one entry per test method; state the command or steps visibly, and the behavior it covers.
|
||||
- List only tests that have run, with their results; add later runs when the description is updated. State whether the evidence comes from a local run or from CI.
|
||||
- Put reviewable evidence, such as test output, screenshots, recordings, or logs, in that entry's `<details><summary>Proof</summary>` block; when the full output is long, the block may hold the command, the counts, and a result summary.
|
||||
|
||||
### Change-specific disclosures
|
||||
|
||||
Write each disclosure in the section it concerns, usually Changes, after a bold English label that names it, such as **Existing test behavior**; do not add headings.
|
||||
|
||||
| Change | What the description states | Rule owner |
|
||||
|---|---|---|
|
||||
| Changing the behavior an existing test describes | Why the behavior changed | The Tests describe behavior rule in [root AGENTS.md](../../../AGENTS.md) |
|
||||
| Raising a ceiling in `scripts/doc-budgets.manifest.json` | Each raised ceiling with its old and new value, and why the words are necessary | [Wordcount budgets](../../../docs/AGENTS.md#wordcount-budgets) |
|
||||
| Changing product-user-visible GUI behavior | An embedded demonstration GIF, with the disclosures that record-browser-gif requires beside the embed; for each GUI change without a GIF, a statement that none is embedded and why | [record-browser-gif](../record-browser-gif/SKILL.md#every-gui-pull-request-includes-a-gif) |
|
||||
| Using a translation term the terminology list lacks | The source of the rendering, or a proposed rendering under pending terms | [Translation rules](../../../docs/i18n/translation-rules.md) |
|
||||
|
||||
## Human and Agent Roles
|
||||
|
||||
This section records who made the decisions in each stage of the PR, and how autonomously the Agent worked.
|
||||
|
||||
### Levels
|
||||
|
||||
Levels are ordered from least to most AI involvement; each level pairs an English name with a Chinese name. Autonomy is the Agent's autonomy in that stage, a multiple of 5% within the chosen level's range.
|
||||
|
||||
| Level | Meaning | Agent autonomy |
|
||||
|---|---|---|
|
||||
| hint 人工独立 | A human does the work; AI takes no part or only offers passive suggestions that the human accepts or rejects | 0–10% |
|
||||
| assist 人工主导 | A human supplies the main content or every key decision; AI completes part of the work | 15–25% |
|
||||
| pair 人工引导 | A human gives the key direction or constraints and revises the AI's output several times; the human understands the internals | 30–50% |
|
||||
| copilot 人工把关 | AI completes the whole task; a human reviews, selects, or rejects, and decides or clarifies key points without supplying substantive content | 55–85% |
|
||||
| auto 模型自主 | AI completes the task autonomously; a human only sets the goal or accepts the output | 90–100% |
|
||||
| n/a 暂不适用 | The stage does not exist or has not happened yet | Omitted |
|
||||
|
||||
### Stages and criteria
|
||||
|
||||
Stages appear in a fixed order; each stage pairs an English name with a Chinese name. The criteria below cover assist through auto; judge hint by its meaning in the level table.
|
||||
|
||||
**breakdown 问题拆解**: after the problem appears, whether a human takes part in breaking it down, or the Agent acts on it directly. Confirming that a problem should be fixed belongs to this stage, not to design.
|
||||
|
||||
- auto 模型自主: the Agent acts on the problem directly.
|
||||
- copilot 人工把关: the Agent identifies the subproblems, root cause, and scope; a human only confirms them or removes items from the Agent's list.
|
||||
- pair 人工引导: a human contributes one or two key facts, such as a suspected direction, a key subproblem, a trigger condition, or a scope limit, or requires tracing the root cause; the Agent identifies the rest.
|
||||
- assist 人工主导: a human supplies the list of problems or subproblems that make up the PR's scope, or the root-cause judgment, and sets the scope; the Agent traces the root causes or works from that list.
|
||||
|
||||
**design 方案设计**: who supplies the logical design, and how many trade-offs and detailed changes the human makes. Grade by who supplied the design structure that was adopted, not by who proposed candidates first: when the Agent proposed candidates and the human then supplied the adopted structure, the stage is assist. Design structure means the module split, data flow, key interfaces, or state transitions; for documentation, the directory, page split, and responsibility layering. Constraints stated as observable outcomes are constraints, not structure. A structure that the human reached through successive corrections counts as supplied by the human.
|
||||
|
||||
- auto 模型自主: a human adopts the Agent's design as proposed, or only confirms an option the Agent recommends.
|
||||
- copilot 人工把关: a human chooses among the Agent's designs, or rejects parts of one.
|
||||
- pair 人工引导: the Agent proposes the design structure; a human gives key constraints, names, or several revisions.
|
||||
- assist 人工主导: a human supplies the design structure (module split, data flow, or key interfaces); the Agent fills in the mechanism or writes it up.
|
||||
|
||||
**testing 验收测试**: who sets the acceptance goals, test cases, and test matrix, and who checks the results. For documentation-only changes, the acceptance goals are content requirements. Instructions that only order the work, such as writing tests before the fix, do not count.
|
||||
|
||||
- auto 模型自主: the Agent sets the goals and cases; a human at most approves publishing or confirms a scope the Agent recommends.
|
||||
- copilot 人工把关: a human reviews or comments on the cases, the test matrix, or the results the Agent reports.
|
||||
- pair 人工引导: a human sets quantified targets or required scenarios, such as a trigger condition or a before-and-after contrast that must be covered, or, for documentation-only changes, content requirements; the Agent designs the concrete cases.
|
||||
- assist 人工主导: a human defines the test matrix, its dimensions or rows, and runs or tries the result to check it; the Agent may fill in the values and cases.
|
||||
|
||||
**implementation 技术实现**: human intervention in the changed content, including production code, documentation (READMEs, JSDoc, Agent Notes, comments), CI, and release scripts.
|
||||
|
||||
- auto 模型自主: the Agent writes every change, and the records show no human diff review.
|
||||
- copilot 人工把关: a human reviews the diff, including staging it piece by piece, and accepts it or makes a few comments that do not require redoing work.
|
||||
- pair 人工引导: a human requires reverting or redoing any part of the work, or specifies a refactoring or an implementation approach.
|
||||
- assist 人工主导: a human writes the key parts in the diff; the Agent completes the rest. Dictating content for the Agent to write is not writing it.
|
||||
|
||||
**review 评审处理**: who decides whether each handled review comment is fixed or declined. Comments from bots and from people both count; grade by who decided, not by who posted the reply. When no review comment has been handled, the line stays n/a, even if comments exist or arrived after merge.
|
||||
|
||||
- auto 模型自主: the Agent decides on and replies to every comment.
|
||||
- copilot 人工把关: a human gives only a blanket authorization or category-level instructions, or responds to only a few items on a handling list the Agent prepared and leaves the rest to the Agent.
|
||||
- pair 人工引导: a human responds to every item other than wording, in the threads or on the Agent's list, deciding to fix, decline, or defer, even with a short acceptance; merged duplicate comments count as one item, and wording comments may be handled by category; the Agent carries out the decisions and replies.
|
||||
- assist 人工主导: a human posts replies to reviewers directly, or leads the discussion of disputed items; deciding while the Agent posts the replies is pair.
|
||||
|
||||
### Grading rules
|
||||
|
||||
- Grade from session records and task records. Messages a human sends in a subagent session count; the instructions an Agent writes to a subagent are not human messages.
|
||||
- Count only work that ends up in this PR, including work from earlier sessions that ends up in this PR. A human's request to revert or redo work counts even when the reverted work does not remain in the PR.
|
||||
- In breakdown, design, testing, and implementation, the human is the author working with the Agent; reviewers' approvals and comments count only in review.
|
||||
- Process and ordering instructions, such as branching, working order, staging, and when to commit, push, or open the PR, do not count as human involvement in any stage.
|
||||
- When the records suffice, grade at the level with the most human involvement whose criterion the records meet, and express the human and Agent shares through the autonomy value. Only when the records do not suffice, take the level with less human involvement that the records support.
|
||||
- When a stage covers several changes, grade by the main change and state how the other changes differ in the description. The main change is the one with the largest share of the diff and review effort; when no single change dominates, grade by the change with the most human involvement. When later work changes which change is main, regrade the stage.
|
||||
- Within the chosen level's range, choose a lower autonomy value the closer the human involvement comes to the criterion of the level with more human involvement, and a higher value the closer it comes to the level with less.
|
||||
|
||||
### Line format
|
||||
|
||||
The section starts with one level line, followed by one line per stage in the fixed order:
|
||||
|
||||
```markdown
|
||||
## Human and Agent Roles
|
||||
|
||||
Levels and Agent autonomy: <ins>**hint 人工独立**</ins> 0–10%, <ins>**assist 人工主导**</ins> 15–25%, <ins>**pair 人工引导**</ins> 30–50%, <ins>**copilot 人工把关**</ins> 55–85%, <ins>**auto 模型自主**</ins> 90–100%; <ins>**n/a 暂不适用**</ins> marks a stage that does not exist or has not happened yet.
|
||||
|
||||
- <ins>**<level English name> <level Chinese name>**</ins> <autonomy> · <stage English name> <stage Chinese name> — <description>
|
||||
```
|
||||
|
||||
- Write the level in bold and underlined; omit the autonomy value for n/a 暂不适用.
|
||||
- Write the description in the PR description's language, in one or two sentences of at most 80 Chinese characters or 40 English words. State who made which decisions: name one or two representative decisions, and give only a count for the rest.
|
||||
- State the description as settled fact, without hedges such as "not seen in the session" or "pending author confirmation".
|
||||
- Refer to people as the author or a reviewer, never by username. Leave out judgments, session times, session ids, direct quotes, and the running or handling of checks and CI (local skips, hooks, reruns, unrelated failures).
|
||||
|
||||
### Update timing
|
||||
|
||||
- When you create the PR, the review line reads `- <ins>**n/a 暂不适用**</ins> · review 评审处理 — ` followed by a statement, in the description's language, that no review comments have been handled yet.
|
||||
- When the description is updated after the PR exists, rewrite the affected lines so that they cover all work so far, including earlier stages whose decisions or human share the later work changed; do not append a history of changes.
|
||||
- The review line records only handled review comments; it omits approvals, merge timing, and unhandled comments.
|
||||
- A review round ends when its fixes are pushed and its comments answered. Comments that arrive after merge do not trigger an update.
|
||||
|
||||
### Example
|
||||
|
||||
Filled in from the session and review records of #6013:
|
||||
|
||||
```markdown
|
||||
- <ins>**pair 人工引导**</ins> 40% · breakdown 问题拆解 — The author identified two process-specific values that do not belong in the system prompt and pointed to the custom model route; the Agent traced the root cause.
|
||||
- <ins>**assist 人工主导**</ins> 20% · design 方案设计 — The author supplied the design structure: runtime context, frozen legacy sections for old sessions, and a separate package; the Agent filled in the projection matching.
|
||||
- <ins>**pair 人工引导**</ins> 40% · testing 验收测试 — The author required two scenarios: restoring an old session after upgrade, and changing ports after upgrade; the Agent wrote the cases and snapshots.
|
||||
- <ins>**pair 人工引导**</ins> 35% · implementation 技术实现 — The Agent wrote the code and the Agent Note; the author required one revert and specified 3 approaches, such as getOrigin() instead of an origin property.
|
||||
- <ins>**pair 人工引导**</ins> 40% · review 评审处理 — The author decided each bot comment, such as no upgrade guide and keeping a devDependency; the Agent fixed and replied.
|
||||
```
|
||||
|
||||
## Creating and updating the description
|
||||
|
||||
1. Write the description to a file that git ignores, such as `.artifacts/pr/body.md`. Create the PR with `gh pr create --body-file <file>`, or update it with `gh pr edit <number> --body-file <file>`; when the description embeds a GIF, follow [record-browser-gif](../record-browser-gif/SKILL.md) and use `--attach`.
|
||||
2. Before an update, save the current description with `gh pr view <number> --json body --jq .body`, and change only the sections that need it: keep content and media the author wrote, and remove statements that no longer match the code.
|
||||
3. After writing, read the description back and confirm that all four sections are present and that Human and Agent Roles matches the current code and review state.
|
||||
@@ -206,19 +206,47 @@ test('keeps Task Issues limited to summary and deliverables', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test('structures the pull request template around motivation, changes, and testing', () => {
|
||||
test('keeps the pull request template aligned with the dsh-pr-description skill', () => {
|
||||
const source = readFileSync(new URL('../pull_request_template.md', import.meta.url), 'utf8')
|
||||
for (const heading of ['## Motivation', '## Changes', '## Testing']) {
|
||||
assert.match(source, new RegExp(`^${heading.replaceAll('#', '\\#')}$`, 'm'))
|
||||
}
|
||||
const skillPath = '.agents/skills/dsh-pr-description/SKILL.md'
|
||||
const skill = readFileSync(new URL(`../../${skillPath}`, import.meta.url), 'utf8')
|
||||
assert.ok(source.startsWith(`<!-- 本 PR 正文按 ${skillPath} 填写。 -->\n`), 'the template points to the skill')
|
||||
assert.deepEqual(source.match(/^## .+$/gm), [
|
||||
'## Motivation',
|
||||
'## Changes',
|
||||
'## Testing',
|
||||
'## Human and Agent Roles',
|
||||
])
|
||||
assert.doesNotMatch(source, /^### /m)
|
||||
assert.match(
|
||||
source,
|
||||
/<!-- 高层次说明命令[^\n]+ -->\n<!-- 高层次说明用户[^\n]+ -->/,
|
||||
)
|
||||
assert.match(source, /^## Motivation\n\n<!-- [^\n]*Fixes #N[^\n]* -->$/m, 'the template prompts for an Issue reference')
|
||||
assert.match(source, /- <!-- [^\n]+ -->\n\n <details>\n <summary>Proof<\/summary>/)
|
||||
assert.equal(source.match(/<details>/g)?.length, 1)
|
||||
assert.equal(source.match(/<\/details>/g)?.length, 1)
|
||||
|
||||
const levelLine = /^Levels and Agent autonomy: .+$/m
|
||||
assert.ok(skill.match(levelLine), 'the skill shows the role level line')
|
||||
assert.equal(source.match(levelLine)?.[0], skill.match(levelLine)?.[0], 'the template level line matches the skill')
|
||||
const stagesSection = skill.match(/^### Stages and criteria\n([\s\S]*?)(?=^### )/m)?.[1]
|
||||
assert.ok(stagesSection, 'the skill has a Stages and criteria section')
|
||||
const skillStages = [...stagesSection.matchAll(/^\*\*(.+?)\*\*:/gm)].map(([, stage]) => stage)
|
||||
assert.ok(skillStages.length > 0, 'the skill lists role stages')
|
||||
for (const stage of skillStages) {
|
||||
assert.match(stage, /^[a-z]+ [\u4e00-\u9fff]+$/, `stage name pairs one English word with a Chinese name: ${stage}`)
|
||||
}
|
||||
const reviewStage = 'review 评审处理'
|
||||
assert.equal(skillStages.at(-1), reviewStage, 'review is the last stage')
|
||||
assert.deepEqual(
|
||||
[...source.matchAll(/^- <ins>\*\*(?:待填级别\*\*<\/ins> \?%|n\/a 暂不适用\*\*<\/ins>) · (.+?) — .+$/gm)].map(([, stage]) => stage),
|
||||
skillStages,
|
||||
'the template has one line per skill stage, in skill order',
|
||||
)
|
||||
for (const stage of skillStages.slice(0, -1)) {
|
||||
assert.ok(source.includes(`- <ins>**待填级别**</ins> ?% · ${stage} — 待填说明\n`), `${stage} has a placeholder line`)
|
||||
}
|
||||
assert.ok(
|
||||
source.includes(`- <ins>**n/a 暂不适用**</ins> · ${reviewStage} — 尚无已处理的评审意见。\n`),
|
||||
'the review line starts as n/a, as the skill requires at PR creation',
|
||||
)
|
||||
})
|
||||
|
||||
test('ignores Issue title, body presentation, and assignee ownership', () => {
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
<!-- 本 PR 正文按 .agents/skills/dsh-pr-description/SKILL.md 填写。 -->
|
||||
|
||||
## Motivation
|
||||
|
||||
<!-- 用一句话说明需要解决的问题,并以 Fixes #NN 或 Related #NN 引用同仓库 Issue。 -->
|
||||
<!-- 引用同仓库 Issue:Fixes #N 或 Refs #N。 -->
|
||||
|
||||
## Changes
|
||||
|
||||
<!-- 高层次说明命令、配置、API、协议或持久化格式的变化;没有则写 None。 -->
|
||||
<!-- 高层次说明用户、模型或系统可观察行为的变化;没有则写 None。 -->
|
||||
|
||||
## Testing
|
||||
|
||||
<!-- 每种测试方法添加一个条目。方法保持可见,将可复核证据放进对应的 Proof 区域。 -->
|
||||
<!-- 测试设计 -->
|
||||
|
||||
- <!-- 说明命令或步骤,以及它覆盖的行为。 -->
|
||||
- <!-- 测试条目 -->
|
||||
|
||||
<details>
|
||||
<summary>Proof</summary>
|
||||
|
||||
<!-- 提供测试输出、截图、录屏、日志或其他可复核证据。 -->
|
||||
|
||||
</details>
|
||||
|
||||
## Human and Agent Roles
|
||||
|
||||
Levels and Agent autonomy: <ins>**hint 人工独立**</ins> 0–10%, <ins>**assist 人工主导**</ins> 15–25%, <ins>**pair 人工引导**</ins> 30–50%, <ins>**copilot 人工把关**</ins> 55–85%, <ins>**auto 模型自主**</ins> 90–100%; <ins>**n/a 暂不适用**</ins> marks a stage that does not exist or has not happened yet.
|
||||
|
||||
- <ins>**待填级别**</ins> ?% · breakdown 问题拆解 — 待填说明
|
||||
- <ins>**待填级别**</ins> ?% · design 方案设计 — 待填说明
|
||||
- <ins>**待填级别**</ins> ?% · testing 验收测试 — 待填说明
|
||||
- <ins>**待填级别**</ins> ?% · implementation 技术实现 — 待填说明
|
||||
- <ins>**n/a 暂不适用**</ins> · review 评审处理 — 尚无已处理的评审意见。
|
||||
@@ -415,13 +415,8 @@ jobs:
|
||||
max-parallel: 1
|
||||
matrix:
|
||||
include:
|
||||
- node: '22.19'
|
||||
name: node 22.19
|
||||
runner: ubuntu-latest
|
||||
gate_concurrency: '1'
|
||||
# Pinned inside 24.0-24.11.1: those releases carry the v1 internal
|
||||
# loader while reporting major 24, and every other job tracks the
|
||||
# latest 24, which is v2. A bare `24` here would retest that same v2.
|
||||
# Pin the supported floor, whose internal loader is v1. Primary
|
||||
# jobs track latest 24 and exercise v2, introduced in 24.12.0.
|
||||
- node: '24.9'
|
||||
name: node 24.9
|
||||
runner: ubuntu-latest
|
||||
|
||||
@@ -81,7 +81,7 @@ Package groups: [packages/README.md](packages/README.md). [Experimental status](
|
||||
## Commands
|
||||
|
||||
```sh
|
||||
pnpm install # pnpm workspaces, node ^22.19 || >=24
|
||||
pnpm install # pnpm workspaces, node >=24.9
|
||||
pnpm run clean # remove build outputs and safe residue from deleted packages
|
||||
pnpm run test # unit tests
|
||||
pnpm run test:coverage # CI coverage gate: per-file 100% on packages/*/*/src
|
||||
@@ -129,7 +129,7 @@ Real-API tests/demos read `DEEPSEEK_API_KEY`, optional `DEEPSEEK_BASE_URL`, and
|
||||
## Conventions
|
||||
|
||||
- Packages use `@deepseek-ai/dsh-<name>`; vendor is [rescoped](docs/rescope.md) and `private: true`. Harness packages declare `@deepseek-ai/cordis` in `peerDependencies`/`devDependencies`. Workspace dependency sections use DSH `workspace:*`, vendor/native `workspace:~` ([rules](.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md)).
|
||||
- ESM everywhere (`"type": "module"`). Use package names across packages and `.ts` in local relative imports. Config subprocesses run built `lib/` under plain Node; source regressions use their declared launcher ([testing policy](docs/testing.md#test-subprocess-launch-modes)). The `dsh` CLI source launch runs through tsx's ESM-only hook (`node --import tsx/esm`); modules it reaches must stay ESM (no CJS-only exports) — Node's native TypeScript modes are unavailable across the engines range ([source-launch contract](.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.md)). Raw/Web `cordis.yml` bare plugins must appear in their resolver manifest's `dependencies`; `verify-cordis-config` enforces it.
|
||||
- ESM everywhere (`"type": "module"`). Use package names across packages and `.ts` in local relative imports. Config subprocesses run built `lib/` under plain Node; source regressions use their declared launcher ([testing policy](docs/testing.md#test-subprocess-launch-modes)). The `dsh` CLI source launch runs through tsx's ESM-only hook (`node --import tsx/esm`); modules it reaches must stay ESM (no CJS-only exports) ([source-launch contract](.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.md)). Raw/Web `cordis.yml` bare plugins must appear in their resolver manifest's `dependencies`; `verify-cordis-config` enforces it.
|
||||
- **Registrations are effects**: every contribution goes through `ctx.effect()` / `ctx.on()`; a registry's `register()` returns the disposer.
|
||||
- **Typed events use declaration merging** and merge-extensible maps. Event JSDoc needs `@mode` and payload `@param`. Public service methods document parameters and non-void returns. `SessionEventMap` members are required-on-read unless the envelope carries `ignorable: true`. Bump `SESSION_FORMAT_VERSION` only when existing discriminators cannot prevent unsafe interpretation; same-version changes require safe reader behavior ([review](.agents/notes/implemented/process/2026-10-08-session-reader-compatibility-review.md)).
|
||||
- **Switch on discriminant tags.** Closed unions end in `assertNever`; merge-extensible unions fall through a documented default.
|
||||
@@ -161,6 +161,7 @@ Real-API tests/demos read `DEEPSEEK_API_KEY`, optional `DEEPSEEK_BASE_URL`, and
|
||||
- **Both SDKs project the loop.** Agent-loop, session-lifecycle, and `SessionEventMap` changes update the TypeScript and Python SDK expected outputs in the same PR; `pnpm run test` covers neither ([surfaces](docs/testing.md#when-a-snapshot-test-is-required)).
|
||||
- **Choose PR history deliberately.** Split independent changes and fix the introducing PR before propagation. Standalone/stack branches may merge-forward or rebase. Rewrites use `--force-with-lease`, abort on remote movement, never raw `--force`; preserve an in-progress merge-forward checkpoint before taking a newer base ([rationale](.agents/notes/implemented/process/2026-08-02-native-github-stacks-and-optional-rebases.md)).
|
||||
- **Labels:** one PR `kind/*`, all material `area/*`, and native Issue Type ([taxonomy](.agents/notes/implemented/process/2026-08-08-unified-github-label-taxonomy.md)).
|
||||
- **PR descriptions:** write and update every PR description with [dsh-pr-description](.agents/skills/dsh-pr-description/SKILL.md).
|
||||
- TODO markers: `FIXME`/`TODO`/`XXX` by urgency ([semantics](docs/development.md)).
|
||||
- Files end with exactly one trailing newline; `git diff --cached --check` (pre-commit) gates it.
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ The Cordis framework and its foundation libraries are source-vendored into this
|
||||
|
||||
## Runtime npm dependencies
|
||||
|
||||
External packages installed for runtime use or distributed inside the prebuilt browser artifacts. Browser inputs are resolved through the shipping tsdown and Vite configurations, independently of npm dependency sections. 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.
|
||||
External packages installed for runtime use or embedded in distributed Host or browser artifacts. Browser inputs are resolved through the shipping tsdown and Vite configurations, independently of npm dependency sections. Host embedding declarations identify the distributing package and its embedded dependencies. 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 |
|
||||
| --- | --- |
|
||||
@@ -35,9 +35,11 @@ External packages installed for runtime use or distributed inside the prebuilt b
|
||||
| [`@anthropic-ai/claude-agent-sdk`](https://github.com/anthropics/claude-agent-sdk-typescript) | SEE LICENSE IN README.md |
|
||||
| [`@anthropic-ai/sdk`](https://github.com/anthropics/anthropic-sdk-typescript) | MIT |
|
||||
| [`@babel/code-frame`](https://github.com/babel/babel) | MIT |
|
||||
| [`@botiverse/oar`](https://github.com/botiverse/oar) | Apache-2.0 |
|
||||
| [`@browserbasehq/stagehand`](https://github.com/browserbase/stagehand) | MIT |
|
||||
| [`@deepseek-ai/libreoffice-kit`](https://github.com/deepseek-ai/dsh-libreoffice-kit) | MPL-2.0 |
|
||||
| [`@earendil-works/pi-ai`](https://github.com/earendil-works/pi) | MIT |
|
||||
| [`@earendil-works/pi-coding-agent`](https://github.com/earendil-works/pi) | MIT |
|
||||
| [`@eslint-community/regexpp`](https://github.com/eslint-community/regexpp) | MIT |
|
||||
| [`@fortune-sheet/core`](https://github.com/ruilisi/fortune-sheet) | MIT |
|
||||
| [`@fortune-sheet/react`](https://github.com/ruilisi/fortune-sheet) | MIT |
|
||||
@@ -83,6 +85,7 @@ External packages installed for runtime use or distributed inside the prebuilt b
|
||||
| [`clsx`](https://github.com/lukeed/clsx) | MIT |
|
||||
| [`commander`](https://github.com/tj/commander.js) | MIT |
|
||||
| [`compression`](https://github.com/expressjs/compression) | MIT |
|
||||
| [`cross-spawn`](https://github.com/moxystudio/node-cross-spawn) | MIT |
|
||||
| [`diff`](https://github.com/kpdecker/jsdiff) | BSD-3-Clause |
|
||||
| [`dompurify`](https://github.com/cure53/DOMPurify) | (MPL-2.0 OR Apache-2.0) |
|
||||
| [`electron-updater`](https://github.com/electron-userland/electron-builder) | MIT |
|
||||
@@ -130,6 +133,7 @@ External packages installed for runtime use or distributed inside the prebuilt b
|
||||
| [`sherpa-onnx-node`](https://github.com/csukuangfj/sherpa-onnx) | Apache-2.0 |
|
||||
| [`shiki`](https://github.com/shikijs/shiki) | MIT |
|
||||
| [`simple-icons`](https://github.com/simple-icons/simple-icons) | CC0-1.0 |
|
||||
| [`string_decoder`](https://github.com/nodejs/string_decoder) | MIT |
|
||||
| [`supports-color`](https://github.com/chalk/supports-color) | MIT |
|
||||
| [`tar`](https://github.com/isaacs/node-tar) | BlueOak-1.0.0 |
|
||||
| [`tsx`](https://github.com/privatenumber/tsx) | MIT |
|
||||
@@ -137,6 +141,7 @@ External packages installed for runtime use or distributed inside the prebuilt b
|
||||
| [`typescript`](https://github.com/microsoft/TypeScript) | Apache-2.0 |
|
||||
| [`undici`](https://github.com/nodejs/undici) | MIT |
|
||||
| [`use-sync-external-store`](https://github.com/facebook/react) | MIT |
|
||||
| [`which`](https://github.com/isaacs/node-which) | ISC |
|
||||
| [`ws`](https://github.com/websockets/ws) | MIT |
|
||||
| [`xlsx`](https://git.sheetjs.com/SheetJS/sheetjs) | Apache-2.0 |
|
||||
| [`yaml`](https://github.com/eemeli/yaml) | ISC |
|
||||
@@ -145,7 +150,8 @@ External packages installed for runtime use or distributed inside the prebuilt b
|
||||
|
||||
pnpm applies local patches to the following packages at install time, so shipped artifacts carry modified copies; each patch file is the complete record of the modification:
|
||||
|
||||
- `@earendil-works/[email protected]` — [`patches/@[email protected]`](patches/@[email protected].0.patch)
|
||||
- `@botiverse/[email protected]` — [`patches/@[email protected]`](patches/@[email protected].0.patch)
|
||||
- `@earendil-works/[email protected]` — [`patches/@[email protected]`](patches/@[email protected])
|
||||
- `@electron/[email protected]` — [`patches/@[email protected]`](patches/@[email protected])
|
||||
- `@fortune-sheet/[email protected]` — [`patches/@[email protected]`](patches/@[email protected])
|
||||
- `@fortune-sheet/[email protected]` — [`patches/@[email protected]`](patches/@[email protected])
|
||||
@@ -186,7 +192,7 @@ The [kit repository](https://github.com/deepseek-ai/dsh-libreoffice-kit) supplie
|
||||
|
||||
## Development-only npm dependencies
|
||||
|
||||
External packages **directly declared** for development, tests, types, or tooling, without a runtime installation or browser-build relationship. A package here may still be pulled in transitively by a runtime dependency — `pnpm-lock.yaml` is the authority on that full closure.
|
||||
External packages **directly declared** for development, tests, types, or tooling, without a runtime installation or distribution inside a Host or browser bundle. A package here may still be pulled in transitively by a runtime dependency — `pnpm-lock.yaml` is the authority on that full closure.
|
||||
|
||||
| Package | License |
|
||||
| --- | --- |
|
||||
|
||||
@@ -146,8 +146,6 @@ flowchart LR
|
||||
cfg --> plugin_dsh_base_subagent_fork_in_process
|
||||
plugin_dsh_base_tool_subagent_control["tool-subagent-control<br/>@deepseek-ai/dsh-tool-subagent-control"]
|
||||
cfg --> plugin_dsh_base_tool_subagent_control
|
||||
plugin_dsh_base_tool_subagent_list_agents["tool-subagent-list-agents<br/>@deepseek-ai/dsh-tool-subagent-control/list-agents"]
|
||||
cfg --> plugin_dsh_base_tool_subagent_list_agents
|
||||
plugin_dsh_base_tool_subagent["tool-subagent<br/>@deepseek-ai/dsh-tool-subagent"]
|
||||
cfg --> plugin_dsh_base_tool_subagent
|
||||
plugin_dsh_base_tool_subagent_fork["tool-subagent-fork<br/>@deepseek-ai/dsh-tool-subagent"]
|
||||
@@ -273,7 +271,6 @@ flowchart LR
|
||||
| `subagent-spawn-in-process` | `@deepseek-ai/dsh-subagent-spawn-in-process` |
|
||||
| `subagent-fork-in-process` | `@deepseek-ai/dsh-subagent-fork-in-process` |
|
||||
| `tool-subagent-control` | `@deepseek-ai/dsh-tool-subagent-control` |
|
||||
| `tool-subagent-list-agents` | `@deepseek-ai/dsh-tool-subagent-control/list-agents` |
|
||||
| `tool-subagent` | `@deepseek-ai/dsh-tool-subagent` |
|
||||
| `tool-subagent-fork` | `@deepseek-ai/dsh-tool-subagent` |
|
||||
| `ptc-runtime` | `@deepseek-ai/dsh-ptc-runtime-node` |
|
||||
|
||||
@@ -170,5 +170,8 @@
|
||||
},
|
||||
"./lib/*": "./lib/*",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=24.9.0"
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,8 @@
|
||||
en: 8d2cfff9bab7327f
|
||||
zh: 21515ab6e6798504
|
||||
/dsh-cli-behavior-reference/web-profile:
|
||||
en: d768b6213e75a890
|
||||
zh: 731791b496ce14ef
|
||||
en: 850434e8ca162221
|
||||
zh: 4893613f97a21eb8
|
||||
/dsh-cli-behavior-reference/shared-deployment-behavior:
|
||||
en: 6a7abc9339e8f37f
|
||||
zh: 133adb834c8e195d
|
||||
|
||||
@@ -126,7 +126,7 @@ The production Web runner needs built package and frontend artifacts (`pnpm run
|
||||
|
||||
Process shutdown gives the plugin tree up to five seconds to dispose. The first `SIGINT`/`SIGTERM` starts that graceful drain — `SIGTERM` is a supervisor's ordinary stop request and exits 0 on every surface, `SIGINT` reports 130; a second signal forces immediate exit. If one-shot normal completion is already stuck in disposal, the first `Ctrl+C` is the escalation and exits immediately instead of being swallowed.
|
||||
|
||||
The base-backed modes treat the invoking directory as the default workspace root, load applicable `AGENTS.md` or `CLAUDE.md` instructions with a 65,536-byte render budget, and use an in-memory SQLite session content index. The standalone `sdk-minimal` profile uses the invoking directory as its sandbox-policy root and includes `read_image`, but omits text filesystem tools, instruction discovery, and SQLite. An enabled HMR plugin watches both `cordis.patch.yml` layers (profile and home) and reapplies valid edits transactionally; without HMR, the process applies them once. A one-shot surface exits through its bounded shutdown, which disposes any live watchers.
|
||||
The base-backed modes treat the invoking directory as the default workspace root, load applicable `AGENTS.md` or `CLAUDE.md` instructions with a 65,536-byte render budget for each instruction chain, and use an in-memory SQLite session content index. The standalone `sdk-minimal` profile uses the invoking directory as its sandbox-policy root and includes `read_image`, but omits text filesystem tools, instruction discovery, and SQLite. An enabled HMR plugin watches both `cordis.patch.yml` layers (profile and home) and reapplies valid edits transactionally; without HMR, the process applies them once. A one-shot surface exits through its bounded shutdown, which disposes any live watchers.
|
||||
|
||||
New sessions in base-backed profiles default to the `workspace-write` permission preset. Bash and filesystem mutations are restricted to the session workspace and platform temporary roots; reads and network access are not confined, while process visibility depends on the selected sandbox backend — bwrap runs commands in a private PID namespace that hides host processes, and Landlock and Seatbelt leave host process visibility unchanged. `DSH_PERMISSION_MODE` changes the process fallback. Stored General-settings permissions affect later Web sessions, not an already-open one. The standalone `sdk-minimal` tree instead pins `danger-full-access` and mounts no approval or permission-settings service.
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ dsh web --help
|
||||
|
||||
进程关闭时,插件树最多有 5 秒完成 dispose。首次收到 `SIGINT` 或 `SIGTERM` 时会开始优雅排空:`SIGTERM` 是监督进程发出的常规停止请求,在所有运行模式下都以 0 退出;`SIGINT` 则报告 130。第二次收到信号时会立即强制退出。如果一次性运行在正常结束时已经卡在 dispose 阶段,第一次按下 `Ctrl+C` 就会直接升级为强制退出,而不会被忽略。
|
||||
|
||||
基于 base 的模式都将运行命令时所在的目录作为默认 workspace 根目录,以 65,536 字节渲染预算加载适用的 `AGENTS.md` 或 `CLAUDE.md` 指令,并使用内存 SQLite 会话内容索引。独立的 `sdk-minimal` profile 把运行命令时所在的目录作为沙箱策略根目录并包含 `read_image`,但省略文本文件系统工具、指令发现与 SQLite。启用的 HMR 插件会监视 profile 与 home 两个 `cordis.patch.yml` 配置层的有效变更,并以事务方式重新应用;未启用 HMR 时只应用一次。一次性运行模式通过有界关闭流程退出,该流程会 dispose 所有实时监视器。
|
||||
基于 base 的模式都将运行命令时所在的目录作为默认 workspace 根目录,以每条指令链 65,536 字节的渲染预算加载适用的 `AGENTS.md` 或 `CLAUDE.md` 指令,并使用内存 SQLite 会话内容索引。独立的 `sdk-minimal` profile 把运行命令时所在的目录作为沙箱策略根目录并包含 `read_image`,但省略文本文件系统工具、指令发现与 SQLite。启用的 HMR 插件会监视 profile 与 home 两个 `cordis.patch.yml` 配置层的有效变更,并以事务方式重新应用;未启用 HMR 时只应用一次。一次性运行模式通过有界关闭流程退出,该流程会 dispose 所有实时监视器。
|
||||
|
||||
基于 base 的 profile 中,新会话默认使用 `workspace-write` 权限预设。Bash 和文件系统修改仅限于会话 workspace 与平台临时根目录;读取和网络访问不受限制,进程可见性则取决于所选沙箱后端——bwrap 在私有 PID 命名空间中运行命令并隐藏宿主进程,Landlock 与 Seatbelt 保持宿主进程可见性不变。`DSH_PERMISSION_MODE` 更改进程后备值。General settings 中存储的权限影响后续 Web 会话,不改变已打开的会话。独立的 `sdk-minimal` 配置树则固定为 `danger-full-access`,且不挂载 approval 或权限 settings 服务。
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Node 22 startup-output smoke for the shipped Web CLI composition.
|
||||
* Node 24 startup-output smoke for the shipped Web CLI composition.
|
||||
*
|
||||
* Only the dedicated Node compatibility gate opts this test in after building
|
||||
* both artifacts; ordinary Vitest inventory deterministically skips it.
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/** Account catalog diagnostics preserve the shipped ACP profile's JSON-RPC stream. */
|
||||
import { once } from 'node:events'
|
||||
import { mkdir, mkdtemp, writeFile } from 'node:fs/promises'
|
||||
import { createServer } from 'node:http'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { PROTOCOL_VERSION } from '@agentclientprotocol/sdk'
|
||||
import { launchAcpTestAgent, type AgentUnderTest, type LaunchedAcpTestAgent } from '@deepseek-ai/dsh-session-snapshot'
|
||||
import { expect, it } from 'vitest'
|
||||
import { cleanupAcpExampleTest } from './cleanup.ts'
|
||||
|
||||
it('keeps account model-listing diagnostics on stderr during API-key session creation', async () => {
|
||||
const cwd = await mkdtemp(join(tmpdir(), 'dsh-acp-stdout-'))
|
||||
const token = 'acp-catalog-fixture-token'
|
||||
const requests: Array<{ method: string | undefined; path: string | undefined; token: string | string[] | undefined }> = []
|
||||
const server = createServer((request, response) => {
|
||||
requests.push({ method: request.method, path: request.url, token: request.headers['x-dsh-auth-token'] })
|
||||
request.resume()
|
||||
if (request.method !== 'GET' || request.url !== '/models') {
|
||||
response.writeHead(500).end('unexpected request')
|
||||
return
|
||||
}
|
||||
response.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({
|
||||
data: [
|
||||
{
|
||||
id: 'deepseek-flash', name: 'Account fixture model', input_modalities: ['text'], output_modalities: ['text'],
|
||||
context_window: 1_048_576, max_output_tokens: 393_216, api_capabilities: { anthropic_messages: {} },
|
||||
},
|
||||
{ id: 'deepseek-v4-pro' },
|
||||
],
|
||||
}))
|
||||
})
|
||||
let launched: LaunchedAcpTestAgent | undefined
|
||||
try {
|
||||
server.listen(0, '127.0.0.1')
|
||||
await once(server, 'listening')
|
||||
const address = server.address()
|
||||
if (address === null || typeof address === 'string') throw new Error('account catalog server has no port')
|
||||
const origin = `http://127.0.0.1:${address.port}`
|
||||
await mkdir(join(cwd, '.dsh'))
|
||||
await writeFile(join(cwd, '.dsh', '.credentials.yaml'), JSON.stringify({
|
||||
version: 1,
|
||||
records: {
|
||||
'deepseek-account-platform/default': { kind: 'grant', payload: { version: 1, token, issuer: origin } },
|
||||
},
|
||||
}) + '\n', { mode: 0o600 })
|
||||
const configPath = join(cwd, 'catalog.patch.yml')
|
||||
await writeFile(configPath, JSON.stringify([
|
||||
{ id: 'deepseek-account', config: { platformOrigin: origin, inferenceOrigin: origin, allowLoopbackHttp: true } },
|
||||
{ id: 'llm-deepseek-account', config: { baseURL: `${origin}/anthropic` } },
|
||||
{ id: 'llm-deepseek', config: { baseURL: `${origin}/anthropic`, models: [{ id: 'fixture-api-key-model' }] } },
|
||||
// Catalog discovery requires the account service to have loaded the stored grant.
|
||||
{
|
||||
id: 'acp', inject: ['acpAppStartup', 'deepseekAccount'],
|
||||
config: { provider: 'deepseek-official', model: 'fixture-api-key-model' },
|
||||
},
|
||||
{ id: 'session-persistence-jsonl', config: { root: join(cwd, 'sessions'), compression: 'none' } },
|
||||
]) + '\n')
|
||||
const agent: AgentUnderTest = {
|
||||
binScript: fileURLToPath(new URL('../../../../src/bin.ts', import.meta.url)),
|
||||
configPath,
|
||||
profile: 'acp',
|
||||
tsconfigPath: fileURLToPath(new URL('../../../../../../tsconfig.json', import.meta.url)),
|
||||
}
|
||||
launched = launchAcpTestAgent({
|
||||
agent,
|
||||
cwd,
|
||||
env: { DEEPSEEK_API_KEY: 'acp-fixture-api-key', DEEPSEEK_BASE_URL: `${origin}/anthropic`, DSH_TELEMETRY_DISABLED: '1' },
|
||||
})
|
||||
await launched.client.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} })
|
||||
const created = await launched.client.newSession({ cwd, mcpServers: [] })
|
||||
expect(created.configOptions).toContainEqual(expect.objectContaining({
|
||||
id: 'model', currentValue: JSON.stringify(['deepseek-official', 'fixture-api-key-model']),
|
||||
}))
|
||||
await launched.client.closeSession({ sessionId: created.sessionId })
|
||||
// Closing joins both pipes; receiving a stdout response alone does not drain stderr.
|
||||
await launched.close()
|
||||
|
||||
expect(launched.child.signalCode).toBeNull()
|
||||
expect(launched.child.exitCode).toBe(0)
|
||||
expect(requests).toEqual([{ method: 'GET', path: '/models', token }])
|
||||
const frames = launched.rawStdout().trim().split('\n').map(line => JSON.parse(line) as Record<string, unknown>)
|
||||
expect(frames.length).toBeGreaterThanOrEqual(3)
|
||||
for (const frame of frames) expect(frame).toMatchObject({ jsonrpc: '2.0' })
|
||||
expect(launched.stderr()).toContain(`[llm-deepseek-account] model listing from ${origin}/models loaded 2 model(s), skipped 0 row(s)`)
|
||||
expect(launched.stderr()).toContain('[llm-deepseek-account] model "deepseek-v4-pro" declares no api_capabilities.anthropic_messages')
|
||||
expect(launched.stderr()).not.toContain(token)
|
||||
} finally {
|
||||
const cleanup = await Promise.allSettled([cleanupAcpExampleTest(launched, cwd)])
|
||||
server.closeAllConnections()
|
||||
cleanup.push(...await Promise.allSettled([
|
||||
server.listening ? new Promise<void>((resolve, reject) => server.close((error) => {
|
||||
if (error === undefined) resolve()
|
||||
else reject(error)
|
||||
})) : undefined,
|
||||
]))
|
||||
const failures: unknown[] = []
|
||||
for (const result of cleanup) if (result.status === 'rejected') failures.push(result.reason)
|
||||
if (failures.length > 0) throw new AggregateError(failures, 'ACP catalog test cleanup failed')
|
||||
}
|
||||
})
|
||||
@@ -30,3 +30,9 @@
|
||||
name: '@deepseek-ai/dsh-llm-deepseek-api-key'
|
||||
config:
|
||||
baseURL: 'http://127.0.0.1:9'
|
||||
|
||||
- id: sandbox-policy
|
||||
inject: []
|
||||
config:
|
||||
mode: !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'
|
||||
workspaceRoot: !!js process.cwd()
|
||||
@@ -31,3 +31,9 @@
|
||||
- id: session-log-deepseek
|
||||
config:
|
||||
enabled: false
|
||||
|
||||
- id: sandbox-policy
|
||||
inject: []
|
||||
config:
|
||||
mode: !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'
|
||||
workspaceRoot: !!js process.cwd()
|
||||
@@ -30,3 +30,9 @@
|
||||
config:
|
||||
root: './.sessions'
|
||||
compression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'"
|
||||
|
||||
- id: sandbox-policy
|
||||
inject: []
|
||||
config:
|
||||
mode: !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'
|
||||
workspaceRoot: !!js process.cwd()
|
||||
@@ -24,3 +24,9 @@
|
||||
- insert:
|
||||
- id: retry-snapshot-backend
|
||||
name: './tests/fixtures/retry-snapshot-backend.mjs'
|
||||
|
||||
- id: sandbox-policy
|
||||
inject: []
|
||||
config:
|
||||
mode: !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'
|
||||
workspaceRoot: !!js process.cwd()
|
||||
@@ -27,3 +27,9 @@
|
||||
- id: session-log-deepseek
|
||||
config:
|
||||
enabled: false
|
||||
|
||||
- id: sandbox-policy
|
||||
inject: []
|
||||
config:
|
||||
mode: !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'
|
||||
workspaceRoot: !!js process.cwd()
|
||||
@@ -30,3 +30,9 @@
|
||||
- id: session-log-deepseek
|
||||
config:
|
||||
enabled: false
|
||||
|
||||
- id: sandbox-policy
|
||||
inject: []
|
||||
config:
|
||||
mode: !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'
|
||||
workspaceRoot: !!js process.cwd()
|
||||
@@ -10,6 +10,7 @@
|
||||
disabled: true
|
||||
|
||||
- id: sandbox-policy
|
||||
inject: []
|
||||
config:
|
||||
mode: workspace-write
|
||||
workspaceRoot: !!js process.cwd()
|
||||
|
||||
@@ -35,3 +35,9 @@
|
||||
- id: session-log-deepseek
|
||||
config:
|
||||
enabled: false
|
||||
|
||||
- id: sandbox-policy
|
||||
inject: []
|
||||
config:
|
||||
mode: !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'
|
||||
workspaceRoot: !!js process.cwd()
|
||||
Loaded 100 of 548 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user