diff --git a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml index 5b400d15a2..aaa8fdbedc 100644 --- a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md index 27d37ed541..60228735f5 100644 --- a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md +++ b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md @@ -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 `-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--` 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--` 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` 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` 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` 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 diff --git a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md index 4cc6e20459..272c0e80c9 100644 --- a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md @@ -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) 及其已安装依赖树位于 `-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--` 写入 `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--` 写入 `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` 标签流水线,构建一个 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` 发布标签只是一项一致性断言,与仓库版本不同时会被拒绝;源码 `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` 发布标签只是一项一致性断言,与仓库版本不同时会被拒绝;源码 `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)拥有缓存和启用行为的说明。 ## 曾考虑的替代方案 diff --git a/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.i18n.yaml index ca2693afef..d3c4c85807 100644 --- a/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.md b/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.md index b205104489..d06af266ec 100644 --- a/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.md +++ b/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.md @@ -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. diff --git a/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.zh.md b/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.zh.md index b838d7255a..d07312ce99 100644 --- a/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.zh.md @@ -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。 diff --git a/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.i18n.yaml index 7354602835..4573f0a33f 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.md b/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.md index 5ca2b44b24..967c7285df 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.md +++ b/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.md @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.zh.md b/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.zh.md index d6d49fb44c..87318c3316 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.zh.md @@ -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)。 ## 后果 diff --git a/.agents/notes/implemented/architecture/2026-07-28-api-browser-trust-boundary.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-28-api-browser-trust-boundary.i18n.yaml index a102c752b0..fefceef44e 100644 --- a/.agents/notes/implemented/architecture/2026-07-28-api-browser-trust-boundary.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-28-api-browser-trust-boundary.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-07-28-api-browser-trust-boundary.md b/.agents/notes/implemented/architecture/2026-07-28-api-browser-trust-boundary.md index 397c93e084..9b0c573210 100644 --- a/.agents/notes/implemented/architecture/2026-07-28-api-browser-trust-boundary.md +++ b/.agents/notes/implemented/architecture/2026-07-28-api-browser-trust-boundary.md @@ -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. diff --git a/.agents/notes/implemented/architecture/2026-07-28-api-browser-trust-boundary.zh.md b/.agents/notes/implemented/architecture/2026-07-28-api-browser-trust-boundary.zh.md index 6d3896fd2c..0721f0bf6b 100644 --- a/.agents/notes/implemented/architecture/2026-07-28-api-browser-trust-boundary.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-28-api-browser-trust-boundary.zh.md @@ -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 方法使用的浏览器身份。 diff --git a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml index 7afdf2c22c..5be7e083a3 100644 --- a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml @@ -72,5 +72,5 @@ en: 7433bdb522bb2083 zh: 545c17613b636f00 /agent-note-typert-gateway-targeted-method-calls/consequences: - en: c3459afdb606cfbe - zh: ec69f2bb39170a7c + en: abab48251550f584 + zh: 2568c46d78dbefdf diff --git a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md index 8e91598f7d..58135ff56e 100644 --- a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md +++ b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md @@ -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. diff --git a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md index fd6cd2bcfc..21d51800ae 100644 --- a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md @@ -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。 diff --git a/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.i18n.yaml index e33e7f556d..ac13880ddd 100644 --- a/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md b/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md index 9cb0940f76..c2407b2afd 100644 --- a/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md +++ b/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.zh.md b/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.zh.md index e1424f24e5..9a9b4365d1 100644 --- a/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.zh.md @@ -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。后续变更获得新确认,升版本决策需要自身的递增头版本转换。检查点不改变未知事件协议。 ### 写入器与发布真源 diff --git a/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.i18n.yaml index 3afca5571b..a5863d41c7 100644 --- a/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.md b/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.md index ad2bb26b84..68c9ef1235 100644 --- a/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.md +++ b/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.md @@ -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: diff --git a/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.zh.md b/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.zh.md index 70563962b3..79017823a7 100644 --- a/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-15-client-shells-and-dynamic-packages.zh.md @@ -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 的总体积减少,才将库纳入基座。 请求只有两种提供方: diff --git a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.i18n.yaml index 8cc0c37e4d..ea55baad51 100644 --- a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.md b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.md index c2ad167f14..7a6aa226b4 100644 --- a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.md +++ b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.md @@ -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. diff --git a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.zh.md b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.zh.md index 91d44f59e0..608661bd5e 100644 --- a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.zh.md @@ -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 输出推断接受,也不会创建第二份本地游标存储。缺少存活会话的直接调用会省略会话字段;宿主包清单仍然可用。 diff --git a/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.i18n.yaml index 5e193c9063..ba50248d0c 100644 --- a/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.md b/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.md index 7d4bb1a9c2..27a8315fe6 100644 --- a/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.md +++ b/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.md @@ -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--`. 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. diff --git a/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.zh.md b/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.zh.md index eb7d5ff8eb..f9a50ebf5a 100644 --- a/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.zh.md @@ -46,7 +46,7 @@ SDK 用户通过 profile 自定义插件。`dsh plugin --profile ...` 管 ### 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--`。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 载体。 diff --git a/.agents/notes/implemented/architecture/2026-08-24-browser-token-authentication.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-24-browser-token-authentication.i18n.yaml index a67b37efe1..a48e1e3ffb 100644 --- a/.agents/notes/implemented/architecture/2026-08-24-browser-token-authentication.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-24-browser-token-authentication.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-08-24-browser-token-authentication.md b/.agents/notes/implemented/architecture/2026-08-24-browser-token-authentication.md index 47d6afe1bf..0038ca7b9b 100644 --- a/.agents/notes/implemented/architecture/2026-08-24-browser-token-authentication.md +++ b/.agents/notes/implemented/architecture/2026-08-24-browser-token-authentication.md @@ -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. diff --git a/.agents/notes/implemented/architecture/2026-08-24-browser-token-authentication.zh.md b/.agents/notes/implemented/architecture/2026-08-24-browser-token-authentication.zh.md index 13ecf55e41..a08323b629 100644 --- a/.agents/notes/implemented/architecture/2026-08-24-browser-token-authentication.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-24-browser-token-authentication.zh.md @@ -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 与凭据有效期仍由本说明定义。 diff --git a/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.i18n.yaml index 72e167ce59..f12b7ff463 100644 --- a/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.md b/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.md index f8f6599fe6..ccb70a68ab 100644 --- a/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.md +++ b/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.md @@ -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. diff --git a/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.zh.md b/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.zh.md index 6d14494858..c75bc1c411 100644 --- a/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.zh.md @@ -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 条件导出仍是不同入口;运行时解析不会合并包的双重实现。 diff --git a/.agents/notes/implemented/architecture/2026-09-11-posix-ssh-runtime.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-11-posix-ssh-runtime.i18n.yaml index 423de99127..6c236fcf0f 100644 --- a/.agents/notes/implemented/architecture/2026-09-11-posix-ssh-runtime.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-11-posix-ssh-runtime.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-09-11-posix-ssh-runtime.md b/.agents/notes/implemented/architecture/2026-09-11-posix-ssh-runtime.md index d54600faa6..3bd1c340d1 100644 --- a/.agents/notes/implemented/architecture/2026-09-11-posix-ssh-runtime.md +++ b/.agents/notes/implemented/architecture/2026-09-11-posix-ssh-runtime.md @@ -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. diff --git a/.agents/notes/implemented/architecture/2026-09-11-posix-ssh-runtime.zh.md b/.agents/notes/implemented/architecture/2026-09-11-posix-ssh-runtime.zh.md index a6b3a84c37..5ad2d3c471 100644 --- a/.agents/notes/implemented/architecture/2026-09-11-posix-ssh-runtime.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-11-posix-ssh-runtime.zh.md @@ -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 容器;原生主机检查负责限制执行的证据。 安全证据要求同时尝试同用户连接与替换套接字监听端点,并确认其无法占用预留流、获知流密钥、修改另一执行或收到其明文输出。进程生命周期证据区分直接退出与托管进程范围静止,并测试启动发布前及程序启动后的取消。源码与构建后配置组合检查验证已安装辅助程序/引导安排,不向程序环境传递主机凭据。 diff --git a/.agents/notes/implemented/architecture/2026-09-19-concrete-web-bind-address.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-19-concrete-web-bind-address.i18n.yaml new file mode 100644 index 0000000000..f729428b68 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-19-concrete-web-bind-address.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-09-19-concrete-web-bind-address.md b/.agents/notes/implemented/architecture/2026-09-19-concrete-web-bind-address.md new file mode 100644 index 0000000000..3f6be5132e --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-19-concrete-web-bind-address.md @@ -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. diff --git a/.agents/notes/implemented/architecture/2026-09-19-concrete-web-bind-address.zh.md b/.agents/notes/implemented/architecture/2026-09-19-concrete-web-bind-address.zh.md new file mode 100644 index 0000000000..4df6b8384f --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-19-concrete-web-bind-address.zh.md @@ -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` 条目。 diff --git a/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.i18n.yaml index 604c1fe195..41376c7c73 100644 --- a/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.md b/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.md index 5ef27cb076..f0235d8040 100644 --- a/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.md +++ b/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.md @@ -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 | diff --git a/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.zh.md b/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.zh.md index e69db37e7e..326e6f028b 100644 --- a/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.zh.md @@ -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 | diff --git a/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.i18n.yaml index e7aef9adc0..448629299d 100644 --- a/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md b/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md index d529b27542..dbdd104da0 100644 --- a/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md +++ b/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md @@ -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. diff --git a/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.zh.md b/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.zh.md index 13abff45a0..6c9df974e8 100644 --- a/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.zh.md @@ -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 覆盖激活和模型可见行为。 diff --git a/.agents/notes/implemented/architecture/2026-09-23-native-https-web-listener.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-23-native-https-web-listener.i18n.yaml new file mode 100644 index 0000000000..0aad921cce --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-23-native-https-web-listener.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-09-23-native-https-web-listener.md b/.agents/notes/implemented/architecture/2026-09-23-native-https-web-listener.md new file mode 100644 index 0000000000..123f762559 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-23-native-https-web-listener.md @@ -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. diff --git a/.agents/notes/implemented/architecture/2026-09-23-native-https-web-listener.zh.md b/.agents/notes/implemented/architecture/2026-09-23-native-https-web-listener.zh.md new file mode 100644 index 0000000000..c34ad19b31 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-23-native-https-web-listener.zh.md @@ -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 凭据及不同传输下的拒绝。 diff --git a/.agents/notes/implemented/architecture/2026-09-24-bounded-session-log-upload.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-24-bounded-session-log-upload.i18n.yaml index 64f1ca3e65..cc7f108d4c 100644 --- a/.agents/notes/implemented/architecture/2026-09-24-bounded-session-log-upload.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-24-bounded-session-log-upload.i18n.yaml @@ -15,5 +15,5 @@ en: 88985b4e1f5c7a4f zh: 32617f7ff75ee825 /agent-note-bounded-deepseek-session-log-upload/consequences: - en: e19fb6136ffd2250 - zh: 01dacd7a44c3f837 + en: 288834c514df377a + zh: a33a4af2b4d9ebf4 diff --git a/.agents/notes/implemented/architecture/2026-09-24-bounded-session-log-upload.md b/.agents/notes/implemented/architecture/2026-09-24-bounded-session-log-upload.md index b980765eed..27ae7e4117 100644 --- a/.agents/notes/implemented/architecture/2026-09-24-bounded-session-log-upload.md +++ b/.agents/notes/implemented/architecture/2026-09-24-bounded-session-log-upload.md @@ -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. diff --git a/.agents/notes/implemented/architecture/2026-09-24-bounded-session-log-upload.zh.md b/.agents/notes/implemented/architecture/2026-09-24-bounded-session-log-upload.zh.md index 13dd90ed0a..8e2d6d4785 100644 --- a/.agents/notes/implemented/architecture/2026-09-24-bounded-session-log-upload.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-24-bounded-session-log-upload.zh.md @@ -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 的适配器上的同一回退。 diff --git a/.agents/notes/implemented/architecture/2026-09-29-explicit-session-history-preparation.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-29-explicit-session-history-preparation.i18n.yaml new file mode 100644 index 0000000000..ddd6d80912 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-29-explicit-session-history-preparation.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-09-29-explicit-session-history-preparation.md b/.agents/notes/implemented/architecture/2026-09-29-explicit-session-history-preparation.md new file mode 100644 index 0000000000..84b30b6cb4 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-29-explicit-session-history-preparation.md @@ -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. diff --git a/.agents/notes/implemented/architecture/2026-09-29-explicit-session-history-preparation.zh.md b/.agents/notes/implemented/architecture/2026-09-29-explicit-session-history-preparation.zh.md new file mode 100644 index 0000000000..31925f7a9f --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-29-explicit-session-history-preparation.zh.md @@ -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 重定向提案讨论,本决策不实现该流程。 diff --git a/.agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.i18n.yaml b/.agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.i18n.yaml index ee7758db34..c8cf21b92d 100644 --- a/.agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.md b/.agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.md index 0fa78afa4e..1e67961bf1 100644 --- a/.agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.md +++ b/.agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.md @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.zh.md b/.agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.zh.md index 293f74a5ee..4c2bb2854c 100644 --- a/.agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.zh.md +++ b/.agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.zh.md @@ -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。只有展开内容才开始翻译。折叠、切换设置或卸载都会中止未完成工作并忽略迟到结果。 ## 考虑过的替代方案 diff --git a/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.i18n.yaml b/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.i18n.yaml index 4af09bcaae..3060e831f9 100644 --- a/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.md b/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.md index 0b0ad0cf55..a67acd25f9 100644 --- a/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.md +++ b/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.md @@ -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. diff --git a/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.zh.md b/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.zh.md index 6fbdaa6e8f..0949f6b72c 100644 --- a/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.zh.md +++ b/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.zh.md @@ -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 的行仍在使用时拒绝执行。激活失败可以保留已保存的依赖和选择,不加入回滚。离线元信息、发布版本锁定、源码目标选择、同版本来源差异、干净安装产物和发布顺序有专门检查。原生提供方认证不属于目录发现过程。 diff --git a/.agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.i18n.yaml b/.agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.i18n.yaml index bab272d8e1..b22fc768ec 100644 --- a/.agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.md b/.agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.md index def042e26a..c39f289a39 100644 --- a/.agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.md +++ b/.agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.md @@ -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. diff --git a/.agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.zh.md b/.agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.zh.md index 1f513060ea..4b58743cb5 100644 --- a/.agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.zh.md +++ b/.agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.zh.md @@ -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 兼容范围。 diff --git a/.agents/notes/implemented/architecture/2026-10-08-desktop-shell-configuration.i18n.yaml b/.agents/notes/implemented/architecture/2026-10-08-desktop-shell-configuration.i18n.yaml new file mode 100644 index 0000000000..7bd206e1f8 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-08-desktop-shell-configuration.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-10-08-desktop-shell-configuration.md b/.agents/notes/implemented/architecture/2026-10-08-desktop-shell-configuration.md new file mode 100644 index 0000000000..9531ff4bff --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-08-desktop-shell-configuration.md @@ -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. diff --git a/.agents/notes/implemented/architecture/2026-10-08-desktop-shell-configuration.zh.md b/.agents/notes/implemented/architecture/2026-10-08-desktop-shell-configuration.zh.md new file mode 100644 index 0000000000..3dac97f51a --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-08-desktop-shell-configuration.zh.md @@ -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。 diff --git a/.agents/notes/implemented/architecture/2026-10-09-reasoning-content-slot-factory.i18n.yaml b/.agents/notes/implemented/architecture/2026-10-09-reasoning-content-slot-factory.i18n.yaml new file mode 100644 index 0000000000..ba86de4f69 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-09-reasoning-content-slot-factory.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/architecture/2026-10-09-reasoning-content-slot-factory.md b/.agents/notes/implemented/architecture/2026-10-09-reasoning-content-slot-factory.md new file mode 100644 index 0000000000..b1c3f5c7d6 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-09-reasoning-content-slot-factory.md @@ -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 +
+ {renderSlot('conversation.chat.reasoning.body', { text, running })} +
+``` + +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. diff --git a/.agents/notes/implemented/architecture/2026-10-09-reasoning-content-slot-factory.zh.md b/.agents/notes/implemented/architecture/2026-10-09-reasoning-content-slot-factory.zh.md new file mode 100644 index 0000000000..73986249ef --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-09-reasoning-content-slot-factory.zh.md @@ -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 +
+ {renderSlot('conversation.chat.reasoning.body', { text, running })} +
+``` + +默认 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 仍是独占替换位置。此方案不解决多个正文转换插件的自动组合,也不将显示变换提升为脱敏或模型输入修改能力。 diff --git a/.agents/notes/implemented/bug-fix/2026-09-15-authenticated-browser-host-preferences.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-15-authenticated-browser-host-preferences.i18n.yaml new file mode 100644 index 0000000000..1fe1553059 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-15-authenticated-browser-host-preferences.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/bug-fix/2026-09-15-authenticated-browser-host-preferences.md b/.agents/notes/implemented/bug-fix/2026-09-15-authenticated-browser-host-preferences.md new file mode 100644 index 0000000000..120903b88d --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-15-authenticated-browser-host-preferences.md @@ -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. diff --git a/.agents/notes/implemented/bug-fix/2026-09-15-authenticated-browser-host-preferences.zh.md b/.agents/notes/implemented/bug-fix/2026-09-15-authenticated-browser-host-preferences.zh.md new file mode 100644 index 0000000000..10aa7af847 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-15-authenticated-browser-host-preferences.zh.md @@ -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 状态。 diff --git a/.agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml b/.agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml index a38c0852ba..aab0051c22 100644 --- a/.agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/feature/2026-08-05-agent-teams.md b/.agents/notes/implemented/feature/2026-08-05-agent-teams.md index 74556d041f..2643101a91 100644 --- a/.agents/notes/implemented/feature/2026-08-05-agent-teams.md +++ b/.agents/notes/implemented/feature/2026-08-05-agent-teams.md @@ -30,7 +30,7 @@ The `spawn_teammate` tool prefixes the initial task with a user-role `