Merge master and resolve integration conflicts for #5570
This commit is contained in:
commit
ea6140cee0
1986 files changed
+33667
-10582
No files matched your search
+8
-8
@@ -18,14 +18,14 @@
|
||||
en: 5fdde26e9e8c6108
|
||||
zh: 629559d733c1f0c5
|
||||
/agent-note-single-file-executable-sdk-runtime-distribution-single-exe/decision/plugin-resolution-the-vfs-holds-a-real-package-tree-the-closure-manifest-is-the-deploy-root:
|
||||
en: c35ea4b858448b49
|
||||
zh: 1f8ff0a9f82ca881
|
||||
en: 985e0eb63f3d1fd6
|
||||
zh: 337cc44fadc6227e
|
||||
/agent-note-single-file-executable-sdk-runtime-distribution-single-exe/decision/build-pipeline-and-artifacts:
|
||||
en: d3bbd24ead02af4f
|
||||
zh: 64623d88bee4314b
|
||||
en: 921085cb9d894688
|
||||
zh: 12d99fe153d36738
|
||||
/agent-note-single-file-executable-sdk-runtime-distribution-single-exe/decision/python-sdk-distribution-two-carriers-exe-for-production-node-for-development:
|
||||
en: 9bd60549d4a52ec0
|
||||
zh: e2343b7210212ee0
|
||||
en: 02e6b36f9f92c83a
|
||||
zh: edd8b2db7a787483
|
||||
/agent-note-single-file-executable-sdk-runtime-distribution-single-exe/decision/naming-lineage:
|
||||
en: 31a5aecb8bf828bb
|
||||
zh: 2dffb6fd2e0f8194
|
||||
@@ -33,8 +33,8 @@
|
||||
en: 0426f598f3f44e64
|
||||
zh: fc66728e17f3bcfb
|
||||
/agent-note-single-file-executable-sdk-runtime-distribution-single-exe/testing:
|
||||
en: b3c9820f4dc7c6c8
|
||||
zh: 3b5768ef1770bfcb
|
||||
en: 8e7f46bbb595afa9
|
||||
zh: 4641894ed285d717
|
||||
/agent-note-single-file-executable-sdk-runtime-distribution-single-exe/alternatives-considered:
|
||||
en: 50d965ca81a401ba
|
||||
zh: 0c6296245b97b9ac
|
||||
|
||||
+5
-5
@@ -44,19 +44,19 @@ The deploy root is [`python/sdk-runtime/package.json`](../../../../python/sdk-ru
|
||||
|
||||
The deploy root includes `@deepseek-ai/dsh-mcp-client` as an explicitly supported custom-configuration plugin even though no shipped preset mounts it. An external config can therefore connect to user-supplied stdio and Streamable HTTP MCP servers and register their tools; the distribution does not carry those servers. The optional resource service provides MCP Resources; MCP Prompts remain unsupported. The executable and installed-wheel smokes start a temporary stdio server, discover its tool, and complete one model-requested call.
|
||||
|
||||
The [Office kit](2026-09-14-independent-libreoffice-kit.md) and its installed dependency tree live in `<executable-stem>-office/node_modules`, preserving complete package contents and helper permissions. Top-level `pkg.ignore` excludes kit packages from the SEA archive, including assets selected by dependency `files` fields; small dependencies shared with other consumers remain available in the VFS. The private Python bootstrap uses Node `registerHooks` to resolve the kit entry and its package manifest from the real directory. Native executables and URL Workers therefore retain their ordinary paths without modifying the kit or the public CLI. Missing required packages and missing installed package manifests fail the copy; absent optionals preserve the kit's engine selection.
|
||||
The [Office kit](2026-09-14-independent-libreoffice-kit.md) and its installed dependency tree live in the explicitly downloaded sidecar’s `node_modules`, preserving complete package contents and helper permissions. Top-level `pkg.ignore` excludes kit packages from the SEA archive, including assets selected by dependency `files` fields; small dependencies shared with other consumers remain available in the VFS. The private Python bootstrap uses Node `registerHooks` to resolve the kit entry and its package manifest from the real directory. Native executables and URL Workers therefore retain their ordinary paths without modifying the kit or the public CLI. The build locks the selected engine and dependency locations to exact npm tarballs; downloads verify their integrity before publication.
|
||||
|
||||
### 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 target-specific Office directory 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` 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, `-office/` directory, 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/node_modules/@deepseek-ai/dsh/lib/bin.js` 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 22.19 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, its Office directory, 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.
|
||||
[`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.
|
||||
|
||||
The Python client launches the packaged `dsh` command with the selected profile (`sdk` by default), ordered patch files, and an explicit Harness home. The profile owns JSON-RPC serving and application composition; missing homes, profiles, bundles, patches, and server rows fail without an external complete-config fallback.
|
||||
|
||||
@@ -74,7 +74,7 @@ The verification surface has three tiers. Mechanism tier: the measured conclusio
|
||||
|
||||
Manual-driving caveat: the bin treats stdin EOF as "the client is gone" and disposes immediately, so a short-lived pipe aborts an in-flight turn — pipe-driven runs must keep stdin open until the turn ends.
|
||||
|
||||
The installed-wheel `sdk-office` scenario copies the target's complete payload to a private temporary directory, requires exactly one target backend (the declared native target, or WASM when no native target is declared), and converts DOCX once through the shipped SDK profile with that engine. It checks that the kit module resolves inside the relocated directory, that conversion reports the expected backend, and that the output contains PDF bytes. Wheel validation checks the declared engine assets and native helper execute bit; CI uploads the completed wheel, so nested helper permissions stay inside the ZIP archive.
|
||||
The installed-wheel `sdk-office` scenario relocates the lightweight wheel payload and explicitly downloads the complete npm Office sidecar with its independent Node. It requires exactly one target backend, verifies that the SDK loads the kit from the downloaded sidecar, and converts DOCX through the skill CLI with an empty PATH. Python Office operations use the same CLI. Explicit downloads reuse the existing build preparation implementation; SDK startup performs no downloads. The [runtime reference](../../../../python/sdk-runtime/README.md) owns cache and activation behavior.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
+5
-5
@@ -44,19 +44,19 @@ exe 的 VFS 内是**构建产物形态的真实包树**(各包的 `lib/` + 真
|
||||
|
||||
部署根目录显式包含 `@deepseek-ai/dsh-mcp-client`,将其作为自定义配置可用的插件,即使随附 preset 均未挂载该插件。外部配置因此可以连接由用户提供的 stdio 与 Streamable HTTP MCP server 并注册其工具;分发物不包含这些 server。可选资源服务提供 MCP Resources;MCP Prompts 仍不受支持。可执行程序与已安装 wheel 包的冒烟测试会启动临时 stdio server,发现其工具,并完成一次由模型请求的调用。
|
||||
|
||||
[Office kit](2026-09-14-independent-libreoffice-kit.zh.md) 及其已安装依赖树位于 `<executable-stem>-office/node_modules`,保留完整包内容与辅助程序权限。顶层 `pkg.ignore` 从 SEA 归档排除 kit 包,包括依赖 `files` 字段选中的资源;与其他使用方共享的小型依赖仍可在 VFS 中使用。Python 私有 bootstrap 使用 Node `registerHooks` 从真实目录解析 kit 入口及其包清单。因此原生可执行程序与 URL Worker 保留普通文件路径,无需修改 kit 或公开 CLI。必需包缺失或已安装包缺少清单会使复制失败;未安装的可选包保留 kit 自身的引擎选择规则。
|
||||
[Office kit](2026-09-14-independent-libreoffice-kit.zh.md) 及其已安装依赖树位于显式下载的 sidecar 的 `node_modules`,保留完整包内容与辅助程序权限。顶层 `pkg.ignore` 从 SEA 归档排除 kit 包,包括依赖 `files` 字段选中的资源;与其他使用方共享的小型依赖仍可在 VFS 中使用。Python 私有 bootstrap 使用 Node `registerHooks` 从真实目录解析 kit 入口及其包清单。因此原生可执行程序与 URL Worker 保留普通文件路径,无需修改 kit 或公开 CLI。构建将所选引擎和依赖位置锁定到确切的 npm 归档;下载会在发布缓存前校验完整性。
|
||||
|
||||
### 构建流水线与产物
|
||||
|
||||
[`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 目录一同拷回运行时目录。该 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` → 将 `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/` 目录和可选的 macOS helper,以及供仓库开发使用的构建注入 `runtime/node/` 闭包树。`resolve_bundled_launch_args()` 默认选择可执行文件;显式设置 `DSH_RUNTIME_MODE=node` 会在系统 Node 22.19 或更高版本上运行 `runtime/node/node_modules/@deepseek-ai/dsh/lib/bin.js`。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 22.19 或更高版本上运行 `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、其 Office 目录及架构匹配的 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 包。
|
||||
[`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 包。
|
||||
|
||||
Python 客户端使用所选 profile(默认 `sdk`)、有序 patch 文件和显式 Harness home 启动打包后的 `dsh` 命令。Profile 负责 JSON-RPC 服务和应用组合;缺失 home、profile、bundle、patch 或 server 配置项都会失败,不存在外部完整配置回退。
|
||||
|
||||
@@ -74,7 +74,7 @@ Python 客户端使用所选 profile(默认 `sdk`)、有序 patch 文件和
|
||||
|
||||
手工驱动注意:`bin` 将 stdin EOF 视为「客户端已离开」并立即 dispose,生命周期较短的管道会中止进行中的轮次——管道驱动必须保持 stdin 打开,直到轮次结束。
|
||||
|
||||
安装后 wheel 的 `sdk-office` 场景会将目标的完整载荷复制到私有临时目录,要求仅存在一个目标后端(使用已声明的原生目标,未声明原生目标时使用 WASM),并通过随附 SDK profile 使用该引擎转换一次 DOCX。它检查 kit 模块确实解析到迁移后的目录、转换报告预期后端,且输出包含 PDF 字节。wheel 校验会检查声明的引擎资源与原生辅助程序执行位;CI 上传已完成的 wheel,因此嵌套辅助程序权限保留在 ZIP 归档中。
|
||||
安装后 wheel 的 `sdk-office` 场景迁移轻量 wheel 载荷,并显式下载完整 npm Office sidecar 及其独立 Node。它要求仅有一个目标后端,验证 SDK 从下载的 sidecar 加载 kit,并通过 skill CLI 在空 PATH 下转换 DOCX。Python Office 操作使用同一 CLI。显式下载复用现有构建准备实现;SDK 启动不执行下载。[运行时参考](../../../../python/sdk-runtime/README.zh.md)拥有缓存和启用行为的说明。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
|
||||
+2
-2
@@ -15,8 +15,8 @@
|
||||
en: 4921a060c657b790
|
||||
zh: 2f9632469c6f0b60
|
||||
/agent-note-provider-routed-llm-adapters-and-a-generic-pi-ai-backend/decision/explicit-pi-ai-provider-profiles:
|
||||
en: e24610c474a94bc4
|
||||
zh: 96a89035cb8eabf9
|
||||
en: 1346315312eb35e2
|
||||
zh: 3555f91c3afb49f8
|
||||
/agent-note-provider-routed-llm-adapters-and-a-generic-pi-ai-backend/decision/recorded-assistant-route-and-replay-state:
|
||||
en: ed60d078eacc67df
|
||||
zh: a22631faf93deee2
|
||||
|
||||
@@ -36,6 +36,8 @@ The adapter calls pi-ai's `streamSimple()` so each catalog model chooses its reg
|
||||
|
||||
pi-ai's common stream options do not expose stop sequences. `dsh-llm-pi-ai` rejects a defined Harness `stop` option with `UNSUPPORTED_OPTION` rather than silently ignoring it or growing a second provider-specific payload implementation. `dsh-llm-deepseek` continues to support `stop` through its native request serializer.
|
||||
|
||||
pi-ai owns protocol-specific system-message placement and the concatenation fallback for models without in-history system support. Harness preserves later system messages in transcript order and reports catalog capabilities to prompt admission and shared tool-history projection. Duplicating pi-ai's fallback or applying Anthropic's placement rules to every protocol would create a second serialization policy that can drift when pi-ai changes. Capability reporting remains a Harness responsibility because the loop needs it before serialization.
|
||||
|
||||
### Recorded assistant route and replay state
|
||||
|
||||
Assistant messages carry the request's `provider` and `model`, plus an optional JSON-serializable adapter replay state. A successful `assistant/message` session event records those fields and `deriveMessages()` returns them with the assistant message. User, system, context, and tool-result messages carry no assistant route fields. The provider/model fields are authoritative loop data; an adapter owns only its opaque replay-state payload.
|
||||
|
||||
@@ -36,6 +36,8 @@ Status: implemented
|
||||
|
||||
pi-ai 的通用流选项不支持停止序列。若 Harness `stop` 选项已定义,`dsh-llm-pi-ai` 会以 `UNSUPPORTED_OPTION` 拒绝请求,不会静默忽略,也不会增加第二套提供方专用 payload 实现。`dsh-llm-deepseek` 继续通过原生请求序列化器支持 `stop`。
|
||||
|
||||
pi-ai 负责各协议的系统消息位置,以及不支持历史内系统消息的模型所需的拼接回退。Harness 按对话记录顺序保留后续系统消息,并向提示词准入和共享工具历史投影报告目录能力。重复实现 pi-ai 的回退,或将 Anthropic 的位置规则应用于所有协议,会形成第二套序列化策略,并可能随 pi-ai 的变化而偏离。能力报告仍由 Harness 负责,因为循环在序列化之前就需要这些信息。
|
||||
|
||||
### 已记录的助手路由与回放状态
|
||||
|
||||
助手消息携带请求的 `provider` 和 `model`,以及可选的 JSON 可序列化适配器回放状态。成功的 `assistant/message` 会话事件记录这些字段,`deriveMessages()` 返回助手消息时也会包含它们。用户、系统、上下文与工具结果消息不携带助手路由字段。提供方/模型字段是 agent loop 的权威数据;适配器仅拥有其不透明回放状态 payload。
|
||||
|
||||
+2
-2
@@ -27,8 +27,8 @@
|
||||
en: fbd101d4576afa92
|
||||
zh: 35f5d3c2f6cad6a8
|
||||
/agent-note-cooperative-tool-cancellation-at-the-registry-boundary/alternatives-considered:
|
||||
en: 1f205f1a78d4fd5b
|
||||
zh: 6c17e35da2909302
|
||||
en: c4d86feb135d2ac4
|
||||
zh: 67c56c0bc2213949
|
||||
/agent-note-cooperative-tool-cancellation-at-the-registry-boundary/consequences:
|
||||
en: 24a9ad2c4c7c86b5
|
||||
zh: debdd29d272e07ea
|
||||
@@ -62,7 +62,7 @@ No registry test can prove that arbitrary third-party same-process code observes
|
||||
|
||||
**Forbid around wrappers from replacing the signal.** Rejected because deadlines and nested operational scopes need lexical derivation. Capturing and fusing the caller signal preserves composition without allowing detachment.
|
||||
|
||||
**Race the tool promise against cancellation.** Rejected because it reports completion while side effects may remain live, violating the [quiescent-disposal rule](../../../../docs/defensive-patterns.md#dispose-must-reach-quiescence-not-just-request-it).
|
||||
**Race the tool promise against cancellation.** Rejected because it reports completion while side effects may remain live, violating the [quiescent-disposal rule](../../../../docs/defensive-patterns/general.md#dispose-must-reach-quiescence-not-just-request-it).
|
||||
|
||||
## Consequences
|
||||
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ Status: implemented
|
||||
|
||||
**禁止环绕包装层替换信号。** 不予采纳,因为截止时间和嵌套操作作用域需要词法派生信号。捕获并融合调用方信号既保留组合能力,也不允许切断调用方取消。
|
||||
|
||||
**让工具 promise 与取消竞速。** 不予采纳,因为这种方式会在副作用仍可能存活时报告完成,违反[dispose(资源释放)必须完全停稳的规则](../../../../docs/defensive-patterns.zh.md#dispose-must-reach-quiescence-not-just-request-it)。
|
||||
**让工具 promise 与取消竞速。** 不予采纳,因为这种方式会在副作用仍可能存活时报告完成,违反[dispose(资源释放)必须完全停稳的规则](../../../../docs/defensive-patterns/general.zh.md#dispose-must-reach-quiescence-not-just-request-it)。
|
||||
|
||||
## 后果
|
||||
|
||||
|
||||
+4
-4
@@ -9,11 +9,11 @@
|
||||
en: c25db2ef111b2276
|
||||
zh: 557891ca9e030ae5
|
||||
/agent-note-one-carrier-level-browser-trust-boundary-for-all-api-routes/decision:
|
||||
en: 1a1af1e8125ba4b0
|
||||
zh: dd6c282651a06af5
|
||||
en: ca8044c9e0013be6
|
||||
zh: d60af7af291299c3
|
||||
/agent-note-one-carrier-level-browser-trust-boundary-for-all-api-routes/alternatives-considered:
|
||||
en: 1a4b0ae8072e8e50
|
||||
zh: d469553ad3b5d112
|
||||
/agent-note-one-carrier-level-browser-trust-boundary-for-all-api-routes/consequences:
|
||||
en: 8acf5e6f55a747c2
|
||||
zh: bbd0b10927302965
|
||||
en: e31df28a2e82a64d
|
||||
zh: 4ca7097ebbc24cd2
|
||||
@@ -13,9 +13,11 @@ The web GUI host serves `/api` over plain loopback HTTP (default `127.0.0.1:3080
|
||||
Enforce browser trust once, at the carrier, for the entire `/api` prefix — two halves:
|
||||
|
||||
- **Media-type fence (dsh-client-connection)**: every `/api` POST must declare `application/json`, else 415 before parsing. Cross-site "simple" requests thereby stop existing: any cross-site attempt is forced into a CORS preflight this server never answers.
|
||||
- **Authority fence (dsh-client-connection, `src/api-request-trust.ts`)**: every request must present a `Host` that is loopback or matches a `trustedHosts` entry (exact on `host:port`, any port on port-less entries, WHATWG-normalized; rebinding defense). Deliberately no shortcut for unmarked requests: over plain HTTP a browser attaches neither `Origin` nor Fetch-Metadata to reads (EventSource, images, navigations — those headers go only to trustworthy destinations), so an unmarked request may be a rebound browser read whose response the page can read, and Host is the one header rebinding cannot forge; non-browser clients pass via loopback, the derived LAN IP literals, or a declared authority. An attached `Origin` must equal the Host authority; `sec-fetch-site: cross-site` is refused outright. A `trustedHosts` entry that is not a bare, canonical authority fails the plugin load — WHATWG parsing would otherwise quietly authorize the hostname inside a typo or broaden an exact-port grant. `host.pickDirectory` loses its bespoke guard and rides the same fence.
|
||||
- **Authority fence (dsh-client-connection, `src/api-request-trust.ts`)**: every request must present a `Host` that is loopback or matches a `trustedHosts` entry (exact on `host:port`, any port on port-less entries, WHATWG-normalized; rebinding defense). Deliberately no shortcut for unmarked requests: over plain HTTP a browser attaches neither `Origin` nor Fetch-Metadata to reads (EventSource, images, navigations — those headers go only to trustworthy destinations), so an unmarked request may be a rebound browser read whose response the page can read, and Host is the one header rebinding cannot forge; non-browser clients pass via loopback, a declared authority, or the listener's own bind literal that [Bind one concrete Web address](2026-09-19-concrete-web-bind-address.md) admits. An attached `Origin` must equal the Host authority; `sec-fetch-site: cross-site` is refused outright. A `trustedHosts` entry that is not a bare, canonical authority fails the plugin load — WHATWG parsing would otherwise quietly authorize the hostname inside a typo or broaden an exact-port grant. `host.pickDirectory` loses its bespoke guard and rides the same fence.
|
||||
|
||||
Reachability is the webserver binding's policy (`host: 127.0.0.1 | 0.0.0.0`), and this fence is a confused-deputy defense rather than identity. Connection applies the separate [browser token authentication](2026-08-24-browser-token-authentication.md) after the fence. The fence does not inspect peer socket addresses: binding expresses reachability, `trustedHosts` names accepted authorities, and the socket address adds nothing the Host/Origin checks need.
|
||||
Reachability is the webserver binding's policy (`host`; [Bind one concrete Web address](2026-09-19-concrete-web-bind-address.md) owns which literals it accepts), and this fence is a confused-deputy defense rather than identity. Connection applies the separate [browser token authentication](2026-08-24-browser-token-authentication.md) after the fence. The fence does not inspect peer socket addresses: binding expresses reachability, `trustedHosts` names accepted authorities, and the socket address adds nothing the Host/Origin checks need.
|
||||
|
||||
`HostConnectionHandle.allowsRemoteAuthorities` projects whether any validated `trustedHosts` entry names a non-loopback hostname: a loopback listener behind a proxy can still serve remote browsers, so the directory picker requires a policy without remote authorities before it picks the native chooser.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -26,6 +28,6 @@ Reachability is the webserver binding's policy (`host: 127.0.0.1 | 0.0.0.0`), an
|
||||
## Consequences
|
||||
|
||||
- Any future `/api` method is covered by construction; there is no per-route trust decision left to forget.
|
||||
- A custom non-loopback composition must trust its serving authorities or requests are refused, then satisfy browser authentication like every loopback request. The shipped CLI rejects `--host 0.0.0.0`; `--trusted-host` only extends the Host/Origin fence and grants no identity.
|
||||
- A non-loopback listener accepts its own bind IP directly ([Bind one concrete Web address](2026-09-19-concrete-web-bind-address.md)); other remote authorities require configured trust. Every accepted authority still requires browser authentication. `--trusted-host` extends only the Host/Origin fence and grants no identity.
|
||||
- Clients must label POST bodies `application/json` (ours always did; raw-fetch tests gained the header).
|
||||
- Host and Origin remain request-routing evidence only. The process token and signed cookie establish the browser identity used by every Host method.
|
||||
@@ -13,9 +13,11 @@ Web GUI 宿主以纯 loopback HTTP 提供 `/api`(默认 `127.0.0.1:3080`;CLI
|
||||
在载体层对整个 `/api` 前缀一次性执行浏览器信任检查——分为两部分:
|
||||
|
||||
- **媒体类型栅栏(dsh-client-connection)**:每个 `/api` POST 必须声明 `application/json`,否则在解析前以 415 拒绝。跨站「简单请求」由此不复存在:任何跨站尝试都被逼进一次本服务器从不应答的 CORS 预检。
|
||||
- **权威栅栏(dsh-client-connection,`src/api-request-trust.ts`)**:每个请求的 `Host` 都必须是回环地址,或与某个 `trustedHosts` 条目匹配(带端口的 `host:port` 条目精确匹配,不带端口的条目匹配任意端口,均经 WHATWG 归一化;rebinding 防御)。刻意不为无标记请求开捷径:明文 HTTP 下浏览器的读取(EventSource、图片、导航——这些头只发给可信目标)既不带 `Origin` 也不带 Fetch-Metadata,因此无标记请求可能是被重绑页面发起且响应可被读走的读取,而 Host 是重绑唯一伪造不了的请求头;非浏览器客户端经由回环地址、推导的 LAN IP 字面量或已声明的权威通过。若带 `Origin` 则必须与 Host 权威完全一致;`sec-fetch-site: cross-site` 一律拒绝。不是单纯规范化 authority 的 `trustedHosts` 条目会导致插件加载失败——否则 WHATWG 解析会悄悄授权笔误里的 hostname,或放大精确端口授权。`host.pickDirectory` 失去专属守卫,与其他请求同栅而行。
|
||||
- **权威栅栏(dsh-client-connection,`src/api-request-trust.ts`)**:每个请求的 `Host` 都必须是回环地址,或与某个 `trustedHosts` 条目匹配(带端口的 `host:port` 条目精确匹配,不带端口的条目匹配任意端口,均经 WHATWG 归一化;rebinding 防御)。刻意不为无标记请求开捷径:明文 HTTP 下浏览器的读取(EventSource、图片、导航——这些头只发给可信目标)既不带 `Origin` 也不带 Fetch-Metadata,因此无标记请求可能是被重绑页面发起且响应可被读走的读取,而 Host 是重绑唯一伪造不了的请求头;非浏览器客户端经由回环地址、已声明的权威,或[绑定一个具体的 Web 地址](2026-09-19-concrete-web-bind-address.zh.md)所接纳的监听器自身绑定字面量通过。若带 `Origin` 则必须与 Host 权威完全一致;`sec-fetch-site: cross-site` 一律拒绝。不是单纯规范化 authority 的 `trustedHosts` 条目会导致插件加载失败——否则 WHATWG 解析会悄悄授权笔误里的 hostname,或放大精确端口授权。`host.pickDirectory` 失去专属守卫,与其他请求同栅而行。
|
||||
|
||||
可达性由 webserver 的绑定配置(`host: 127.0.0.1 | 0.0.0.0`)控制,这道栅栏是混淆代理人防御,而不是身份。Connection 在栅栏之后应用独立的[浏览器令牌认证](2026-08-24-browser-token-authentication.zh.md)。栅栏不检查对端 socket 地址:绑定表达可达性,`trustedHosts` 点名接受的 authority,socket 地址提供不了 Host/Origin 校验需要的额外信息。
|
||||
可达性由 webserver 的绑定配置(`host`;[绑定一个具体的 Web 地址](2026-09-19-concrete-web-bind-address.zh.md)拥有它接受哪些字面量)控制,这道栅栏是混淆代理人防御,而不是身份。Connection 在栅栏之后应用独立的[浏览器令牌认证](2026-08-24-browser-token-authentication.zh.md)。栅栏不检查对端 socket 地址:绑定表达可达性,`trustedHosts` 点名接受的 authority,socket 地址提供不了 Host/Origin 校验需要的额外信息。
|
||||
|
||||
`HostConnectionHandle.allowsRemoteAuthorities` 表示是否有通过校验的 `trustedHosts` 条目命名非回环主机名:代理后的回环监听器也能服务远程浏览器,因此目录选择器要求策略不接纳远程 authority,才会选择原生交互。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
@@ -26,6 +28,6 @@ Web GUI 宿主以纯 loopback HTTP 提供 `/api`(默认 `127.0.0.1:3080`;CLI
|
||||
## 后果
|
||||
|
||||
- 未来任何 `/api` 方法天然在覆盖范围内;不存在会被遗忘的按路由信任决定。
|
||||
- 自定义非 loopback 组合必须信任其服务 authority,否则请求会被拒绝;随后仍像每个 loopback 请求一样满足浏览器认证。随附 CLI 拒绝 `--host 0.0.0.0`;`--trusted-host` 只扩展 Host/Origin 栅栏,绝不授予身份。
|
||||
- 非回环监听器直接接受自己的绑定 IP([绑定一个具体的 Web 地址](2026-09-19-concrete-web-bind-address.zh.md));其他远程 authority 需要配置的信任。每个被接受的 authority 仍须满足浏览器认证。`--trusted-host` 只扩展 Host/Origin 栅栏,绝不授予身份。
|
||||
- 客户端必须给 POST 体标注 `application/json`(我们自己的客户端一向如此;裸 fetch 测试补上了该头)。
|
||||
- Host 与 Origin 仍只是请求路由证据。进程令牌与签名 cookie 建立每个 Host 方法使用的浏览器身份。
|
||||
+2
-2
@@ -72,5 +72,5 @@
|
||||
en: 7433bdb522bb2083
|
||||
zh: 545c17613b636f00
|
||||
/agent-note-typert-gateway-targeted-method-calls/consequences:
|
||||
en: c3459afdb606cfbe
|
||||
zh: ec69f2bb39170a7c
|
||||
en: abab48251550f584
|
||||
zh: 2568c46d78dbefdf
|
||||
@@ -520,7 +520,7 @@ A consumer may import a Remote contract that is not currently mounted on the Hos
|
||||
|
||||
Connection's general channel API must suit both the current HTTP carrier and a future WebSocket carrier. If the Client Remote or Gateway exposes `fetch`, an HTTP request, or a route handle, WebSocket migration will pierce the Remote layer again. Those physical objects must therefore remain internal to Connection.
|
||||
|
||||
Remote endpoints use Connection's `trusted-host` authority. Loopback is accepted by default and LAN callers require an explicit trusted-host configuration, but this layer adds no per-method caller authorization; every trusted host can invoke a mounted Remote endpoint.
|
||||
Remote endpoints use Connection's `trusted-host` authority. Loopback and the listener's own bind address are accepted by default; any other LAN authority requires an explicit trusted-host configuration. This layer adds no per-method caller authorization; every accepted authority can invoke a mounted Remote endpoint.
|
||||
|
||||
`hasSeen()` favors strict-definition safety over SRC availability. While a strict descriptor is withdrawn, such as during HMR, the Gateway continues to claim the endpoint and reports it unavailable instead of falling back to a weak SRC descriptor. Re-registration restores it; only a Typert registry restart forgets the historical strict definition.
|
||||
|
||||
|
||||
@@ -520,7 +520,7 @@ SRC 弱 descriptor 不验证普通 JSON 内部结构。Host Remote 签名变化
|
||||
|
||||
Connection 的通用 channel API 必须同时适合当前 HTTP carrier 和后续 WebSocket carrier。若 Client Remote 或 Gateway 暴露 `fetch`、HTTP request 或 route handle,WebSocket 迁移会再次穿透 Remote 层,因此这些物理对象必须留在 Connection 内部。
|
||||
|
||||
Remote endpoint 使用 Connection 的 `trusted-host` authority。系统默认接受 loopback;LAN 调用方必须通过显式 trusted-host 配置接入,但本层不增加逐方法调用方授权,因此每个 trusted host 都能调用已挂载的 Remote endpoint。
|
||||
Remote endpoint 使用 Connection 的 `trusted-host` authority。系统默认接受 loopback 与监听器自身的绑定地址;其他 LAN authority 必须通过显式 trusted-host 配置接入。本层不增加逐方法调用方授权,因此每个被接受的 authority 都能调用已挂载的 Remote endpoint。
|
||||
|
||||
`hasSeen()` 优先保障 strict definition 的安全性,而非 SRC 可用性。strict descriptor 撤回时(例如 HMR 期间),Gateway 会继续认领 endpoint 并报告不可用,而不会回退到弱 SRC descriptor。重新注册即可恢复;只有重启 Typert 注册表才会忘记历史 strict definition。
|
||||
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
en: c11f54e24be6cc3b
|
||||
zh: c1c274fbb161a57b
|
||||
/agent-note-session-log-versioning-one-integer-an-upgrade-chain-and-a-per-event-ignorable-marker/decision:
|
||||
en: 084bcb4de484ef32
|
||||
zh: 1edf1445913ac7b1
|
||||
en: f2c17cd6b83acdba
|
||||
zh: 148162c0a8043947
|
||||
/agent-note-session-log-versioning-one-integer-an-upgrade-chain-and-a-per-event-ignorable-marker/decision/writer-and-publication-authority:
|
||||
en: 461eaa03c6a4f441
|
||||
zh: 7e5b131326dc702c
|
||||
|
||||
@@ -10,15 +10,17 @@ Session logs must be upgradable after release, and the runtime that ships first
|
||||
|
||||
## Decision
|
||||
|
||||
The [reader-compatibility review decision](../process/2026-10-08-session-reader-compatibility-review.md) supersedes the structural bump criterion. This note owns the integer, unknown-event marker, direction-aware refusal, and generation-preservation decisions.
|
||||
|
||||
**One monotonic integer, no major/minor split.** Whether a version step is auto-upgradable is a property of that step — expressed by whether its upgrader exists — not something a two-level numbering scheme should promise in advance; design time rarely reveals whether the next change will turn out "major".
|
||||
|
||||
**The writer decides bumps, not the reader.** A bump is required exactly when an old runtime could no longer handle a new log with full semantic correctness. "Parses without error" is not the bar: silently skipping content that shapes reconstruction is a wrong read. Only structural changes qualify — header shape, event envelope, core event semantics, the surface mechanism (`SurfaceEventType` set, `SurfaceOp` variants). When unsure, bump: a near-identity upgrader is almost free, a missed bump silently corrupts old readers.
|
||||
**Bump when existing discriminators cannot prevent unsafe interpretation.** Successful parsing alone does not establish compatibility: silently skipping content required for reconstruction is a wrong read. Same-version changes require safe old-reader handling and preservation of historical meaning by new readers. The linked reader-compatibility decision defines the review obligations.
|
||||
|
||||
**Read rules by direction.** Equal version: read normally. Newer than the reader: refuse, name the direction ("written by a newer harness — upgrade"), and point at the raw log artifact so the user can still see the text (`SessionFormatUnsupportedError`, distinct from `SessionPersistenceCorruptionError` because nothing is damaged). Older than the reader: every event-body operation first runs the complete adjacent chain in memory and leaves the source path, bytes, and inode unchanged. Read handles may consume that current logical result directly; a write open exclusively publishes the final current generation under its canonical versioned filename before append. Header-only listing remains non-mutating and reports the numerically highest canonical generation. Catalog generation and module initialization reject a missing adjacent step, so a published first-party build never exposes a partial historical chain. Retained lower generations are not automatic fallback or a downgrade compatibility promise.
|
||||
|
||||
**A per-event `ignorable` marker covers vocabulary growth, so ordinary event additions never bump the version.** The event vocabulary is decided by which plugins are mounted, which a single version integer cannot describe. A reader meeting an unrecognized event type refuses to interpret the log unless the event carries `ignorable: true` in its envelope. The default is *required*: forgetting the marker over-refuses a resumable session (an inconvenience), while a default of ignorable would make the same mistake silently resume a gutted one (a safety failure). The architecture makes this sound: model-visible content flows only through the four `surfaceOp`-marked surface event types plus the `request/header`/`request/context` folds, so the dangerous unknowns are exactly the non-surface events that change how the rest of the log is read (`session/end-seed` is the existing example).
|
||||
|
||||
The [accepted compatibility checkpoint](../../../../docs/session-format-status.md#finalization-record) prevents an accepted transition from reusing its header increase for later breaking changes. Backward-compatible evolution uses new acknowledgement records at the same Session version; breaking changes require a higher writer. The checkpoint does not change the unknown-event protocol.
|
||||
**Accepted machine records remain immutable.** The [accepted compatibility checkpoint](../../../../docs/session-format-status.md#finalization-record) preserves accepted declarations and schemas. Later changes receive new acknowledgements, and a version-bump decision needs its own increasing header transition. The checkpoint does not change the unknown-event protocol.
|
||||
|
||||
### Writer and publication authority
|
||||
|
||||
|
||||
+4
-2
@@ -10,15 +10,17 @@ Session log 在发布后必须能升级格式,而最先发布的运行时决
|
||||
|
||||
## 决定
|
||||
|
||||
[读取器兼容性评审决策](../process/2026-10-08-session-reader-compatibility-review.zh.md)取代结构性升版本标准。本说明负责整数版本、未知事件标记、区分方向的拒绝与代际保留决策。
|
||||
|
||||
**一个单调递增的整数,不分大小版本。**某一步能不能自动升级是那一步自己的属性,由它的升级器存在与否表达,不该由两级编号方案提前承诺;设计时很少能预知下一个变更算不算"大"。
|
||||
|
||||
**升不升版本由写入方决定,与读取方能力无关。**当且仅当老运行时无法在语义上完全正确地处理新日志时才必须升版本。"解析不报错"不是标准:静默跳过影响重建的内容就是读错。只有结构性变更够得上这条线:header 形状、事件信封、核心事件语义、surface 机制(`SurfaceEventType` 集合、`SurfaceOp` 变体)。拿不准就升:近似恒等的升级器几乎没有成本,漏升一次会让老读取器静默读坏。
|
||||
**既有判别信息无法阻止不安全解释时升版本。** 仅解析成功不能证明兼容:静默跳过重建所需内容属于错误读取。同版本变更要求旧读取器安全处理,并由新读取器保留历史含义。所链接的读取器兼容性决策定义评审义务。
|
||||
|
||||
**读取规则按方向区分。**版本相等:正常读。比读取器新:拒绝,说明方向("由更新的 harness 写入,请升级"),并给出原始日志文件的路径,用户仍能看到文本(`SessionFormatUnsupportedError`,与 `SessionPersistenceCorruptionError` 区分,因为数据没有损坏)。比读取器旧:每个事件正文操作先在内存中运行完整相邻链,并保持源路径、字节与 inode 不变。读句柄可以直接使用该 current 逻辑结果;写 open 则在 append 前把最终 current generation 排他发布到其规范版本文件名。仅 header 的列表保持不变更,并报告数值最高的规范 generation。catalog 生成与模块初始化会拒绝缺失的相邻步骤,因此已发布第一方 build 绝不会暴露不完整历史链。保留的低 generation 不是自动 fallback,也不构成 downgrade compatibility 承诺。
|
||||
|
||||
**逐事件的 `ignorable` 标记吸收词汇表增长,普通的新增事件永远不用升版本。**事件词汇表由挂载了哪些插件决定,单个版本整数描述不了它。读取器遇到不认识的事件类型时拒绝解读日志,除非该事件的信封带 `ignorable: true`。默认为必需:忘写标记的后果是把一个本可恢复的会话拒绝过头(体验问题),而默认可忽略会让同样的疏忽静默恢复出残缺会话(安全事故)。架构保证了这条规则成立:模型可见内容只经四种带 `surfaceOp` 标记的 surface 事件加 `request/header`、`request/context` 折叠进入重建,危险的未知事件恰好是那些不进 surface 但改变日志其余部分解读方式的事件(`session/end-seed` 是现存例子)。
|
||||
|
||||
[已接受兼容性检查点](../../../../docs/session-format-status.zh.md#finalization-record)阻止已接受转换复用原来的头版本递增来授权后续破坏性变更。向后兼容的演进在相同 Session 版本上使用新的确认记录;破坏性变更要求更高的写入器版本。检查点不改变未知事件协议。
|
||||
**已接受的机器记录保持不可变。** [已接受兼容性检查点](../../../../docs/session-format-status.zh.md#finalization-record)保留已接受的声明与 schema。后续变更获得新确认,升版本决策需要自身的递增头版本转换。检查点不改变未知事件协议。
|
||||
|
||||
### 写入器与发布真源
|
||||
|
||||
|
||||
+2
-2
@@ -15,8 +15,8 @@
|
||||
en: a1750de2d48551af
|
||||
zh: ffea0b75c292f186
|
||||
/agent-note-client-shell-layering-and-dynamic-package-boundaries/decision/shared-module-requests:
|
||||
en: cedddcca8a7bae6d
|
||||
zh: a568a1130229f48c
|
||||
en: 96661da17622132a
|
||||
zh: dab3424093130c2f
|
||||
/agent-note-client-shell-layering-and-dynamic-package-boundaries/decision/parser-preloading-and-react-handoff:
|
||||
en: 4b503618b271fc28
|
||||
zh: e2148f44efa769d2
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ The `staticLinked` preset leaves every bare specifier as an external import in `
|
||||
|
||||
### Shared module requests
|
||||
|
||||
Dynamic browser bundles implicitly externalize the common baseline: `PLATFORM_MODULES` names shell-seeded React, Cordis, and static UI identities, while `PRELOADED_CLIENT_EXTERNALS` is reserved for a dynamic identity that must arrive before shell boot and is currently empty. A package uses `dsh.client.external` only for an exact non-baseline value request. Type-only imports are erased and create no request; permitted third-party implementation libraries remain private bundle contents.
|
||||
Dynamic browser bundles implicitly externalize the common baseline: `PLATFORM_MODULES` names shell-seeded React, Cordis, selected third-party libraries, and static UI identities, while `PRELOADED_CLIENT_EXTERNALS` is reserved for a dynamic identity that must arrive before shell boot and is currently empty. A package uses `dsh.client.external` only for an exact non-baseline value request. Type-only imports are erased and create no request; non-baseline third-party implementation libraries remain private bundle contents. Shared third-party libraries use the shell's declared versions without per-plugin version negotiation; promotion into the baseline requires a measured reduction across the shell and shipped plugin bundles.
|
||||
|
||||
A request has exactly two suppliers:
|
||||
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ Client npm 依赖区段描述安装和开发关系,但不能可靠描述 bundl
|
||||
|
||||
### 共享模块请求
|
||||
|
||||
动态浏览器 bundle 会隐式 external 统一基座:`PLATFORM_MODULES` 命名由外壳播种的 React、Cordis 和静态 UI 身份,`PRELOADED_CLIENT_EXTERNALS` 则为必须先于 shell 启动到达的动态身份预留,当前为空。包只在精确请求基座外实体时使用 `dsh.client.external`。纯类型 import 会被擦除,不产生请求;允许的第三方实现库保留为 bundle 私有内容。
|
||||
动态浏览器 bundle 会隐式 external 统一基座:`PLATFORM_MODULES` 命名由外壳播种的 React、Cordis、选定的第三方库和静态 UI 身份,`PRELOADED_CLIENT_EXTERNALS` 则为必须先于 shell 启动到达的动态身份预留,当前为空。包只在精确请求基座外实体时使用 `dsh.client.external`。纯类型 import 会被擦除,不产生请求;基座外的第三方实现库保留为 bundle 私有内容。共享第三方库使用外壳声明的版本,不为单个插件协商版本;只有测量确认外壳与随包插件 bundle 的总体积减少,才将库纳入基座。
|
||||
|
||||
请求只有两种提供方:
|
||||
|
||||
|
||||
+5
-5
@@ -9,8 +9,8 @@
|
||||
en: 4945ce54f66df55c
|
||||
zh: 9fbd833c1d8122a7
|
||||
/agent-note-deepseek-llm-api-request-extensions-for-session-logs-and-plugin-packages/decision:
|
||||
en: 2388519a1afaac92
|
||||
zh: 6bf758f0560e29a5
|
||||
en: d2452e4a39db0b6b
|
||||
zh: 87f7e0be1f613285
|
||||
/agent-note-deepseek-llm-api-request-extensions-for-session-logs-and-plugin-packages/incremental-session-log-field:
|
||||
en: 4e3138fbdd94259b
|
||||
zh: 8fda801522045b88
|
||||
@@ -21,7 +21,7 @@
|
||||
en: eb69576e756f4aa1
|
||||
zh: 5c1923159da3f7a2
|
||||
/agent-note-deepseek-llm-api-request-extensions-for-session-logs-and-plugin-packages/verification:
|
||||
en: 9935338a0970d09d
|
||||
en: ed730f117365f007
|
||||
zh: a9a36702bc72a9f7
|
||||
/agent-note-deepseek-llm-api-request-extensions-for-session-logs-and-plugin-packages/alternatives-considered:
|
||||
en: 5faf7699272f99b4
|
||||
@@ -33,5 +33,5 @@
|
||||
en: b6eff63f49220dcc
|
||||
zh: 8e4a6c785ed2256a
|
||||
/agent-note-deepseek-llm-api-request-extensions-for-session-logs-and-plugin-packages/consequences:
|
||||
en: 853cd7b21db6208b
|
||||
zh: bfaf459f006fe2db
|
||||
en: fed545acefb91f2a
|
||||
zh: c474a94bb956bbf6
|
||||
+3
-3
@@ -14,7 +14,7 @@ Both values belong only on the official DeepSeek adapter path. Adding them to `G
|
||||
|
||||
## Decision
|
||||
|
||||
`@deepseek-ai/dsh-deepseek-llm-api-extensions` registers `ctx.deepseekLlmApiExtensions`, an additive registry of top-level fields for `deepseek-official` request bodies. A contributor claims one declaration-merged field with `register()`. The adapter invokes `prepare()` after serializing the exact wire messages, passes the request cancellation signal, rejects preparation or base-field collision before HTTP, merges the detached fields, and calls the captured `accept()` transaction after HTTP 2xx. A merged body that fails to serialize is sent without extension fields or acceptance under the [bounded-upload decision](2026-09-24-bounded-session-log-upload.md). The registry stops awaiting preparation after cancellation even if a contributor ignores the signal. Acceptance failures remain request failures under `REQUEST_EXTENSION`; transport and non-2xx failures never accept a contribution. A composition without the registry retains the reusable base adapter. Shipped compositions mount the registry and both contributors: both package metadata and Session-log upload are enabled by default; `session-log-deepseek.enabled: false` disables log upload under the [default-upload decision](2026-09-14-session-log-upload-default.md). Keyless `deepseek-official` replay invokes preparation with a synthetic empty base body and the same acceptance transaction before its first recorded chunk, preserving post-2xx extension side effects rather than field bytes.
|
||||
`@deepseek-ai/dsh-deepseek-llm-api-extensions` registers `ctx.deepseekLlmApiExtensions`, an additive registry of top-level fields for `deepseek-official` request bodies. A contributor claims one declaration-merged field with `register()`. The adapter invokes `prepare()` after serializing the exact wire messages, passes the request cancellation signal, rejects base-field collision before HTTP, merges the detached fields, and calls the captured `accept()` transaction after HTTP 2xx. A merged body that fails to serialize is sent without extension fields or acceptance under the [bounded-upload decision](2026-09-24-bounded-session-log-upload.md). The registry stops awaiting preparation after cancellation even if a contributor ignores the signal. Extension fields are auxiliary and best effort: the registry logs and omits a contributor whose preparation throws, and the adapter logs an acceptance failure without failing the model request; transport and non-2xx failures never accept a contribution. A composition without the registry retains the reusable base adapter. Shipped compositions mount the registry and both contributors: both package metadata and Session-log upload are enabled by default; `session-log-deepseek.enabled: false` disables log upload under the [default-upload decision](2026-09-14-session-log-upload-default.md). Keyless `deepseek-official` replay invokes preparation with a synthetic empty base body and the same acceptance transaction before its first recorded chunk, preserving post-2xx extension side effects rather than field bytes.
|
||||
|
||||
The provider-neutral `llm` package and `llm-pi-ai` contain no extension type, service lookup, field merge, or acceptance call.
|
||||
|
||||
@@ -50,7 +50,7 @@ The process-lifetime manifest-identity cache remains separate because in-process
|
||||
|
||||
## Verification
|
||||
|
||||
Registry tests pin duplicate ownership, effect-scoped disposal, detached field values, concurrent and abortable preparation, receiver-preserving acceptance, one acceptance settlement, and failure aggregation. Session tests pin default-on and explicit-off policies, full-first/suffix-later delivery, direct complete event envelopes independent of base-body messages, incremental watermark folding, persisted restart recovery, fork identity fencing, and out-of-order acceptance. Package-inventory tests pin default-on and explicit-off policies, host and standing-preset discovery, conflicting Loader resolution bases, manifest resolution, lifecycle filtering, and exact name/version ordering. The direct adapter mock proves pre-HTTP preparation failure, cancellation, non-2xx non-acceptance, 2xx acceptance before a later stream failure, and field collision. Keyless replay pins post-2xx extension acceptance, and the TypeScript JSON-RPC plus Python packaged-runtime snapshots project the acceptance event through both SDKs. Real Loader composition pins default package metadata and Session upload plus explicit upload disablement, one real-API request mounts both shipped extensions and proves the official endpoint accepts them, and pi-ai tests retain their unchanged wire requests.
|
||||
Registry tests pin duplicate ownership, effect-scoped disposal, detached field values, concurrent and abortable preparation, receiver-preserving acceptance, one acceptance settlement, and failure aggregation. Session tests pin default-on and explicit-off policies, full-first/suffix-later delivery, direct complete event envelopes independent of base-body messages, incremental watermark folding, persisted restart recovery, fork identity fencing, and out-of-order acceptance. Package-inventory tests pin default-on and explicit-off policies, host and standing-preset discovery, conflicting Loader resolution bases, manifest resolution, lifecycle filtering, and exact name/version ordering. The direct adapter mock proves pre-HTTP preparation rejection, cancellation, non-2xx non-acceptance, 2xx acceptance before a later stream failure, and field collision. Keyless replay pins post-2xx extension acceptance, and the TypeScript JSON-RPC plus Python packaged-runtime snapshots project the acceptance event through both SDKs. Real Loader composition pins default package metadata and Session upload plus explicit upload disablement, one real-API request mounts both shipped extensions and proves the official endpoint accepts them, and pi-ai tests retain their unchanged wire requests.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -85,7 +85,7 @@ About 98% of the measured v1 real-session events were `assistant/chunk`. Omittin
|
||||
|
||||
## Consequences
|
||||
|
||||
Official DeepSeek requests carry active package versions to their resolved `baseURL`, including configured gateways. Unless Session-log upload is disabled, each eligible request also carries newly unaccepted Session events, up to `maxBytes` per request. The fields are model-hidden and add no prompt tokens or KV-cache changes, but can substantially increase HTTP body size. Manifest resolution, field collision, acceptance logging, or provider schema rejection fails the model request rather than silently dropping metadata. A merged body that fails to serialize is the exception: the request proceeds without extension fields and logs a warning.
|
||||
Official DeepSeek requests carry active package versions to their resolved `baseURL`, including configured gateways. Unless Session-log upload is disabled, each eligible request also carries newly unaccepted Session events, up to `maxBytes` per request. The fields are model-hidden and add no prompt tokens or KV-cache changes, but can substantially increase HTTP body size. Field collision or provider schema rejection fails the model request. A contributor preparation failure, an acceptance failure, or a merged body that fails to serialize logs a warning and lets the request proceed; a package without a usable version reports only its name.
|
||||
|
||||
The `delivery-accepted` event becomes part of the canonical log and is itself delivered on a later request. Crash recovery can duplicate a suffix but does not infer acceptance from assistant output or create a second local cursor store. Direct calls without a live Session omit the session field; host package inventory remains available.
|
||||
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
`@deepseek-ai/dsh-deepseek-llm-api-extensions` 注册 `ctx.deepseekLlmApiExtensions`,即 `deepseek-official` 请求正文顶层字段的增量注册表。贡献方通过 `register()` 认领一个经声明合并的字段。适配器在序列化确切协议消息后调用 `prepare()`、传入请求取消信号,在 HTTP 前拒绝准备失败或基础字段冲突,合并分离字段,并在 HTTP 2xx 后调用捕获的 `accept()` 事务。合并后的正文无法序列化时,按[有上限上传决策](2026-09-24-bounded-session-log-upload.zh.md)不带扩展字段发送,也不执行接受。即使贡献方忽略信号,注册表也会在取消后停止等待准备。接受失败仍以 `REQUEST_EXTENSION` 使请求失败;传输失败与非 2xx 失败绝不会接受贡献。未挂载注册表的组合会保留可复用基础适配器。随附组合会挂载注册表与两个贡献方:插件包元数据和会话日志上传均默认开启;按[默认上传决策](2026-09-14-session-log-upload-default.zh.md),设置 `session-log-deepseek.enabled: false` 可关闭日志上传。无密钥 `deepseek-official` 回放会使用合成的空基础正文执行准备,并在第一个已记录分片前调用同一接受事务;它保持的是 2xx 后扩展副作用,而非字段字节。
|
||||
`@deepseek-ai/dsh-deepseek-llm-api-extensions` 注册 `ctx.deepseekLlmApiExtensions`,即 `deepseek-official` 请求正文顶层字段的增量注册表。贡献方通过 `register()` 认领一个经声明合并的字段。适配器在序列化确切协议消息后调用 `prepare()`、传入请求取消信号,在 HTTP 前拒绝基础字段冲突,合并分离字段,并在 HTTP 2xx 后调用捕获的 `accept()` 事务。合并后的正文无法序列化时,按[有上限上传决策](2026-09-24-bounded-session-log-upload.zh.md)不带扩展字段发送,也不执行接受。即使贡献方忽略信号,注册表也会在取消后停止等待准备。扩展字段是辅助信息,按尽力而为处理:贡献方准备时抛错,注册表会记录告警并省略该字段;接受失败时,适配器记录告警而不使模型请求失败;传输失败与非 2xx 失败绝不会接受贡献。未挂载注册表的组合会保留可复用基础适配器。随附组合会挂载注册表与两个贡献方:插件包元数据和会话日志上传均默认开启;按[默认上传决策](2026-09-14-session-log-upload-default.zh.md),设置 `session-log-deepseek.enabled: false` 可关闭日志上传。无密钥 `deepseek-official` 回放会使用合成的空基础正文执行准备,并在第一个已记录分片前调用同一接受事务;它保持的是 2xx 后扩展副作用,而非字段字节。
|
||||
|
||||
提供方无关的 `llm` 包与 `llm-pi-ai` 不包含任何扩展类型、服务查找、字段合并或接受调用。
|
||||
|
||||
@@ -85,7 +85,7 @@ Status: implemented
|
||||
|
||||
## 后果
|
||||
|
||||
DeepSeek 官方请求会把存活包版本发送到解析后的 `baseURL`,包括已配置 gateway。除非关闭会话日志上传,否则符合条件的请求还会携带新的未接受会话事件,每次最多 `maxBytes`。这些字段对模型不可见,不增加提示词 token,也不改变 KV Cache,但可能显著增大 HTTP 正文。Manifest 解析、字段冲突、接受记录或提供方 schema 拒绝会使模型请求失败,而不会静默丢弃元数据。合并后的正文无法序列化是例外:请求不带扩展字段继续发送,并记录一条告警。
|
||||
DeepSeek 官方请求会把存活包版本发送到解析后的 `baseURL`,包括已配置 gateway。除非关闭会话日志上传,否则符合条件的请求还会携带新的未接受会话事件,每次最多 `maxBytes`。这些字段对模型不可见,不增加提示词 token,也不改变 KV Cache,但可能显著增大 HTTP 正文。字段冲突或提供方 schema 拒绝会使模型请求失败。贡献方准备失败、接受失败或合并后的正文无法序列化时,系统记录告警并让请求继续;没有可用版本的包只报告名称。
|
||||
|
||||
`delivery-accepted` 事件会成为权威日志的一部分,并在后续请求中自行交付。崩溃恢复可能重复后缀,但不会根据 assistant 输出推断接受,也不会创建第二份本地游标存储。缺少存活会话的直接调用会省略会话字段;宿主包清单仍然可用。
|
||||
|
||||
|
||||
+2
-2
@@ -21,8 +21,8 @@
|
||||
en: 8ee88035cf64eeb3
|
||||
zh: 6184e50fc4d47348
|
||||
/agent-note-one-dsh-launcher-for-application-profiles/decision/python-runtime:
|
||||
en: 5962abadfdece709
|
||||
zh: 5db20e96deed49d1
|
||||
en: 4b4cf5071b740c85
|
||||
zh: 595e5fe8127f9067
|
||||
/agent-note-one-dsh-launcher-for-application-profiles/decision/enforcement:
|
||||
en: 9ce8d86b65064f38
|
||||
zh: 27b8ad426cb319a2
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ Direct SDK use follows normal Harness-home resolution: explicit `dshHome`, inher
|
||||
|
||||
### Python runtime
|
||||
|
||||
The Python runtime wheel stages [`python/sdk-runtime/runtime-bootstrap.mjs`](../../../../python/sdk-runtime/runtime-bootstrap.mjs) as the `dsh-python-runtime-closure` entry. Its ordinary branch calls the public CLI export; a provider-private selector dispatches to the internal subprocess runner before CLI parsing and is not an application entry point. The [native-containment reference](../../../../packages/subprocess/subprocess-local/README.md) owns that private dispatch. The Python client selects `dsh --profile sdk` by default, ordered patch files, and an explicit Harness home; the runnable example under `python/sdk/examples` selects `sdk-minimal`. The installed `dsh` console command exposes the same profile grammar, including the `web` profile.
|
||||
The Python runtime wheel stages [`python/sdk-runtime/runtime-bootstrap.mjs`](../../../../python/sdk-runtime/runtime-bootstrap.mjs) as the `dsh-python-runtime-closure` entry. Its ordinary branch calls the public CLI export; a provider-private selector dispatches to the internal subprocess runner before CLI parsing and is not an application entry point. A private `DSH_RUNTIME_DOWNLOAD` selector prepares optional resources before application launch and is removed before spawning preparation subprocesses; ordinary CLI arguments do not select downloads. The [native-containment reference](../../../../packages/subprocess/subprocess-local/README.md) owns that private dispatch. The Python client selects `dsh --profile sdk` by default, ordered patch files, and an explicit Harness home; the runnable example under `python/sdk/examples` selects `sdk-minimal`. The installed `dsh` console command exposes the same profile grammar, including the `web` profile.
|
||||
|
||||
The executable family is `deepseek-harness-sdk-runtime-<platform>-<arch>`. The SDK wire, wheel and import distribution names, sidecar names, and wire identity `deepseek-harness-sdk-runtime` remain stable. The SDK package family is `@deepseek-ai/dsh-sdk-client`, `@deepseek-ai/dsh-sdk-protocol`, and `@deepseek-ai/dsh-sdk-jsonrpc-server`; `@deepseek-ai/dsh-acp` remains the ACP protocol plugin. There is no Python-specific Node application, checked-in complete config, compatibility package, forwarding executable, fallback parser, or SDK/ACP launcher alias. [docs/architecture.md](../../../../docs/architecture.md) owns this launch, and the [`python/sdk-runtime` README](../../../../python/sdk-runtime/README.md) owns the Windows carrier.
|
||||
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ SDK 用户通过 profile 自定义插件。`dsh plugin --profile <name> ...` 管
|
||||
|
||||
### Python 运行时
|
||||
|
||||
Python 运行时 wheel 将 [`python/sdk-runtime/runtime-bootstrap.mjs`](../../../../python/sdk-runtime/runtime-bootstrap.mjs) 暂存为 `dsh-python-runtime-closure` 入口。其普通分支调用公开 CLI export;提供方私有选择会在 CLI 解析前分派到内部子进程 runner,而不是应用入口。[原生 containment 参考](../../../../packages/subprocess/subprocess-local/README.zh.md)负责该私有分派。Python 客户端默认选择 `dsh --profile sdk`、有序 patch 文件与显式 Harness home;`python/sdk/examples` 下的可运行示例选择 `sdk-minimal`。安装的 `dsh` 控制台命令暴露相同的 profile 语法,包括 `web` profile。
|
||||
Python 运行时 wheel 将 [`python/sdk-runtime/runtime-bootstrap.mjs`](../../../../python/sdk-runtime/runtime-bootstrap.mjs) 暂存为 `dsh-python-runtime-closure` 入口。其普通分支调用公开 CLI export;提供方私有选择会在 CLI 解析前分派到内部子进程 runner,而不是应用入口。私有 `DSH_RUNTIME_DOWNLOAD` 选择器在应用启动前准备可选资源,并在启动资源准备子进程前移除;普通 CLI 参数不选择下载。[原生 containment 参考](../../../../packages/subprocess/subprocess-local/README.zh.md)负责该私有分派。Python 客户端默认选择 `dsh --profile sdk`、有序 patch 文件与显式 Harness home;`python/sdk/examples` 下的可运行示例选择 `sdk-minimal`。安装的 `dsh` 控制台命令暴露相同的 profile 语法,包括 `web` profile。
|
||||
|
||||
可执行文件族是 `deepseek-harness-sdk-runtime-<platform>-<arch>`。SDK 协议格式、wheel 与 import 分发名称、伴随文件名称,以及协议 identity `deepseek-harness-sdk-runtime` 保持稳定。SDK 包族是 `@deepseek-ai/dsh-sdk-client`、`@deepseek-ai/dsh-sdk-protocol` 与 `@deepseek-ai/dsh-sdk-jsonrpc-server`;`@deepseek-ai/dsh-acp` 继续作为 ACP 协议插件。仓库不保留 Python 专用 Node 应用、检入的完整配置、兼容包、转发可执行文件、后备解析器或 SDK/ACP 启动别名。[docs/architecture.md](../../../../docs/architecture.zh.md)负责该启动方式,[`python/sdk-runtime` README](../../../../python/sdk-runtime/README.zh.md)负责 Windows 载体。
|
||||
|
||||
|
||||
+4
-4
@@ -9,8 +9,8 @@
|
||||
en: 01293500639c9d79
|
||||
zh: 6d19e4843955c811
|
||||
/agent-note-browser-launch-token-authentication/decision:
|
||||
en: 063f9698b8a94287
|
||||
zh: 848b2c1134b7a100
|
||||
en: 54efa59339f75256
|
||||
zh: 0f53ead3844151c6
|
||||
/agent-note-browser-launch-token-authentication/verification:
|
||||
en: 189b36d0fc5f3a17
|
||||
zh: 08993cb585204fc0
|
||||
@@ -18,5 +18,5 @@
|
||||
en: 5275c87f19fe6eee
|
||||
zh: ccb922367c4bb3e2
|
||||
/agent-note-browser-launch-token-authentication/consequences:
|
||||
en: 0714a8a7cc74918a
|
||||
zh: 0f3878b09463ab2e
|
||||
en: af55274a2f345cdd
|
||||
zh: 4d1810141ef03bae
|
||||
@@ -14,13 +14,13 @@ The Web Host runs tool-capable Sessions with the current operating-system user's
|
||||
|
||||
Each Host process generates a random launch token, retained by the root application context across Connection hot reloads. `dsh-web-app` prints and opens the normal root URL with that token in the query once per process. `frontend-static` asks Connection to authorize index responses: only `GET /?token=...` exchanges the process token for a cookie, then redirects to clean `./`; the token is not accepted on API paths or in an Authorization header. An obsolete token paired with a valid cookie redirects to clean `./`. Missing and invalid credentials receive one minimal 401 response. Static non-index assets remain public.
|
||||
|
||||
The cookie is a signed, authority-bound bearer. Its deterministic name and signed payload both include the normalized hostname plus port, so one Harness home can run independent Web ports without cookie collisions. The payload carries safe-integer issue and expiry times under an absolute lifetime; `cookieMaxAgeDays` defaults to 30. The cookie is host-only, `Path=/`, `HttpOnly`, and `SameSite=Strict`. It omits `Secure` because the shipped server uses loopback HTTP. There is no logout operation.
|
||||
The cookie is a signed, authority-bound bearer. Its deterministic name and signed payload both include the normalized hostname plus port, so one Harness home can run independent Web ports without cookie collisions. The payload carries safe-integer issue and expiry times under an absolute lifetime; `cookieMaxAgeDays` defaults to 30. The cookie is host-only, `Path=/`, `HttpOnly`, and `SameSite=Strict`. The HTTP listener omits `Secure`. There is no logout operation.
|
||||
|
||||
The HMAC secret is a versioned `grant` record at `client-connection/browser-session` in `ctx.credentials`; the local provider stores it in `$DSH_HOME/.credentials.yaml`. Connection loads or creates the record during activation and retains the secret for synchronous request verification. An active Connection continues using its loaded secret if the durable record changes; the next activation loads the replacement or creates a missing record, so deleting the record and restarting the process revokes every existing cookie. Invalid owner payloads fail loud instead of being replaced. The launch token itself is never persisted and changes on every process start, while an unexpired cookie remains valid across restarts on the same authority.
|
||||
|
||||
The in-page Web Worker preview exposes no network socket. Its page-owned `postMessage` tunnel enters the real route first, then retries a 401 or 403 through the worker-local fetch handler. This keeps Connection interceptors while limiting the authentication bypass to the page that created the Host worker.
|
||||
|
||||
The shipped CLI continues to reject `--host 0.0.0.0`. Authentication does not imply supported network deployment, TLS, forwarding-header interpretation, or proxy configuration.
|
||||
The shipped CLI accepts concrete local bind addresses but rejects wildcard addresses such as `--host 0.0.0.0`. Authentication does not configure TLS, forwarding-header interpretation, or proxies.
|
||||
|
||||
## Verification
|
||||
|
||||
@@ -42,6 +42,8 @@ Unit coverage pins process-token retention across Connection reloads, one secret
|
||||
|
||||
Possession of the browser cookie authorizes the complete tool-capable Host API, matching the authority the Web application exposes after Session creation. `Host` does not grant a higher method tier, and a method migration between API Proxy and Typert Remote cannot change its caller set.
|
||||
|
||||
The persistent secret makes cookies survive restarts but gives a stolen cookie up to the configured absolute lifetime. Deleting the record and restarting the process is the global revocation mechanism; the active Connection intentionally avoids credential-provider work on each request. Omitting `Secure` preserves loopback HTTP and permits plaintext transmission if an operator makes the same cookie authority reachable over an unencrypted network. The startup URL contains a process credential and must be treated as sensitive output; runtime diagnostics do not repeat it.
|
||||
The persistent secret makes cookies survive restarts but gives a stolen cookie up to the configured absolute lifetime. Deleting the record and restarting the process is the global revocation mechanism; the active Connection intentionally avoids credential-provider work on each request. An HTTP listener permits plaintext cookie transmission if its authority is reachable over an unencrypted network. The startup URL contains a process credential and must be treated as sensitive output; runtime diagnostics do not repeat it.
|
||||
|
||||
The decision partially supersedes the authentication deferral and unauthenticated non-loopback consequences in the [browser trust note](2026-07-28-api-browser-trust-boundary.md). That note remains active authority for media-type, Host, Origin, Fetch-Metadata, and configured-authority validation. No active Agent Note is archived: the overlap is partial and both security rules retain future decision value.
|
||||
|
||||
The [native HTTPS listener decision](2026-09-23-native-https-web-listener.md) supersedes the HTTP-only transport and omitted-`Secure` assumptions here. Listener protocol now determines `Secure`; the token exchange, signed-cookie authority, and credential lifetime remain owned by this note.
|
||||
+5
-3
@@ -14,13 +14,13 @@ Web Host 以当前操作系统用户的权限运行具有工具能力的 Session
|
||||
|
||||
每个 Host 进程生成随机启动令牌,并由应用根 context 跨 Connection 热重载保留。`dsh-web-app` 每个进程只打印并打开一次 query 中带该令牌的普通根 URL。`frontend-static` 请求 Connection 授权 index 响应:只有 `GET /?token=...` 会把进程令牌交换为 cookie,再重定向到干净的 `./`;API 路径和 Authorization header 都不接受该令牌。过时令牌如果同时带有有效 cookie,会重定向到干净的 `./`。缺失与无效凭据得到同一份最小 401 响应。非 index 静态资产保持公开。
|
||||
|
||||
cookie 是签名且绑定 authority 的 bearer。确定性名称与签名 payload 都包含规范化 hostname 和 port,因此同一 Harness home 可以在不同 Web port 运行而不发生 cookie 冲突。payload 在绝对有效期内携带安全整数形式的签发与过期时间;`cookieMaxAgeDays` 默认为 30。cookie 是 host-only、`Path=/`、`HttpOnly`、`SameSite=Strict`。随附服务器使用 loopback HTTP,因此不设置 `Secure`。这里没有 logout 操作。
|
||||
cookie 是签名且绑定 authority 的 bearer。确定性名称与签名 payload 都包含规范化 hostname 和 port,因此同一 Harness home 可以在不同 Web port 运行而不发生 cookie 冲突。payload 在绝对有效期内携带安全整数形式的签发与过期时间;`cookieMaxAgeDays` 默认为 30。cookie 是 host-only、`Path=/`、`HttpOnly`、`SameSite=Strict`。HTTP 监听器不设置 `Secure`。这里没有 logout 操作。
|
||||
|
||||
HMAC 密钥是 `ctx.credentials` 中位于 `client-connection/browser-session` 的版本化 `grant` 记录;本地提供方将其存入 `$DSH_HOME/.credentials.yaml`。Connection 在激活期间加载或创建该记录,并保留密钥以同步校验请求。持久记录发生变化后,当前 Connection 继续使用已加载的密钥;下一次激活会加载替换记录或创建缺失记录,因此删除记录并重启进程会撤销全部既有 cookie。无效 owner payload 会明确失败,而不是被覆盖。启动令牌本身绝不持久化并在每次进程启动时变化;未过期 cookie 则能在相同 authority 上跨重启继续有效。
|
||||
|
||||
页内 Web Worker preview 不暴露网络 socket。其由页面持有的 `postMessage` tunnel 先进入真实 route,收到 401 或 403 后再经 worker 本地 fetch handler 重试。这样既保留 Connection interceptor,又把认证绕过限制在创建 Host worker 的页面内。
|
||||
|
||||
随附 CLI 继续拒绝 `--host 0.0.0.0`。认证不代表支持网络部署、TLS、转发 header 解释或代理配置。
|
||||
随附 CLI 接受本机具体绑定地址,但拒绝 `--host 0.0.0.0` 等通配地址。认证不会配置 TLS、转发 header 解释或代理。
|
||||
|
||||
## 验证
|
||||
|
||||
@@ -42,6 +42,8 @@ HMAC 密钥是 `ctx.credentials` 中位于 `client-connection/browser-session`
|
||||
|
||||
持有浏览器 cookie 就能调用完整的工具型 Host API,这与 Web 应用在创建 Session 后暴露的 authority 一致。`Host` 不授予更高的方法层级,方法在 API Proxy 与 Typert Remote 之间迁移也不会改变调用者集合。
|
||||
|
||||
持久密钥使 cookie 跨重启生效,也让被盗 cookie 最多保有配置的绝对有效期。删除记录并重启进程是全局撤销机制;当前 Connection 刻意避免在每个请求上访问凭据提供方。不设置 `Secure` 保留 loopback HTTP,但如果操作者让同一 cookie authority 经未加密网络可达,cookie 会以明文传输。启动 URL 含进程凭据,必须视为敏感输出;运行时诊断不会重复它。
|
||||
持久密钥使 cookie 跨重启生效,也让被盗 cookie 最多保有配置的绝对有效期。删除记录并重启进程是全局撤销机制;当前 Connection 刻意避免在每个请求上访问凭据提供方。如果 HTTP listener 的 authority 经未加密网络可达,cookie 就可能以明文传输。启动 URL 含进程凭据,必须视为敏感输出;运行时诊断不会重复它。
|
||||
|
||||
本决策部分取代[浏览器信任说明](2026-07-28-api-browser-trust-boundary.zh.md)中的认证延期与未认证非 loopback 后果。该说明仍是媒体类型、Host、Origin、Fetch-Metadata 和配置 authority 校验的有效权威。没有 active Agent Note 被归档:重叠只发生在局部,两条安全规则都保有未来决策价值。
|
||||
|
||||
[原生 HTTPS 监听器决策](2026-09-23-native-https-web-listener.zh.md)取代本说明仅支持 HTTP 传输及不设置 `Secure` 的前提。`Secure` 由监听器协议决定;令牌交换、签名 cookie 的 authority 与凭据有效期仍由本说明定义。
|
||||
+2
-2
@@ -12,8 +12,8 @@
|
||||
en: e01df5ba880c2972
|
||||
zh: e62074a6a9ba5685
|
||||
/agent-note-bundle-the-desktop-runtime-and-retain-external-plugins/package-ownership:
|
||||
en: 37cae6b28fd359ef
|
||||
zh: 63118e361fa8f84c
|
||||
en: f683605bf4655db8
|
||||
zh: de09d610ee1d4193
|
||||
/agent-note-bundle-the-desktop-runtime-and-retain-external-plugins/transactions-and-upgrades:
|
||||
en: 53d48f71fcc1c52e
|
||||
zh: 7d61923484dbec1c
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ The resource descriptor records the exact release, Node version, platform, archi
|
||||
|
||||
The [Desktop file policy](../../../../apps/desktop/scripts/runtime-file-policy.ts) applies after production npm installation and before native signing or descriptor generation. npm publication lists serve library consumers and can include declarations, maps, tests, and native build inputs; they do not identify the files needed by the Desktop process. The Desktop copy omits declarations and recognized source maps because Host execution uses JavaScript and generated Typert artifacts. The Host inherits the user environment. Published npm packages and external plugin directories retain their own files. Source debugger navigation is a development-package capability.
|
||||
|
||||
Package-specific exclusions remove Domino tests, fs-ext compilation outputs, Koffi's Windows import library, and non-target node-pty prebuilds and debug symbols. The policy retains native executable dependencies, node-pty's ConPTY source distribution, licenses, and unrecognized assets; broad `src`, `test`, `.ts`, or `.map` exclusions could remove executable code or runtime data. Copy tests preserve sentinel assets and seal the filtered inventory; the Electron [payload smoke](../../../../apps/desktop/tests/fixtures/runtime-payload-smoke.mjs) verifies PTY output, native file seeking, FFI, image conversion, and HTML parsing. Runtime preparation still verifies every retained byte and boots the complete Host with an external plugin.
|
||||
Package-specific exclusions remove Domino tests, fs-ext compilation outputs, Koffi's Windows import library, and non-target node-pty prebuilds and debug symbols. The policy retains native executable dependencies, licenses, and unrecognized assets; node-pty's ConPTY source distribution is retained only for Windows targets; broad `src`, `test`, `.ts`, or `.map` exclusions could remove executable code or runtime data. Copy tests preserve sentinel assets and seal the filtered inventory; the Electron [payload smoke](../../../../apps/desktop/tests/fixtures/runtime-payload-smoke.mjs) verifies PTY output, native file seeking, FFI, image conversion, and HTML parsing. Runtime preparation still verifies every retained byte and boots the complete Host with an external plugin.
|
||||
|
||||
The shared profile runner supplies missing installation and selected-bundle dependencies through the runtime resolution. pnpm-installed packages take precedence. Runtime resolution delegates to the selected package paths, so Host and plugin imports reaching the same export share its module instance. Distinct ESM and CommonJS conditional exports remain distinct entry points; runtime resolution does not merge a package’s dual implementations.
|
||||
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ Desktop 初始化时安装核心依赖图,会重复发布构建器已经完成
|
||||
|
||||
[桌面文件规则](../../../../apps/desktop/scripts/runtime-file-policy.ts)在生产 npm 依赖安装之后、原生签名或描述文件生成之前执行。npm 发布列表服务于库的使用者,可以包含声明、map、测试和原生构建输入,不能直接表示桌面进程需要哪些文件。桌面副本排除声明和已识别的 source map,因为 Host 执行 JavaScript 和生成的 Typert 产物。Host 继承用户环境。已发布的 npm 包和外部插件目录保留各自的文件。源码调试导航由开发包提供。
|
||||
|
||||
包专用排除项包括 Domino 测试、fs-ext 编译产物、Koffi 的 Windows 导入库,以及非目标平台的 node-pty 预构建文件和调试符号。规则保留原生可执行依赖、node-pty 的 ConPTY 源分发内容、许可证和未知资源;宽泛排除 `src`、`test`、`.ts` 或 `.map` 可能移除可执行代码或运行时数据。复制测试保留哨兵资源并封存过滤后的清单;Electron 的[产物 smoke](../../../../apps/desktop/tests/fixtures/runtime-payload-smoke.mjs)验证 PTY 输出、原生文件定位、FFI、图像转换和 HTML 解析。运行时准备仍会验证每个保留字节,并携带外部插件启动完整 Host。
|
||||
包专用排除项包括 Domino 测试、fs-ext 编译产物、Koffi 的 Windows 导入库,以及非目标平台的 node-pty 预构建文件和调试符号。规则保留原生可执行依赖、许可证和未知资源;node-pty 的 ConPTY 源分发内容仅在 Windows 目标中保留;宽泛排除 `src`、`test`、`.ts` 或 `.map` 可能移除可执行代码或运行时数据。复制测试保留哨兵资源并封存过滤后的清单;Electron 的[产物 smoke](../../../../apps/desktop/tests/fixtures/runtime-payload-smoke.mjs)验证 PTY 输出、原生文件定位、FFI、图像转换和 HTML 解析。运行时准备仍会验证每个保留字节,并携带外部插件启动完整 Host。
|
||||
|
||||
共享 profile runner 通过 runtime resolution 补全安装包与选中 bundle 缺失的依赖。pnpm 安装的包优先。运行时解析委托给所选包路径,因此 Host 与插件导入同一导出时共享其模块实例。不同的 ESM 与 CommonJS 条件导出仍是不同入口;运行时解析不会合并包的双重实现。
|
||||
|
||||
|
||||
@@ -9,8 +9,11 @@
|
||||
en: f67f6add03a36e85
|
||||
zh: 656be288f8b2e1d2
|
||||
/agent-note-posix-ssh-execution-providers/decision:
|
||||
en: b2d1e370d008f0f3
|
||||
zh: 3e98919538d3fb78
|
||||
en: bc22949098fc3028
|
||||
zh: 76618180bc0bac56
|
||||
/agent-note-posix-ssh-execution-providers/decision/helper-deployment:
|
||||
en: c479fdb58ed617f1
|
||||
zh: 17cfefa778e3fd71
|
||||
/agent-note-posix-ssh-execution-providers/alternatives-considered:
|
||||
en: 4efffb3edba91689
|
||||
zh: 40100a70e5f55a42
|
||||
@@ -18,8 +21,8 @@
|
||||
en: f29359ab89feb084
|
||||
zh: db7505b8d4a5351a
|
||||
/agent-note-posix-ssh-execution-providers/verification:
|
||||
en: 822c7c029a77f3c0
|
||||
zh: 4a2eaaf3372cba8b
|
||||
en: ee72e88fd40d6769
|
||||
zh: 56f0e0c8f3365973
|
||||
/agent-note-posix-ssh-execution-providers/deferred-work:
|
||||
en: f300f112c9a09f7e
|
||||
zh: b4708a0b7f596420
|
||||
@@ -22,12 +22,20 @@ Each stream receives a fresh 256-bit TLS pre-shared key through private administ
|
||||
|
||||
Collected stdout and stderr carry bounded tail snapshots through handlers that only update output observations. Capture continues while a snapshot is waiting for transport. Final snapshots preserve raw-byte offsets, and completed spill files use the local provider’s retained-output storage after connection disposal.
|
||||
|
||||
Readiness verifies the installed helper digest and, when PTC is configured, the installed Node bootstrap digest. These checks pin expected deployment artifacts; they do not authenticate a malicious remote operating system. The helper disables Node debugger activation through `SIGUSR1`: file-effect confinement can still permit same-user signals, which must not expose the helper's unrestricted filesystem and process services. File-effect confinement delegates to the remote local sandbox provider and retains its full/partial disclosure and platform limitations.
|
||||
Readiness verifies the installed helper script or executable digest and, when a script deployment supplies a PTC bootstrap, that bootstrap's digest. Executable deployments embed the PTC worker and require no separate bootstrap. These checks pin expected deployment artifacts; they do not authenticate a malicious remote operating system. The helper disables Node debugger activation through `SIGUSR1`: file-effect confinement can still permit same-user signals, which must not expose the helper's unrestricted filesystem and process services. File-effect confinement delegates to the remote local sandbox provider and retains its full/partial disclosure and platform limitations.
|
||||
|
||||
Path canonicalization belongs where the files exist. The shared policy resolver preserves absolute execution-world spelling; enforcing providers resolve symlinks and `..` on their own filesystem. Headless records and validates cwd through `ctx.fs`. Host-path projection remains unavailable for SSH, so Node execution requires an explicitly installed remote bootstrap.
|
||||
Path canonicalization belongs where the files exist. The shared policy resolver preserves absolute execution-world spelling; enforcing providers resolve symlinks and `..` on their own filesystem. Headless records and validates cwd through `ctx.fs`. Host-path projection remains unavailable for SSH; PTC uses an explicitly installed remote bootstrap for script deployments or the helper executable's embedded worker.
|
||||
|
||||
A lost connection invalidates pending operations without reconnect or replay. Helper EOF, signals and a heartbeat lease initiate remote native cleanup. The client cannot turn lease expiry into an observed successful termination: an interrupted mutation or launch can have an unknown outcome. Administrative request deadlines, consumer execution deadlines and cleanup remain separate owners.
|
||||
|
||||
### Helper deployment
|
||||
|
||||
The explicit `launch.kind` selects remote `node-script` or `executable` invocation independently of the local Harness carrier. A local `process.pkg` flag cannot describe the remote installation. Protocol 2 includes the remote runtime kind in its readiness response and rejects protocol or launch-kind mismatches, so a client cannot apply script bootstrap rules to an embedded worker. Client and helper protocol upgrades must be deployed together.
|
||||
|
||||
The executable embeds Node, the helper, managed subprocess runners and the PTC worker. It reuses the [SDK executable packaging decision](2026-07-10-single-file-executable-sdk-runtime-distribution.md): `pkg --sea` preserves a package tree and dynamic module resolution instead of maintaining a separate static plugin registry. That record owns the comparison with bare Node SEA. The embedded runtime serves these private entry points; project commands still depend on the remote machine's development tools.
|
||||
|
||||
The four release targets cover Linux and macOS on x64 and arm64, matching the POSIX providers and their supported native resources. Packaging does not add Windows or musl support. Landlock and macOS PTY launchers remain real files beside the executable because the operating system cannot execute paths inside pkg's virtual filesystem. The installation unit is therefore the complete archive directory, with native resources and integrity metadata, rather than the executable alone. The [helper runtime reference](../../../../packages/ssh/ssh-helper-runtime/README.md) owns the build matrix, deployment baselines and archive verification commands.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Frame every stream over SSH exec stdout/stdin.** This avoids Unix-socket forwarding, but requires per-stream credits, queue bounds and independent EOF semantics inside the application protocol. Independent SSH channels use the transport’s existing flow control and keep program output away from administrative framing.
|
||||
@@ -48,7 +56,7 @@ The portable-consumer decision remains active; this note supplies its SSH realiz
|
||||
|
||||
## Verification
|
||||
|
||||
Required protocol and lifecycle evidence covers malformed frames, bounds, reservation cancellation, authenticated stream publication and disconnect errors. Native SSH acceptance must exercise remote file guards and symlink identity, Bash confinement, fd 7 binary traffic, control progress under paused output, terminal operations, LSP and Node execution. Live checks require an explicitly configured disposable remote workspace; keyless tests do not provision one.
|
||||
Required protocol and lifecycle evidence covers malformed frames, bounds, reservation cancellation, authenticated stream publication and disconnect errors. Native SSH acceptance must exercise remote file guards and symlink identity, Bash confinement, fd 7 binary traffic, control progress under paused output, terminal operations, LSP and Node execution. External-host checks require an explicitly configured disposable remote workspace. Packaged Linux acceptance provisions a disposable OpenSSH container without Node; native-host checks own confinement evidence.
|
||||
|
||||
Security evidence requires both a same-user connector and a replaced socket listener that cannot claim a reserved stream, learn its key, alter another run or receive its plaintext output. Process-lifecycle evidence distinguishes direct exit from managed-range quiescence and tests cancellation both before launch publication and after the payload starts. Source and built profile checks verify the installed helper/bootstrap arrangement without transferring host credentials to program environments.
|
||||
|
||||
|
||||
@@ -22,12 +22,20 @@ SSH 提供经过认证的字节通道及逐通道流量控制,但普通 exec
|
||||
|
||||
收集的 stdout 与 stderr 通过仅更新输出观测的处理器传递有界尾部快照。快照等待传输时,输出捕获继续进行。最终快照保留原始字节偏移,已完成的 spill 文件在连接释放后使用本地提供方的保留输出存储。
|
||||
|
||||
就绪流程验证已安装辅助程序的摘要,并在配置 PTC 时验证已安装 Node 引导程序的摘要。这些检查固定预期部署产物,不用于认证恶意远端操作系统。辅助程序禁用通过 `SIGUSR1` 启动 Node 调试器:文件效果限制仍可能允许同用户信号,这些信号不得暴露辅助程序不受限的文件系统和进程服务。文件效果限制委托给远端本地沙箱提供方,保留其完整/部分执行披露及平台限制。
|
||||
就绪流程验证已安装辅助脚本或可执行文件的摘要;脚本部署提供 PTC 引导程序时,还验证该引导程序的摘要。可执行文件部署内嵌 PTC 工作进程,无需单独的引导程序。这些检查固定预期部署产物,不用于认证恶意远端操作系统。辅助程序禁用通过 `SIGUSR1` 启动 Node 调试器:文件效果限制仍可能允许同用户信号,这些信号不得暴露辅助程序不受限的文件系统和进程服务。文件效果限制委托给远端本地沙箱提供方,保留其完整/部分执行披露及平台限制。
|
||||
|
||||
路径规范化属于文件实际存在的位置。共享策略解析器保留执行环境中的绝对路径写法;执行限制的提供方在自己的文件系统上解析符号链接与 `..`。headless 通过 `ctx.fs` 记录和验证 cwd。SSH 不提供主机路径投影,因此 Node 执行需要显式安装的远端引导程序。
|
||||
路径规范化属于文件实际存在的位置。共享策略解析器保留执行环境中的绝对路径写法;执行限制的提供方在自己的文件系统上解析符号链接与 `..`。headless 通过 `ctx.fs` 记录和验证 cwd。SSH 不提供主机路径投影;PTC 在脚本部署中使用显式安装的远端引导程序,在可执行文件部署中使用辅助程序内嵌的工作进程。
|
||||
|
||||
连接丢失会使待处理操作失效,不进行重连或重放。辅助进程 EOF、信号及心跳租期会启动远端原生清理。客户端不能把租期到期当作观察到的成功终止:被中断的修改或启动可能结果未知。管理请求截止时限、消费方执行截止时限及清理分别由各自归属方负责。
|
||||
|
||||
### 辅助程序部署
|
||||
|
||||
显式的 `launch.kind` 选择远端的 `node-script` 或 `executable` 调用方式,与本地 Harness 的运行载体相互独立。本地 `process.pkg` 标志无法描述远端安装。协议 2 在就绪响应中包含远端运行时类型,并拒绝协议版本或启动类型不匹配,避免客户端把脚本引导规则用于内嵌工作进程。客户端与辅助程序的协议升级必须一起部署。
|
||||
|
||||
可执行文件内嵌 Node、辅助程序、托管子进程运行器与 PTC 工作进程。它复用 [SDK 可执行文件打包决策](2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md):`pkg --sea` 保留包目录树与动态模块解析,无需维护独立的静态插件注册表。该记录负责与裸 Node SEA 的方案比较。内嵌运行时服务于这些私有入口;项目命令仍依赖远端机器的开发工具。
|
||||
|
||||
四个发布目标覆盖 Linux 和 macOS 的 x64 与 arm64,对应 POSIX 提供方及其支持的原生资源。打包不会增加 Windows 或 musl 支持。Landlock 与 macOS PTY 启动器保留为可执行文件旁的真实文件,因为操作系统无法执行 pkg 虚拟文件系统中的路径。因此,安装单元是包含原生资源与完整性元数据的完整归档目录,而非单个可执行文件。[辅助程序运行时参考](../../../../packages/ssh/ssh-helper-runtime/README.zh.md)负责构建矩阵、部署基线及归档验证命令。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**在 SSH exec stdout/stdin 上为所有流分帧。** 这避免了 Unix 套接字转发,但要求应用协议自行实现逐流额度、队列上限及独立 EOF 语义。独立 SSH 通道使用传输层既有流控,并将程序输出与管理帧分开。
|
||||
@@ -48,7 +56,7 @@ SSH 提供经过认证的字节通道及逐通道流量控制,但普通 exec
|
||||
|
||||
## 验证
|
||||
|
||||
必要的协议及生命周期证据覆盖畸形消息、上限、预留取消、经过认证的流发布及断连错误。原生 SSH 验收必须覆盖远端文件保护与符号链接身份、Bash 限制、fd 7 二进制流、输出暂停时的控制进展、终端操作、LSP 及 Node 执行。实时检查要求显式配置的可丢弃远端工作区;无密钥测试不创建远端环境。
|
||||
必要的协议及生命周期证据覆盖畸形消息、上限、预留取消、经过认证的流发布及断连错误。原生 SSH 验收必须覆盖远端文件保护与符号链接身份、Bash 限制、fd 7 二进制流、输出暂停时的控制进展、终端操作、LSP 及 Node 执行。外部主机检查要求显式配置的可丢弃远端工作区。打包产物的 Linux 验收会创建未安装 Node 的可丢弃 OpenSSH 容器;原生主机检查负责限制执行的证据。
|
||||
|
||||
安全证据要求同时尝试同用户连接与替换套接字监听端点,并确认其无法占用预留流、获知流密钥、修改另一执行或收到其明文输出。进程生命周期证据区分直接退出与托管进程范围静止,并测试启动发布前及程序启动后的取消。源码与构建后配置组合检查验证已安装辅助程序/引导安排,不向程序环境传递主机凭据。
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Bilingual-pair consistency record for 2026-09-19-concrete-web-bind-address.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-09-19-concrete-web-bind-address.md
|
||||
/agent-note-bind-one-concrete-web-address:
|
||||
en: f70f2b6b24ef16dd
|
||||
zh: 64100dbc55eee33d
|
||||
/agent-note-bind-one-concrete-web-address/problem:
|
||||
en: ffc265e61eadc31b
|
||||
zh: fcc70f0a8817914c
|
||||
/agent-note-bind-one-concrete-web-address/decision:
|
||||
en: 5fe4f2ddba2ee2da
|
||||
zh: bb140a16f056d4b4
|
||||
/agent-note-bind-one-concrete-web-address/alternatives-considered:
|
||||
en: 0e4d475347796821
|
||||
zh: e621e606bc5b1045
|
||||
/agent-note-bind-one-concrete-web-address/consequences:
|
||||
en: ba7a839bca09992d
|
||||
zh: a11171005405d1e2
|
||||
/agent-note-bind-one-concrete-web-address/testing:
|
||||
en: 4bb8f4bf4ad943c3
|
||||
zh: 2e24d56d01b52574
|
||||
@@ -0,0 +1,35 @@
|
||||
# Agent Note: Bind one concrete Web address
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-09-19-concrete-web-bind-address.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The Web listener accepted `127.0.0.1` or `0.0.0.0`. A wildcard bind exposed every interface of the machine, so the bundle sampled the LAN IPv4 addresses at bind time into a `webRuntime` service and appended them to `trustedHosts`, granting authorities for interfaces the operator never named and mixing inferred reachability into configured trust. A container or workstation that should expose exactly one address had no way to say so, IPv6 addresses were not accepted at all, and a browser dialing the exposed address sent it as `Host` only to be refused unless the sampled list happened to contain it.
|
||||
|
||||
## Decision
|
||||
|
||||
`webserver.config.host` accepts one concrete IPv4 or IPv6 address literal, loopback by default; the unspecified address in every spelling (`0.0.0.0`, `::`, `::ffff:0.0.0.0`, and their expansions) is rejected when the config loads, and the shipped `dsh web` command refuses the same spellings for `--host` before any consumer activates. An IPv6 `%zone` is kept for `listen`; classification through the exported `isWildcardHost` and `isLoopbackHost` reads the address alone.
|
||||
|
||||
The browser Host/Origin fence accepts the listener's own bind IP literal as `Host` on any port, independently of `trustedHosts` and ignoring the zone. A browser dialing that literal presents it as `Host`, the literal is no rebinding target, and the Origin and cross-site checks still apply. DNS names resolving to the address, other interfaces, and proxy authorities still require `--trusted-host`. Connection reads the invocation authorities directly from the `webStartup` provider; the `webRuntime` service and LAN sampling no longer exist.
|
||||
|
||||
The advertised URL uses IPv4 for mapped loopback binds, brackets other IPv6 binds, drops a loopback zone, and requires `--public-url` for a zoned non-loopback bind because a zone cannot appear in a URL. A non-loopback bind serves plain HTTP and warns at startup even when an HTTPS `--public-url` fronts it. `web-runtime` is a required startup entry, so a bind failure fails startup instead of leaving a silent listener-less process. Desktop pins `--host 127.0.0.1`, the address Electron dials.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep sampling LAN addresses into `trustedHosts`.** Rejected: the listener already owns the concrete bind address. Sampling grants authorities for other interfaces and mixes inferred reachability with configured trust; direct bind-IP acceptance preserves that distinction without another runtime service.
|
||||
|
||||
**Accept the bind IP only on the bound port.** Rejected: a port-less `trustedHosts` entry already matches any port, a proxy on the same address may listen elsewhere, and the port adds nothing to the rebinding defense the Host check provides.
|
||||
|
||||
**Keep `0.0.0.0` with a warning.** Rejected: binding every interface exposes remote code execution to every network the machine joins, and the operator can name each address that should be exposed.
|
||||
|
||||
## Consequences
|
||||
|
||||
An operator exposes exactly the interfaces named by `--host` and reaches the listener through that literal without further configuration; every other remote authority is a `--trusted-host` decision. Compositions that bound `0.0.0.0` fail at load with a message naming the rejected spelling. IPv6-only hosts are supported end to end, including the printed and opened URL.
|
||||
|
||||
The [native HTTPS listener decision](2026-09-23-native-https-web-listener.md) supersedes this note's HTTP-only transport assumption. The non-loopback warning applies only without TLS; bind-address validation and authority admission remain unchanged.
|
||||
|
||||
## Testing
|
||||
|
||||
`packages/host/webserver/tests/webserver.spec.ts` classifies wildcard and loopback spellings and rejects wildcard config; `packages/bundle/web-app/tests/startup.spec.ts` refuses wildcard `--host` before consumers activate; `packages/client/connection/tests/api-request-trust.host.spec.ts` and `node-half.host.spec.ts` admit the bind literal on any port without admitting other authorities; `packages/bundle/web-app/tests/web-app.spec.ts` covers the bracketed IPv6 URL and the plain-HTTP warning; `apps/cli/tests/profiles/web/tests/public-url.expected.e2e.ts` boots the built profile on a non-loopback and a zoned loopback address; `web-failure-matrix.expected.e2e.ts` and `packages/boot/app-boot/tests/app-boot.spec.ts` report the required `web-runtime` entry.
|
||||
@@ -0,0 +1,35 @@
|
||||
# Agent Note: 绑定一个具体的 Web 地址
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-09-19-concrete-web-bind-address.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
Web 监听器只接受 `127.0.0.1` 或 `0.0.0.0`。通配绑定会暴露机器的每个接口,因此 bundle 在绑定时把 LAN IPv4 地址采样进 `webRuntime` 服务并追加到 `trustedHosts`,为操作者从未点名的接口授予 authority,并把推断的可达性混入配置的信任。应当只暴露一个地址的容器或工作站无法表达这一点,IPv6 地址完全不被接受,浏览器拨号已暴露地址时把它作为 `Host` 发送,却只在采样列表恰好包含它时才被接受。
|
||||
|
||||
## 决策
|
||||
|
||||
`webserver.config.host` 接受一个具体的 IPv4 或 IPv6 地址字面量,默认回环;未指定地址的所有拼写(`0.0.0.0`、`::`、`::ffff:0.0.0.0` 及其展开形式)在配置加载时被拒绝,随附的 `dsh web` 命令在任何消费者激活前对 `--host` 拒绝同样的拼写。IPv6 的 `%zone` 保留给 `listen`;通过导出的 `isWildcardHost` 与 `isLoopbackHost` 分类时只读取地址本身。
|
||||
|
||||
浏览器 Host/Origin 栅栏接受监听器自身的绑定 IP 字面量作为任意端口上的 `Host`,独立于 `trustedHosts` 并忽略 zone。拨号该字面量的浏览器把它作为 `Host` 呈现,该字面量不是重绑目标,Origin 与跨站检查仍然生效。解析到该地址的 DNS 名称、其他接口与代理 authority 仍需 `--trusted-host`。Connection 直接从 `webStartup` 提供方读取调用 authority;`webRuntime` 服务与 LAN 采样不再存在。
|
||||
|
||||
公告 URL 对 mapped 回环绑定使用 IPv4,为其他 IPv6 绑定加方括号、剥离回环 zone,并因 zone 不能出现在 URL 中而要求带 zone 的非回环绑定显式提供 `--public-url`。非回环绑定提供明文 HTTP,即使前置 HTTPS 的 `--public-url` 也会在启动时警告。`web-runtime` 是必需的启动条目,因此绑定失败会使启动失败,而不是留下一个没有监听器的静默进程。Desktop 固定 `--host 127.0.0.1`,即 Electron 拨号的地址。
|
||||
|
||||
## 备选方案
|
||||
|
||||
**继续把采样的 LAN 地址加入 `trustedHosts`。** 否决:监听器已经持有具体绑定地址。采样会授权其他接口的 authority,并把推断的可达性混入配置的信任;直接接受绑定 IP 可以保留两者的区分,无需另设运行时服务。
|
||||
|
||||
**只在绑定端口上接受绑定 IP。** 否决:不带端口的 `trustedHosts` 条目本就匹配任意端口,同一地址上的代理可能监听其他端口,端口对 Host 检查提供的重绑防御没有增益。
|
||||
|
||||
**保留 `0.0.0.0` 并给出警告。** 否决:绑定所有接口会向机器加入的每个网络暴露远程代码执行,而操作者可以逐个点名应暴露的地址。
|
||||
|
||||
## 影响
|
||||
|
||||
操作者只暴露 `--host` 点名的接口,并无需额外配置即可通过该字面量访问监听器;其他每个远程 authority 都是一个 `--trusted-host` 决定。曾绑定 `0.0.0.0` 的组合在加载时失败,并给出点名被拒绝拼写的消息。纯 IPv6 主机得到端到端支持,包括打印与打开的 URL。
|
||||
|
||||
[原生 HTTPS 监听器决策](2026-09-23-native-https-web-listener.zh.md)取代本说明仅支持 HTTP 传输的前提。非回环警告只适用于未启用 TLS 的监听器;绑定地址校验与 authority 接纳保持不变。
|
||||
|
||||
## 测试
|
||||
|
||||
`packages/host/webserver/tests/webserver.spec.ts` 分类通配与回环拼写并拒绝通配配置;`packages/bundle/web-app/tests/startup.spec.ts` 在消费者激活前拒绝通配 `--host`;`packages/client/connection/tests/api-request-trust.host.spec.ts` 与 `node-half.host.spec.ts` 在任意端口上接纳绑定字面量而不接纳其他 authority;`packages/bundle/web-app/tests/web-app.spec.ts` 覆盖带方括号的 IPv6 URL 与明文 HTTP 警告;`apps/cli/tests/profiles/web/tests/public-url.expected.e2e.ts` 在非回环地址与带 zone 的回环地址上启动已构建的 profile;`web-failure-matrix.expected.e2e.ts` 与 `packages/boot/app-boot/tests/app-boot.spec.ts` 报告必需的 `web-runtime` 条目。
|
||||
+2
-2
@@ -30,8 +30,8 @@
|
||||
en: 743f2dceda3072aa
|
||||
zh: f4d53ddc13a4127e
|
||||
/agent-note-projection-cache-read-only-face-matches-the-lifecycle-identity-the-client-store-separates-cached-and-sequenced-rows/decision/the-client-store-separates-cached-and-sequenced-rows:
|
||||
en: 8a4e9e9ac2ada06f
|
||||
zh: b5884535cd934f72
|
||||
en: dd1cc7ac96a5a25d
|
||||
zh: bdea08d20f166e65
|
||||
/agent-note-projection-cache-read-only-face-matches-the-lifecycle-identity-the-client-store-separates-cached-and-sequenced-rows/decision/what-stays-untouched:
|
||||
en: 1cefba02bbfb2647
|
||||
zh: 5a96942565ab9d72
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ Writer classification:
|
||||
| the `projections` block of an `api-session/added` summary (`manager.handleSessionAdded`) | by the block's `kind`; the summary comes from a live session, so in practice `sequenced` |
|
||||
| the history first page's `projections` (`projections.seed` in `session.ts`) | sequenced |
|
||||
| the control baseline, live sessions only (`manager.replaceControlBaseline`) | sequenced |
|
||||
| the `session.projections` result of `refreshProjections` (a body observation) | sequenced |
|
||||
| the `session.projections` result of `refreshProjections` | sequenced for an exact read; cached only when [migration is required](2026-09-29-explicit-session-history-preparation.md) |
|
||||
| push frames (the `projection` frame handled by `manager`) | sequenced |
|
||||
| the `title` after a successful rename (`session.ts`) | sequenced |
|
||||
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ seq 比较只在同一条 Host 连接内发生:`handleConnected` 先整表 `cl
|
||||
| `api-session/added` 摘要的 `projections` block(`manager.handleSessionAdded`) | 按 block 的 `kind`;该摘要来自活会话,实际为 `sequenced` |
|
||||
| history 首页 `projections`(`session.ts` 的 `projections.seed`) | sequenced |
|
||||
| control 基线,仅活会话(`manager.replaceControlBaseline`) | sequenced |
|
||||
| `refreshProjections` 的 `session.projections` 结果(正文观察) | sequenced |
|
||||
| `refreshProjections` 的 `session.projections` 结果 | 准确读取为 sequenced;仅[需要迁移](2026-09-29-explicit-session-history-preparation.zh.md)时为 cached |
|
||||
| 推送 frame(`manager` 处理 `projection` frame) | sequenced |
|
||||
| rename 成功后的 `title`(`session.ts`) | sequenced |
|
||||
|
||||
|
||||
+6
-6
@@ -9,11 +9,11 @@
|
||||
en: 2ba6c43b20a3d347
|
||||
zh: 5647179ddaa38ad9
|
||||
/agent-note-ship-experimental-capabilities-as-optional-bundles/decision:
|
||||
en: e6ffc783859d7509
|
||||
zh: 8678f323985a24f4
|
||||
en: 6c8583bdefc5e36c
|
||||
zh: 0928b4f2876bbfe4
|
||||
/agent-note-ship-experimental-capabilities-as-optional-bundles/alternatives-considered:
|
||||
en: 9ff0511566948d8b
|
||||
zh: 4e1e37b79f63d390
|
||||
en: d6f40bf02df37ab7
|
||||
zh: e5b85ce9d41e8754
|
||||
/agent-note-ship-experimental-capabilities-as-optional-bundles/consequences:
|
||||
en: 9d2b313f15fc9ce0
|
||||
zh: 522341c83565ac33
|
||||
en: 38d27583533cd028
|
||||
zh: 9bf11f5489c1aa43
|
||||
+4
-2
@@ -14,7 +14,7 @@ The Web plugin page offered two optional bundles, Agent Teams and voice input. A
|
||||
|
||||
An optional bundle's dependencies are downloaded with every `dsh` installation. [Reasoning translation](2026-10-05-anonymous-reasoning-translation.md) uses three experimental packages without adding third-party runtime dependencies. Admission therefore considers installation cost and whether a switch supplies a usable composition without an additional configuration form. Browser and computer providers remain explicit compositions because they add substantial runtimes or require an external executable. Stagehand requires model credentials; Python PTC replaces a core provider and conflicts with TypeScript workflow consumers. Demonstrative hooks, webhooks, and mods stay outside the list. [Session Inspector](../feature/2026-09-24-session-inspector.md) owns the Inspector's distinct activation behavior.
|
||||
|
||||
Bundles add scoped rows with the [profile patch operation](../../../../packages/boot/app-boot/README.md): `preset` names a declaration row, and the ordinary patch operates on its child list. Full Web presets receive the optional model tools; minimal retains its current shell and working-directory tools. Search, Ralph, and terminals supply their own isolated services. Search opens one in-memory index on demand per retained preset revision; the Host search policy stays unchanged. Badge skills and title providers act on the Host. Existing Agents retain their preset generation, and later user patch layers retain precedence. Title replacement waits for disposal of the previous provider.
|
||||
Bundles insert ordinary Host rows; optional model tools register in the global tool layer, so every preset, including minimal, receives them. Plugin management, profile composition, and reload need no bundle-specific behavior. Search, Ralph, and terminals supply their own isolated services in a Host group. Search opens one in-memory index on demand; the Host search policy stays unchanged. Badge skills and title providers act on the Host. Switching a bundle registers or removes its tools for live Agents, and later user patch layers retain precedence. A replacement title provider may register once the previous provider's disposal starts; the previous provider's late results never commit.
|
||||
|
||||
Claude Code and Codex install on demand through the separate `ON_DEMAND_BUNDLES` catalog; the [on-demand native-bundle decision](2026-10-05-official-on-demand-bundles.md) owns the cost, version, and offline-discovery trade-offs.
|
||||
|
||||
@@ -26,6 +26,8 @@ Claude Code and Codex install on demand through the separate `ON_DEMAND_BUNDLES`
|
||||
|
||||
**Register preset extensions at runtime.** A separate registration path would need its own ordering, module resolution, generation updates, and inspection behavior. Profile patches keep one effective composition for boot, dumps, validation, and user overrides.
|
||||
|
||||
**Insert rows into named presets with a `preset` patch operation.** Limiting tools to full presets required a second patch language across profile composition, reload, plugin management, and the plugin page, plus retained-generation removal checks. Global tools reach the same Agents through the existing Host composition.
|
||||
|
||||
## Consequences
|
||||
|
||||
Official discovery does not imply experimental status or a different plugin lifecycle. Switching a bundle off keeps its installed files; failed activation retains the existing saved-selection behavior. Scoped rows appear read-only under their bundle, and removal respects modules retained by live preset generations. Individual and combined composition tests cover the supported presets; live switching and recorded Sessions cover activation and model-visible behavior.
|
||||
Official discovery does not imply experimental status or a different plugin lifecycle. Switching a bundle off keeps its installed files; failed activation retains the existing saved-selection behavior. Bundle rows are ordinary Host rows with their usual switches. Individual and combined composition tests cover the Host rows and every preset's tool catalog; live switching and recorded Sessions cover activation and model-visible behavior.
|
||||
+4
-2
@@ -14,7 +14,7 @@ Web 插件页只提供两个可选 bundle:Agent Teams 与语音输入。Auto r
|
||||
|
||||
可选 bundle 的依赖随每次 `dsh` 安装下载。[思考过程翻译](2026-10-05-anonymous-reasoning-translation.zh.md)使用三个实验包,不增加第三方运行时依赖。因此准入需要考虑安装成本,以及开关能否在不增加配置表单的情况下提供可用组合。浏览器与电脑提供方仍使用显式组合,因为它们会增加较大的运行时或要求外部可执行文件。Stagehand 需要模型凭据;Python PTC 替换核心提供方,并与 TypeScript workflow 消费方冲突。演示性钩子、webhook 与模组不进入名单。[Session Inspector](../feature/2026-09-24-session-inspector.zh.md) 管理 Inspector 特有的激活行为。
|
||||
|
||||
Bundle 通过 [profile patch 操作](../../../../packages/boot/app-boot/README.zh.md)添加作用域行:`preset` 指定声明行,普通 patch 对其子列表生效。完整 Web 预设获得可选模型工具;minimal 保持当前的 shell 与工作目录工具。搜索、Ralph 与终端提供自己的隔离服务。搜索在每个保留的预设修订中按需打开一个内存索引;Host 搜索策略保持不变。徽章技能与标题提供方作用于 Host。已有 Agent 保留其预设代际,后续用户 patch 层继续优先。标题替换等待前一个提供方完成清理。
|
||||
Bundle 插入普通 Host 行;可选模型工具注册到全局工具层,因此包括 minimal 在内的所有预设都会获得它们。插件管理、profile 组合与重载无需针对 bundle 的特殊行为。搜索、Ralph 与终端在 Host 组内提供自己的隔离服务。搜索按需打开一个内存索引;Host 搜索策略保持不变。徽章技能与标题提供方作用于 Host。切换 bundle 会为运行中的 Agent 注册或移除其工具,后续用户 patch 层继续优先。前一个标题提供方开始清理后,替代提供方即可注册;前一个提供方的迟到结果不会提交。
|
||||
|
||||
Claude Code 与 Codex 通过独立的 `ON_DEMAND_BUNDLES` 目录按需安装,其成本、版本与离线发现取舍由[按需原生 bundle 决策](2026-10-05-official-on-demand-bundles.zh.md)管理。
|
||||
|
||||
@@ -26,6 +26,8 @@ Claude Code 与 Codex 通过独立的 `ON_DEMAND_BUNDLES` 目录按需安装,
|
||||
|
||||
**在运行时注册预设扩展。** 独立注册路径需要自己的排序、模块解析、代际更新与检查行为。Profile patch 为启动、配置导出、校验和用户覆盖保留同一个有效组合。
|
||||
|
||||
**用 `preset` patch 操作向指定预设插入行。** 只把工具限定在完整预设中,需要在 profile 组合、重载、插件管理和插件页中引入第二套 patch 语言,并为保留代际增加删除检查。全局工具通过现有 Host 组合即可到达同样的 Agent。
|
||||
|
||||
## 后果
|
||||
|
||||
官方发现不表示实验性状态,也不引入不同的插件生命周期。关闭 bundle 保留已安装文件;激活失败保留现有的已保存选择行为。作用域行在 bundle 内只读展示,删除操作尊重 live 预设代际仍持有的模块。独立与组合测试覆盖受支持的预设;实时切换与已记录 Session 覆盖激活和模型可见行为。
|
||||
官方发现不表示实验性状态,也不引入不同的插件生命周期。关闭 bundle 保留已安装文件;激活失败保留现有的已保存选择行为。Bundle 行是普通 Host 行,使用常规开关。独立与组合测试覆盖 Host 行及每个预设的工具目录;实时切换与已记录 Session 覆盖激活和模型可见行为。
|
||||
@@ -0,0 +1,22 @@
|
||||
# Bilingual-pair consistency record for 2026-09-23-native-https-web-listener.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-09-23-native-https-web-listener.md
|
||||
/agent-note-native-https-on-the-web-listener:
|
||||
en: bdb6e7794af113f0
|
||||
zh: b9f325b0cbe8d34d
|
||||
/agent-note-native-https-on-the-web-listener/problem:
|
||||
en: 685a00e6f84f642d
|
||||
zh: 404e463f56ec5a1f
|
||||
/agent-note-native-https-on-the-web-listener/decision:
|
||||
en: 7f9ea8a4f2577d2f
|
||||
zh: e9b2a1fc6944428e
|
||||
/agent-note-native-https-on-the-web-listener/alternatives-considered:
|
||||
en: 44c051936dd2529e
|
||||
zh: 7588669ec4c302ef
|
||||
/agent-note-native-https-on-the-web-listener/consequences:
|
||||
en: 6a8be07b24f957ba
|
||||
zh: fbf48476e092dfc8
|
||||
/agent-note-native-https-on-the-web-listener/testing:
|
||||
en: f92f7ec224dba94d
|
||||
zh: 0f0e0304c834f0af
|
||||
@@ -0,0 +1,41 @@
|
||||
# Agent Note: Native HTTPS on the Web listener
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-09-23-native-https-web-listener.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
A directly reached Web listener needs to encrypt launch tokens and session cookies without requiring a separate reverse proxy. Advertising an HTTPS URL does not encrypt the listener or establish certificate trust.
|
||||
|
||||
## Decision
|
||||
|
||||
WebServer owns optional TLS through `tls: { certFile, keyFile }`, exposed by `dsh web` as paired `--tls-cert` and `--tls-key` flags. Certificate and unencrypted private-key PEM files are loaded before binding; unreadable, empty, invalid, or mismatched material fails activation without an HTTP fallback. HTTP remains the default. Both transports share routing, upgrades, and disposal; disposal also owns incomplete TLS handshakes.
|
||||
|
||||
`webServer.protocol` is the source for the bind-address URL, the non-loopback plain-HTTP warning, and Connection's `Secure` cookie attribute. Neither forwarded headers nor `publicUrl` control encryption or that attribute. Explicit Host-port grants normalize under the listener's scheme, while Origin comparison uses the Origin's HTTP(S) scheme so HTTPS proxies can retain HTTP upstreams. TLS grants no additional Host authority and does not replace authentication.
|
||||
|
||||
The API authority check does not equate a proxy's upstream transport with the browser page's scheme. An HTTPS listener can accept a matching HTTP Origin, but `sec-fetch-site: cross-site` still rejects the request and authentication remains required. Schemeful `SameSite=Strict` cookies prevent cross-scheme browser credential delivery; this is not support for serving a public browser frontend over HTTP with a TLS upstream.
|
||||
|
||||
HTTP cookie audiences retain their existing spelling so enabling TLS support does not invalidate unrelated HTTP sessions. HTTPS cookie names and signed audiences include the `https:` scheme, preventing replay of an HTTP credential on any HTTPS port even if the cookie is renamed. Default ports follow URL normalization without a separate port-443 spelling.
|
||||
|
||||
Certificate issuance, trust, renewal, and replacement belong to the operator. Files are read once per activation; replacement requires a listener reload or process restart. The certificate must cover the hostname or IP the TLS client dials. The default port remains 3080; `--port` selects another port, subject to operating-system permission.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Require a TLS-terminating proxy.** This remains supported, but requires another service for a directly reached listener.
|
||||
|
||||
**Derive transport or cookie security from `publicUrl` or forwarding headers.** An advertisement does not describe the receiving socket, and request headers cannot establish trusted proxy identity. Native listener state keeps this decision independent of proxy policy.
|
||||
|
||||
**Issue, renew, or generate certificates.** This would add certificate lifecycle and trust-distribution responsibilities. An automatically generated certificate would not establish browser trust.
|
||||
|
||||
**Fall back to HTTP on certificate errors.** This would expose credentials despite an explicit request for encryption; activation must fail instead.
|
||||
|
||||
## Consequences
|
||||
|
||||
Direct HTTPS needs no TLS-terminating proxy. An HTTPS proxy in front of an HTTP listener still owns external cookie rewriting. An HTTPS listener emits `Secure` cookies, so public browser-facing deployments must retain HTTPS rather than depend on local-browser exceptions for HTTP. Deployment procedures belong to the [public deployments guide](../../../../docs/user/guide/public-deployments.md).
|
||||
|
||||
This partially supersedes the HTTP-only assumptions in [browser launch-token authentication](2026-08-24-browser-token-authentication.md) and [concrete bind addresses](2026-09-19-concrete-web-bind-address.md). Those notes remain active for cookie payloads and lifetimes, and bind admission respectively.
|
||||
|
||||
## Testing
|
||||
|
||||
The WebServer suite covers verified HTTPS, invalid material, and disposal. Connection covers listener-owned cookie attributes, cross-protocol credential replay, and exact-port grants; Web startup rejects unpaired flags. The built CLI's `public-url.expected.e2e.ts` authenticates over verified TLS with a `Secure` cookie while retaining HTTP and proxy cases; its failure matrix verifies unreadable TLS material exits without opening a listener. Desktop's startup suite covers matching WS/WSS credentials and refusal on a different transport.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Agent Note: Web 监听器原生 HTTPS
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-09-23-native-https-web-listener.md) | 中文
|
||||
|
||||
## Problem
|
||||
|
||||
浏览器直接访问的 Web 监听器需要在不依赖独立反向代理的情况下加密启动 token 与会话 cookie。公告 HTTPS URL 不会加密监听器,也不会建立证书信任。
|
||||
|
||||
## Decision
|
||||
|
||||
WebServer 通过 `tls: { certFile, keyFile }` 拥有可选 TLS,由 `dsh web` 的成对 `--tls-cert` 与 `--tls-key` flag 暴露。证书与不带口令的私钥 PEM 文件在绑定前加载;材料不可读、为空、无效或不匹配时激活失败,绝不回退到 HTTP。HTTP 仍为默认值。两种传输共用路由、升级与释放;释放也负责未完成的 TLS 握手。
|
||||
|
||||
`webServer.protocol` 决定绑定地址 URL、非 loopback 明文 HTTP 警告及 Connection 的 `Secure` cookie 属性。转发 header 与 `publicUrl` 都不控制加密或该属性。显式 Host 端口授权按监听器的 scheme 归一化,Origin 比较则按 Origin 的 HTTP(S) scheme 进行,以允许 HTTPS 代理保留 HTTP 上游。TLS 不授予额外的 Host authority,也不替代认证。
|
||||
|
||||
API authority 校验不会把代理的上游传输等同于浏览器页面的 scheme。HTTPS 监听器可接受 authority 匹配的 HTTP Origin,但 `sec-fetch-site: cross-site` 仍会拒绝请求,且请求仍须认证。区分 scheme 的 `SameSite=Strict` cookie 阻止浏览器跨 scheme 携带凭据;这不表示支持以 HTTP 向公开浏览器前端提供页面、同时使用 TLS 上游。
|
||||
|
||||
HTTP cookie audience 保留既有写法,避免启用 TLS 支持时使无关的 HTTP 会话失效。HTTPS cookie 名称与签名 audience 包含 `https:` scheme,因此即使修改 cookie 名称,HTTP 凭据也不能在任何 HTTPS 端口上重放。默认端口遵循 URL 归一化,不为 443 端口另设写法。
|
||||
|
||||
证书签发、信任、续期与更换归操作者负责。每次激活只读取一次文件;更换后需重新加载监听器或重启进程。证书必须覆盖 TLS 客户端拨号的 hostname 或 IP。默认端口仍为 3080;`--port` 可选择其他端口,但受操作系统权限约束。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**要求终止 TLS 的代理。** 仍然支持,但会为直接访问的监听器增加一个服务。
|
||||
|
||||
**根据 `publicUrl` 或转发 header 推导传输或 cookie 安全属性。** 公告不描述接收请求的 socket,请求 header 也不能建立可信代理身份。原生监听器状态使这一决策独立于代理策略。
|
||||
|
||||
**签发、续期或生成证书。** 这会增加证书生命周期与信任分发职责。自动生成证书本身不能建立浏览器信任。
|
||||
|
||||
**证书错误时回退到 HTTP。** 这会在操作者明确要求加密时暴露凭据;激活必须失败。
|
||||
|
||||
## Consequences
|
||||
|
||||
直接 HTTPS 不需要终止 TLS 的代理。HTTP 监听器前的 HTTPS 代理仍负责外部 cookie 重写。HTTPS 监听器签发 `Secure` cookie,因此公开部署的浏览器侧必须保持 HTTPS,而不应依赖本地浏览器对 HTTP 的例外。部署步骤归[公开部署指南](../../../../docs/user/guide/public-deployments.zh.md)所有。
|
||||
|
||||
本决策部分取代[浏览器启动 token 认证](2026-08-24-browser-token-authentication.zh.md)与[具体绑定地址](2026-09-19-concrete-web-bind-address.zh.md)中仅支持 HTTP 的前提。这些说明分别继续定义 cookie 载荷与有效期及绑定准入,保持活跃。
|
||||
|
||||
## Testing
|
||||
|
||||
WebServer 套件覆盖校验证书的 HTTPS、无效材料与释放。Connection 覆盖由监听器决定的 cookie 属性、跨协议凭据重放和精确端口授权;Web 启动拒绝未配对的 flag。已构建 CLI 的 `public-url.expected.e2e.ts` 使用 `Secure` cookie 经校验证书的 TLS 完成认证,并保留 HTTP 与代理用例;失败矩阵验证 TLS 材料不可读时进程退出且不打开监听器。Desktop 启动套件覆盖匹配的 WS/WSS 凭据及不同传输下的拒绝。
|
||||
+2
-2
@@ -15,5 +15,5 @@
|
||||
en: 88985b4e1f5c7a4f
|
||||
zh: 32617f7ff75ee825
|
||||
/agent-note-bounded-deepseek-session-log-upload/consequences:
|
||||
en: e19fb6136ffd2250
|
||||
zh: 01dacd7a44c3f837
|
||||
en: 288834c514df377a
|
||||
zh: a33a4af2b4d9ebf4
|
||||
@@ -25,6 +25,6 @@ This supersedes complete-suffix delivery and fail-closed serialization in the [r
|
||||
|
||||
## Consequences
|
||||
|
||||
A backlog drains at up to 8 MiB per accepted request by default. At the end-to-end rate measured for the 269 MiB upload, about 8 MiB/s, an 8 MiB field adds about 1 s to each request while a backlog drains, and a 0.86 GB backlog drains in about 100 requests. In the same 96,341-event Session, the median per-step append is 11 KiB and the largest event is 414 KiB, so ordinary steps stay far below the limit and only an unusual event or a small configured limit reaches the oversized-event path. While a backlog drains, the endpoint receives a `throughSeq` behind the Session's current tip. Preparation failures, acceptance failures, and provider rejections still fail the model request.
|
||||
A backlog drains at up to 8 MiB per accepted request by default. At the end-to-end rate measured for the 269 MiB upload, about 8 MiB/s, an 8 MiB field adds about 1 s to each request while a backlog drains, and a 0.86 GB backlog drains in about 100 requests. In the same 96,341-event Session, the median per-step append is 11 KiB and the largest event is 414 KiB, so ordinary steps stay far below the limit and only an unusual event or a small configured limit reaches the oversized-event path. While a backlog drains, the endpoint receives a `throughSeq` behind the Session's current tip. Provider rejections still fail the model request; preparation and acceptance failures are logged and do not.
|
||||
|
||||
Plugin tests cover the exact byte boundary, UTF-8 counting, backlog draining across accepted requests, the oversized-event warning with its unchanged watermark, an event past the string limit, and the 8 MiB default through plugin configuration. Adapter tests cover the base-body fallback through the provider plugin's logger and through an adapter without a reporter.
|
||||
@@ -25,6 +25,6 @@ DeepSeek 适配器把合并后请求正文的序列化失败视为扩展失败
|
||||
|
||||
## 后果
|
||||
|
||||
默认情况下,积压按每次已接受请求最多 8 MiB 补传。按那次 269 MiB 上传实测的端到端速度(约 8 MiB/s),补传期间 8 MiB 的字段让每次请求多约 1 秒,0.86 GB 的积压约 100 次请求补完。在同一个 96,341 条事件的会话中,每步追加量的中位数是 11 KiB,最大单条事件是 414 KiB,因此普通步骤远低于上限,只有异常事件或很小的配置上限才会走到超限事件路径。积压补传期间,端点收到的 `throughSeq` 会落后于会话当前末尾。准备失败、接受失败与提供方拒绝仍会使模型请求失败。
|
||||
默认情况下,积压按每次已接受请求最多 8 MiB 补传。按那次 269 MiB 上传实测的端到端速度(约 8 MiB/s),补传期间 8 MiB 的字段让每次请求多约 1 秒,0.86 GB 的积压约 100 次请求补完。在同一个 96,341 条事件的会话中,每步追加量的中位数是 11 KiB,最大单条事件是 414 KiB,因此普通步骤远低于上限,只有异常事件或很小的配置上限才会走到超限事件路径。积压补传期间,端点收到的 `throughSeq` 会落后于会话当前末尾。提供方拒绝仍会使模型请求失败;准备失败与接受失败只记录告警,不会使请求失败。
|
||||
|
||||
插件测试覆盖精确字节边界、UTF-8 计数、跨已接受请求的积压补传、超限事件告警及其不变的水位、超过字符串上限的事件,以及经插件配置生效的 8 MiB 默认值。适配器测试覆盖经提供方插件 logger 报告的基础正文回退,以及没有 reporter 的适配器上的同一回退。
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-09-29-explicit-session-history-preparation.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-09-29-explicit-session-history-preparation.md
|
||||
/agent-note-open-the-session-when-migration-is-required:
|
||||
en: d409772933407317
|
||||
zh: ac258a6d1743965f
|
||||
/agent-note-open-the-session-when-migration-is-required/problem:
|
||||
en: ab63c8127d68d294
|
||||
zh: d3f99489c96ff1d4
|
||||
/agent-note-open-the-session-when-migration-is-required/decision:
|
||||
en: 32627d744a6e7410
|
||||
zh: 2836a95ee5390111
|
||||
/agent-note-open-the-session-when-migration-is-required/alternatives-considered:
|
||||
en: baea781018a49252
|
||||
zh: bcc452029bf229fd
|
||||
/agent-note-open-the-session-when-migration-is-required/consequences:
|
||||
en: 979eebb53a6bf711
|
||||
zh: 013743b0b7a000ce
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
# Agent Note: Open the Session when migration is required
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-09-29-explicit-session-history-preparation.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Expanding a subagent catalog or forking from the sidebar can trigger an old Session's format migration.
|
||||
|
||||
## Decision
|
||||
|
||||
The WorkspaceBrowser row-menu fork request carries `allowMigration: false`, which the Host handles by checking `stat` format information before calling the original fork flow. Only a source format requiring migration is refused, with an app-wide Toast offering to open the original Session. That action only navigates and does not retry fork. Neither an absent live Session nor unopened Client history justifies refusal.
|
||||
|
||||
Other fork callers omit that option and allow automatic migration.
|
||||
|
||||
`session.projections` also checks the stored format first; current-format cold history still uses the original observation flow for exact projections. Success returns `kind: sequenced` and its real `asOfSeq`; required migration returns `kind: migration-required` hints. The Client retains that read state, and cached values cannot replace sequenced values published by history or control.
|
||||
|
||||
The `formatStatus` returned by `stat` carries the migration library's existing classification; `header` remains the current logical format. It describes the stored format, not migration-task progress or prepared in-memory history.
|
||||
|
||||
When migration is required and `subagentCatalog` is absent, Web marks the Session's row “Migration required” and keeps its history-opening actions without descendant expansion or changing its subagent mode. Opening the Session supplies its catalog through history and control projections. Available catalogs remain usable, including empty catalogs. Session-list lineage resolves navigation addresses, not missing catalog membership. Ordinary loading, cancellation, and storage errors do not become this migration branch.
|
||||
|
||||
Create/adopt, full-text search, and ordinary history opening allow automatic migration.
|
||||
|
||||
This decision adds a migration exception to [catalog projection reads](../../archived/simplification/2026-09-08-web-subagent-catalog-projections.md). Exact reads and leases in [Session observations](2026-08-25-session-observations-and-projection-owned-client-state.md), and identity checks and cached/sequenced ordering in [projection-cache reads](2026-09-19-projection-cache-listing-identity-and-cached-rows.md), still apply.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Treat every cold Session as needing migration.** Residency and stored format are different; this would reject otherwise valid forks and discard catalog information available on disk.
|
||||
|
||||
**Globally forbid forks that automatically migrate on the Host.** The sidebar's interaction choice does not govern other callers, so its request explicitly carries the restriction.
|
||||
|
||||
**Automatically open and retry the sidebar fork.** Opening is an explicit user action, and fork does not run automatically after migration.
|
||||
|
||||
**Derive catalogs from Session-list parent ids.** This adds a second membership derivation; explicit Session opening obtains the catalog.
|
||||
|
||||
**Give cached hints `asOfSeq: -1`.** Hints have no event cut in current history and cannot participate in sequence comparisons.
|
||||
|
||||
## Consequences
|
||||
|
||||
Sessions requiring migration use the existing opening flow to prepare history; Sessions that need no migration remain cold-readable and forkable. Catalog hints may lag the log and yield to subsequent exact projections.
|
||||
|
||||
A separate follow-redirection proposal discusses future single-Session migration progress; this decision does not implement that flow.
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
# Agent Note: 需要迁移时引导打开会话
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-09-29-explicit-session-history-preparation.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
展开 subagent 目录或从侧栏 fork 可能触发旧会话的格式迁移。
|
||||
|
||||
## 决定
|
||||
|
||||
WorkspaceBrowser 行菜单的 fork 请求携带 `allowMigration: false`,由 Host 在调用原 fork 流程前检查 `stat` 的格式信息。只有源格式需要迁移时才拒绝,并显示带“打开原会话”操作的应用级 Toast。该操作只导航,不自动重发 fork。没有 live Session 或 Client 尚未打开历史不构成拒绝理由。
|
||||
|
||||
其他 fork 调用者省略该选项,允许自动迁移。
|
||||
|
||||
`session.projections` 同样先检查存储格式,当前格式冷历史继续通过原观察流程读取准确投影。成功时返回 `kind: sequenced` 和真实 `asOfSeq`;需要迁移时返回 `kind: migration-required` 的缓存提示。Client 保留这一读取状态,缓存值不能覆盖 history 或 control 已发布的有序值。
|
||||
|
||||
`stat` 返回的 `formatStatus` 携带迁移库已有的分类;`header` 仍是当前逻辑格式。它描述存储格式,不描述迁移任务进度或内存准备缓存。
|
||||
|
||||
在需要迁移且缺少 `subagentCatalog` 时,Web 将该会话行标为“需要迁移”,保留打开历史的操作,但不能展开下级,也不改变子会话的 mode。打开会话后,历史与 control 投影提供其目录。已有目录仍可使用,包括空目录。会话列表的谱系用于解析导航地址,不用于补齐缺失的目录成员。普通加载、取消和存储错误不转换成这一迁移分支。
|
||||
|
||||
create/adopt、全文搜索和普通历史打开允许自动迁移。
|
||||
|
||||
本决策为[目录投影查询](../../archived/simplification/2026-09-08-web-subagent-catalog-projections.md)增加迁移例外。[会话观察](2026-08-25-session-observations-and-projection-owned-client-state.zh.md)的准确读取和租约,以及[投影缓存读取](2026-09-19-projection-cache-listing-identity-and-cached-rows.zh.md)的身份校验与缓存/有序分层继续适用。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**把所有冷会话都视作需要迁移。** 是否驻留与存储格式不同;这样会拒绝可以正常完成的 fork,并丢失能够从磁盘读取的目录信息。
|
||||
|
||||
**Host 全局禁止自动迁移的 fork。** 侧栏的交互选择不适用于其他调用者,因此限制由该请求显式携带。
|
||||
|
||||
**侧栏 fork 自动打开并重试。** 打开会话由用户明确选择,fork 不在迁移后自动执行。
|
||||
|
||||
**按会话列表中的父 id 派生目录。** 这需要第二套成员派生逻辑;显式打开会话即可获取目录。
|
||||
|
||||
**让缓存提示携带 `asOfSeq: -1`。** 提示没有当前历史的事件切点,不能参与序号比较。
|
||||
|
||||
## 影响
|
||||
|
||||
需要迁移的会话通过现有打开流程完成准备;无需迁移的会话仍可冷读和 fork。目录的缓存提示可能落后于日志,后续准确投影替换它们。
|
||||
|
||||
后续单会话迁移进度由独立的 follow 重定向提案讨论,本决策不实现该流程。
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
en: 5e10347fa87f98b5
|
||||
zh: cf3569631af8d66f
|
||||
/agent-note-anonymous-translation-remains-separate-from-model-history/decision:
|
||||
en: f13c134f53ed63c9
|
||||
zh: 868aacd453d26c82
|
||||
en: 3c4745cc96d89ffd
|
||||
zh: 939d5cd494346311
|
||||
/agent-note-anonymous-translation-remains-separate-from-model-history/alternatives-considered:
|
||||
en: 083d50f8564b0378
|
||||
zh: 50af332e64ad06f3
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ The experimental translator owns explicit Google or Bing routing, provider-langu
|
||||
|
||||
Bing is the configured default. Enabling the bundle permits transmission of expanded reasoning to the chosen service, disclosed in plugin metadata and settings. Failure never selects another provider. Original remains available; translated text neither replaces persisted assistant output nor enters model input. The [optional-bundle admission policy](2026-09-21-experimental-capabilities-as-optional-bundles.md) keeps the feature disabled until selected.
|
||||
|
||||
The reasoning-body chain carries original text and streaming status. Native Chat owns its original Markdown fallback; consumers do not replace the assistant renderer or alter Session projection. Only expanded bodies start translation. Collapsing, changing selection, or unloading aborts outstanding work and excludes late results.
|
||||
The GUI registers a translation wrapper in the standard `conversation.chat.reasoning.body` Slot and calls the official `conversation.chat.reasoning.content` Factory for original or translated text. The default body is also an ordinary registration; the host supplies no inline Markdown fallback or plugin action state. The translation wrapper owns its toolbar and passes its localized labels to the Factory, which supplies default labels when callers omit them and fixes compact typography. The [reasoning Body Slot and Content Factory design](2026-10-09-reasoning-content-slot-factory.md) supersedes this record's body composition mechanism; external requests remain separate from model history. Consumers do not replace the assistant renderer or alter Session projection. Only expanded bodies start translation. Collapsing, changing selection, or unloading aborts outstanding work and excludes late results.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ Status: implemented
|
||||
|
||||
配置的默认服务为 Bing。启用 Bundle 允许将展开的思考内容发送给所选服务,插件元数据与设置公开说明接收方。失败时不选择其他 Provider。原文始终可访问;译文不替换持久化 assistant 输出,也不进入模型输入。[可选 Bundle 准入规则](2026-09-21-experimental-capabilities-as-optional-bundles.zh.md) 保证功能在显式选择前保持关闭。
|
||||
|
||||
reasoning-body chain 传递原始文本与流式状态。原生 Chat 负责原始 Markdown fallback;消费者不替换 assistant 渲染器,也不改变 Session projection。只有展开内容才开始翻译。折叠、切换设置或卸载都会中止未完成工作并忽略迟到结果。
|
||||
GUI 向标准 `conversation.chat.reasoning.body` Slot 注册翻译包装,并调用官方 `conversation.chat.reasoning.content` Factory 展示原文或译文。默认正文同样是普通注册项;宿主不提供内联 Markdown fallback 或插件操作状态。翻译包装自行维护工具栏,并将自己的本地化 labels 传入 Factory;Factory 在调用者省略 labels 时提供默认文案,并固定紧凑排版。[思考正文的 Slot 与 Factory 设计](2026-10-09-reasoning-content-slot-factory.zh.md)取代本记录的正文组合方式,外部请求与模型历史隔离的决策保持不变。消费者不替换 assistant 渲染器,也不改变 Session projection。只有展开内容才开始翻译。折叠、切换设置或卸载都会中止未完成工作并忽略迟到结果。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
|
||||
+4
-4
@@ -9,11 +9,11 @@
|
||||
en: a590bcef6c9760c0
|
||||
zh: 19e6f61c2c7d8d3b
|
||||
/agent-note-discover-official-bundles-without-installing-their-runtimes/decision:
|
||||
en: 31e3c52841f31a11
|
||||
zh: ac2ad6e54ef9e150
|
||||
en: f0fabc0d4fbd1178
|
||||
zh: cf19938a7a7dea93
|
||||
/agent-note-discover-official-bundles-without-installing-their-runtimes/alternatives-considered:
|
||||
en: 42cb0f808a145871
|
||||
zh: 225012615d1ecdf5
|
||||
/agent-note-discover-official-bundles-without-installing-their-runtimes/consequences:
|
||||
en: 899b07d90024e019
|
||||
zh: 01d22ac4b2444feb
|
||||
en: 293286dcd7e3325e
|
||||
zh: c6c15212e85d050b
|
||||
@@ -12,13 +12,13 @@ Native subagent integrations bring their own runtimes. Shipping those dependenci
|
||||
|
||||
`ON_DEMAND_BUNDLES` admits the Claude Code and Codex bundles to an installation-owned catalog. The catalog embeds their package-owned localized metadata and icons, so discovery works offline without resolving or importing either package. Official identifies project maintenance; optional delivery and experimental maturity remain independent properties.
|
||||
|
||||
Selection uses the ordinary third-party bundle installer, profile dependency, scoped patches, compatibility evaluation, progress, cancellation, registry fallback, and build-script approval. Native authentication and configuration remain provider-owned.
|
||||
Selection uses the ordinary third-party bundle installer, profile dependency, Host row patches, compatibility evaluation, progress, cancellation, registry fallback, and build-script approval. Native authentication and configuration remain provider-owned.
|
||||
|
||||
The running installation owns the target. Published applications offer the exact DSH release version and save an exact registry dependency pin. Source Web and development Desktop offer ordinary `link:` dependencies into the same checkout as the running CLI, using its prepared workspace dependencies and peers. The source target follows the actual installation, never the current working directory or a prerelease suffix; an incomplete checkout does not fall back to the registry. A development checkout can share a version string with different published code, so replacing an installed registry copy or another checkout’s link compares the source as well as the version. Replacement is explicit, preserves selection, and reports required restarts; startup does not download providers. [Plugin Manager](../../../../packages/boot/plugin-manager/README.md#use-this-package) owns the operational details.
|
||||
|
||||
Every Official package remains in the DSH release family. Packed DSH dependencies, optional dependencies, and peers use the exact family version. Publication prerequisites place catalog packages before the DSH package advertising them without adding their runtimes to DSH production dependencies. Catalog verification rejects missing metadata, unsupported membership, and stale generated content.
|
||||
|
||||
The [shipped optional-bundle policy](../process/2026-09-15-shipped-optional-bundles.md) still owns lightweight installation-provided bundles. The [optional-composition decision](2026-09-21-experimental-capabilities-as-optional-bundles.md) owns shared patch and preset-generation behavior. Native bundles require the full Web preset targets; custom profiles mount their providers and tools explicitly. The [identity compatibility decision](2026-10-06-web-desktop-plugin-identity-compatibility.md) owns retained names, selectors, and deferred naming cleanup.
|
||||
The [shipped optional-bundle policy](../process/2026-09-15-shipped-optional-bundles.md) still owns lightweight installation-provided bundles. The [optional-composition decision](2026-09-21-experimental-capabilities-as-optional-bundles.md) owns shared patch behavior and global optional tools. Native bundles insert ordinary Host rows, so any profile can select them; profiles that keep the package without its bundle mount the same rows explicitly. The [identity compatibility decision](2026-10-06-web-desktop-plugin-identity-compatibility.md) owns retained names, selectors, and deferred naming cleanup.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -34,4 +34,4 @@ The [shipped optional-bundle policy](../process/2026-09-15-shipped-optional-bund
|
||||
|
||||
## Consequences
|
||||
|
||||
Registry delivery requires a reachable registry; development delivery requires a prepared, built checkout and normal rebuild/restart discipline. Equal source version strings do not certify identical build outputs. Off only deselects; Remove remains separate and respects retained preset modules. Activation failure can retain the saved dependency and selection; there is no rollback. Offline metadata, release pins, source-target selection, same-version source mismatches, clean installed artifacts, and release ordering have dedicated checks. Native-provider authentication is not part of catalog discovery.
|
||||
Registry delivery requires a reachable registry; development delivery requires a prepared, built checkout and normal rebuild/restart discipline. Equal source version strings do not certify identical build outputs. Off only deselects; Remove remains separate and refuses while the bundle's rows are in use. Activation failure can retain the saved dependency and selection; there is no rollback. Offline metadata, release pins, source-target selection, same-version source mismatches, clean installed artifacts, and release ordering have dedicated checks. Native-provider authentication is not part of catalog discovery.
|
||||
@@ -12,13 +12,13 @@ Status: implemented
|
||||
|
||||
`ON_DEMAND_BUNDLES` 将 Claude Code 和 Codex bundle 纳入安装方维护的目录。目录内嵌包自身拥有的本地化元信息和图标,因此发现过程离线可用,无需解析或导入这两个包。Official 表示项目维护;可选交付与实验成熟度仍是独立属性。
|
||||
|
||||
选择操作复用普通第三方 bundle 的安装器、profile 依赖、作用域 patch、兼容性判断、进度、取消、注册表回退和构建脚本审批。原生认证与配置仍由提供方拥有。
|
||||
选择操作复用普通第三方 bundle 的安装器、profile 依赖、Host 行 patch、兼容性判断、进度、取消、注册表回退和构建脚本审批。原生认证与配置仍由提供方拥有。
|
||||
|
||||
当前运行的安装决定目标。已发布应用提供精确的 DSH 发布版本,并保存精确的注册表依赖版本。源码 Web 和开发 Desktop 提供指向当前 CLI 所在 checkout 的普通 `link:` 依赖,使用其中已准备好的工作区依赖和 peer。源码目标跟随实际安装位置,不由当前工作目录或预发布版本后缀决定;不完整的 checkout 不回退到注册表。开发 checkout 可以与内容不同的已发布代码共用版本字符串,因此替换已安装的注册表副本或其他 checkout 的链接时,同时比较来源和版本。替换需显式触发,保留选择状态,并报告是否需要重启;启动时不下载提供方。[插件管理器](../../../../packages/boot/plugin-manager/README.zh.md#use-this-package)拥有操作细节。
|
||||
|
||||
所有 Official 包仍属于 DSH 发布家族。打包后的 DSH 依赖、可选依赖与 peer 均使用家族精确版本。发布前置关系要求先发布目录包,再发布宣传它们的 DSH 包,同时不把这些运行时加入 DSH 生产依赖。目录校验拒绝缺失的元信息、不受支持的成员和过期生成内容。
|
||||
|
||||
[随附可选 bundle 策略](../process/2026-09-15-shipped-optional-bundles.zh.md)仍管理安装随附的轻量 bundle。[可选组合决策](2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)管理共享 patch 与预设代际行为。原生 bundle 要求完整 Web 预设目标存在;自定义 profile 显式挂载提供方和工具。[身份兼容决策](2026-10-06-web-desktop-plugin-identity-compatibility.zh.md)管理保留名称、选择器和暂缓的命名清理。
|
||||
[随附可选 bundle 策略](../process/2026-09-15-shipped-optional-bundles.zh.md)仍管理安装随附的轻量 bundle。[可选组合决策](2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)管理共享 patch 行为与全局可选工具。原生 bundle 插入普通 Host 行,因此任何 profile 都可以选择它们;保留包但不选择其 bundle 的 profile 显式挂载相同的行。[身份兼容决策](2026-10-06-web-desktop-plugin-identity-compatibility.zh.md)管理保留名称、选择器和暂缓的命名清理。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
@@ -34,4 +34,4 @@ Status: implemented
|
||||
|
||||
## 后果
|
||||
|
||||
注册表交付要求注册表可达;开发交付要求准备并构建 checkout,遵循正常的重新构建和重启流程。源码版本字符串相同不证明构建输出相同。关闭仅取消选择;移除保持独立,并尊重预设仍保留的模块。激活失败可以保留已保存的依赖和选择,不加入回滚。离线元信息、发布版本锁定、源码目标选择、同版本来源差异、干净安装产物和发布顺序有专门检查。原生提供方认证不属于目录发现过程。
|
||||
注册表交付要求注册表可达;开发交付要求准备并构建 checkout,遵循正常的重新构建和重启流程。源码版本字符串相同不证明构建输出相同。关闭仅取消选择;移除保持独立,并在 bundle 的行仍在使用时拒绝执行。激活失败可以保留已保存的依赖和选择,不加入回滚。离线元信息、发布版本锁定、源码目标选择、同版本来源差异、干净安装产物和发布顺序有专门检查。原生提供方认证不属于目录发现过程。
|
||||
+6
-6
@@ -9,14 +9,14 @@
|
||||
en: 8112c1c059d1cf10
|
||||
zh: 852d23c56a93a555
|
||||
/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/decision:
|
||||
en: ac725c281d7dfb41
|
||||
zh: 5adcaa6ca6eab384
|
||||
en: 90d74300f045bf6e
|
||||
zh: 088e88944478ca2a
|
||||
/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/deferred-naming-cleanup:
|
||||
en: 4c428f65ef611d07
|
||||
zh: 1fdc5467b0203d08
|
||||
/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/alternatives-considered:
|
||||
en: 1bf03723fea6c9a4
|
||||
zh: 2d0d2fc997f7c022
|
||||
en: 65af55b5775c9bc5
|
||||
zh: 2ea75b3fd33ac0c2
|
||||
/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/consequences:
|
||||
en: 60f7c823b5d2ed6f
|
||||
zh: 75f375487cab3c56
|
||||
en: e8e96b0354f1a3c6
|
||||
zh: ad1bfe3600e14ff1
|
||||
+4
-4
@@ -12,9 +12,9 @@ Moving a capability into the experimental directory changes its ownership and su
|
||||
|
||||
The moved packages retain the published names declared by the [experimental naming policy](../../../../scripts/experimental-package-policy.ts). That typed directory-to-name map is the sole maintained inventory of naming exceptions. Package manifests, consumers, installation checks, and bundler checks agree with it; a missing, moved, or duplicated exception is rejected. [Experimental status](../../../../packages/experimental/README.md#status) remains independent of spelling, Official discovery, and explicit selection.
|
||||
|
||||
Existing badge, Ralph, and native delegation row IDs remain valid selectors inside their applicable Host or preset scope. New isolation groups and newly introduced capabilities retain their new IDs. Later user layers still replace configuration or disable rows, and a complete preset replacement remains authoritative. Selecting an optional bundle is still explicit: a patch targeting an absent row does not select its bundle.
|
||||
Existing badge, Ralph, and native delegation row IDs remain valid Host row selectors. New isolation groups and newly introduced capabilities retain their new IDs. Later user layers still replace configuration or disable rows. Selecting an optional bundle is still explicit: a patch targeting an absent row does not select its bundle.
|
||||
|
||||
Compatibility protection covers Web and Desktop profiles, including existing native provider package identities, selections, configuration, and authentication. Headless, SDK, and ACP compositions may require changes. Native bundles use the ordinary shared patch operation and require the full preset targets; separate provider-only compatibility wrappers are unnecessary for the protected profiles. Public APIs remain pre-stable, and new output metadata stays required unless an actual consumer needs an accommodation.
|
||||
Compatibility protection covers Web and Desktop profiles, including existing native provider package identities, selections, configuration, and authentication. Native bundles insert ordinary Host rows, so Headless, SDK, and ACP compositions can select them too; separate provider-only compatibility wrappers are unnecessary. Public APIs remain pre-stable, and new output metadata stays required unless an actual consumer needs an accommodation.
|
||||
|
||||
## Deferred naming cleanup
|
||||
|
||||
@@ -31,10 +31,10 @@ These naming changes are deferred, not prerequisites for experimental classifica
|
||||
|
||||
**Publish aliases or keep duplicate compatibility rows.** Parallel identities need continuing resolution and removal rules and can register the same capability twice. Retaining the established identity avoids that additional mechanism.
|
||||
|
||||
**Keep provider-only bundles through additional wrapper packages.** Web and Desktop already provide the required presets. Extra delivery packages would preserve compositions outside the selected compatibility scope and increase catalog and release maintenance.
|
||||
**Keep provider-only bundles through additional wrapper packages.** Native bundles insert ordinary Host rows that compose into any profile. Extra delivery packages would preserve compositions outside the selected compatibility scope and increase catalog and release maintenance.
|
||||
|
||||
## Consequences
|
||||
|
||||
The repository carries a bounded naming-exception map and tests that classify retained names through direct imports, aliases, transitive dependencies, and packed or bundled artifacts. An unprefixed name does not grant admission to the default product. Existing row identities have Web/Desktop composition coverage for user overrides, disabled state, and preset replacement. No general alias or automatic profile migration mechanism is introduced.
|
||||
The repository carries a bounded naming-exception map and tests that classify retained names through direct imports, aliases, transitive dependencies, and packed or bundled artifacts. An unprefixed name does not grant admission to the default product. Existing row identities have Web/Desktop composition coverage for user overrides and disabled state. No general alias or automatic profile migration mechanism is introduced.
|
||||
|
||||
The [classification decision](../process/2026-10-05-product-package-classification.md), [optional-composition decision](2026-09-21-experimental-capabilities-as-optional-bundles.md), and [on-demand discovery decision](2026-10-05-official-on-demand-bundles.md) retain their separate responsibilities. Released Session generations and persisted hook event names remain protected independently of this profile compatibility scope.
|
||||
+4
-4
@@ -12,9 +12,9 @@ Status: implemented
|
||||
|
||||
迁移的包保留[实验包命名策略](../../../../scripts/experimental-package-policy.ts)声明的已发布名称。这个带类型的目录到包名映射是命名例外唯一手工维护的清单。包清单、消费者、安装检查和打包检查与其保持一致;缺失、迁移到其他位置或重复归属的例外会被拒绝。[实验状态](../../../../packages/experimental/README.zh.md#status)仍独立于名称拼写、Official 发现和显式选择。
|
||||
|
||||
既有徽章、Ralph 和原生委派行 ID 在对应的 Host 或预设作用域中保持有效。新增隔离组和新引入的能力保留各自的新 ID。后续用户层仍可替换配置或禁用行,完整预设替换仍具有决定权。可选 bundle 仍须显式选择:指向缺失行的 patch 不会自动选择其 bundle。
|
||||
既有徽章、Ralph 和原生委派行 ID 作为 Host 行选择器保持有效。新增隔离组和新引入的能力保留各自的新 ID。后续用户层仍可替换配置或禁用行。可选 bundle 仍须显式选择:指向缺失行的 patch 不会自动选择其 bundle。
|
||||
|
||||
兼容性保护覆盖 Web 和 Desktop profile,包括既有原生提供方的包身份、选择状态、配置和认证。Headless、SDK 和 ACP 组合可以要求修改。原生 bundle 使用普通的共享 patch 操作并要求完整预设目标存在;受保护的 profile 不需要单独的仅提供方兼容包装包。公共 API 仍处于稳定前阶段,新增输出元信息保持必填,除非实际消费者需要适配。
|
||||
兼容性保护覆盖 Web 和 Desktop profile,包括既有原生提供方的包身份、选择状态、配置和认证。原生 bundle 插入普通 Host 行,因此 Headless、SDK 和 ACP 组合也可以选择它们;不需要单独的仅提供方兼容包装包。公共 API 仍处于稳定前阶段,新增输出元信息保持必填,除非实际消费者需要适配。
|
||||
|
||||
## 暂缓的命名清理
|
||||
|
||||
@@ -31,10 +31,10 @@ Status: implemented
|
||||
|
||||
**发布别名或保留重复兼容行。** 并行身份需要持续维护解析和移除规则,还可能重复注册同一能力。保留既有身份可以避免增加这种机制。
|
||||
|
||||
**通过额外包装包保留仅提供方 bundle。** Web 和 Desktop 已提供所需预设。额外交付包会保留选定兼容范围之外的组合,并增加目录和发布维护成本。
|
||||
**通过额外包装包保留仅提供方 bundle。** 原生 bundle 插入普通 Host 行,可组合到任何 profile。额外交付包会保留选定兼容范围之外的组合,并增加目录和发布维护成本。
|
||||
|
||||
## 后果
|
||||
|
||||
仓库维护范围明确的命名例外映射,并测试通过直接导入、别名、传递依赖及打包产物识别这些保留名称。没有实验前缀的名称不因此获准进入默认产品。既有行身份具有覆盖用户配置、禁用状态和预设替换的 Web/Desktop 组合测试。不引入通用别名或自动 profile 迁移机制。
|
||||
仓库维护范围明确的命名例外映射,并测试通过直接导入、别名、传递依赖及打包产物识别这些保留名称。没有实验前缀的名称不因此获准进入默认产品。既有行身份具有覆盖用户配置和禁用状态的 Web/Desktop 组合测试。不引入通用别名或自动 profile 迁移机制。
|
||||
|
||||
[分类决策](../process/2026-10-05-product-package-classification.zh.md)、[可选组合决策](2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)和[按需发现决策](2026-10-05-official-on-demand-bundles.zh.md)继续承担各自独立的职责。已发布 Session 代际和持久化 hook 事件名的保护独立于此 profile 兼容范围。
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-10-08-desktop-shell-configuration.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-08-desktop-shell-configuration.md
|
||||
/agent-note-desktop-shell-configuration-independent-of-the-host:
|
||||
en: 860c8afa57227b89
|
||||
zh: bec6a19319050aed
|
||||
/agent-note-desktop-shell-configuration-independent-of-the-host/problem:
|
||||
en: 9f652292b62cd015
|
||||
zh: 14a97b5a4884c008
|
||||
/agent-note-desktop-shell-configuration-independent-of-the-host/decision:
|
||||
en: e58311d3d5ebb6b6
|
||||
zh: b4c791fcfde7b986
|
||||
/agent-note-desktop-shell-configuration-independent-of-the-host/alternatives-considered:
|
||||
en: 349210f3cf5f168b
|
||||
zh: 019acfc3c3d71b4b
|
||||
/agent-note-desktop-shell-configuration-independent-of-the-host/consequences:
|
||||
en: a523f43819051d4b
|
||||
zh: 69926bc3761195ea
|
||||
@@ -0,0 +1,29 @@
|
||||
# Agent Note: Desktop shell configuration independent of the Host
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-08-desktop-shell-configuration.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Desktop updates and native recovery can run before the Host starts or while it is unavailable. Depending on Host configuration prevents these functions from resolving their preferences when needed. A shared settings page does not identify which process owns each preference.
|
||||
|
||||
## Decision
|
||||
|
||||
Electron main owns `userData/desktop/settings.json` independently of Host startup and Cordis evaluation. The test authentication popup switch uses this file; the [Desktop reference](../../../../apps/desktop/README.md#local-desktop-settings) owns its fields and behavior.
|
||||
|
||||
Ownership follows runtime dependencies: Electron-owned functions needing configuration before Host startup or while the Host is unavailable use shell configuration. Host plugin settings, including shared Web/Desktop preferences, continue to use Cordis. Displaying a setting in Desktop alone does not justify shell ownership. A unified settings interface can use separate storage owners.
|
||||
|
||||
The `desktop` subdirectory groups shell configuration without relocating existing files. The [shortcut decision](2026-09-20-device-local-shortcut-preferences.md) retains ownership of `userData/keybindings.json` and its persistence semantics. The [shared Web application decision](2026-09-10-desktop-web-wrapper.md) still governs Host composition and profile configuration.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Put every preference in Cordis.** This requires a working Host. Reading `cordis.patch.yml` directly in Electron duplicates configuration evaluation; a patch alone omits plugin defaults and other configuration layers.
|
||||
|
||||
**Move every setting shown in Desktop.** Interface placement does not imply a Host-independent requirement. This splits shared Web/Desktop preferences and moves configuration away from its consuming plugins.
|
||||
|
||||
## Consequences
|
||||
|
||||
Shell and Host configuration have separate owners. Each setting needs documented ownership and effective timing. Independent shell configuration supports additional Host-independent functions without introducing a second application backend.
|
||||
|
||||
Existing settings are candidates for individual evaluation when their functionality changes or a Host-independent use is required. This is an evaluation direction, not a scheduled migration or predefined list. A migration must identify a single authoritative source, transfer existing values and define when changes take effect, without ambiguous precedence between two stores. Shared Host-managed settings remain in Cordis unless their ownership requirements change.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Agent Note: Desktop 壳层配置独立于 Host
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-08-desktop-shell-configuration.md) | 中文
|
||||
|
||||
## Problem
|
||||
|
||||
Desktop 更新和原生恢复可以在 Host 启动前或不可用时运行。依赖 Host 配置会使这些功能无法在需要时解析偏好。统一设置页面不能决定每项偏好由哪个进程负责。
|
||||
|
||||
## Decision
|
||||
|
||||
Electron 主进程负责 `userData/desktop/settings.json`,不依赖 Host 启动或 Cordis 求值。测试鉴权弹窗开关使用此文件;[Desktop 参考文档](../../../../apps/desktop/README.zh.md#local-desktop-settings)统一说明字段和行为。
|
||||
|
||||
配置归属按运行依赖划分:由 Electron 负责且需要在 Host 启动前或不可用时读取配置的功能,使用壳层配置。Host 插件设置,包括 Web/Desktop 共用偏好,继续使用 Cordis。仅仅展示在 Desktop 中,不构成归属壳层的理由。统一设置界面可以使用不同的存储归属。
|
||||
|
||||
`desktop` 子目录集中存放壳层配置,不迁移已有文件。[快捷键决策](2026-09-20-device-local-shortcut-preferences.zh.md)继续负责 `userData/keybindings.json` 及其持久化语义。[共享 Web 应用决策](2026-09-10-desktop-web-wrapper.zh.md)仍约束 Host 组合和 profile 配置。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**所有偏好都放入 Cordis。** 这需要可用的 Host。由 Electron 直接读取 `cordis.patch.yml` 会重复实现配置求值;单独的 patch 不包含插件默认值和其他配置层。
|
||||
|
||||
**迁移所有在 Desktop 展示的设置。** 界面位置不意味着需要独立于 Host。这会拆分 Web/Desktop 共用偏好,并让配置脱离实际使用它的插件。
|
||||
|
||||
## Consequences
|
||||
|
||||
壳层与 Host 配置分别有自己的归属。每项设置需说明归属和生效时机。独立壳层配置支持更多不依赖 Host 的功能,但不引入第二套应用后端。
|
||||
|
||||
现有设置在功能调整或需要独立于 Host 使用时,可逐项评估。这是评估方向,不是已排期的迁移计划或预设清单。实际迁移需明确唯一配置来源、迁移已有值并定义生效时机,避免两套存储间存在含糊的优先级。共用的 Host 设置在归属要求不变时继续使用 Cordis。
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# Bilingual-pair consistency record for 2026-10-09-reasoning-content-slot-factory.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-09-reasoning-content-slot-factory.md
|
||||
/agent-note-standard-reasoning-body-slot-and-reusable-content-factory:
|
||||
en: 55862b96f6731c81
|
||||
zh: 425ab1ff7043a606
|
||||
/agent-note-standard-reasoning-body-slot-and-reusable-content-factory/problem:
|
||||
en: 91000d9dd4a47647
|
||||
zh: 6146424ac5c3987b
|
||||
/agent-note-standard-reasoning-body-slot-and-reusable-content-factory/decision:
|
||||
en: 0a45affd1b125113
|
||||
zh: 3af28a27b7721bc7
|
||||
/agent-note-standard-reasoning-body-slot-and-reusable-content-factory/decision/registration-and-rendering:
|
||||
en: 0af348d91eedc16e
|
||||
zh: a06d6f59cc271e94
|
||||
/agent-note-standard-reasoning-body-slot-and-reusable-content-factory/decision/data-state-and-lifecycle:
|
||||
en: e220ebf28a113e0b
|
||||
zh: dfedd85ac5d76ccb
|
||||
/agent-note-standard-reasoning-body-slot-and-reusable-content-factory/decision/translation-plugin-integration:
|
||||
en: 145a1ae617a49f43
|
||||
zh: b2eb8443bb1515a7
|
||||
/agent-note-standard-reasoning-body-slot-and-reusable-content-factory/alternatives-considered:
|
||||
en: 89adfc6fbad716f3
|
||||
zh: 098cca9e73c5eeaf
|
||||
/agent-note-standard-reasoning-body-slot-and-reusable-content-factory/verification:
|
||||
en: 23e6e98d176f08f4
|
||||
zh: 2b89720b8914552b
|
||||
/agent-note-standard-reasoning-body-slot-and-reusable-content-factory/consequences:
|
||||
en: 60a32f46ff947154
|
||||
zh: 6498e791d4c2c166
|
||||
@@ -0,0 +1,102 @@
|
||||
# Agent Note: Standard reasoning Body Slot and reusable Content Factory
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-09-reasoning-content-slot-factory.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Reasoning bodies need to let third parties replace or wrap their presentation while reusing the official Markdown renderer, localized labels, and compact typography. An optional renderer parameter that makes the host choose between direct rendering and a chain fallback leaves the default body outside the registration system and forces plugins to maintain another Markdown call.
|
||||
|
||||
The render position and the base implementation serve different purposes: the position accepts plugin registrations and lets the framework select one, while any component can call the implementation directly. Calling the base implementation must not repeat plugin selection for that position or require a Session.
|
||||
|
||||
## Decision
|
||||
|
||||
The design uses existing [Component Factories](2026-09-10-component-factories-and-local-slots.md) and ordinary Slots without adding framework dispatch machinery.
|
||||
|
||||
| Name | Type | Owner and responsibility |
|
||||
| --- | --- | --- |
|
||||
| `conversation.chat.reasoning.body` | `single`, `session` Slot | Body position declared by the Assistant node registration; receives only original text and streaming state when expanded. |
|
||||
| `conversation.chat.reasoning.content` | `root` Factory | Standard reasoning content provided by `ui-chat`; accepts text, streaming state, and optional labels, supplies default labels, and fixes `variant="compact"`. |
|
||||
|
||||
The Factory receives no Session identity, reads no Session, and owns no translation state. The current Factory API requires `scope`; `root` expresses the absence of a Session dependency, while each render position still has an independent React instance. The Chat plugin registers the Factory independently of any Body occupant, so replacing a body does not remove the base implementation.
|
||||
|
||||
### Registration and rendering
|
||||
|
||||
1. `ui-chat` registers the Content Factory with component props derived through `FactoryComponentPropsOf`. When the caller omits labels, the Factory constructs stable defaults from Chat's locale seat.
|
||||
2. The Assistant node registration declares the Body Slot. An expanded `ReasoningRow` only calls `renderSlot`; it accepts no optional renderer, supplies no inline fallback, and does not check whether a plugin is enabled.
|
||||
3. The official default Body is an ordinary registration with `priority: 100`. It forwards only `text` and `running` to the Content Factory.
|
||||
4. Third parties wait for the Body declaration through `slots.inject` and register their Body with a smaller priority value. They may call the Content Factory directly and add their own controls around it without importing private official React components.
|
||||
5. When a third party unregisters, the framework selects the official default registration again. Multiple third-party registrations follow existing single-slot priorities; they do not form middleware or render multiple bodies together.
|
||||
|
||||
```text
|
||||
ReasoningRow
|
||||
└─ conversation.chat.reasoning.body
|
||||
├─ 官方默认 Body ───────────┐
|
||||
└─ 第三方 Body → 自有控件 ─┤
|
||||
└─ conversation.chat.reasoning.content
|
||||
└─ MarkdownText
|
||||
```
|
||||
|
||||
The body render position:
|
||||
|
||||
```tsx ignore-check
|
||||
<div className={css.thinkBody}>
|
||||
{renderSlot('conversation.chat.reasoning.body', { text, running })}
|
||||
</div>
|
||||
```
|
||||
|
||||
The default Body and third-party wrappers use the same reusable implementation; a third party passes labels when it needs custom copy:
|
||||
|
||||
```tsx ignore-check
|
||||
renderFactorySlot('conversation.chat.reasoning.content', {
|
||||
text: displayText,
|
||||
running,
|
||||
labels,
|
||||
})
|
||||
```
|
||||
|
||||
### Data, state, and lifecycle
|
||||
|
||||
- `ReasoningBodyOwnerProps` contains only readonly `text: string` and `running: boolean`. `ReasoningContentInput` also accepts `labels?: MarkdownLabels`; supplied labels are used unchanged without field-level merging, and omission selects the official defaults. Callers own the localization and reference stability of custom labels.
|
||||
- The Body's `text` is always the original reasoning. A third party selects original or derived display text and passes that value to the Factory without rewriting the Session or Chat projection.
|
||||
- `ReasoningRow` retains its existing disclosure state, summary, shimmer, and title behavior. Plugin-specific selection state, buttons, and styles stay inside the plugin's Body wrapper; the official implementation supplies no `setHeaderAction`, header-action type, or corresponding state.
|
||||
- Registrations follow Cordis effect lifetimes. Collapsing the Body declaration removes its contributions through `slots.inject`; unloading the Chat plugin removes the Content Factory.
|
||||
- Factory components and the default Body have stable module-level identities. Text changes update props instead of rebuilding component types or keys. Plugin replacement or HMR may remount components according to existing framework semantics; unmounted components have no state-retention guarantee.
|
||||
|
||||
### Translation plugin integration
|
||||
|
||||
The [anonymous translation design](2026-10-05-anonymous-reasoning-translation.md) retains its external-request, privacy, failure, and cancellation decisions; this design supersedes its body composition mechanism. The translation plugin registers in the standard Body Slot, renders original/translation selection, status, and retry controls in its own toolbar, and wraps the official Content Factory with its own state and optional custom labels rather than calling `MarkdownText` directly.
|
||||
|
||||
Translation requests, splitting, caching, persistence, and settings remain unchanged. The translation toggle belongs to the plugin body rather than the official Think title. The design adds no Slot outside the body, does not handle user-message masking, and does not change the general Markdown parser.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Translation-specific chain and host fallback.** The default content is not an ordinary registration, the host maintains a second render path, and third parties cannot directly reuse the complete official body implementation.
|
||||
|
||||
**Expose only the Body Slot.** This permits replacement, but wrapping plugins still have to copy the official Markdown call, labels, and variant.
|
||||
|
||||
**Provide only the Content Factory.** This permits implementation reuse but supplies no actual body position for third-party replacement registrations.
|
||||
|
||||
**Export React components across plugins.** This bypasses existing Factory loading, lifecycle, and type derivation and adds feature-plugin runtime dependencies.
|
||||
|
||||
**Name the Factory as general Markdown.** This implementation fixes reasoning-specific compact typography and supplies Chat defaults; it is not a general entry point for all Markdown uses.
|
||||
|
||||
**Make the official implementation maintain plugin header actions.** The default body has no such requirement; a dedicated setter makes the host own plugin state, cleanup, and button styles.
|
||||
|
||||
**Force official labels in the Factory.** Default copy must not prevent caller customization; an optional parameter provides an override without replacing the whole body implementation.
|
||||
|
||||
## Verification
|
||||
|
||||
- The body host only calls `conversation.chat.reasoning.body`, with no old `reasoning-body` chain, optional renderer, or inline Markdown fallback.
|
||||
- Both the default Body and translation Body render through `conversation.chat.reasoning.content`; omitted labels use official defaults, and custom labels reach Markdown unchanged.
|
||||
- The Factory works without a Session Provider, and two render positions do not share component-local state.
|
||||
- The official implementation has no header-action type, setter, state, or dedicated styles; the translation wrapper supplies its own selection and retry controls.
|
||||
- Enabling, disabling, or hot-reloading the translation plugin selects the corresponding Body; default output, original-text access, streaming, disclosure, and cancellation behavior remain consistent.
|
||||
- Unit and assembly tests cover registration disposal, a Session-independent Factory, default and custom labels, shared implementation reuse, and independent occurrences; existing browser replay covers default bodies and translation switching.
|
||||
|
||||
## Consequences
|
||||
|
||||
The old Slot name, chain-selector registrations, and header-action callback are unsupported, so third parties must migrate. Test fixtures and the Client inspect catalog must follow the new declarations rather than offer obsolete registration examples.
|
||||
|
||||
The Body remains an exclusive replacement position. This design does not automatically compose multiple text-transformation plugins or turn display transformations into redaction or model-input modification.
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
# Agent Note: 思考正文的标准 Slot 与可复用 Content Factory
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-09-reasoning-content-slot-factory.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
思考正文需要允许第三方替换或包装展示,同时复用官方的 Markdown、本地化文案和紧凑排版。将扩展做成可选的 renderer 参数,再由宿主选择直接渲染或 chain fallback,会让默认正文存在于注册体系之外,插件也只能另行维护 Markdown 调用。
|
||||
|
||||
渲染位置与基础实现是两种不同的能力:位置允许多个插件注册并由框架选中一个,实现则应允许任何组件直接调用。调用基础实现不应再次触发该位置的插件选择,也不应要求 Session。
|
||||
|
||||
## 决策
|
||||
|
||||
沿用现有 [Component Factory](2026-09-10-component-factories-and-local-slots.zh.md) 和普通 Slot,不增加框架分发机制。
|
||||
|
||||
| 名称 | 类型 | 所有者与职责 |
|
||||
| --- | --- | --- |
|
||||
| `conversation.chat.reasoning.body` | `single`、`session` Slot | Assistant 节点注册声明的正文位置;展开时只接收原始文本与流式状态。 |
|
||||
| `conversation.chat.reasoning.content` | `root` Factory | `ui-chat` 提供的标准思考正文;接收文本、流式状态及可选 labels,提供默认文案并固定 `variant="compact"`。 |
|
||||
|
||||
Factory 不接收 Session 身份,不读取 Session,不持有翻译状态。现有 Factory API 要求填写 `scope`,使用 `root` 表达无 Session 依赖;每个调用位置仍有独立的 React 实例。Factory 由 Chat 插件注册,而非依附于某个 Body occupant,第三方替换正文不会撤掉基础实现。
|
||||
|
||||
### 注册与调用
|
||||
|
||||
1. `ui-chat` 注册 Content Factory,组件 props 由 `FactoryComponentPropsOf` 推导。调用者未传 labels 时,Factory 使用 Chat 的 locale seat 构造稳定的默认文案。
|
||||
2. Assistant 节点注册声明 Body Slot。`ReasoningRow` 展开时只调用 `renderSlot`,不接收可选 renderer,不提供内联 fallback,也不判断插件是否启用。
|
||||
3. 官方以 `priority: 100` 注册普通默认 Body。该组件只将 `text`、`running` 传给 Content Factory。
|
||||
4. 第三方通过 `slots.inject` 等待 Body 声明,以较小的 priority 注册自己的 Body。第三方可直接调用 Content Factory,并在外层增加自己的控件;无需导入官方私有 React 组件。
|
||||
5. 第三方注销后,框架重新选中官方默认注册项。多个第三方注册遵循现有 single 优先级,不形成 middleware,也不同时渲染多份正文。
|
||||
|
||||
```text
|
||||
ReasoningRow
|
||||
└─ conversation.chat.reasoning.body
|
||||
├─ 官方默认 Body ───────────┐
|
||||
└─ 第三方 Body → 自有控件 ─┤
|
||||
└─ conversation.chat.reasoning.content
|
||||
└─ MarkdownText
|
||||
```
|
||||
|
||||
正文调用位置:
|
||||
|
||||
```tsx ignore-check
|
||||
<div className={css.thinkBody}>
|
||||
{renderSlot('conversation.chat.reasoning.body', { text, running })}
|
||||
</div>
|
||||
```
|
||||
|
||||
默认 Body 与第三方包装都使用同一个可复用实现;第三方需要自定义文案时传入 labels:
|
||||
|
||||
```tsx ignore-check
|
||||
renderFactorySlot('conversation.chat.reasoning.content', {
|
||||
text: displayText,
|
||||
running,
|
||||
labels,
|
||||
})
|
||||
```
|
||||
|
||||
### 数据、状态与生命周期
|
||||
|
||||
- `ReasoningBodyOwnerProps` 仅包含只读的 `text: string` 与 `running: boolean`。`ReasoningContentInput` 另外允许 `labels?: MarkdownLabels`;传入的文案原样使用,不做字段级合并,省略时才使用官方默认文案。调用者负责自定义文案的本地化与引用稳定性。
|
||||
- Body 的 `text` 始终是原始思考。第三方选择原文或派生显示文本后,只将该值交给 Factory,不改写 Session 或 Chat projection。
|
||||
- 折叠状态、摘要、流光和标题由 `ReasoningRow` 保持现有行为。插件自己的切换状态、按钮及样式留在其 Body 包装中;官方不提供 `setHeaderAction`、标题动作类型或相应状态。
|
||||
- 注册沿用 Cordis effect 生命周期。Body 声明撤销时,其贡献随 `slots.inject` 清理;Content Factory 在 Chat 插件卸载时撤销。
|
||||
- Factory 组件与默认 Body 使用模块级稳定身份,文本变化作为 props 更新,不以文本重建组件类型或 key。插件替换或 HMR 可按现有框架语义重新挂载,不承诺保留被卸载组件的本地状态。
|
||||
|
||||
### 翻译插件接入
|
||||
|
||||
[免登录翻译设计](2026-10-05-anonymous-reasoning-translation.zh.md)保留外部请求、隐私、失败与取消决策;其正文组合方式由本方案取代。翻译插件注册到标准 Body Slot,在自己的工具栏内渲染原文/译文切换、状态及重试按钮,使用自己的状态和可选自定义 labels 包装官方 Content Factory,不直接调用 `MarkdownText`。
|
||||
|
||||
翻译请求、分片、缓存、持久化和设置保持不变。翻译切换按钮属于插件正文,不再由官方思考标题承载;不增加正文外的 Slot,不处理用户消息遮挡,也不改动通用 Markdown 解析器。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**翻译专用 chain 与宿主 fallback。** 默认内容不是普通注册项,宿主需要维护第二条渲染路径,第三方无法直接复用完整的官方正文实现。
|
||||
|
||||
**只开放 Body Slot。** 可以替换正文,但第三方包装时仍需复制官方 Markdown 调用、labels 与 variant。
|
||||
|
||||
**只提供 Content Factory。** 可以复用实现,但缺少允许第三方注册替换的实际正文位置。
|
||||
|
||||
**跨插件导出 React 组件。** 绕过现有 Factory 的加载、生命周期与类型推导,增加 feature-plugin 运行时依赖。
|
||||
|
||||
**将 Factory 命名为通用 Markdown。** 该实现固定思考正文的紧凑排版,并提供 Chat 默认文案,不是所有 Markdown 场景的通用入口。
|
||||
|
||||
**官方替插件维护标题动作。** 官方默认正文没有该需求;保留专用 setter 会让宿主承担插件的状态、清理与按钮样式。
|
||||
|
||||
**Factory 强制使用官方 labels。** 默认文案不应阻止调用者定制;可选参数即可表达覆盖,无需替换整套正文实现。
|
||||
|
||||
## 验证
|
||||
|
||||
- 正文宿主只调用 `conversation.chat.reasoning.body`,没有旧 `reasoning-body` chain、可选 renderer 或内联 Markdown fallback。
|
||||
- 官方默认 Body 和翻译 Body 都通过 `conversation.chat.reasoning.content` 渲染;未传 labels 使用官方默认文案,传入自定义 labels 则原样交给 Markdown。
|
||||
- Factory 在没有 Session Provider 的调用位置也能使用;两个调用位置不共享组件本地状态。
|
||||
- 官方不存在标题动作类型、setter、状态或专用样式;翻译包装自行提供切换与重试控件。
|
||||
- 翻译插件启用、停用及 HMR 后,框架选中对应 Body;默认输出、原文访问、流式展示、折叠和取消行为保持一致。
|
||||
- 单元与装配测试覆盖注册撤销、无 Session Factory、labels 缺省与覆盖、默认与第三方共用实现及双实例;已有浏览器回放覆盖默认正文和翻译切换。
|
||||
|
||||
## 后果
|
||||
|
||||
旧 Slot 名称、chain selector 注册及标题动作回调不再受支持,第三方需要迁移。测试夹具与 Client inspect catalog 必须同步到新声明,避免仍向插件开发者提供旧注册方式。
|
||||
|
||||
Body 仍是独占替换位置。此方案不解决多个正文转换插件的自动组合,也不将显示变换提升为脱敏或模型输入修改能力。
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
# Bilingual-pair consistency record for 2026-09-15-authenticated-browser-host-preferences.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/bug-fix/2026-09-15-authenticated-browser-host-preferences.md
|
||||
/agent-note-share-host-preferences-with-every-authenticated-browser:
|
||||
en: f4b8b4500f8dca9c
|
||||
zh: bf27ded54fba703b
|
||||
/agent-note-share-host-preferences-with-every-authenticated-browser/problem:
|
||||
en: 22bf57b616581253
|
||||
zh: d1108a4042646e21
|
||||
/agent-note-share-host-preferences-with-every-authenticated-browser/decision:
|
||||
en: 2d54784b8b3558cf
|
||||
zh: 31190a9b284867b7
|
||||
/agent-note-share-host-preferences-with-every-authenticated-browser/alternatives-considered:
|
||||
en: 299105745b084fc0
|
||||
zh: 8c2d82de44ac2aad
|
||||
/agent-note-share-host-preferences-with-every-authenticated-browser/consequences:
|
||||
en: 520028b8c4031009
|
||||
zh: 8c130624261538cc
|
||||
/agent-note-share-host-preferences-with-every-authenticated-browser/testing:
|
||||
en: e433f139fe72e69f
|
||||
zh: 279cc8b1cd3d5377
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Agent Note: Share Host preferences with every authenticated browser
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-09-15-authenticated-browser-host-preferences.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
[Persist Web user preferences through Host settings](../../archived/bug-fix/2026-08-06-host-backed-web-preferences.md) chose the preference store by page locality, so a non-loopback page kept Appearance, Language, busy-Enter, and the welcome acknowledgement in a process-local copy. A trusted remote host, and a deployment published behind the configured public URL, therefore lost every choice on reload even though Connection authenticates the complete settings API. The second mode also split the shared runtime: theme, locale, the settings shell, the Models page, and the welcome notice each rendered a durable row that could never persist for a remote page.
|
||||
|
||||
## Decision
|
||||
|
||||
Locality selects nothing. One `settings.describe` mirror, one `ctx.configForms.get(entryId)` per namespace derived from it, and the `settings.mutate` write path serve every authenticated browser. Browser token authentication of the Connection API is the identity boundary; the fixed `remote.$host` locality facts are request-routing evidence that no store selector consults. The mirror's state space is `idle`, `loading`, and `ready`, a form snapshot carries no mode, and a form reports `unavailable` only for a namespace the Host did not register.
|
||||
|
||||
The settings shell keeps one locality-dependent UI element: it builds its document controller from `ctx.remote.$host.isLoopback`, so only a loopback page renders **Open configuration file** and issues the `hasDocument` read behind it. The `settings/openSettingsDocument` Remote itself stays browser-authenticated like every other Host method; the omission is a presentation choice for pages that cannot see the Host's native editor, not a persistence rule.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep the memory-branch locality gate.** Rejected: browser token authentication already covers every Host method for remote pages, so the gate protected nothing, and locality is request-routing evidence rather than identity. It also made every Host-backed row inert on trusted remote and public deployments.
|
||||
|
||||
## Consequences
|
||||
|
||||
Every authenticated browser reaching one Host shares one preference document. Two browsers on the same deployment observe each other's Appearance, Language, busy-Enter, and welcome-acknowledgement choices, and a value written remotely is the value a loopback browser loads next. Preference access is governed by API authentication and `settings.describe.writable`.
|
||||
|
||||
A remote page omits the **Open configuration file** action, the one locality-dependent UI element that remains. Concurrent writers on one document resolve through the namespace revision each form sends as `expectedRevision`.
|
||||
|
||||
## Testing
|
||||
|
||||
Focused unit coverage pins the contract: the ui-settings mirror and configuration-form suites construct both objects with no persistence selector, the theme apply spec persists from a non-loopback bench and asserts the Host write, the ui-settings-general apply spec keeps the shared describe read and the Host write path off-loopback while asserting the native document action stays unregistered, and the welcome store retains its acknowledgement across a settings-plugin recreation. The keyless Web settings scenario `apps/web/tests/remote-settings.e2e.ts` acknowledges the welcome notice, configures a model provider, and selects the dark Appearance from a trusted remote page, reloads after each, and re-asserts the welcome, model, and Appearance state from the Host settings document.
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Agent Note: 每个已认证浏览器共用 Host 偏好
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-09-15-authenticated-browser-host-preferences.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
[通过 Host settings 持久化 Web 用户偏好](../../archived/bug-fix/2026-08-06-host-backed-web-preferences.md)按页面本地性选择偏好存储,因此非回环页面把 Appearance、Language、繁忙态 Enter 与欢迎提示的确认留在一份进程内副本中。可信远程主机,以及配置了公共 URL 的部署,因而在重新加载时丢失全部选择,尽管 Connection 认证完整的 settings API。这套第二种模式也割裂了共享运行时:主题、语言、设置外壳、Models 页面与欢迎提示各自都在渲染一行对远程页面永远无法持久化的界面。
|
||||
|
||||
## 决策
|
||||
|
||||
本地性不选择任何东西。一个 `settings.describe` 镜像、由它派生的逐 namespace `ctx.configForms.get(entryId)`,以及 `settings.mutate` 写入路径服务每个已认证浏览器。Connection API 的浏览器令牌认证是身份边界;固定的 `remote.$host` 本地性事实只是请求路由的依据,没有任何存储选择器会读取它。镜像的状态空间只有 `idle`、`loading` 与 `ready`,表单快照不携带 mode,表单也只有在 Host 未注册该 namespace 时才报告 `unavailable`。
|
||||
|
||||
settings 外壳保留唯一一个依赖本地性的 UI 元素:它依据 `ctx.remote.$host.isLoopback` 构造文档控制器,因此只有回环页面会渲染**打开配置文件**并发出其背后的 `hasDocument` 读取。`settings/openSettingsDocument` Remote 本身与其他 Host 方法一样保持浏览器认证;省略该操作是面向看不到 Host 原生编辑器的页面的展示选择,而非持久化规则。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
**保留内存分支的本地性门禁。** 已否决:浏览器令牌认证已覆盖远程页面的每个 Host 方法,该门禁保护不了任何东西,而本地性只是请求路由的依据而非身份。它还会让受信远程与公共部署上每一行依赖 Host 的界面失效。
|
||||
|
||||
## 后果
|
||||
|
||||
每个访问同一 Host 的已认证浏览器共用一份偏好文档。同一部署上的两个浏览器会看到彼此选择的 Appearance、Language、繁忙态 Enter 与欢迎提示确认,远程写入的值就是回环浏览器下一次加载的值。偏好可访问性由 API 认证与 `settings.describe.writable` 决定。
|
||||
|
||||
远程页面省略**打开配置文件**操作,这是仍依赖本地性的唯一 UI 元素。同一文档上的并发写入由各表单作为 `expectedRevision` 发送的 namespace revision 裁决。
|
||||
|
||||
## 测试
|
||||
|
||||
聚焦的单元测试固定当前契约:ui-settings 的镜像与配置表单测试在构造这两个对象时不传持久化选择器,主题 apply 测试从非回环 bench 持久化并断言 Host 写入,ui-settings-general 的 apply 测试在非回环下保留共享 describe 读取与 Host 写入路径、同时断言原生文档操作保持未注册,欢迎 store 则在 settings 插件重建后保留其确认。无密钥 Web settings 场景 `apps/web/tests/remote-settings.e2e.ts` 从可信远程页面确认欢迎通知、配置一个模型提供商并选择深色 Appearance,每步之后重新加载,并从 Host settings 文档重新确认欢迎提示、模型与 Appearance 状态。
|
||||
@@ -18,11 +18,11 @@
|
||||
en: 83e446c928a20c47
|
||||
zh: b0a2bd24419dd815
|
||||
/agent-note-durable-agent-teams-over-continuable-children/provisioning-and-recovery:
|
||||
en: 085846d0078c0898
|
||||
zh: 40b2ca3ae6864451
|
||||
en: 4345ac9a6ac7ad04
|
||||
zh: 1d19ecbb128a75a3
|
||||
/agent-note-durable-agent-teams-over-continuable-children/mailbox-and-task-transactions:
|
||||
en: ab2ad55021eb34e0
|
||||
zh: 2d1996df7a69201d
|
||||
en: bfc3063536837c0e
|
||||
zh: 6b8e69ba85cc2bb1
|
||||
/agent-note-durable-agent-teams-over-continuable-children/shared-checkout-boundary:
|
||||
en: 34920da68b5763e0
|
||||
zh: 7604392594e4b8a8
|
||||
@@ -30,11 +30,11 @@
|
||||
en: e6348054f024725b
|
||||
zh: 8740636e99940308
|
||||
/agent-note-durable-agent-teams-over-continuable-children/alternatives-considered:
|
||||
en: bb266fe7a97f0828
|
||||
zh: 7c86c2d6d11ba02a
|
||||
en: 8cb2fd8839f5f8b7
|
||||
zh: e302ca03e569de65
|
||||
/agent-note-durable-agent-teams-over-continuable-children/testing:
|
||||
en: 35ce199351d8b63d
|
||||
zh: 332f0520e27e3309
|
||||
en: 092ac3c2b6f552aa
|
||||
zh: c688c1bec5ad8a07
|
||||
/agent-note-durable-agent-teams-over-continuable-children/consequences:
|
||||
en: c3740ae56809e537
|
||||
zh: 76a1e77d14cb6f26
|
||||
en: da9cd22b1185a793
|
||||
zh: e20d7ef9226f237f
|
||||
@@ -30,7 +30,7 @@ The `spawn_teammate` tool prefixes the initial task with a user-role `<system-re
|
||||
|
||||
## Provisioning and recovery
|
||||
|
||||
Creation first appends and flushes a `team/member` provisioning snapshot in the Lead Session, then starts the reserved continuable child through the selected fresh or fork provider. Failure before initial inbox acceptance appends a failed snapshot. Success flushes the child's accepted inbox item before appending active. Recovery recognizes that initial message while it is still pending or after it enters user-message history. Names are reserved by the first provisioning record and never reused, including after failure. Disposal closes admission, aborts and awaits admitted creation and mailbox-dispatch transactions, then stops every live child recorded by the roster; a failed child remains cleanup-owned until its Activation exits, and cleanup rejection fails disposal.
|
||||
Creation first appends and flushes a `team/member` provisioning snapshot in the Lead Session, then starts the reserved continuable child through the selected fresh or fork provider. Failure before initial inbox acceptance appends a failed snapshot. Success flushes the child's accepted inbox item before appending active. Recovery recognizes that initial message while it is still pending or after it enters user-message history. Names are reserved by the first provisioning record and never reused, including after failure. Disposal closes admission, aborts and awaits admitted creation and send operations, then stops every live child recorded by the roster; a failed child remains cleanup-owned until its Activation exits, and cleanup rejection fails disposal.
|
||||
|
||||
A root recovery reconciles an unterminated provisioning record against the child's independently persisted Session. Matching direct-parent and continuable descriptors plus a recorded initial user message prove successful admission and produce active; absence, corruption, mismatched provider/lineage, or a missing admitted message produces failed. The creator re-reads the terminal phase under the same Lead-log serializer; if recovery marked failed while creation succeeded, it drains the child and reports a provisioning conflict instead of retaining an orphan. This avoids reconstructing an initial prompt that was never retained in the Team log and contains plugin-reload races.
|
||||
|
||||
@@ -38,11 +38,11 @@ Fresh children have no inherited conversation. Fork children capture the Lead's
|
||||
|
||||
## Mailbox and task transactions
|
||||
|
||||
Peer communication is a Lead-log mailbox. `team/message/queued` is appended and flushed before delivery. The target message carries the stable message id and sender identity in both durable source metadata and a short model-visible prefix. A target receipt is acknowledged with `team/message/delivered` only after its pending inbox item or recorded user message is flushed. Immediate admission is serialized per target in queued-log order, recovery retries queued-minus-delivered in the same order, and delivery folds live or persisted target inbox/history state before cold resume. Every current-version Team payload is runtime-validated before entering replay state. The Team runtime tracks dispatch and asynchronous acknowledgement work from synchronous admission until settlement; disposal closes admission and awaits both before removing the service. Current waiters wake only after the owning Team event flush succeeds.
|
||||
The historical Lead-log mailbox remains a readable format: queued and delivered records keep their stable identity and sender, and Team payload validation still applies to them. Team no longer delivers or acknowledges them; new sends follow the [direct inbox decision](../simplification/2026-09-26-team-direct-inbox.md).
|
||||
|
||||
Unknown mailbox content stays as decoded JSON during event projection and checkpoint admission. Validation inspects its type without rebuilding its fields, because generic object parsing can omit a valid own `__proto__` key. Locally declared content variants undergo structural validation; opaque plugin fields do not gain Team semantics. Cache invalidation is necessary because parser correction alone cannot recover keys omitted from cached state. The [Team documentation](../../../../packages/experimental/agent-team/README.md) owns the restore behavior.
|
||||
|
||||
`send_message` always attempts Steer delivery. A running target receives the message at the nearest step boundary; an inactive target starts a turn if loaded or cold-resumes otherwise. Success means the message is already durable even when temporary delivery failure leaves it queued. The mechanism provides process-local retry and target-Session de-duplication, not a cross-process exactly-once claim. The [Team Steer messaging decision](../../archived/simplification/2026-08-30-team-send-message-steer.md) owns the single-tool scheduling rationale.
|
||||
`send_message` uses Steer for running, idle, and cold targets. New sends return inbox acceptance. The [Team Steer messaging decision](../../archived/simplification/2026-08-30-team-send-message-steer.md) records the single-tool scheduling rationale.
|
||||
|
||||
Shared tasks are complete snapshots with Team-local ids and monotonic revisions. Every mutation carries `expectedRevision`. Any member creates, reads, or claims a ready unowned task; the owner or Lead edits and transitions it, while only the Lead assigns another member. Numeric task ids remain within the safe-integer allocation range, and exhaustion fails without reusing an id. Dependencies must name non-deleted tasks and form a complete DAG. Deleted tasks are retained tombstones. `writeScopes` are normalized path prefixes that produce overlap diagnostics but never block claim or authorize a write.
|
||||
|
||||
@@ -74,20 +74,20 @@ Publication requires `apply` to return a new state object per applied Team event
|
||||
|
||||
**Enable Teams in the default catalog.** Rejected because scoped Team controls would shadow same-named legacy globals and unsolicited delegation would add latency and token cost to simple tasks. An opt-in profile bundle inserts Team and disables the legacy controls without adding Team packages to shipped dependency graphs.
|
||||
|
||||
**Use an in-memory board and mailbox.** Rejected because child settlement, HMR, and process interruption would lose accepted coordination state and make retries ambiguous.
|
||||
**Use an in-memory board and mailbox.** Roster and task state must survive settlement, HMR, and interruption. Historical mailbox records stay readable; message ownership follows the target inbox.
|
||||
|
||||
**Return Team tool results as untyped JSON.** Rejected because an undeclared result type lets `execute` drift from the value the model is promised without a compiler error, and it invites indentation that costs tokens on every roster, task, and receipt. Each Team tool therefore declares its complete result schema and one shared helper renders it compactly.
|
||||
|
||||
## Testing
|
||||
|
||||
Package tests cover identity, name and authority checks, provider selection, reserved-id persistence collisions, child-before-Lead flush ordering, durable provisioning failure and pending-inbox JSONL reconciliation, concurrent target-local ordering, pending/history de-duplication, mailbox limits, post-flush notification, bounded disposal with in-flight creation and dispatch cancellation, failed-member cleanup, task CAS and DAG validation, write-scope warnings, wait cancellation/timeout, inbox-preserving interruption, ordinary-fork isolation, legacy-control shadowing, compact declared-schema result rendering, and scoped registration HMR at per-file 100% coverage. A keyless product snapshot loads the Agent Teams profile bundle through `dsh --profile headless` and pins its complete model-visible tool list, Team policy, and durable workflow projection for two teammates, dependent tasks, peer delivery, waiting, completion, and aggregation. A CLI e2e reuses the same deterministic adapter and verifies normal process exit with persisted Team and child logs.
|
||||
Package tests cover Team authority, provisioning durability, task transitions, direct inbox acceptance, sender attribution, lifecycle cancellation, and historical mailbox readability. The keyless Team profile snapshot pins the policy, tools, and workflow; the CLI composition test checks persisted Team and child logs.
|
||||
|
||||
Model-facing member identities and availability follow the [tool projection reference](../../../../packages/experimental/tool-agent-team/README.md); service residency and durable identity remain distinct.
|
||||
|
||||
## Consequences
|
||||
|
||||
The Lead Session grows with whole task/member snapshots and mailbox acknowledgements. This favors independently inspectable recovery over compact deltas; configured task and pending-mail bounds cap active state, while deleted and delivered history remains append-only until broader Session retention applies.
|
||||
The Lead Session retains whole task/member snapshots and historical mailbox records. This favors inspectable recovery over compact deltas; deleted and delivered history remains append-only until broader Session retention applies.
|
||||
|
||||
An active roster member can be non-resident, so `inactive` is not failure and a send can incur cold-resume latency. Temporary inspection, resume, or inbox-admission failure can leave a durable message queued for recovery. A failed member permanently consumes its name and member slot, making provisioning failures visible instead of silently recycling identity.
|
||||
An active roster member can be non-resident, so `inactive` is not failure and sends can incur cold-resume latency. Admission failures reject new sends. Historical pending mail is never delivered. Failed provisioning permanently consumes its name and member slot.
|
||||
|
||||
Coordination reduces likely checkout conflicts but cannot eliminate writes outside filesystem compare-and-set tools. The final diff and tests remain the Lead's integration boundary.
|
||||
@@ -30,7 +30,7 @@ Lead 必须等待所需工作后才能给出最终答案。进程 teardown 仍
|
||||
|
||||
## Provisioning and recovery
|
||||
|
||||
创建操作先在 Lead Session 中追加并 flush `team/member` provisioning 快照,再通过选定 fresh 或 fork provider 启动预留的 continuable child。初始 inbox 获准前的失败会追加 failed 快照;成功会先 flush child 中已接受的 inbox 条目,再追加 active。恢复会在初始消息仍处于 pending 或已进入用户消息历史时识别它。名字由第一条 provisioning 记录永久保留,包括失败后也不能复用。dispose 会关闭准入,中止并等待已获准的创建与 mailbox dispatch 事务,再停止 roster 记录的所有 live child;failed child 在 Activation 退出前仍由 cleanup 拥有,cleanup 拒绝会让 dispose 失败。
|
||||
创建操作先在 Lead Session 中追加并 flush `team/member` provisioning 快照,再通过选定 fresh 或 fork provider 启动预留的 continuable child。初始 inbox 获准前的失败会追加 failed 快照;成功会先 flush child 中已接受的 inbox 条目,再追加 active。恢复会在初始消息仍处于 pending 或已进入用户消息历史时识别它。名字由第一条 provisioning 记录永久保留,包括失败后也不能复用。dispose 会关闭准入,中止并等待已获准的创建与发送操作,再停止 roster 记录的所有 live child;failed child 在 Activation 退出前仍由 cleanup 拥有,cleanup 拒绝会让 dispose 失败。
|
||||
|
||||
Root 恢复时会把未终结 provisioning 记录与独立持久 child Session 对账。直接 parent 与 continuable descriptor 匹配,并且已经记录初始用户消息,才能证明准入成功并转为 active;缺失、损坏、provider/lineage 不匹配或缺少已准入消息都会转为 failed。creator 会在同一 Lead 日志 serializer 内重读终态;如果 recovery 在创建成功时先标记 failed,creator 会 drain child 并报告 provisioning conflict,而不是遗留孤儿。这样既无需重建从未保存在 Team 日志中的初始 prompt,也能约束插件 reload 竞争。
|
||||
|
||||
@@ -38,11 +38,11 @@ fresh child 不继承对话。fork child 只捕获一次 Lead 已完成 turn 前
|
||||
|
||||
## Mailbox and task transactions
|
||||
|
||||
Peer 通讯使用 Lead 日志 mailbox。投递前先追加并 flush `team/message/queued`。target message 会在持久 source metadata 与短模型可见前缀中同时携带稳定 message id 和 sender identity。只有 pending inbox 条目或已记录用户消息完成 flush,Lead 日志才写入 `team/message/delivered` acknowledgement。即时准入按 target 和 queued 日志顺序串行化,恢复按同一顺序重试 queued-minus-delivered,并在冷恢复前折叠 live 或 persisted target 的 inbox/历史状态。每个当前版本 Team payload 都会经过运行时验证后才进入 replay state。Team runtime 从同步准入到 settlement 全程跟踪 dispatch 与异步 acknowledgement 工作;dispose 会关闭准入,并在移除服务前等待两者。当前 waiter 只在所属 Team event flush 成功后被唤醒。
|
||||
历史 Lead-log mailbox 仍是可读格式:queued 与 delivered 记录保留稳定身份与发送者,Team payload 校验仍适用于它们。Team 不再投递或确认这些记录;新发送遵循[直接 inbox 决策](../simplification/2026-09-26-team-direct-inbox.zh.md)。
|
||||
|
||||
事件投影和 checkpoint 准入期间,未知 mailbox 内容保持为已解码 JSON。校验只检查其 type,不重建字段,因为通用对象解析可能省略有效的自有 `__proto__` 键。本地声明的内容变体接受结构校验;不透明的插件字段不获得 Team 语义。缓存失效是必要的,因为只修正解析器无法恢复缓存状态中省略的键。[Team 文档](../../../../packages/experimental/agent-team/README.zh.md)拥有恢复行为的说明。
|
||||
|
||||
`send_message` 始终尝试 Steer 投递。running target 在最近的步骤边界收到消息,inactive target 在已加载时启动一个轮次,否则冷恢复。即使临时投递失败让消息保持 queued,成功也表示消息已经持久化。该机制提供进程内重试与 target Session 去重,不宣称跨进程 exactly-once。[Team Steer 消息决策](../../archived/simplification/2026-08-30-team-send-message-steer.md)负责单工具调度的理由。
|
||||
`send_message` 对 running、idle 和 cold target 使用 Steer。新发送返回 inbox 接收结果。[Team Steer 消息决策](../../archived/simplification/2026-08-30-team-send-message-steer.md)记录单一工具调度的理由。
|
||||
|
||||
共享 task 是带 Team-local id 与单调 revision 的完整快照。每次变更都携带 `expectedRevision`。任意 member 可以创建、读取或 claim ready 且无 owner 的任务;Owner 或 Lead 可以编辑和转换;只有 Lead 可以分配给另一个 member。数字 task id 保持在安全整数分配范围内;该范围耗尽时会失败,不会复用 id。依赖必须指向未删除任务,并形成完整 DAG。删除任务保留为 tombstone。`writeScopes` 是规范化路径前缀,只产生重叠诊断,绝不会阻止 claim 或授予写权限。
|
||||
|
||||
@@ -74,20 +74,20 @@ Web panel 读取 Lead Session 的 `agentTeam` 传输投影,因为现有投影
|
||||
|
||||
**在默认工具目录中启用 Team。** 拒绝,因为 scoped Team control 会覆盖同名旧全局工具,主动 delegation 也会给简单任务增加延迟和 token 成本。opt-in profile bundle 会插入 Team 并禁用旧 control,同时不向随附依赖图添加 Team 包。
|
||||
|
||||
**使用内存 task board 与 mailbox。** 拒绝,因为 child settlement、HMR 与进程中断会丢失已接受协调状态,并让重试变得含糊。
|
||||
**使用内存 board 与 mailbox。** roster 与任务状态必须经受 settlement、HMR 与中断。历史 mailbox 记录保持可读;消息归属目标 inbox。
|
||||
|
||||
**让 Team 工具返回未类型化 JSON。** 拒绝,因为未声明的结果类型会让 `execute` 在没有编译错误的情况下偏离对模型的承诺,也会引入在每份 roster、task 与回执上都消耗 token 的缩进。因此每个 Team 工具都声明完整的结果 schema,并由一个共享 helper 紧凑渲染。
|
||||
|
||||
## Testing
|
||||
|
||||
Package test 以逐文件 100% coverage 覆盖身份、名字与权限检查、provider 选择、预留 id 持久化冲突、child-before-Lead flush 顺序、持久 provisioning 失败与 pending-inbox JSONL 对账、target-local 并发顺序、pending/history 去重、mailbox 限额、flush 后 notification、取消在途创建与 dispatch 的有界 dispose、failed member cleanup、task CAS 与 DAG 校验、write-scope warning、wait cancel/timeout、保留 inbox 的 interrupt、普通 fork 隔离、旧 control shadowing、声明 schema 的紧凑结果渲染与 scoped registration HMR。一条 keyless 产品快照会通过 `dsh --profile headless` 加载 Agent Teams profile bundle,并为两个 teammate、依赖任务、peer 投递、等待、完成和汇总固定完整的面向模型工具列表、Team policy 与持久 workflow 投影。CLI e2e 会复用同一个确定性 adapter,并验证带持久 Team 与 child 日志的正常退出。
|
||||
包测试覆盖 Team 权限、provisioning 持久性、任务转换、直接 inbox 接收、发送者归属、生命周期取消和历史 mailbox 可读性。无密钥 Team profile 快照固定 policy、工具与工作流;CLI 组合测试检查持久 Team 和 child 日志。
|
||||
|
||||
模型可见的成员身份与可用状态遵循[工具投影说明](../../../../packages/experimental/tool-agent-team/README.zh.md);服务驻留状态和持久身份仍保持区分。
|
||||
|
||||
## Consequences
|
||||
|
||||
Lead Session 会随着完整 task/member 快照与 mailbox acknowledgement 增长。该设计用可独立检查的恢复能力换取更紧凑的 delta;配置的 task 与 pending-mail 限额限制 active state,而 deleted 与 delivered 历史会保持 append-only,直到更广泛的 Session retention 生效。
|
||||
Lead Session 保留完整 task/member 快照和历史 mailbox 记录。这优先支持可检查的恢复,而不是紧凑增量;deleted 与 delivered 历史在更广泛的 Session 保留策略生效前仍只追加。
|
||||
|
||||
active roster member 可以不驻留,因此 `inactive` 不表示失败,send 可能产生 cold-resume 延迟。临时 inspection、resume 或 inbox 准入失败可能留下持久 queued 消息等待恢复。failed member 会永久占用名字与 member slot,使 provisioning failure 保持可见而不是静默回收身份。
|
||||
active roster 成员可能不驻留,因此 `inactive` 不表示失败,发送可能产生冷恢复延迟。准入失败会拒绝新发送。历史 pending 邮件永远不会被投递。provisioning 失败永久占用其名称和成员槽位。
|
||||
|
||||
协调可以降低 checkout 冲突概率,但无法消除文件系统 CAS 工具之外的写入。最终 diff 与测试仍是 Lead 的集成边界。
|
||||
@@ -21,8 +21,8 @@
|
||||
en: 237018634b5f9dcf
|
||||
zh: cbc49d9c1b794065
|
||||
/agent-note-experimental-auto-review-before-each-tool-call/decision/process-catalog-and-children:
|
||||
en: 6ebe6f90241fd322
|
||||
zh: eafdebc5277e83a1
|
||||
en: 0042c645360c8361
|
||||
zh: 5dba39466d376559
|
||||
/agent-note-experimental-auto-review-before-each-tool-call/alternatives-considered:
|
||||
en: fe97128ed05b9e43
|
||||
zh: 4aaa344933fa89b4
|
||||
|
||||
@@ -58,7 +58,7 @@ A persisted Auto Session cannot publish when the complete integration is absent
|
||||
|
||||
The permission owner publishes one complete process catalog through generated `permissionPresets` Remote methods; the BFF explicitly mounts it and forwards a payload-free invalidation event. One browser directory subscribes before reading and serves both pickers. Epoch and connection-generation checks publish only the winning complete result. A winning failure or connection reset clears the previous snapshot; only a later existing read, notification, or reset retries. Disposed or stale settlements cannot publish. Each catalog invalidation dismisses the slash picker and its pending confirmation through the command owner while preserving the draft, so a picker waiting on its own read keeps its failure and retry state; reopening loads the current catalog. Session projection carries current selection only, so catalog installation or removal writes no Session event or sequence.
|
||||
|
||||
Auto appears with a superscript `EXP` badge. Both visible current-session pickers require the experimental confirmation; explicit `/permission auto` is already consent. The composer uses the generic Menu's existing portal placement to stay within the viewport while keeping its 218–360px bounds. The slash popup keeps `min(220px, 100%)` with `max-width: 100%`, including when a narrow composer collapses its trigger.
|
||||
Auto appears with an `EXP` badge in picker options; the selected composer control omits it. Both visible current-session pickers require the experimental confirmation; explicit `/permission auto` is already consent. The composer uses the generic Menu's existing portal placement to stay within the viewport while keeping its 218–360px bounds. The slash popup keeps `min(220px, 100%)` with `max-width: 100%`, including when a narrow composer collapses its trigger.
|
||||
|
||||
The [delegation-time policy capture](../../../../packages/subagent/subagent/README.md) records Auto or Full access before the first await and appends that existing preset event after fork seeding and sandbox/approval overrides. One-shot and continuable creation share the rule; cold resume reads only the child log. Later parent switches do not alter that child, while a later child switch can win. Read Only and Workspace Write retain sandbox inheritance plus `approval: never` and may therefore remain `custom`. Auto children classify each call independently using the existing lineage and messages, without parent call metadata, delegation records, receipts, Header/descriptor additions, or a Session-format change. Out-of-process children retain their own permission systems after the parent delegation is allowed.
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ Reviewer 可以输出 reasoning blocks,随后恰好一个 JSON text block 和
|
||||
|
||||
Permission owner 通过生成的 `permissionPresets` Remote 方法发布一份完整进程目录;BFF 显式挂载它,并转发无 payload 的失效事件。一个浏览器目录在读取前订阅,为两个选择器提供数据。Epoch 与 connection-generation 检查只发布胜出的完整结果。胜出读取失败或 connection reset 会清空旧快照;只有后续既有读取、通知或 reset 才重试。已 dispose 或陈旧的结算不能发布。每次目录失效都通过命令 owner 关闭 slash 选择器与待确认对话框,同时保留草稿,因此等待自身读取的选择器仍保留失败与重试状态;重新打开时加载当前目录。Session 投影只携带当前选择,因此目录安装或移除不写 Session 事件或序号。
|
||||
|
||||
Auto 带右上标 `EXP`。两个可见当前会话选择器都要求实验确认;显式 `/permission auto` 已构成同意。Composer 使用通用 Menu 既有 portal 定位保持在视口内,同时保留 218–360px 边界。Slash popup 保留 `min(220px, 100%)` 与 `max-width: 100%`,窄 composer 将 trigger 折叠时也一样。
|
||||
Auto 在选择器选项中带 `EXP` 标记;选中后的 composer 控件不显示该标记。两个可见当前会话选择器都要求实验确认;显式 `/permission auto` 已构成同意。Composer 使用通用 Menu 既有 portal 定位保持在视口内,同时保留 218–360px 边界。Slash popup 保留 `min(220px, 100%)` 与 `max-width: 100%`,窄 composer 将 trigger 折叠时也一样。
|
||||
|
||||
[委派时权限捕获](../../../../packages/subagent/subagent/README.zh.md)在首次 await 前记录 Auto 或 Full access,并在 fork seed 与 sandbox/approval override 之后追加该既有 preset event。单次与可继续创建共用此规则;cold resume 只读取 child 日志。后续 parent 切换不改变该 child,后续 child 自己的切换仍可胜出。Read Only 与 Workspace Write 保留 sandbox 继承加 `approval: never`,因此可能保持 `custom`。Auto child 使用既有 lineage 与消息独立分类每次调用,不增加父 call metadata、委派记录、receipt、Header/descriptor 字段或 Session format。进程外 child 在父委派获准后保留自己的权限系统。
|
||||
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
en: c8e926bfc65d7195
|
||||
zh: e0e007960b68fe80
|
||||
/agent-note-in-tree-persistence-type-history/decision:
|
||||
en: 0d6257a57763ad48
|
||||
zh: 6ce3cb7228bba27a
|
||||
en: 94be3d6c7df3063b
|
||||
zh: 7e8fb08d7fd95f85
|
||||
/agent-note-in-tree-persistence-type-history/decision/finalized-checkpoints:
|
||||
en: 7dd42139850de3bf
|
||||
zh: 0783bff2f0084f71
|
||||
en: b508035a163340a3
|
||||
zh: ba5b2b7e8cef4b2f
|
||||
/agent-note-in-tree-persistence-type-history/alternatives-considered:
|
||||
en: 5cdac1b3f15f89f6
|
||||
zh: 36cda3b92d31c916
|
||||
en: 1cf6d0a5555ae50a
|
||||
zh: 70ea1ba535e09442
|
||||
/agent-note-in-tree-persistence-type-history/consequences:
|
||||
en: 672c8cb59d291dc2
|
||||
zh: cbec90fbca92320a
|
||||
@@ -10,13 +10,15 @@ A persisted event can retain the same payload type expression while a referenced
|
||||
|
||||
## Decision
|
||||
|
||||
The [reader-compatibility review decision](2026-10-08-session-reader-compatibility-review.md) supersedes the automatic version floor. This note owns normalized type history, per-root acknowledgements, and accepted checkpoint preservation.
|
||||
|
||||
One normalized type model supplies the readable persistence catalog, the complete schema inventory, and transitive per-root digests. Roots identify the logical Session header, the physical JSONL header line, the event envelope, and every repository-declared event. Source locations, alias names, ordinary comments, and property order are presentation details outside the digest. Explicit compatibility annotations carry fingerprinted policy data under the [source-attribution policy](2026-09-17-persistence-schema-review.md). Historical records retain source file paths without line numbers because their selected source trees differ from the current checkout. Referenced and recursive types participate in structural comparison; opaque values retain explicit coverage limits.
|
||||
|
||||
The dedicated [persistence-change records](../../../../docs/persistence-changes/README.md) bind acknowledgement to the after digest of each affected root. A generated companion stores complete after schemas. The initial record covers all roots; later records name each root's predecessor. A predecessor's after schema supplies the next before schema. Verification rejects ambiguous history and requires current source to match the terminal recorded state without consulting Git history or remote services.
|
||||
|
||||
Automatic classification permits optional body additions, required-to-optional body changes, and ordinary event additions within one version. The scoped [source-attribution policy](2026-09-17-persistence-schema-review.md) also permits qualified new producer kinds when both saved schemas opt in. The [event-payload version rule](../../../../docs/persistence-changes/README.md#compatibility-rules) admits higher numeric payload versions when every old alternative remains unchanged and records the older-reader limitation. Other structural changes require a version-bump decision that includes an increasing header version in that record. The [format-version procedure](../../../../docs/cookbook/adding-a-session-format-version.md) continues to own adjacent migration work. Every structural difference requires an explicit record, including additions allowed within the same version.
|
||||
Automatic classification permits optional body additions, required-to-optional body changes, and ordinary event additions within one version. The scoped [source-attribution policy](2026-09-17-persistence-schema-review.md) also permits qualified new producer kinds when both saved schemas opt in. The [event-payload version rule](../../../../docs/persistence-changes/README.md#compatibility-rules) admits higher numeric payload versions when every old alternative remains readable and records the older-reader limitation. Other structural changes require an explicit same-version choice in the recording CLI or an increasing header version. Every structural difference needs an acknowledgement; its Compatibility and Verification sections explain both reader directions and executed checks. CI validates saved decisions without inferring how they were authored. The [format-version procedure](../../../../docs/cookbook/adding-a-session-format-version.md) owns adjacent migration work when a bump is required.
|
||||
|
||||
The new document kind preserves the compatibility reasoning and evidence for a historical type transition. Agent Notes retain mechanism-level decisions; they do not become a growing inventory of individual acknowledgements. Machine declarations remain identical across the bilingual pair and are parsed once.
|
||||
The persistence-change document kind preserves compatibility reasoning and evidence for a historical type transition. Agent Notes retain mechanism-level decisions; they do not become a growing inventory of individual acknowledgements. Machine declarations remain identical across the bilingual pair and are parsed once.
|
||||
|
||||
Retrospective [release comparisons](../../../../docs/persistence-changes/releases/README.md) use a separate `persistence-release` kind and pinned tag manifest. They retain changed after schemas and reconstruct adjacent releases, including unchanged tags. Earlier releases predate these acknowledgement rules, so their classifications are informational and their observed versions are not rewritten to satisfy a modern version floor. Historical parsing preserves optional surface operations; the current parser and acknowledgement chain remain strict. This separation permits approximate backfills without turning them into authorization for current changes.
|
||||
|
||||
@@ -28,7 +30,7 @@ Explicit update refreshes an unaccepted terminal record without deleting its aut
|
||||
|
||||
### Finalized checkpoints
|
||||
|
||||
Finalization records acceptance independently of publication. A terminal record must not reuse its original header increase to authorize another breaking change to already-written data. The [status record](../../../../docs/session-format-status.md#finalization-record) therefore requires a complete compatibility checkpoint: accepted record semantics remain locked, while compatible current-source changes proceed through new records and breaking changes require a higher writer. The existing classifier and unknown-event protocol are unchanged. This adds no runtime field and cannot detect behavior changes hidden behind unchanged declarations. Coordinated edits to the in-tree authority and checkpoint still require review.
|
||||
Finalization records acceptance independently of publication. The [status record](../../../../docs/session-format-status.md#finalization-record) requires a complete compatibility checkpoint that locks accepted machine declarations and after schemas. Further schema changes need new acknowledgements; an explicit version-bump decision must contain its own header increase and cannot reuse an accepted transition. The checkpoint adds no runtime field, does not change unknown-event handling, and cannot detect behavior changes hidden behind unchanged declarations. Coordinated edits to the in-tree authority and checkpoint still require review.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -40,7 +42,7 @@ Finalization records acceptance independently of publication. A terminal record
|
||||
|
||||
**Treat every changed digest as a version bump.** Optional body additions, required-to-optional changes, and ordinary event additions do not always require a new persistence version. Conservative classification distinguishes these cases and leaves the compatibility explanation to review.
|
||||
|
||||
**Infer behavior from types or permit arbitrary compatibility overrides.** A type graph cannot prove replay semantics. The checker confines itself to detectable structure and rejects decisions below the mechanical classification; it does not claim to detect behavior-only changes.
|
||||
**Infer behavior from types or require proof of human review.** A type graph cannot establish replay semantics, and a recorded choice cannot establish how it was authored. The checker validates declared transitions and header-version constraints. Reviewers inspect actual reader behavior and the acknowledgement’s explanation independently.
|
||||
|
||||
## Consequences
|
||||
|
||||
|
||||
@@ -10,13 +10,15 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
[读取器兼容性评审决策](2026-10-08-session-reader-compatibility-review.zh.md)取代自动最低版本要求。本说明负责规范化类型历史、逐根确认与已接受检查点保留。
|
||||
|
||||
一个规范化类型模型提供可读持久化目录、完整 schema 清单和包含传递引用的逐根摘要。根标识逻辑会话头、物理 JSONL 头行、事件封装以及每个仓库内声明的事件。源码位置、别名名称、普通注释和属性顺序属于摘要之外的展示细节。历史记录保留源码文件路径而不包含行号,因为其选定的源码树与当前工作树不同。被引用类型和递归类型参与结构比较;不透明值保留明确的覆盖限制。显式兼容性标记依照[消息源归属策略](2026-09-17-persistence-schema-review.zh.md)携带纳入指纹的策略数据。
|
||||
|
||||
专用的[持久化变更记录](../../../../docs/persistence-changes/README.zh.md)将确认绑定到每个受影响根的变更后摘要。生成的伴随文件保存完整的变更后 schema。初始记录覆盖所有根;后续记录为每个根指定前驱。前驱的变更后 schema 提供下一次的变更前 schema。验证拒绝有歧义的历史,并要求当前源码匹配最终记录状态,无需查询 Git 历史或远端服务。
|
||||
|
||||
自动分类允许可选事件体新增、事件体属性从必选改为可选,以及普通事件新增保持同一版本。当双方保存的 schema 均声明支持时,受限的[消息源归属策略](2026-09-17-persistence-schema-review.zh.md)也允许添加符合条件的生产者 kind。[事件载荷版本规则](../../../../docs/persistence-changes/README.zh.md#compatibility-rules) 允许在原样保留所有旧分支时增加更高的数字载荷版本,并记录旧读取器限制。其他结构变更需要升版本决策,并在该记录中包含递增的头部版本。[格式版本流程](../../../../docs/cookbook/adding-a-session-format-version.zh.md)继续负责相邻迁移工作。每个结构差异都需要显式记录,包括允许保持同一版本的新增。
|
||||
自动分类允许可选事件体新增、事件体属性从必选改为可选,以及普通事件新增保持同一版本。当双方保存的 schema 均声明支持时,受限的[消息源归属策略](2026-09-17-persistence-schema-review.zh.md)也允许添加符合条件的生产者 kind。[事件载荷版本规则](../../../../docs/persistence-changes/README.zh.md#compatibility-rules)允许在每个旧分支仍可读取时增加更高的数字载荷版本,并记录旧读取器限制。其他结构变更要求在记录 CLI 中显式选择同版本,或递增头版本。每个结构差异都需要确认;其兼容性与验证部分说明两个方向的读取器行为及已执行检查。CI 验证保存的决策,不推断其编写方式。需要升版本时,[格式版本流程](../../../../docs/cookbook/adding-a-session-format-version.zh.md)负责相邻迁移工作。
|
||||
|
||||
新文档类型保留历史类型转换的兼容性说明和证据。Agent Note 保留机制层面的决策,不积累单次确认清单。双语对中的机器声明保持相同,且只解析一次。
|
||||
持久化变更文档类型保留历史类型转换的兼容性说明和证据。Agent Note 保留机制层面的决策,不积累单次确认清单。双语对中的机器声明保持相同,且只解析一次。
|
||||
|
||||
回溯性的[发行版本比较](../../../../docs/persistence-changes/releases/README.zh.md)使用独立的 `persistence-release` 类型和固定 tag 清单。它们保留变化后的 schema,并重建相邻版本,包括未变的 tag。早期版本先于这些确认规则,因此其分类只提供信息,观察到的版本号不会为满足当前最低版本要求而被改写。历史解析保留可选的 surface 操作;当前解析器和确认链仍保持严格。这一区分允许近似回填,同时不会将其变成当前变更的授权。
|
||||
|
||||
@@ -28,7 +30,7 @@ Status: implemented
|
||||
|
||||
### 定稿检查点
|
||||
|
||||
定稿独立于发布事实记录接受状态。末端记录不能复用原有头版本递增,继续授权对已写入数据进行破坏性变更。因此,[状态记录](../../../../docs/session-format-status.zh.md#finalization-record)要求完整兼容性检查点:已接受记录的语义保持锁定,当前源码的兼容变更通过新记录继续,破坏性变更要求更高的写入器版本。现有分类器与未知事件协议保持不变。这不会添加运行时字段,也不能检测未变声明后隐藏的行为变化。对目录内真源与检查点的协同修改仍须审阅。
|
||||
定稿独立于发布事实记录接受状态。[状态记录](../../../../docs/session-format-status.zh.md#finalization-record)要求完整兼容性检查点,锁定已接受的机器声明与变更后 schema。后续 schema 变化需要新的确认;显式升版本决策必须包含自身的头版本递增,不能复用已接受转换。检查点不添加运行时字段,不改变未知事件处理,也不能检测未变声明后隐藏的行为变化。对目录内真源与检查点的协同修改仍须审阅。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
@@ -40,7 +42,7 @@ Status: implemented
|
||||
|
||||
**把每次摘要变化都视为升版本。** 可选事件体新增、必选改可选,以及普通事件新增不一定需要新的持久化版本。保守分类区分这些情况,并将兼容性说明交给审阅。
|
||||
|
||||
**从类型推断行为,或允许任意兼容性豁免。** 类型图无法证明回放语义。检查器仅覆盖可检测结构,并拒绝低于机械分类要求的决策;它不声称能够检测纯行为变更。
|
||||
**从类型推断行为,或要求人类评审证明。** 类型图无法证明回放语义,所记录的选择也无法证明其编写方式。检查器验证声明的转换与头版本约束。评审者独立检查实际读取器行为与确认说明。
|
||||
|
||||
## 影响
|
||||
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
# Bilingual-pair consistency record for 2026-10-08-session-reader-compatibility-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/process/2026-10-08-session-reader-compatibility-review.md
|
||||
/agent-note-session-versions-follow-reader-compatibility:
|
||||
en: b27e6ad9824a02e4
|
||||
zh: 034a5dc50838db3d
|
||||
/agent-note-session-versions-follow-reader-compatibility/problem:
|
||||
en: fa4f3e56f3b22e07
|
||||
zh: a8010ab822474c2c
|
||||
/agent-note-session-versions-follow-reader-compatibility/decision:
|
||||
en: ab45f42c587793e8
|
||||
zh: b1c2eab9e8642824
|
||||
/agent-note-session-versions-follow-reader-compatibility/migration-obligations:
|
||||
en: b218b28f19c41f33
|
||||
zh: 7e99314983707984
|
||||
/agent-note-session-versions-follow-reader-compatibility/alternatives-considered:
|
||||
en: be63c3e727b18cd3
|
||||
zh: 91b4e2ffaa42bdb9
|
||||
/agent-note-session-versions-follow-reader-compatibility/consequences:
|
||||
en: 0ee67db581e8e03d
|
||||
zh: ece4f775c8008f8a
|
||||
@@ -0,0 +1,39 @@
|
||||
# Agent Note: Session versions follow reader compatibility
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-08-session-reader-compatibility-review.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
A structural schema difference does not establish that a Session needs a new integer version. An optional environment header field illustrates the cost: sessions that omit the feature can retain identical logs, while sessions that require it can be refused by older readers. A global bump also excludes feature-free sessions and requires a new generation and adjacent migration. Conversely, an unknown field or value does not establish safe refusal: permissive readers can ignore required meaning, and recovery can mistake unsupported records for damaged tails.
|
||||
|
||||
## Decision
|
||||
|
||||
The user chose reader behavior as the version criterion. A same-version change is permitted when older readers either resume correctly, safely ignore optional features while preserving their records, or refuse required features before execution, mutation, or tail repair. Feature absence must preserve existing behavior. Required-feature refusal needs an upgrade diagnostic; already released binaries retain their existing diagnostics, so review identifies any limitation explicitly.
|
||||
|
||||
New readers preserve the meaning of supported historical records and accept valid mixtures of old and new representations within one Session. Removing a required field or changing its representation is not automatically a bump: the replacement must remain distinguishable, and released-reader behavior must satisfy the same admission requirements. A version increase is required when no effective existing discriminator prevents unsafe interpretation. Reusing a logged value with a different meaning is one example; missing admission checks are another.
|
||||
|
||||
The schema classifier identifies structural changes requiring compatibility review instead of declaring them inherently version-breaking. Every schema difference still needs an acknowledgement. Established safe cases infer same-version; other same-version changes require an explicit decision when recording or updating. The existing Compatibility and Verification sections record reader behavior and executed checks. The checker validates the decision and recorded schemas; it cannot prove that the explanation matches runtime behavior. An explicit version-bump decision still requires its own increasing `SessionHeader.version`. Accepted machine records and checkpoints remain immutable; prose remains outside checkpoint hashes.
|
||||
|
||||
The user treats a recorded `same-version` decision as explicit regardless of whether the CLI or an editor produced it. The CLI requires `--decision same-version` for flagged record and update operations; CI validates the saved decision against history, schemas, and header-version constraints. Neither mechanism attests that a human reviewed the transition. Reviewers use base/head inventory comparison rather than current-tree consistency checks to inspect already acknowledged changes.
|
||||
|
||||
This decision partially supersedes the structural bump criterion in the [version-mechanism note](../architecture/2026-08-10-session-log-version-mechanism.md) and the automatic version floor in the [persistence-type-history note](2026-09-11-persistence-type-history.md). Their monotonic integer, required unknown-event default, type history, and immutable generation decisions remain active. The [review procedure](../../../../docs/cookbook/reviewing-persistence-type-changes.md) owns authoring and validation details.
|
||||
|
||||
## Migration obligations
|
||||
|
||||
One codec per integer version and one migration per adjacent pair remain sufficient. A version's reader supports its released representations without changing established meanings; an eventual successor migration accepts that same historical range, including mixed records. Historical conversion can keep producing a valid older representation within the target version. Same-version reads do not run an adjacent migration, so a compatibility claim cannot depend on an unimplemented normalization step. Header and payload extensions still need feature-specific admission and preservation through their actual consumers.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Require bumps for header changes and other structural edits.** This mechanically excludes safe refusal and optional features, imposing migration work even when existing discriminators already protect readers.
|
||||
|
||||
**Treat every unknown field or value as a refusal mechanism.** Released readers do not uniformly reject unknown values before recovery. Compatibility evidence must inspect the actual admission and mutation paths; a changed schema alone supplies no guarantee.
|
||||
|
||||
**Permit same-version changes without recorded reader evidence.** A schema graph cannot establish safe execution, lossless retention, historical interpretation, or mixed-record behavior. Explicit review preserves these obligations without making the classifier pretend to prove them.
|
||||
|
||||
**Add a separate JSON review object and hash its evidence.** The user rejected duplicating the existing Compatibility and Verification prose. Extra input fields and renewal rules add authoring work without establishing semantic safety. An explicit same-version choice and the existing acknowledgement keep the decision visible without a second evidence format.
|
||||
|
||||
## Consequences
|
||||
|
||||
The rule reduces unnecessary version transitions while making old-reader and new-reader evidence explicit. It changes review and acknowledgement enforcement, not runtime admission, diagnostics, codecs, or migration behavior. The environment example motivates the policy; it does not add an `env` field. Unsupported-feature detection, payload readers, and any necessary migration work remain obligations of each feature change. Focused verification must cover feature absence, optional retention or required refusal, historical input, and mixed representations as applicable; a written review is not a substitute for those results.
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
# Agent Note: Session 版本取决于读取器兼容性
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-08-session-reader-compatibility-review.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
schema 的结构差异不能证明 Session 需要新的整数版本。可选的环境头字段体现了这一成本:未使用该特性的会话可以保留完全相同的日志,需要该特性的会话则可以被旧读取器拒绝。全局升版本也会排除未使用特性的会话,并要求新的代际和相邻迁移。反过来,未知字段或值不能证明拒绝是安全的:宽松读取器可能忽略必需的含义,而恢复流程可能把不支持的记录误判为损坏的尾部。
|
||||
|
||||
## 决策
|
||||
|
||||
用户选择以读取器行为作为版本标准。当旧读取器能够正确恢复、安全忽略可选特性并保留其记录,或在执行、修改及尾部修复之前拒绝必需特性时,允许保持同一版本。未使用特性时必须保留现有行为。拒绝必需特性需要升级提示;已发布二进制保留其现有诊断,因此评审明确标出相关限制。
|
||||
|
||||
新读取器保留受支持历史记录的含义,并接受同一 Session 中新旧表示的有效混合。删除必需字段或改变其表示不自动要求升版本:替代表示必须可区分,已发布读取器的行为必须满足相同的准入要求。当不存在能够阻止不安全解释的有效既有判别标记时,必须升版本。将日志值复用于不同含义是一例;缺少准入检查是另一例。
|
||||
|
||||
schema 分类器标识需要兼容性评审的结构变更,而不将其直接判定为版本破坏。每项 schema 差异仍须确认。已明确安全的情形推断为同版本;其他同版本变更在记录或更新时要求显式决策。已有的兼容性与验证部分记录读取器行为和已执行检查。检查器验证决策与所记录的 schema,但无法证明说明与运行时行为一致。显式升版本决策仍须包含其自身递增的 `SessionHeader.version`。已接受的机器记录与检查点保持不可变;说明文字不参与检查点哈希。
|
||||
|
||||
用户将记录中的 `same-version` 决策视为显式选择,无论它由 CLI 还是编辑器产生。CLI 对被标记的记录与更新操作要求 `--decision same-version`;CI 按历史、schema 和头版本约束验证保存的决策。两种机制都不证明人类已评审转换。评审者使用 base/head 清单比较,而非当前目录一致性检查,来检查已确认的变更。
|
||||
|
||||
本决策部分取代[版本机制说明](../architecture/2026-08-10-session-log-version-mechanism.zh.md)中的结构性升版本标准,以及[持久化类型历史说明](2026-09-11-persistence-type-history.zh.md)中的自动最低版本要求。它们的单调整数、未知事件默认必需、类型历史与不可变代际决策仍然有效。[评审流程](../../../../docs/cookbook/reviewing-persistence-type-changes.zh.md)负责记录编写与验证细节。
|
||||
|
||||
## 迁移义务
|
||||
|
||||
每个整数版本一个 codec、每组相邻版本一个迁移仍然足够。一个版本的读取器支持其已发布表示,且不改变已确立的含义;后继迁移接受相同的历史范围,包括混合记录。历史转换可以继续产生目标版本内有效的旧表示。同版本读取不运行相邻迁移,因此兼容性声明不能依赖尚未实现的规范化步骤。头部与载荷扩展仍须在实际消费方中实现各自的准入与保留。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**对头部变更及其他结构编辑要求升版本。** 这会机械地排除安全拒绝和可选特性,即使既有判别标记已能保护读取器,也强加迁移工作。
|
||||
|
||||
**把每个未知字段或值都当作拒绝机制。** 已发布读取器并不统一在恢复前拒绝未知值。兼容性证据必须检查实际准入与修改路径;schema 变化本身不提供保证。
|
||||
|
||||
**允许同版本变更而不记录读取器证据。** schema 图无法证明安全执行、无损保留、历史解释或混合记录行为。明确评审保留这些义务,同时不让分类器假装已证明它们。
|
||||
|
||||
**添加独立的 JSON 评审对象并对证据做哈希。** 用户拒绝重复已有兼容性与验证说明。额外输入字段与更新规则增加编写工作,却不能证明语义安全性。显式同版本选择与已有确认记录使决策可见,无需第二种证据格式。
|
||||
|
||||
## 影响
|
||||
|
||||
该规则减少不必要的版本转换,同时明确要求旧读取器与新读取器的证据。它改变评审与确认校验,不改变运行时准入、诊断、codec 或迁移行为。环境示例说明政策动机,并未添加 `env` 字段。不支持特性的检测、载荷读取器及必要迁移仍由各项特性变更负责。针对性验证必须按需覆盖特性未使用、可选记录保留或必需特性拒绝、历史输入与混合表示;书面评审不能替代这些结果。
|
||||
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-09-26-team-direct-inbox.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/simplification/2026-09-26-team-direct-inbox.md
|
||||
/agent-note-team-messages-use-the-target-inbox:
|
||||
en: ae17b789652784b5
|
||||
zh: fa462ace95c8cdaa
|
||||
/agent-note-team-messages-use-the-target-inbox/problem:
|
||||
en: 83296699f94c77d3
|
||||
zh: e681a426bf0a703a
|
||||
/agent-note-team-messages-use-the-target-inbox/decision:
|
||||
en: 8d5fed34f19c7302
|
||||
zh: 25417793d3797775
|
||||
/agent-note-team-messages-use-the-target-inbox/alternatives-considered:
|
||||
en: e847556d8e9f1071
|
||||
zh: bf1abe2f0e201eac
|
||||
/agent-note-team-messages-use-the-target-inbox/consequences:
|
||||
en: 24085acb09438dc8
|
||||
zh: 27c92e8de8cf7dbe
|
||||
@@ -0,0 +1,27 @@
|
||||
# Agent Note: Team messages use the target inbox
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-09-26-team-direct-inbox.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
A second Team outbox duplicates Agent inbox ownership with separate identities, acknowledgements, persistence checkpoints, and retry ordering. Callers need an explicit admission result while the continuation owner already provides target locking, lifecycle authorization, and cold recovery.
|
||||
|
||||
## Decision
|
||||
|
||||
New Team sends return the accepted inbox `MessageId` or throw. They use live Lead Steer or the host-only subagent Steer adapter, retaining the actual sender through the existing `agent-message` source and the member name in content. Acceptance follows ordinary Agent persistence; it neither confirms model processing nor adds a synchronous flush guarantee. Team does not retain unaccepted intent, retry it after restart, or deduplicate explicit resends.
|
||||
|
||||
Historical mailbox records stay decodable and projected so existing Lead logs replay, but Team never delivers or acknowledges them. The package is experimental with no stability promise, and a second dispatch path with receipt inspection, per-target ordering, and its own disposal wait costs more than the undelivered old messages it would recover. This partially supersedes the mailbox decision in [Agent Teams](../feature/2026-08-05-agent-teams.md); roster and task decisions remain there. The [package reference](../../../../packages/experimental/agent-team/README.md#understand-the-implementation) owns the resulting limitation.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep the Team outbox for all sends.** This preserves a stronger retry promise but duplicates coordination and requires maintaining two acceptance states. Reintroduce it only for a concrete durable send-intent requirement that the Agent inbox cannot serve.
|
||||
|
||||
**Expand public sibling messaging permissions.** Team membership is separate from subagent parent authority. The host adapter authorizes the exact Lead without misattributing the sender or broadening public child controls.
|
||||
|
||||
**Drain historical pending items after restart.** This honors old accepted intent, but it keeps a second delivery owner beside the Agent inbox for data that only an experimental opt-in composition wrote. Removing the decoder as well is also rejected, because Lead logs containing mailbox records would then fail to replay.
|
||||
|
||||
## Consequences
|
||||
|
||||
New attempts have one identity and one inbox owner. A failed attempt is visible to its caller and has no Team retry promise. Historical logs still cost decoder and projection code. A historical message that its target had not recorded before the upgrade is never delivered. Tests cover direct Lead/peer acceptance, cold recovery, sender attribution, failures, cancellation, disposal, and that historical queued mail stays readable and undelivered after restart.
|
||||
@@ -0,0 +1,27 @@
|
||||
# Agent Note: Team 消息使用目标 inbox
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-09-26-team-direct-inbox.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
第二套 Team outbox 通过独立身份、确认、持久化检查点和重试排序重复了 Agent inbox 的职责。调用方需要明确的准入结果,而 continuation owner 已提供目标锁、生命周期授权和冷恢复。
|
||||
|
||||
## 决策
|
||||
|
||||
新的 Team 发送返回获准 inbox 的 `MessageId` 或抛出错误。它们使用 live Lead Steer 或 host-only subagent Steer adapter,通过现有 `agent-message` source 保留真实发送者,并在正文中保留成员名称。接收遵循普通 Agent 的持久化策略,既不确认模型已处理,也不增加同步 flush 保证。Team 不保留尚未接收的发送意图,不在重启后重试,也不对显式重发去重。
|
||||
|
||||
历史 mailbox 记录继续可解码并参与投影,使现有 Lead 日志能够回放,但 Team 不再投递或确认它们。该包是实验性的,没有稳定性承诺;另设一条带回执检查、同目标排序和独立 dispose 等待的投递路径,其代价高于它能补回的未投递旧消息。这部分取代了 [Agent Teams](../feature/2026-08-05-agent-teams.zh.md) 的 mailbox 决策;roster 和任务决策仍由原记录负责。[包参考](../../../../packages/experimental/agent-team/README.zh.md#understand-the-implementation)定义由此产生的限制。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**为所有发送保留 Team outbox。** 这保留了更强的重试承诺,但重复协调职责,并要求维护两个接收状态。只有具体的持久发送意图需求无法由 Agent inbox 满足时,才重新引入它。
|
||||
|
||||
**扩大公开的 sibling 消息权限。** Team 成员关系与 subagent parent 权限不同。host adapter 授权确切的 Lead,不会错误归属发送者,也不会扩大公开 child control 权限。
|
||||
|
||||
**重启后补送历史 pending 项。** 这能兑现已接收的旧意图,但会在 Agent inbox 之外保留第二个投递 owner,而这些数据只由实验性的可选组合写入。同时删除 decoder 的方案也被否决,因为包含 mailbox 记录的 Lead 日志将无法回放。
|
||||
|
||||
## 后果
|
||||
|
||||
新尝试只有一个身份和一个 inbox owner。失败尝试会向调用方报告,且没有 Team 重试承诺。历史日志仍需要 decoder 与投影代码。升级前目标尚未记录的历史消息永远不会被投递。测试覆盖直接 Lead/peer 接收、冷恢复、发送者归属、失败、取消、dispose(资源释放),以及重启后历史排队消息仍可读取且不被投递。
|
||||
@@ -0,0 +1,28 @@
|
||||
# Bilingual-pair consistency record for 2026-10-07-scaled-benchmark-report.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/testing/2026-10-07-scaled-benchmark-report.md
|
||||
/agent-note-scaled-machine-and-memory-pressure-benchmark-report:
|
||||
en: 486b21866106fb62
|
||||
zh: eb0f2e90eeb426a3
|
||||
/agent-note-scaled-machine-and-memory-pressure-benchmark-report/problem:
|
||||
en: d8e9dee3e22d691a
|
||||
zh: 53d9fae666f032b7
|
||||
/agent-note-scaled-machine-and-memory-pressure-benchmark-report/decision:
|
||||
en: 5587ef207abde949
|
||||
zh: a8c92e13b980c7c1
|
||||
/agent-note-scaled-machine-and-memory-pressure-benchmark-report/decision/machine-scaling:
|
||||
en: f532650e0720e66a
|
||||
zh: dfd6ffa6366c1f09
|
||||
/agent-note-scaled-machine-and-memory-pressure-benchmark-report/decision/memory-pressure:
|
||||
en: bec0d7592ed58486
|
||||
zh: 2be2350c142f133d
|
||||
/agent-note-scaled-machine-and-memory-pressure-benchmark-report/decision/exclusions:
|
||||
en: dc685a11156d6cd3
|
||||
zh: 519bbdbd0b4c69d7
|
||||
/agent-note-scaled-machine-and-memory-pressure-benchmark-report/alternatives-considered:
|
||||
en: 9011ce1b4702608d
|
||||
zh: 214f38794afe13fd
|
||||
/agent-note-scaled-machine-and-memory-pressure-benchmark-report/consequences:
|
||||
en: af04c3e97e85f1e3
|
||||
zh: 533a73e06f708c9d
|
||||
@@ -0,0 +1,43 @@
|
||||
# Agent Note: Scaled machine and memory-pressure benchmark report
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-07-scaled-benchmark-report.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Benchmark budgets state whether one endpoint regressed on the hosted runner. They do not tell readers how slow the same endpoint is on an older laptop or a typical cloud instance, or whether its working set fits a machine with 4 GB or 8 GB of available memory, so a passing or failing number does not convey user impact.
|
||||
|
||||
## Decision
|
||||
|
||||
Every `test:bench` run writes `benchmarks/.dsh-report/report.html`, a self-contained page without network resources. Bench files pass each budgeted wall-clock median to `recordTimings` and whole-process peak RSS to `recordPeakMemory` from [scaling-report.ts](../../../../benchmarks/support/scaling-report.ts) before asserting budgets. Each call carries a case description stating what the case measures and which user scenario it affects; the page shows it when the reader hovers the case. A Vitest global setup clears the results before the run and renders the page after it, including after failed cases. Budgets and assertions do not change.
|
||||
|
||||
### Machine scaling
|
||||
|
||||
[machine-profiles.ts](../../../../benchmarks/support/machine-profiles.ts) lists laptops from 2015, 2019, 2023, and 2026 with SSD storage, mainstream cloud instances with their default system disks, the hosted runners, and the M4 Pro calibration reference. Each profile has a PassMark single-thread rating, retrieved on 2026-10-07, its logical CPU count, and a typical 4 KiB queue-depth-1 latency of its storage. A scaled endpoint is `ms × ((1 − io) × cpuScore(measured) / cpuScore(target) × coreFactor + io × ioLatency(target) / ioLatency(measured))`.
|
||||
|
||||
Where the worker reports process CPU time over the endpoint interval, `io` is the measured share of wall time not spent on CPU, `max(0, 1 − cpuMs / ms)`, and the busy parallelism is `max(1, cpuMs / ms)`. The Session corpus, projection list, Session first history and Agent resume, and Agent continuation cases report it. Other endpoints declare a commented `io` beside the benchmark: in-memory Client and terminal work declares 0, and the browser, Session-open phase, and SDK profile endpoints declare estimates. `coreFactor` is `max(1, parallelism / cores(target)) / max(1, parallelism / cores(measured))`, so an endpoint that kept more CPUs busy than a target has slows down there. The page shows each default and its source, and readers can override and reset `io` per endpoint. The measuring machine is matched from the CPU model, uses its actual available CPU count, and can be changed in the page.
|
||||
|
||||
On Apple M5 Pro the first content-index search measured 58.7 s; the model predicts 168 s on the EPYC 7763 runner, against a recorded hosted median of 144 s.
|
||||
|
||||
### Memory pressure
|
||||
|
||||
The Session corpus list, search, and fork cases report whole-process peak RSS for the largest synthetic working sets. The page shows each peak as a share of 4 GB and 8 GB of available memory. Peaks are not scaled.
|
||||
|
||||
### Exclusions
|
||||
|
||||
Long-Session `first`, `input`, and `streamWall` include paced replay waits and are not recorded. The browser page heap is read after a forced garbage collection, so it is not a peak and is not recorded. Memory size and thermal limits are not modelled. Measured non-CPU time also contains timers, event-loop yields, and child scheduling, and the measuring run reads a just-written corpus that the page cache mostly holds, so cold-cache storage waits on a user machine can be larger. Garbage-collection helper threads add CPU time, so measured parallelism is typically 1–2 and storage wait overlapping that work is not visible. Declared endpoints, including the browser case where Chromium and the Host share the machine, assume one busy CPU.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Run cases under an operating-system memory limit.** A cgroup limit needs root on Linux CI and is unavailable on macOS. The measured peaks stay below 1 GB, so a 4 GB limit would not change the timed path while adding privileged setup to a required gate.
|
||||
|
||||
**Only declared storage shares.** Readers cannot judge a share they have to guess. Process CPU time is cheap to measure in the workers and gives a reproducible default, although the non-CPU time it leaves includes waits other than storage.
|
||||
|
||||
**Benchmark-wide storage share.** Session open restores and projects after closing its file, so one share per benchmark overstates high-latency disks for memory-only endpoints.
|
||||
|
||||
**Multi-core or Geekbench scores.** A multi-core score would overstate machines with many cores for endpoints dominated by one JavaScript thread; the measured busy parallelism applies core limits only where they matter. Geekbench pages were not retrievable for citation; PassMark single-thread ratings were.
|
||||
|
||||
## Consequences
|
||||
|
||||
Readers can see each endpoint on reviewed machines with measured defaults, and adjust the storage share, without changing any gate. The page depends on reviewed constants: cross-ISA ratings, shared vCPUs, and disk latency classes are coarse, so results are estimates rather than measurements. The memory view does not measure swap or reclaim costs. The `node 24 / benchmarks` job publishes the page as the `benchmark-report` artifact.
|
||||
@@ -0,0 +1,43 @@
|
||||
# Agent Note: 按机器缩放与内存压力的基准报告
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-07-scaled-benchmark-report.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
基准预算只说明某个端点是否在托管 runner 上回归。读者无法据此判断同一端点在旧笔记本或典型云服务器上有多慢,也无法判断其工作集能否放进 4 GB 或 8 GB 可用内存,因此通过或失败的数字无法体现对用户的影响。
|
||||
|
||||
## 决定
|
||||
|
||||
每次 `test:bench` 运行都会写出 `benchmarks/.dsh-report/report.html`,这是一个不依赖网络资源的独立页面。基准文件在断言预算之前,把每个有预算的墙钟中位数传给 [scaling-report.ts](../../../../benchmarks/support/scaling-report.ts) 的 `recordTimings`,把整进程峰值 RSS 传给 `recordPeakMemory`。每次调用都带有用例描述,说明该用例测量什么、影响哪个用户场景;读者悬停该用例时页面会显示它。Vitest global setup 在运行前清空结果,在运行后渲染页面,用例失败时也会渲染。预算和断言不变。
|
||||
|
||||
### 机器缩放
|
||||
|
||||
[machine-profiles.ts](../../../../benchmarks/support/machine-profiles.ts) 列出 2015、2019、2023、2026 年使用 SSD 的笔记本,带默认系统盘的主流云服务器,托管 runner,以及 M4 Pro 校准参考机。每个配置包含 2026-10-07 获取的 PassMark 单线程评分、逻辑 CPU 数,以及其存储的典型 4 KiB 队列深度 1 延迟。缩放后的端点为 `ms × ((1 − io) × cpuScore(measured) / cpuScore(target) × coreFactor + io × ioLatency(target) / ioLatency(measured))`。
|
||||
|
||||
当 worker 报告端点区间内的进程 CPU 时间时,`io` 是实测的非 CPU 墙钟时间占比 `max(0, 1 − cpuMs / ms)`,繁忙并行度为 `max(1, cpuMs / ms)`。Session 语料、投影列表、Session 首次历史与 Agent 恢复,以及 Agent 续聊用例会报告 CPU 时间。其他端点在基准旁声明并注释 `io`:内存中的 Client 与终端工作声明为 0,浏览器、Session-open 分阶段和 SDK profile 端点声明估计值。`coreFactor` 为 `max(1, parallelism / cores(target)) / max(1, parallelism / cores(measured))`,因此繁忙 CPU 数超过目标机器 CPU 数的端点会在该机器上变慢。页面显示每个默认值及其来源,读者可以按端点覆盖和重置 `io`。测量机器按 CPU 型号匹配,使用实际可用 CPU 数,也可以在页面中修改。
|
||||
|
||||
在 Apple M5 Pro 上,首次内容索引搜索实测 58.7 s;模型预测 EPYC 7763 runner 上为 168 s,托管 CI 记录的中位数为 144 s。
|
||||
|
||||
### 内存压力
|
||||
|
||||
Session 语料的列表、搜索和 fork 用例报告最大合成工作集的整进程峰值 RSS。页面把每个峰值显示为 4 GB 和 8 GB 可用内存的占比。峰值不做缩放。
|
||||
|
||||
### 排除项
|
||||
|
||||
长 Session 的 `first`、`input` 和 `streamWall` 包含按节奏回放的等待,不记录。浏览器页面堆在强制垃圾回收后读取,不是峰值,不记录。不建模内存容量和散热限制。实测的非 CPU 时间还包含定时器、事件循环让出和子进程调度;测量运行读取的是刚写入、主要位于页面缓存中的语料,因此用户机器上冷缓存的存储等待可能更长。垃圾回收辅助线程会增加 CPU 时间,因此实测并行度通常为 1–2,与这些工作重叠的存储等待不可见。声明的端点,包括 Chromium 与 Host 共用机器的浏览器用例,都假定只有一个繁忙 CPU。
|
||||
|
||||
## 备选方案
|
||||
|
||||
**在操作系统内存限制下运行用例。** Linux CI 上的 cgroup 限制需要 root,macOS 上不可用。实测峰值低于 1 GB,4 GB 限制不会改变计时路径,却会给必需门禁增加特权配置。
|
||||
|
||||
**只使用声明的存储占比。** 读者无法判断需要猜测的占比。在 worker 中测量进程 CPU 时间成本很低,并能给出可复现的默认值,尽管剩余的非 CPU 时间包含存储以外的等待。
|
||||
|
||||
**按基准统一存储占比。** Session open 在关闭文件后才执行恢复和投影,按基准使用一个占比会高估高延迟磁盘对纯内存端点的影响。
|
||||
|
||||
**多核或 Geekbench 分数。** 对于以单个 JavaScript 线程为主的端点,多核分数会高估多核机器;实测繁忙并行度只在相关时施加核心数限制。Geekbench 页面无法获取以供引用,PassMark 单线程评分可以获取。
|
||||
|
||||
## 影响
|
||||
|
||||
读者可以在经审阅的机器上查看每个端点及其实测默认值,并调整存储占比,而不改变任何门禁。页面依赖经审阅的常量:跨指令集评分、共享 vCPU 和磁盘延迟等级都较粗略,因此结果是估计值而不是测量值。内存视图不测量交换和回收成本。`node 24 / benchmarks` job 将该页面作为 `benchmark-report` 产物发布。
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Bilingual-pair consistency record for 2026-09-29-session-migration-follow-redirect.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/architecture/2026-09-29-session-migration-follow-redirect.md
|
||||
/agent-note-present-single-session-migration-through-follow-redirection:
|
||||
en: 294f1872bbef5f59
|
||||
zh: c4bf2852374fbba4
|
||||
/agent-note-present-single-session-migration-through-follow-redirection/problem:
|
||||
en: 8d019bda1e854df3
|
||||
zh: 49cbd8b8d869e406
|
||||
/agent-note-present-single-session-migration-through-follow-redirection/proposal:
|
||||
en: b5d43cb845d165ed
|
||||
zh: c5b3aa8eb7799134
|
||||
/agent-note-present-single-session-migration-through-follow-redirection/proposal/completion-milestone-remains-open:
|
||||
en: 2281727adab80d51
|
||||
zh: 4795be5613b37ede
|
||||
/agent-note-present-single-session-migration-through-follow-redirection/alternatives-considered:
|
||||
en: bfc5d2325e710601
|
||||
zh: 4f285a9f317cd6af
|
||||
/agent-note-present-single-session-migration-through-follow-redirection/acceptance-criteria:
|
||||
en: e5d810300cec5f99
|
||||
zh: 7bcef7a5ef99501e
|
||||
/agent-note-present-single-session-migration-through-follow-redirection/risks:
|
||||
en: 1704cb15dc3c0b5e
|
||||
zh: cc4576d6e47c3b22
|
||||
@@ -0,0 +1,43 @@
|
||||
# Agent Note: Present single-Session migration through follow redirection
|
||||
|
||||
Status: proposed
|
||||
|
||||
English | [中文](2026-09-29-session-migration-follow-redirect.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Opening historical Sessions can wait for lengthy format conversion. Ordinary loading cannot explain the current phase or show migration failure; placing progress inside history events would contaminate the model log and change journal sequence semantics.
|
||||
|
||||
## Proposal
|
||||
|
||||
Add a typed business redirect to single-Session history reads: page or follow returns a migration reference when preparation is required, the Client follows that migration for its current phase and subsequent progress, then returns to the original history read after success. “302” denotes a business result, not a browser HTTP redirect, and accepts no arbitrary URL.
|
||||
|
||||
The Session object layer owns redirection, waiting, failure, and reopening. React must observe an explicit migration state and render a dedicated component; failure retains its cause and retry action. Migration success returns only to history loading, and a valid snapshot establishes history readiness. Paging and reconnect retain existing history and drafts.
|
||||
|
||||
The actual persistence preparation owner retains migration ownership; multiple waiters share work and late joiners receive current state immediately. Closing one view releases only its waiter. Started durable publication retains its run-to-settlement policy. Progress enters neither the Session log nor `session.control` for this flow.
|
||||
|
||||
Create/adopt and full-text search keep automatic processing; fork and catalog reads use the [migration prerequisite](../../implemented/architecture/2026-09-29-explicit-session-history-preparation.md).
|
||||
|
||||
### Completion milestone remains open
|
||||
|
||||
Read-open currently produces validated in-memory history; write-open encodes, verifies, and publishes a new generation. If redirection waits only for read preparation, its reference must retain the prepared result until the original follow takes ownership rather than relying on an evictable short-lived cache. Waiting for publication instead adds write-permission and publication-time requirements to opening old history. Implementation must choose this behavior rather than using an ambiguous done state for both milestones.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Push migration progress through global control.** One history-opening operation can own its waiting, failure, and retry without independent global progress synchronization.
|
||||
|
||||
**Insert progress before the ordinary history journal's opening frame.** The generic journal remains snapshot-first; the Session adapter consumes redirection and migration frames, forwarding only ordinary history to the journal.
|
||||
|
||||
**Automatically replay every RPC that encounters migration.** Commands with committed effects cannot be replayed safely. This proposal covers recoverable history reads rather than a general command-retry platform.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- The Client shows actual migration phases before receiving the history snapshot; failure remains visible without an automatic retry loop.
|
||||
- Concurrent joiners, independent waiter cancellation, and reconnect observe the same operation's current state; an old attempt cannot overwrite a new one.
|
||||
- The resumed history read cannot restart the same migration after cache eviction; cancellation never rolls back a published generation.
|
||||
- Unknown totals produce no fabricated percentages. Unrelated lightweight requests and progress frames receive execution time during migration, with responsiveness measured separately.
|
||||
- Create/search, fork/catalog policies, and existing Session data retain their respective guarantees.
|
||||
|
||||
## Risks
|
||||
|
||||
Redirection spans two requests and needs explicit retention and cancellation ownership. Progress alone does not remove synchronous Host parsing and validation stalls; scheduling changes require measurements. The choice between read preparation and write publication remains an implementation prerequisite.
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
# Agent Note: 单会话迁移通过 follow 重定向呈现
|
||||
|
||||
Status: proposed
|
||||
|
||||
[English](2026-09-29-session-migration-follow-redirect.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
打开历史会话可能等待较长的格式转换。普通 loading 无法解释当前阶段或显示迁移失败;把进度放入历史事件又会污染模型日志,并改变历史流的序号语义。
|
||||
|
||||
## 提案
|
||||
|
||||
对单会话历史读取增加 typed 业务重定向:page 或 follow 在需要迁移时返回迁移引用,Client 转到迁移 follow,接收当前阶段和后续进度;成功后回到原历史读取。这里的“302”是业务结果,不是浏览器 HTTP 跳转,也不接受任意 URL。
|
||||
|
||||
Session 对象层编排跳转、等待、失败和重新读取。React 必须看到迁移中的明确状态并显示专门组件;失败保留原因和重试入口。迁移成功只回到历史加载态,收到有效 snapshot 才表示历史就绪。分页或重连已经拥有的历史与草稿保留。
|
||||
|
||||
迁移操作仍由实际持久化准备入口拥有;多个等待者共享任务,后加入者立即取得当前状态。关闭一个视图只释放其等待,不取消其他等待者。已开始的 durable publication 遵守既有运行至结算规则。进度不写入 Session log,不使用 `session.control` 作为本流程的进度通道。
|
||||
|
||||
create/adopt 和全文搜索保持自动处理;fork 和目录读取采用[迁移前置检查](../../implemented/architecture/2026-09-29-explicit-session-history-preparation.zh.md)。
|
||||
|
||||
### 完成里程碑仍待确定
|
||||
|
||||
现有 read-open 只生成经验证的内存结果;write-open 才编码、校验并发布新 generation。若重定向只等待读准备,迁移引用必须将准备结果保留到原 follow 接手,不能依赖可淘汰的短期缓存。若等待完整发布,则打开旧历史会额外要求写权限并等待发布。实现前必须确定这一行为,不以一个模糊的 done 混用两种里程碑。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**通过全局 control 推送迁移进度。** 单次打开历史的等待、错误与重试更适合由该读取流程拥有,不需要增加独立的全局进度状态同步。
|
||||
|
||||
**直接在普通 history journal 首帧前插入进度。** 通用 journal 保持 snapshot-first;Session 适配层消费重定向和迁移流,只把正常历史交给 journal。
|
||||
|
||||
**所有 RPC 遇到迁移都自动重发。** 已执行副作用的命令不能安全重放。此提案限定可安全恢复的历史读取,不扩展成通用命令重试平台。
|
||||
|
||||
## 验收条件
|
||||
|
||||
- 迁移等待开始后,Client 在收到历史 snapshot 前显示真实阶段;失败保持可见且不会自动循环重试。
|
||||
- 并发加入、单等待者取消和重连均取得同一操作的当前状态;旧尝试的通知不覆盖新尝试。
|
||||
- 完成后的历史读取不会因为缓存淘汰重新启动同一迁移;已发布 generation 不因取消而回滚。
|
||||
- 未知总量不显示伪造百分比。迁移时无关轻量请求和进度帧仍能得到执行机会,响应性单独测量。
|
||||
- create/search、fork/catalog 策略及已有 Session 数据保持各自约定。
|
||||
|
||||
## 风险
|
||||
|
||||
重定向跨越两次请求,需要明确保活与取消所有权。迁移进度并不会消除 Host 主线程的同步解析和验证暂停;相关调度调整必须依据实测。只读准备与写入发布的完成定义仍是实施前置选择。
|
||||
@@ -0,0 +1,46 @@
|
||||
# Bilingual-pair consistency record for 2026-10-02-plugin-market-index-format.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/feature/2026-10-02-plugin-market-index-format.md
|
||||
/agent-note-plugin-market-index-format:
|
||||
en: 8da20a8601d7cb29
|
||||
zh: 74368eed4d4b8e7c
|
||||
/agent-note-plugin-market-index-format/problem:
|
||||
en: ce5e4a362f558af6
|
||||
zh: 29d93e0655b99217
|
||||
/agent-note-plugin-market-index-format/proposal:
|
||||
en: ed9148e95b2ad7c2
|
||||
zh: 25a7add3c56fd681
|
||||
/agent-note-plugin-market-index-format/proposal/example:
|
||||
en: 1b1c960c966cf912
|
||||
zh: 97be87cb14893c26
|
||||
/agent-note-plugin-market-index-format/proposal/root-fields:
|
||||
en: 4fac5a9b8261342d
|
||||
zh: f9168a8a4209feab
|
||||
/agent-note-plugin-market-index-format/proposal/plugin-fields:
|
||||
en: 19d743bd04dc65b3
|
||||
zh: 810a7f98c4fe8301
|
||||
/agent-note-plugin-market-index-format/proposal/version-fields:
|
||||
en: d6c0fa99e1cbb068
|
||||
zh: 3d2a7b85299c679a
|
||||
/agent-note-plugin-market-index-format/proposal/install-arguments:
|
||||
en: c748cdc4d8ce349a
|
||||
zh: 5740fa0d6106e637
|
||||
/agent-note-plugin-market-index-format/proposal/dsh-compatibility:
|
||||
en: 00fe507b9b5dfea7
|
||||
zh: 55de6aaeb3865e84
|
||||
/agent-note-plugin-market-index-format/proposal/generator-obligations:
|
||||
en: 8e848a102fe9d86e
|
||||
zh: feff226ef8321e00
|
||||
/agent-note-plugin-market-index-format/proposal/reader-rules:
|
||||
en: 92903cbe87d9c0ca
|
||||
zh: 54af6cb33e9da252
|
||||
/agent-note-plugin-market-index-format/alternatives-considered:
|
||||
en: 3d7cf519bc8e0593
|
||||
zh: 328dc0747836eaa8
|
||||
/agent-note-plugin-market-index-format/acceptance-criteria:
|
||||
en: 753abee6adf39812
|
||||
zh: 1102bcfdd48f7e90
|
||||
/agent-note-plugin-market-index-format/risks:
|
||||
en: 41170d483b17df80
|
||||
zh: 1bfd8f44e0f09036
|
||||
@@ -0,0 +1,234 @@
|
||||
# Agent Note: Plugin market index format
|
||||
|
||||
Status: proposed
|
||||
|
||||
English | [中文](2026-10-02-plugin-market-index-format.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
DSH installs third-party bundles only from a spec the user already knows: an npm name, a Git URL, a tarball, or a local path. Nothing lets a user browse installable bundles. No official market exists, and one may be added later, so a market mechanism must work with indexes that third parties publish.
|
||||
|
||||
The smallest possible index is a list of npm names or Git repository URLs, one per line. It identifies every plugin, but a client rendering it must fetch each package's manifest, locale files, and icon before it can show a list. A list page that issues one or more requests per row is slow, fails partially, and puts metadata resolution in every client.
|
||||
|
||||
An index must therefore carry everything a list page displays: localized title and description, icon, author, available versions, compatibility, and publish time. Markets also want to publish their own data, such as categories, tags, downloads, and ratings, without DSH knowing what those values mean.
|
||||
|
||||
## Proposal
|
||||
|
||||
Separate the **index** from the **index generator**. DSH defines and reads one JSON index format. How an index is produced, and from which source list, belongs to the generator. DSH's reader, its Discover page, and a reference generator are separate changes that consume this format. Indexes list [profile plugin bundles](../../implemented/architecture/2026-08-05-profile-plugin-bundles.md) and install them through [guided plugin installation](../../implemented/architecture/2026-09-15-guided-plugin-installation.md).
|
||||
|
||||
The market reuses npm's package names, versions, ranges, distribution tags, and person declarations. Readers and generators use maintained npm parsers and validators. Additional fields or changed meanings require a plugin-market requirement that npm does not meet.
|
||||
|
||||
### Example
|
||||
|
||||
```json
|
||||
{
|
||||
"format": 1,
|
||||
"title": { "en": "Example Market", "zh": "示例市场" },
|
||||
"description": { "en": "Community DSH bundles", "zh": "社区 DSH bundle" },
|
||||
"metadata": {
|
||||
"categories": {
|
||||
"productivity": { "title": { "en": "Productivity", "zh": "效率" } }
|
||||
},
|
||||
"labels": {
|
||||
"official": { "title": { "en": "Official", "zh": "官方" } }
|
||||
},
|
||||
"sortKeys": {
|
||||
"downloads": { "title": { "en": "Downloads", "zh": "下载量" }, "order": "desc" },
|
||||
"rating": { "title": { "en": "Rating", "zh": "评分" }, "order": "desc" }
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "@example/dsh-auto-review",
|
||||
"title": { "en": "Auto Review", "zh": "自动审查" },
|
||||
"description": { "en": "Per-tool LLM authorization review", "zh": "逐工具 LLM 授权审查" },
|
||||
"icon": "icons/auto-review.svg",
|
||||
"author": "Example Labs",
|
||||
"homepage": "https://example.com/dsh-auto-review",
|
||||
"metadata": {
|
||||
"category": "productivity",
|
||||
"labels": ["official"],
|
||||
"keywords": ["review", "authorization"],
|
||||
"sortKeys": { "downloads": 12034, "rating": 4.6 }
|
||||
},
|
||||
"dist-tags": { "latest": "0.2.0-rc.1", "next": "0.2.0-rc.2" },
|
||||
"versions": [
|
||||
{
|
||||
"version": "0.2.0-rc.2",
|
||||
"publishTimestamp": 1790476800,
|
||||
"source": { "type": "npm" },
|
||||
"engines": { "dsh": ">=0.2.0 <0.3.0" }
|
||||
},
|
||||
{
|
||||
"version": "0.2.0-rc.1",
|
||||
"publishTimestamp": 1789872000,
|
||||
"source": { "type": "npm" },
|
||||
"engines": { "dsh": "0.2.0 - 0.2.4 || >=0.3.0 <0.4.0" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "dsh-plugin-foo",
|
||||
"title": "Foo",
|
||||
"icon": "https://example.com/foo.png",
|
||||
"dist-tags": { "latest": "1.0.0" },
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"publishTimestamp": 1788000000,
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/author/dsh-plugins",
|
||||
"commit": "3f9c2e1d8a4b6c0e7f1a2b3c4d5e6f708192a3b4",
|
||||
"path": "packages/foo"
|
||||
},
|
||||
"engines": { "dsh": "*" }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`LocalizedText` below is the `LocalizedText` type of [`dsh-package-manifest`](../../../../packages/util/package-manifest/src/types.ts), a string or an object of strings that contains `en`, with two additional rules: object keys are lowercase language ids matching `^[a-z]{2,8}(-[a-z0-9]{1,8})*$`, and every string contains at least one non-whitespace character.
|
||||
|
||||
### Root fields
|
||||
|
||||
| Field | Required | Rule |
|
||||
|---|---|---|
|
||||
| `format` | yes | Integer `1`. |
|
||||
| `title` | yes | `LocalizedText` naming the market. |
|
||||
| `description` | no | `LocalizedText`. |
|
||||
| `metadata` | no | Object holding the three declaration maps below. |
|
||||
| `metadata.categories` | no | Object mapping category id → `{ title: LocalizedText }`. |
|
||||
| `metadata.labels` | no | Object mapping label id → `{ title: LocalizedText }`. |
|
||||
| `metadata.sortKeys` | no | Object mapping sort-key id → `{ title: LocalizedText, order: "asc" \| "desc" }`; `order` is the initial sort direction. |
|
||||
| `plugins` | yes | Array of plugins, possibly empty; its order is the index's default ordering. |
|
||||
|
||||
Category, label, and sort-key ids are non-empty strings. Readers look ids up as own properties, so ids such as `constructor` have no special meaning.
|
||||
|
||||
### Plugin fields
|
||||
|
||||
| Field | Required | Rule |
|
||||
|---|---|---|
|
||||
| `name` | yes | Package name accepted by npm's maintained package-name validator for new packages; unique within the index. |
|
||||
| `title` | no | `LocalizedText`; a reader displays `name` when absent. |
|
||||
| `description` | no | `LocalizedText`. |
|
||||
| `icon` | no | Either `data:<type>;base64,<payload>`, where `<type>` is `image/svg+xml`, `image/png`, `image/jpeg`, or `image/webp`, `<payload>` is canonically padded base64, and the decoded payload is at most 256 KiB; or a URL reference whose resolution against the index URL is an `https:` URL or has the index URL's origin. |
|
||||
| `author` | no | String with at least one non-whitespace character. |
|
||||
| `homepage` | no | Absolute `https:` or `http:` URL of a page about the plugin. |
|
||||
| `metadata` | no | Object holding the values below. |
|
||||
| `metadata.category` | no | One declared category id. |
|
||||
| `metadata.labels` | no | Array of distinct declared label ids. |
|
||||
| `metadata.keywords` | no | Array of free-form strings; no root declaration required. |
|
||||
| `metadata.sortKeys` | no | Object mapping a declared sort-key id → finite number. |
|
||||
| `dist-tags` | no | Object mapping npm distribution-tag names to exact listed version strings; absent means no recorded channels. |
|
||||
| `versions` | yes | Non-empty array of versions; see below. Array order does not select the installation version. |
|
||||
|
||||
`metadata.keywords` conventionally comes from npm `package.json` `keywords`, but generators may use another source, add strings, or adjust values. Neither presence nor equality with the package manifest is required. Readers preserve the strings and their order; an empty array is valid.
|
||||
|
||||
### Version fields
|
||||
|
||||
| Field | Required | Rule |
|
||||
|---|---|---|
|
||||
| `version` | yes | Exact version parsed by npm's `node-semver`; preserve the complete string, including Git build metadata. The exact string is unique within the plugin. |
|
||||
| `publishTimestamp` | yes | Integer Unix timestamp in seconds, in `[0, 10^11)`. |
|
||||
| `source` | yes | Where `version` is installed from; see below. |
|
||||
| `engines` | yes | Object containing `dsh`. |
|
||||
| `engines.dsh` | yes | Non-blank valid npm SemVer range declared in the source manifest; copied unchanged. `*` explicitly declares support for any DSH version. |
|
||||
|
||||
A relative `icon` reference resolves against the index URL, as an HTML image reference resolves against its page. For an index at `https://market.example/dsh/index.json`, `"icon": "icons/foo.svg"` names `https://market.example/dsh/icons/foo.svg`. A generator can therefore publish the index and its icon files as one directory on any static host without knowing the final URL. Absolute icon URLs on other origins must use `https:`; the same-origin allowance lets an `http:` index on a loopback or intranet host serve its own icons. Readers resolve the reference when they read the index, so clients receive absolute URLs.
|
||||
|
||||
`source` takes one of two forms, and readers reject any other `type`:
|
||||
|
||||
- `{ "type": "npm", "registry"?: string }`. `registry` is an `http:` or `https:` URL without credentials, query, or fragment; `http:` is admitted for intranet mirrors, as in DSH's [install registries](../../implemented/architecture/2026-09-18-plugin-install-registries.md).
|
||||
- `{ "type": "git", "url": string, "commit": string, "path"?: string }`. `url` is a canonical `https:` URL (equal to its WHATWG URL serialization) with at least two non-empty path segments and no trailing slash, without credentials, query, or fragment, and not ending in `.tgz` or `.tar.gz`. `commit` is a lowercase 40-hex-digit SHA. `path` is a `/`-separated relative directory whose segments contain only ASCII letters, digits, and `._@+-`, and are neither `.` nor `..`; absent means the repository root.
|
||||
|
||||
### Install arguments
|
||||
|
||||
A client installs one version with exactly these pnpm arguments:
|
||||
|
||||
- npm: the spec `<name>@<version>`. Without `registry`, the client asks its configured registries as it does for any install. With `registry`, the client follows DSH's [registry plan](../../implemented/architecture/2026-09-18-plugin-install-registries.md): it asks `registry` first and falls back to its other configured registries only when `registry` is one of them; any other `registry` is asked alone, so a private registry never falls through to a public one that may publish a different package under the same name and version.
|
||||
- git: the spec `git+<url>.git#<commit>`, without doubling a trailing `.git`, followed by `&path:/<path>` when `path` is present. The `git+` prefix makes pnpm treat URLs with more than two path segments, such as GitLab subgroups, as Git repositories.
|
||||
|
||||
### DSH compatibility
|
||||
|
||||
Every market-listed version must declare valid `engines.dsh`. The reader and market-origin installation evaluate that range against the running DSH version with npm's `node-semver` and `includePrerelease: true`. There is no peer fallback or range synthesis. npm package peer resolution remains outside market host compatibility.
|
||||
|
||||
Installation rechecks the source manifest before accepting a market-origin package. An exact local `name@version` exemption may permit a valid range that rejects the runtime; it cannot make a missing or invalid declaration eligible. Exemptions are not published in the index.
|
||||
|
||||
### Generator obligations
|
||||
|
||||
A reader cannot verify these rules; a generator that violates them publishes a misleading index.
|
||||
|
||||
- `name` and each `version` equal the package manifest of the installed source.
|
||||
- `publishTimestamp` is the registry publish time of `version` for npm sources and the committer time of `commit` for Git sources.
|
||||
- `engines.dsh` copies the manifest's declared range unchanged. A version with a missing, blank, or invalid range is omitted with a diagnostic. A plugin with no eligible version is omitted.
|
||||
- `dist-tags` snapshots npm's [distribution tags](https://docs.npmjs.com/cli/v11/commands/npm-dist-tag/), retaining only targets present among the eligible listed versions. For Git sources, the publisher or market supplies the same exact tag-to-version mapping.
|
||||
- Each listed version declares `dsh.bundle`. Installation independently refuses a package without a bundle patch.
|
||||
- `title`, `description`, and `icon` come from the highest listed version's locale metadata, manifest fields, and icon under the installed-plugin display rules. Use npm's `node-semver.rsort()` order, retaining input order for ties. This metadata choice does not select the installation version. `homepage` copies that version's manifest `homepage` and is absent without one.
|
||||
- `author` comes from the same highest listed version's npm author declaration. Copy an object's `name`; parse shorthand strings with a maintained npm person normalizer. Omit an absent or whitespace-only name and contact fields. Normalize a fresh projection, leaving the source manifest untouched.
|
||||
|
||||
### Reader rules
|
||||
|
||||
A reader rejects the whole index when the document violates any rule in Root fields, Plugin fields, Version fields, or the `source` forms, including an undeclared category, label, or sort-key reference, an unlisted distribution-tag target, a duplicate `name` or exact `version` string, or a `format` other than `1`. Each such defect is a generator bug, and dropping entries silently would hide it. Readers may bound the document size.
|
||||
|
||||
A reader ignores members that this note does not define, at every level; an unknown `source.type` value is rejected. Format 1 can then gain optional members without breaking existing readers; a change that existing readers would misinterpret uses a new `format` value.
|
||||
|
||||
Tag names must parse as registry tag specs under npm's maintained `npm-package-arg`; targets are exact listed version strings. npm sources use canonical published identifiers (`node-semver.valid(version) === version`); Git sources retain build metadata and pin their exact source by commit.
|
||||
|
||||
Categories, labels, and sort keys are opaque: a reader filters and sorts by them, displays their `title`, and never interprets an id. Readers display declarations in the order of their localized `title`. When sorting plugins, a plugin without a value for the selected sort key follows all plugins with a value in either direction, and equal values keep the index order. Sorting by publish time uses a plugin's greatest `publishTimestamp`.
|
||||
|
||||
A reader evaluates the required `engines.dsh` range under DSH compatibility. Versions use npm's `node-semver.rsort()` order, retaining index order for ties. Exact tag targets, not this display order, select installation versions.
|
||||
|
||||
The initial channel is `latest`. Its exact target is the publisher- or market-selected version. Clients install that recorded version or commit, never `name@latest`. Missing or incompatible `latest` requires explicit version or channel selection; clients do not switch automatically. Installation confirmation identifies the version, selected channel if any, prerelease status, index URL, and source.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**A plain list of npm names or repository URLs.** It identifies every plugin but forces one or more requests per row before a list renders, which is the cost this format removes.
|
||||
|
||||
**One opaque install spec string per version.** A reader cannot verify that a free-form spec is pinned. A structured `source` with an exact `version`, or a `commit`, lets the reader verify pinning and lets the client build the spec it installs.
|
||||
|
||||
**Installing the latest version instead of a pinned one.** The installed build could then differ from the metadata the list displayed, and a package release would change what an unchanged index installs.
|
||||
|
||||
**One entry per version.** Every version would repeat the title, description, icon, author, and market metadata, and readers would group entries by `name` to show one plugin.
|
||||
|
||||
**One `source` per plugin, with only the pin per version.** A plugin that moves to another repository, directory, or registry between releases could not list its older versions.
|
||||
|
||||
**Display fields per version.** A list shows one row per plugin, so older display text, icons, authors, and homepages would cost index size without a reader that shows them.
|
||||
|
||||
**Inline icons only.** A list without extra requests needs inline icons, but 256 KiB of raw bytes is about 341 KiB as base64, so a 100-plugin index could reach about 35 MB. URL icons trade extra requests, and disclosure of the user's IP address to the image host, for a small index.
|
||||
|
||||
**Inferring host compatibility from peer dependencies.** The market defines a new standard requiring an author-declared `engines.dsh`. Existing packages must add that declaration before listing; no fallback, workspace mapping, or range intersection is needed.
|
||||
|
||||
**Copying the complete npm person object into the display field.** The list needs a name. The generator supports npm's original declaration while omitting unused contact fields.
|
||||
|
||||
**Opaque sort values for time, including ISO date strings.** Publish time has fixed semantics that clients format and sort, so it is a typed field. Without time, sort values can be numbers only, which needs no per-key value-type rule.
|
||||
|
||||
**Root-level `labels` and `sortKeys`, and a plugin field named `sort`.** Grouping market-defined declarations and values under `metadata` at both levels, with matching member names except the single-valued `category`, separates them from fields that clients interpret.
|
||||
|
||||
**Declaration order as display order.** JavaScript reorders integer-like object keys, so declaration order would need either an id pattern or array declarations. Clients sort declarations by their localized title instead.
|
||||
|
||||
**Selecting the greatest compatible version.** npm's `latest` can name an older release. Inferring a target from SemVer ranking loses the recorded channel choice and is ambiguous for equally ranked builds.
|
||||
|
||||
**Automatic prerelease fallback.** Missing or incompatible channel targets do not mean that the user selected a prerelease.
|
||||
|
||||
**Rejecting unknown members.** Every optional addition would then require a new `format` value and break every deployed reader.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- A validator accepts the example above and rejects one violation of each rule in Root fields, Plugin fields, Version fields, and the `source` forms, with a diagnostic naming the JSON path.
|
||||
- Install arguments built from every example version are accepted by DSH's install-spec parser and by pnpm.
|
||||
- A validator ignores unknown members at every level.
|
||||
- Generator and validator refuse missing, blank, and invalid `engines.dsh`; omitted versions leave no dangling tag target. Index and market-origin installation agree on valid ranges, including hyphen ranges, prereleases, and valid ranges that reject the runtime. Exemptions never bypass the declaration requirement.
|
||||
- Exact tag targets select the same source after equally ranked Git build versions are reordered. Missing targets are rejected; absent or incompatible `latest` causes no automatic switch.
|
||||
- npm author objects and shorthand strings produce the declared name without contact fields or mutation of source identity.
|
||||
|
||||
## Risks
|
||||
|
||||
- An index is unsigned. Titles, authors, labels, and scores are market claims; an `official` label is not DSH verification. Clients must show the index URL and each version's `source`.
|
||||
- A Git version's `name`, `version`, and `engines.dsh` are claims about the repository. A repository can claim any name, including an `@deepseek-ai/dsh-` name, and the installed manifest decides what is actually installed.
|
||||
- An npm version's `source.registry` is equally a claim: it directs the install of any `name`, including an `@deepseek-ai/dsh-` name, to a registry the market chooses, and that registry decides what is installed.
|
||||
- URL icons disclose the user's IP address to image hosts.
|
||||
- Pinned versions and distribution tags stay stale until the index is regenerated.
|
||||
- Market text reaches users, and models through any client tool that lists plugins, without review by DSH.
|
||||
@@ -0,0 +1,234 @@
|
||||
# Agent Note: 插件市场索引格式
|
||||
|
||||
Status: proposed
|
||||
|
||||
[English](2026-10-02-plugin-market-index-format.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
DSH 只能从用户已知的 spec 安装第三方组合包(bundle):npm 包名、Git URL、tarball 或本地路径。用户无法浏览可安装的组合包。目前没有官方市场,以后可能会增加官方市场,因此市场机制必须支持第三方发布的索引。
|
||||
|
||||
最小的索引是一份 npm 包名或 Git 仓库 URL 列表,每行一项。它能标识每个插件,但客户端渲染列表前必须为每个包获取 manifest(元数据清单)、语言文件和图标。列表页面每行发出一个或多个请求时,加载慢、可能部分失败,并且每个客户端都要实现元数据解析。
|
||||
|
||||
因此,索引必须包含列表页面显示的全部内容:本地化标题和描述、图标、作者、可用版本、兼容性和发布时间。市场还需要发布自己的数据,例如分类、标签、下载量和评分,而 DSH 不需要理解这些值的含义。
|
||||
|
||||
## 提案
|
||||
|
||||
将**索引**与**索引生成器**分开。DSH 定义并读取一种 JSON 索引格式。索引如何生成、基于哪份源列表生成,由生成器负责。DSH 的读取器、Discover 页面和参考生成器是使用此格式的独立变更。索引列出[profile 插件组合包](../../implemented/architecture/2026-08-05-profile-plugin-bundles.zh.md),并通过[引导式插件安装](../../implemented/architecture/2026-09-15-guided-plugin-installation.zh.md)安装它们。
|
||||
|
||||
市场复用 npm 的包名、版本、范围、发布标签和 person 声明。读取器与生成器使用维护中的 npm 解析器和校验器。新增字段或改变含义需要 npm 无法满足的插件市场需求。
|
||||
|
||||
### 示例
|
||||
|
||||
```json
|
||||
{
|
||||
"format": 1,
|
||||
"title": { "en": "Example Market", "zh": "示例市场" },
|
||||
"description": { "en": "Community DSH bundles", "zh": "社区 DSH bundle" },
|
||||
"metadata": {
|
||||
"categories": {
|
||||
"productivity": { "title": { "en": "Productivity", "zh": "效率" } }
|
||||
},
|
||||
"labels": {
|
||||
"official": { "title": { "en": "Official", "zh": "官方" } }
|
||||
},
|
||||
"sortKeys": {
|
||||
"downloads": { "title": { "en": "Downloads", "zh": "下载量" }, "order": "desc" },
|
||||
"rating": { "title": { "en": "Rating", "zh": "评分" }, "order": "desc" }
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "@example/dsh-auto-review",
|
||||
"title": { "en": "Auto Review", "zh": "自动审查" },
|
||||
"description": { "en": "Per-tool LLM authorization review", "zh": "逐工具 LLM 授权审查" },
|
||||
"icon": "icons/auto-review.svg",
|
||||
"author": "Example Labs",
|
||||
"homepage": "https://example.com/dsh-auto-review",
|
||||
"metadata": {
|
||||
"category": "productivity",
|
||||
"labels": ["official"],
|
||||
"keywords": ["review", "authorization"],
|
||||
"sortKeys": { "downloads": 12034, "rating": 4.6 }
|
||||
},
|
||||
"dist-tags": { "latest": "0.2.0-rc.1", "next": "0.2.0-rc.2" },
|
||||
"versions": [
|
||||
{
|
||||
"version": "0.2.0-rc.2",
|
||||
"publishTimestamp": 1790476800,
|
||||
"source": { "type": "npm" },
|
||||
"engines": { "dsh": ">=0.2.0 <0.3.0" }
|
||||
},
|
||||
{
|
||||
"version": "0.2.0-rc.1",
|
||||
"publishTimestamp": 1789872000,
|
||||
"source": { "type": "npm" },
|
||||
"engines": { "dsh": "0.2.0 - 0.2.4 || >=0.3.0 <0.4.0" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "dsh-plugin-foo",
|
||||
"title": "Foo",
|
||||
"icon": "https://example.com/foo.png",
|
||||
"dist-tags": { "latest": "1.0.0" },
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"publishTimestamp": 1788000000,
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/author/dsh-plugins",
|
||||
"commit": "3f9c2e1d8a4b6c0e7f1a2b3c4d5e6f708192a3b4",
|
||||
"path": "packages/foo"
|
||||
},
|
||||
"engines": { "dsh": "*" }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
下文中的 `LocalizedText` 即 [`dsh-package-manifest`](../../../../packages/util/package-manifest/src/types.ts) 中的 `LocalizedText` 类型,即字符串或包含 `en` 的字符串对象,并附加两条规则:对象的键是匹配 `^[a-z]{2,8}(-[a-z0-9]{1,8})*$` 的小写语言 id,每个字符串至少包含一个非空白字符。
|
||||
|
||||
### 根字段
|
||||
|
||||
| 字段 | 必填 | 规则 |
|
||||
|---|---|---|
|
||||
| `format` | 是 | 整数 `1`。 |
|
||||
| `title` | 是 | 市场名称,类型为 `LocalizedText`。 |
|
||||
| `description` | 否 | `LocalizedText`。 |
|
||||
| `metadata` | 否 | 包含下列三个声明映射的对象。 |
|
||||
| `metadata.categories` | 否 | 对象,将分类 id 映射为 `{ title: LocalizedText }`。 |
|
||||
| `metadata.labels` | 否 | 对象,将标签 id 映射为 `{ title: LocalizedText }`。 |
|
||||
| `metadata.sortKeys` | 否 | 对象,将排序键 id 映射为 `{ title: LocalizedText, order: "asc" \| "desc" }`;`order` 是初始排序方向。 |
|
||||
| `plugins` | 是 | 插件数组,可以为空;数组顺序即索引的默认排序。 |
|
||||
|
||||
分类、标签和排序键的 id 均为非空字符串。读取器按自有属性查找 id,因此 `constructor` 等 id 没有特殊含义。
|
||||
|
||||
### 插件字段
|
||||
|
||||
| 字段 | 必填 | 规则 |
|
||||
|---|---|---|
|
||||
| `name` | 是 | npm 维护的包名校验器接受的新包名称;在索引内唯一。 |
|
||||
| `title` | 否 | `LocalizedText`;缺省时读取器显示 `name`。 |
|
||||
| `description` | 否 | `LocalizedText`。 |
|
||||
| `icon` | 否 | 以下两种形式之一:`data:<type>;base64,<payload>`,其中 `<type>` 为 `image/svg+xml`、`image/png`、`image/jpeg` 或 `image/webp`,`<payload>` 是按规范填充的 base64,解码后的载荷不超过 256 KiB;或者 URL 引用,它相对于索引 URL 解析的结果是 `https:` URL,或与索引 URL 同源。 |
|
||||
| `author` | 否 | 至少包含一个非空白字符的字符串。 |
|
||||
| `homepage` | 否 | 介绍该插件的页面的绝对 `https:` 或 `http:` URL。 |
|
||||
| `metadata` | 否 | 包含下列值的对象。 |
|
||||
| `metadata.category` | 否 | 一个已声明的分类 id。 |
|
||||
| `metadata.labels` | 否 | 由互不相同的已声明标签 id 组成的数组。 |
|
||||
| `metadata.keywords` | 否 | 自由字符串数组;无需在根部声明。 |
|
||||
| `metadata.sortKeys` | 否 | 对象,将已声明的排序键 id 映射为有限数值。 |
|
||||
| `dist-tags` | 否 | 将 npm 发布标签名称映射为已列出的精确版本字符串的对象;缺省表示未记录发布渠道。 |
|
||||
| `versions` | 是 | 非空的版本数组;见下文。数组顺序不选择安装版本。 |
|
||||
|
||||
`metadata.keywords` 按约定来自 npm `package.json` 的 `keywords`,但生成器可以使用其他来源、添加字符串或调整值。不要求提供此字段,也不要求它与包 manifest 一致。读取器保留字符串及其顺序;允许空数组。
|
||||
|
||||
### 版本字段
|
||||
|
||||
| 字段 | 必填 | 规则 |
|
||||
|---|---|---|
|
||||
| `version` | 是 | npm 的 `node-semver` 解析的精确版本;保留完整字符串,包括 Git 版本编译信息。精确字符串在插件内唯一。 |
|
||||
| `publishTimestamp` | 是 | 以秒为单位的整数 Unix 时间戳,取值范围为 `[0, 10^11)`。 |
|
||||
| `source` | 是 | `version` 的安装来源;见下文。 |
|
||||
| `engines` | 是 | 包含 `dsh` 的对象。 |
|
||||
| `engines.dsh` | 是 | 源 manifest 声明的非空白、有效 npm SemVer 范围;原样复制。`*` 显式声明支持任意 DSH 版本。 |
|
||||
|
||||
相对的 `icon` 引用相对于索引 URL 解析,与 HTML 图片引用相对于所在页面解析的方式相同。对于位于 `https://market.example/dsh/index.json` 的索引,`"icon": "icons/foo.svg"` 指向 `https://market.example/dsh/icons/foo.svg`。因此,生成器可以把索引和图标文件作为一个目录发布到任意静态主机,无需知道最终 URL。指向其他源的绝对图标 URL 必须使用 `https:`;同源规则允许位于 loopback 或内网主机上的 `http:` 索引提供自己的图标。读取器在读取索引时解析该引用,因此客户端收到的是绝对 URL。
|
||||
|
||||
`source` 采用以下两种形式之一,读取器拒绝其他任何 `type`:
|
||||
|
||||
- `{ "type": "npm", "registry"?: string }`。`registry` 是不含凭据、查询或片段的 `http:` 或 `https:` URL;允许 `http:` 是为了支持内网镜像,与 DSH 的[安装 registry](../../implemented/architecture/2026-09-18-plugin-install-registries.zh.md) 一致。
|
||||
- `{ "type": "git", "url": string, "commit": string, "path"?: string }`。`url` 是规范的 `https:` URL(等于其 WHATWG URL 序列化结果),至少包含两个非空路径段且不以斜杠结尾,不含凭据、查询或片段,且不以 `.tgz` 或 `.tar.gz` 结尾。`commit` 是 40 位小写十六进制 SHA。`path` 是以 `/` 分隔的相对目录,各段只包含 ASCII 字母、数字和 `._@+-`,且不是 `.` 或 `..`;缺省表示仓库根目录。
|
||||
|
||||
### 安装参数
|
||||
|
||||
客户端安装某个版本时恰好使用以下 pnpm 参数:
|
||||
|
||||
- npm:spec 为 `<name>@<version>`。没有 `registry` 时,客户端像任何安装一样询问它配置的 registry。存在 `registry` 时,客户端遵循 DSH 的 [registry 计划](../../implemented/architecture/2026-09-18-plugin-install-registries.zh.md):先询问 `registry`,仅当它属于客户端配置的 registry 之一时才回退到其他已配置 registry;其他 `registry` 单独询问,因此私有 registry 不会回退到可能以相同名称和版本发布不同包的公共 registry。
|
||||
- git:spec 为 `git+<url>.git#<commit>`,`url` 已以 `.git` 结尾时不重复添加;存在 `path` 时在其后追加 `&path:/<path>`。`git+` 前缀使 pnpm 将路径段多于两个的 URL(例如 GitLab 子组)视为 Git 仓库。
|
||||
|
||||
### DSH 兼容性
|
||||
|
||||
市场列出的每个版本都必须声明有效的 `engines.dsh`。读取器与市场来源的安装使用 npm 的 `node-semver` 和 `includePrerelease: true`,根据当前运行的 DSH 版本求值。不存在对等依赖回退或范围合成。npm 包的对等依赖解析不属于市场宿主兼容性检查。
|
||||
|
||||
安装接受市场来源的包前,会重新检查源 manifest。精确的本地 `name@version` 豁免可以允许有效范围拒绝当前运行时的情况,但不能使缺失或无效声明符合列出条件。豁免不发布在索引中。
|
||||
|
||||
### 生成器义务
|
||||
|
||||
读取器无法验证以下规则;违反这些规则的生成器会发布误导性的索引。
|
||||
|
||||
- `name` 和每个 `version` 与所安装来源中的包 manifest 一致。
|
||||
- 对于 npm 来源,`publishTimestamp` 是 `version` 在 registry 中的发布时间;对于 Git 来源,它是 `commit` 的提交者时间。
|
||||
- `engines.dsh` 原样复制 manifest 声明的范围。缺失、空白或无效范围的版本会被省略并给出诊断。没有合格版本的插件会被省略。
|
||||
- `dist-tags` 是 npm [发布标签](https://docs.npmjs.com/cli/v11/commands/npm-dist-tag/)的快照,只保留指向合格且已列出版本的目标。对于 Git 来源,发布者或市场提供同样的精确标签到版本映射。
|
||||
- 列出的每个版本都声明了 `dsh.bundle`。安装时也会独立拒绝没有组合包 patch 的包。
|
||||
- `title`、`description` 和 `icon` 来自所列最高版本的语言元数据、manifest 字段和图标,采用已安装插件的显示规则。使用 npm 的 `node-semver.rsort()` 顺序,相等时保留输入顺序。元数据选择不选择安装版本。`homepage` 复制该版本 manifest 的 `homepage`,manifest 没有该字段时省略。
|
||||
- `author` 来自同一最高版本的 npm 作者声明。复制对象的 `name`;使用维护中的 npm person 归一化器解析简写字符串。省略缺失或全空白的名称以及联系字段。归一化新建的投影,不修改源 manifest。
|
||||
|
||||
### 读取器规则
|
||||
|
||||
文档违反根字段、插件字段、版本字段或 `source` 形式中的任何规则时,读取器拒绝整个索引,包括引用了未声明的分类、标签或排序键,发布标签指向未列出的目标,`name` 或精确的 `version` 字符串重复,以及 `format` 不是 `1`。每个此类缺陷都是生成器 bug,静默丢弃条目会掩盖它。读取器可以限制文档大小。
|
||||
|
||||
读取器在每一层都忽略本 Agent Note 未定义的成员;未知的 `source.type` 值会被拒绝。这样格式 1 可以增加可选成员而不破坏现有读取器;现有读取器会误解的变更使用新的 `format` 值。
|
||||
|
||||
标签名称必须能被 npm 维护的 `npm-package-arg` 解析为 registry 标签 spec;目标是已列出的精确版本字符串。npm 来源使用规范发布标识(`node-semver.valid(version) === version`);Git 来源保留版本编译信息并用提交固定精确来源。
|
||||
|
||||
分类、标签和排序键对读取器不透明:读取器按它们筛选和排序,显示其 `title`,从不解释 id 的含义。读取器按本地化 `title` 对声明排序后显示。对插件排序时,无论排序方向如何,没有所选排序键值的插件都排在所有有值的插件之后,值相同的插件保持索引顺序。按发布时间排序时使用插件最大的 `publishTimestamp`。
|
||||
|
||||
读取器按 DSH 兼容性一节的规则求值必填的 `engines.dsh` 范围。版本使用 npm 的 `node-semver.rsort()` 顺序,相等时保留索引顺序。安装版本由精确的标签目标选择,而不是此显示顺序。
|
||||
|
||||
初始渠道是 `latest`。它的精确目标是发布者或市场选择的版本。客户端安装记录的版本或提交,绝不安装 `name@latest`。缺失或不兼容的 `latest` 需要显式选择版本或渠道;客户端不会自动切换。安装确认标明版本、所选渠道(如果有)、先行版本状态、索引 URL 和来源。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**纯 npm 包名或仓库 URL 列表。** 它能标识每个插件,但列表渲染前每行需要一个或多个请求,而此格式正是为消除这一开销而设计。
|
||||
|
||||
**每个版本使用一个不透明的安装 spec 字符串。** 读取器无法验证自由格式的 spec 是否固定了版本。结构化的 `source` 配合精确的 `version` 或 `commit`,读取器可以验证版本固定,客户端可以构建要安装的 spec。
|
||||
|
||||
**安装最新版本而不是固定版本。** 这样安装的构建可能与列表显示的元数据不一致,并且包发布新版本会改变未修改的索引所安装的内容。
|
||||
|
||||
**每个版本一个条目。** 每个版本都会重复标题、描述、图标、作者和市场元数据,读取器还要按 `name` 分组才能显示一个插件。
|
||||
|
||||
**每个插件一个 `source`,每个版本只记录固定点。** 在两次发布之间换了仓库、目录或 registry 的插件将无法列出旧版本。
|
||||
|
||||
**每个版本各有显示字段。** 列表每个插件只显示一行,旧的显示文本、图标、作者和主页会增加索引大小,却没有读取器显示它们。
|
||||
|
||||
**只允许内联图标。** 不发额外请求的列表需要内联图标,但 256 KiB 原始字节编码为 base64 后约为 341 KiB,因此 100 个插件的索引可能达到约 35 MB。URL 图标以额外请求和向图片主机暴露用户 IP 地址为代价,换取较小的索引。
|
||||
|
||||
**从对等依赖推断宿主兼容性。** 市场定义要求作者声明 `engines.dsh` 的新标准。现有包必须添加此声明才能列出;不需要回退、workspace 映射或范围求交。
|
||||
|
||||
**将完整 npm person 对象复制到显示字段。** 列表需要名称。生成器支持 npm 的原始声明,同时省略不用的联系字段。
|
||||
|
||||
**用不透明排序值表示时间,包括 ISO 日期字符串。** 发布时间具有固定语义,客户端会对其格式化和排序,因此它是一个有类型的字段。排除时间后,排序值可以只用数值,无需为每个键规定值类型。
|
||||
|
||||
**根级 `labels` 和 `sortKeys`,以及名为 `sort` 的插件字段。** 在两个层级都把市场定义的声明和值归入 `metadata`,并使用对应的成员名(单值的 `category` 除外),可以将它们与客户端解释的字段区分开。
|
||||
|
||||
**以声明顺序作为显示顺序。** JavaScript 会重排类整数的对象键,因此要保留声明顺序,就需要限定 id 格式或改用数组声明。客户端改为按本地化标题对声明排序。
|
||||
|
||||
**选择最高兼容版本。** npm 的 `latest` 可以指向较旧的发布版本。从 SemVer 排名推断目标会丢失记录的渠道选择,而且在构建排名相等时存在歧义。
|
||||
|
||||
**自动回退到先行版本。** 渠道目标缺失或不兼容,不表示用户选择了先行版本。
|
||||
|
||||
**拒绝未知成员。** 这样每次增加可选成员都需要新的 `format` 值,并会破坏所有已部署的读取器。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 校验器接受上面的示例,并对根字段、插件字段、版本字段和 `source` 形式中的每条规则各拒绝一个违例,诊断信息给出对应的 JSON 路径。
|
||||
- 根据每个示例版本构建的安装参数可被 DSH 的安装 spec 解析器和 pnpm 接受。
|
||||
- 校验器在每一层都忽略未知成员。
|
||||
- 生成器和校验器拒绝缺失、空白和无效的 `engines.dsh`;省略版本后不留下悬空的标签目标。索引和市场来源的安装对有效范围结果一致,包括连字符范围、先行版本以及拒绝当前运行时的有效范围。豁免绝不绕过声明要求。
|
||||
- 排名相等的 Git 构建版本重新排序后,精确标签目标仍选择同一来源。缺失的目标被拒绝;`latest` 缺省或不兼容时不自动切换。
|
||||
- npm 作者对象与简写字符串产生声明的名称,不包含联系字段,也不修改源身份。
|
||||
|
||||
## 风险
|
||||
|
||||
- 索引没有签名。标题、作者、标签和评分是市场的声明;`official` 标签不是 DSH 验证。客户端必须显示索引 URL 以及每个版本的 `source`。
|
||||
- Git 版本的 `name`、`version` 和 `engines.dsh` 是关于仓库的声明。仓库可以声明任意名称,包括 `@deepseek-ai/dsh-` 名称,实际安装的内容由已安装的 manifest 决定。
|
||||
- npm 版本的 `source.registry` 同样是一种声明:它把任意 `name`(包括 `@deepseek-ai/dsh-` 名称)的安装引向市场选择的 registry,由该 registry 决定实际安装的内容。
|
||||
- URL 图标会向图片主机暴露用户的 IP 地址。
|
||||
- 固定版本和发布标签在索引重新生成之前一直保持陈旧。
|
||||
- 市场文本未经 DSH 审核即到达用户,并通过任何列出插件的客户端工具到达模型。
|
||||
@@ -10,7 +10,7 @@ Build tests that remain correct under the repository's real CI topology, not onl
|
||||
## Read the owning rules
|
||||
|
||||
- Use [the testing policy](../../../docs/testing.md) to select unit, coverage, expected-output, snapshot, browser, or real-API evidence.
|
||||
- Use [the defensive patterns](../../../docs/defensive-patterns.md) for lifecycle, subprocess, cancellation, and teardown behavior.
|
||||
- Use [the defensive patterns](../../../docs/defensive-patterns/general.md) for lifecycle, subprocess, cancellation, and teardown behavior.
|
||||
- Read the active Vitest config and GitHub workflow when their worker or job topology affects the test.
|
||||
- For recorded-session scenarios, also follow [the snapshot instructions](../../../snapshots/AGENTS.md).
|
||||
- Use [dsh-pre-push-checks](../dsh-pre-push-checks/SKILL.md) after the test design is sound to select outgoing validation.
|
||||
|
||||
@@ -7,6 +7,13 @@ description: Design and review DeepSeek Harness client UI changes — visual tok
|
||||
|
||||
This skill is guidance, not a complete checklist. It covers judgment calls that lint, typecheck, and the i18n gate cannot make: where feedback appears, what gets reused, and how surfaces behave at window edges and across platforms. Copy quality itself follows [dsh-prose-standard](../dsh-prose-standard/SKILL.md); locale ownership follows the [locale-owned copy decision](../../notes/implemented/architecture/2026-08-23-locale-owned-client-ui-copy.md); styling ownership and token rules live in [docs/web-styling.md](../../../docs/web-styling.md) and the [ui-primitives component catalog](../../../packages/client/ui-primitives/README.md#component-catalog) — this skill adds judgment on top of those rules, not a second copy of them.
|
||||
|
||||
## Architecture prerequisites
|
||||
|
||||
Component structure, state flow, and effect usage follow the architecture rules; read them before redesigning a component:
|
||||
|
||||
- [packages/client/AGENTS.md](../../../packages/client/AGENTS.md) — Slot and props discipline, reactive read, export, ctx, and layering rules.
|
||||
- [Client component rules](../../../docs/defensive-patterns/client-components.md) — those requirements stated with the defects they prevent.
|
||||
|
||||
## Visual foundation
|
||||
|
||||
- **Use existing tokens for color, radius, and spacing.** Before introducing a literal value, search the target page and its nearest established sibling (the Plugins page is the usual reference for full-page surfaces) for the token or value already in use, and adopt it. The same applies to whole treatments — toasts, menus, in-message artifact previews, sidebar tab layout, colors, clearance rules, page padding: reuse the established component or rule wherever one exists instead of inventing a parallel one.
|
||||
@@ -24,7 +31,7 @@ This skill is guidance, not a complete checklist. It covers judgment calls that
|
||||
|
||||
## Feedback surfaces
|
||||
|
||||
Choose the surface by the lifetime of the message relative to the surface that produced it:
|
||||
Apply [dsh-error-handling](../dsh-error-handling/SKILL.md#localized-actionable-visible-reports) to failure reports. Choose the surface by the lifetime of the message relative to the surface that produced it:
|
||||
|
||||
- **Transient operation outcomes use the app-wide Toast primitive**, not in-place notices, and the toast's state and rendering live in a host that outlives the reporting surface (a `shell.overlay` entry, as RowActionToast does) — a toast rendered by the panel itself unmounts with that panel. A deletion can close the panel or tab that requested it; only a toast held outside that surface survives the close. Report success and failure; do not report still-pending states.
|
||||
- **A failed operation keeps the data visible.** A failed deletion keeps its row; the toast announces the failure. Never blank content to show an error for a transient operation.
|
||||
|
||||
@@ -7,14 +7,22 @@ description: Use when reviewing a pull request in the deepseek-harness repo —
|
||||
|
||||
**This skill is guidance, not a complete checklist.** Verify and fetch the PR's live base and exact head, then run `pnpm --silent run change-scope --base <verified-base-ref> --head <verified-head-ref>` before reading the diff and enough surrounding code to understand the design. The report identifies paths and dirty layers but does not replace semantic review. Re-establish the base and rerun it after a retarget or merge. Prioritize correctness, lifecycle, security, and broken required behavior over style; a short review with one substantiated blocker is better than a list of nits.
|
||||
|
||||
**Read the requirements before judging the diff.** Map the changed paths to the artifacts below, open each owning page from [Sources of truth](#sources-of-truth), and only then read the diff against those requirements. Judging a diff against memory of the rules is how a violation passes review.
|
||||
|
||||
## Sources of truth
|
||||
|
||||
- [AGENTS.md](../../../AGENTS.md) and [packages/AGENTS.md](../../../packages/AGENTS.md): standing repository and package authoring rules.
|
||||
- [docs/defensive-patterns.md](../../../docs/defensive-patterns.md): subprocess, callback, async-state, and disposal bug classes.
|
||||
- [docs/defensive-patterns/](../../../docs/defensive-patterns/README.md): the architecture requirements a change must satisfy and the habits behind them. Each page owns one artifact; read every page the diff touches:
|
||||
- [general.md](../../../docs/defensive-patterns/general.md) — timing, async state, value representation, shared ownership, decentralization, refactors, migrations, performance evidence, disposal, callbacks, subprocesses.
|
||||
- [package-surface.md](../../../docs/defensive-patterns/package-surface.md) — exports, package shape, build faces, dependency declarations, resolve hooks.
|
||||
- [remote-and-api.md](../../../docs/defensive-patterns/remote-and-api.md) — remote services, `@Remote` methods, API dispatch, business ownership in `packages/api`.
|
||||
- [client-components.md](../../../docs/defensive-patterns/client-components.md) — client components, slots, stores, hooks, effects, shared base packages.
|
||||
- [conversation-and-session.md](../../../docs/defensive-patterns/conversation-and-session.md) — projection reads, Session retention, conversation nodes, renderers.
|
||||
- [docs/AGENTS.md](../../../docs/AGENTS.md): documentation placement and prose discipline.
|
||||
- [dsh-prose-standard](../dsh-prose-standard/SKILL.md): required coverage and editorial judgment for comments, docs, prompts, and visible strings.
|
||||
- [dsh-ci-test-reliability](../dsh-ci-test-reliability/SKILL.md): isolation and regression-proof rules for resource-owning, asynchronous, or flaky tests and fixtures.
|
||||
- [dsh-client-ui-ux](../dsh-client-ui-ux/SKILL.md): feedback-surface choice, overlay safety, platform window adaptation, and visual token discipline for product-user-visible GUI changes.
|
||||
- [dsh-error-handling](../dsh-error-handling/SKILL.md): failure classification, handling ownership, containment, state guarantees, and actionable reports.
|
||||
- [docs/testing.md](../../../docs/testing.md) and the [quality-gates reference](../../../docs/testing.md): required test tiers and gates.
|
||||
- [Agent Notes](../../notes/README.md): design rationale. Treat disagreement with an Agent Note as a design discussion, not an automatic veto.
|
||||
- For bilingual changes, read [translation-rules.md](../../../docs/i18n/translation-rules.md) and [terminology.md](../../../docs/i18n/terminology.md); the extended translation skill is outside automatic review and runs only on explicit user invocation.
|
||||
@@ -31,7 +39,8 @@ description: Use when reviewing a pull request in the deepseek-harness repo —
|
||||
## Manual checks
|
||||
|
||||
- **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.
|
||||
- **Lifecycle and concurrency:** for async setup, callbacks, processes, or teardown, apply [defensive-patterns.md](../../../docs/defensive-patterns.md). Check races before publication, cancellation during awaits, independent error reporting, callback containment, ownership before reentry, complete detach cleanup, and quiescent 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.
|
||||
|
||||
@@ -13,7 +13,7 @@ kind: persistence-change
|
||||
|
||||
## Skeleton
|
||||
|
||||
The generator fills the machine declaration, schema companion, and consistency records, inferring the minimum version decision from the fixed rules. Supply `--prose` JSON with `en` and `zh` objects, each containing authored `summary`, `compatibility`, and `verification` strings; the [cookbook](../../../../docs/cookbook/reviewing-persistence-type-changes.md) shows the complete operation. The following values illustrate the document fields; use the actual generated root names, record ids, and digests.
|
||||
The generator fills the machine declaration, schema companion, and consistency records. Structural changes outside the established same-version cases require an explicit compatibility decision. Supply `--prose` JSON with `en` and `zh` objects, each containing authored `summary`, `compatibility`, and `verification` strings. Use Compatibility to explain both reader directions and Verification to record executed checks. The [cookbook](../../../../docs/cookbook/reviewing-persistence-type-changes.md) shows the operation. Use the actual generated root names, record ids, and digests in the following fields.
|
||||
|
||||
````markdown
|
||||
# Persistence change: <specific persisted change>
|
||||
@@ -48,7 +48,7 @@ changes:
|
||||
<a id="compatibility"></a>
|
||||
## Compatibility
|
||||
|
||||
Explain the declared decision for the detected paths. For an optional addition, explain why existing records can omit it and older readers can ignore it without changing replay. For required-to-optional changes, explain how readers handle absence. For a version bump, link the adjacent migration and its verification owner.
|
||||
Explain the declared decision for every detected path: how older readers preserve meaning, safely ignore optional data, or refuse required features before execution or recovery; and how new readers preserve historical meaning and mixed representations. Feature absence retains existing behavior. For a version bump, link the adjacent migration and its verification owner.
|
||||
|
||||
<a id="verification"></a>
|
||||
## Verification
|
||||
@@ -66,8 +66,8 @@ None.
|
||||
- Keep exactly one `yaml persistence-change` block. The English and Chinese blocks are byte-identical; the verifier parses the English record once and checks its counterpart.
|
||||
- The filename stem and `id` agree. A root's `previous` identifies its predecessor record; `null` introduces a root. Its `after` matches the generated companion schema digest; `null` records deletion.
|
||||
- One initial `baseline: true` record covers the complete inventory. Routine changes use `baseline: false` and preserve accepted predecessors. A version-bump record carries its own increasing `SessionHeader.version` transition.
|
||||
- Omit `--decision` in the ordinary authoring flow. An explicit value asserts the decision and cannot bypass classification or the required header increase. The command supplies no source changes or validation claims.
|
||||
- Structural differences alone do not infer a bump. A same-version change requiring review needs explicit `--decision same-version`; a changed header version infers `version-bump`. An explicit decision cannot waive the required header increase. The command supplies no source changes or validation claims.
|
||||
- Explain only the detected persistence-type changes. This kind permits the before/after facts and historical evidence needed for that transition; behavior-only changes remain out of scope.
|
||||
- Record creation and terminal-record update generate the catalog pair and consistency sidecars. Review both languages and retain the generated companions. Direct prose edits still require re-recording pairing; do not hand-maintain schema nodes or digests.
|
||||
- Use explicit `--update` only for an unaccepted terminal record. It rejects baselines and depended-on records; `--prose` replaces its authored explanations, while omission preserves them. Accepted records receive successors.
|
||||
- Use explicit `--update` only for an unaccepted terminal record. It rejects baselines and depended-on records; `--prose` replaces its authored explanations, while omission preserves them. A flagged same-version update requires explicit `--decision same-version`; reassess the explanation against the complete transition. Accepted records receive successors.
|
||||
- The [folder reference](../../../../docs/persistence-changes/README.md) owns compatibility rules; the [cookbook](../../../../docs/cookbook/reviewing-persistence-type-changes.md) owns commands and recovery.
|
||||
Loaded 100 of 1986 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user