diff --git a/.agents/notes/AGENTS.md b/.agents/notes/AGENTS.md index e8e1a0ef66..66997859a8 100644 --- a/.agents/notes/AGENTS.md +++ b/.agents/notes/AGENTS.md @@ -1,6 +1,6 @@ # AGENTS.md — Agent Notes -Agent Notes are effectively RFCs written by agents: durable proposals and decision records that preserve rationale, alternatives, consequences, and verification contracts. Follow the [documentation standard](../../docs/AGENTS.md) and the [Agent Note contract](README.md). +Agent Notes are effectively RFCs written by agents: durable proposals and decision records that preserve rationale, alternatives, consequences, and required verification. Follow the [documentation standard](../../docs/AGENTS.md) and the [Agent Note rules](README.md). **Every new Agent Note triggers a supersession check.** Search the active tree for older notes covering the same decision or mechanism, classify any full or partial supersession with [`dsh-archive-agent-notes`](../skills/dsh-archive-agent-notes/SKILL.md), and archive every qualifying implemented triplet in the same PR. Keep partial supersessions active and cross-linked. diff --git a/.agents/notes/README.i18n.yaml b/.agents/notes/README.i18n.yaml index 56eeb36b4d..cd767c17bc 100644 --- a/.agents/notes/README.i18n.yaml +++ b/.agents/notes/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/README.md -README.md: d3a8943a78238d974d54028e38b773e932429b0e -README.zh.md: 4b3a1ee57ea61a8e8ba4d01cf7c719bbf8440e30 +README.md: ae8e4724d610c97d74910d7dec5c95af69e93281 +README.zh.md: d9989dd1d722185145099b706169dfce19559b52 diff --git a/.agents/notes/README.md b/.agents/notes/README.md index d3a8943a78..ae8e4724d6 100644 --- a/.agents/notes/README.md +++ b/.agents/notes/README.md @@ -2,7 +2,7 @@ English | [中文](README.zh.md) -One kind of design doc lives here. An **Agent Note** records a decision or proposal that shapes this codebase — the *why* and *what we gave up*, the parts code and docs can't carry. This file is the entry point and contract: where Agent Notes live, when to write one, and [the in-file format](#the-file-format). +One kind of design doc lives here. An **Agent Note** records a decision or proposal that affects this codebase — the *why* and *what we gave up*, the parts code and docs can't carry. This file defines where Agent Notes live, when to write one, and [the in-file format](#the-file-format). ## Layout and naming @@ -43,13 +43,13 @@ Once sealed, every archived triplet is permanently frozen. Do not edit, translat ## When to write one -Every non-trivial change MUST add or update at least one Agent Note in the same PR. A change is non-trivial when it alters behavior, architecture, a cross-file or cross-package contract, process or tooling, testing strategy, an on-disk, wire, or configuration format, or another decision a maintainer may reasonably revisit. A proposal for substantial future work starts in `proposed/`; a decision already made starts in `implemented/`. Pick the class folder that matches the decision (see [Classification](#classification)). +Every non-trivial change MUST add or update at least one Agent Note in the same PR. A change is non-trivial when it alters behavior, architecture, a contract shared across files or packages, process or tooling, testing strategy, an on-disk, wire, or configuration format, or another decision a maintainer may reasonably revisit. A proposal for substantial future work starts in `proposed/`; a decision already made starts in `implemented/`. Pick the class folder that matches the decision (see [Classification](#classification)). -Updating the Agent Note that already owns the decision satisfies the rule; do not create a duplicate. Only a purely mechanical or local edit with no behavioral, contractual, structural, process, or rationale change is exempt. An Agent Note is never edited into a *different decision*: supersede it with a new one, and keep both notes cross-linked unless the old note is later fully consolidated under the rule below. Editing an `implemented/` Agent Note to track where its existing decision lives is required, not forbidden; see [implemented/AGENTS.md](implemented/AGENTS.md). +Updating the Agent Note that already owns the decision satisfies the rule; do not create a duplicate. Only a purely mechanical or local edit with no change to behavior, contracts, structure, process, or rationale is exempt. An Agent Note is never edited into a *different decision*: supersede it with a new one, and keep both notes cross-linked unless the old note is later fully consolidated under the rule below. Editing an `implemented/` Agent Note to track where its existing decision lives is required, not forbidden; see [implemented/AGENTS.md](implemented/AGENTS.md). -An implemented Agent Note that is fully superseded may be consolidated into the current owning note and deleted. Before deletion, the owner must preserve every unique rationale, alternative, consequence, verification contract, and named coverage gap; repair every inbound link; and delete the Chinese counterpart and consistency record in the same change. Partial supersession does not qualify: keep both notes cross-linked and update every fact that remains current. Consolidation must not rewrite the old file into its opposite or rely on git history as the only copy of rationale. +An implemented Agent Note that is fully superseded may be consolidated into the current owning note and deleted. Before deletion, the owner must preserve every unique rationale, alternative, consequence, required verification, and named coverage gap; repair every inbound link; and delete the Chinese counterpart and consistency record in the same change. Partial supersession does not qualify: keep both notes cross-linked and update every fact that remains current. Consolidation must not rewrite the old file into its opposite or rely on git history as the only copy of rationale. -A feature-addition note may be consolidated into the later removal note only when the feature is absent from production code, configuration, schemas, durable or wire formats, migration, and compatibility behavior; no current documentation presents it as available; and no test exercises it as supported behavior. Removal rationale and tests that verify absence may remain. The removal owner preserves the original motivation, why it no longer justified the feature, alternatives to full removal, the capability given up, conditions for reintroduction, and verification of complete absence. Obsolete implementation inventories and tests that only verified the deleted behavior are not current verification contracts. Removing one transport, default, implementation, or presentation is partial supersession, as is any surviving durable data or compatibility handling. +A feature-addition note may be consolidated into the later removal note only when the feature is absent from production code, configuration, schemas, durable or wire formats, migration, and compatibility behavior; no current documentation presents it as available; and no test exercises it as supported behavior. Removal rationale and tests that verify absence may remain. The removal owner preserves the original motivation, why it no longer justified the feature, alternatives to full removal, the capability given up, conditions for reintroduction, and verification of complete absence. Obsolete implementation inventories and tests that only verified the deleted behavior are not current verification evidence. Removing one transport, default, implementation, or presentation is partial supersession, as is any surviving durable data or compatibility handling. ## The file format @@ -122,4 +122,4 @@ Moving a file between lifecycle folders means updating the `Status:` line and re ### Chinese counterparts -A `.zh.md` counterpart mirrors its English sibling's structure section-for-section under the [i18n contract](../../docs/i18n/README.md); the machine-checked header tokens (`# Agent Note: ` and the `Status:` line) stay in English verbatim. The format gate skips `.zh.md` files — the pairing gate owns their consistency. +A `.zh.md` counterpart mirrors its English sibling's structure section-for-section under the [i18n contract](../../docs/i18n/README.md); the machine-checked header tokens (`# Agent Note: ` and the `Status:` line) stay in English verbatim. The format gate skips `.zh.md` files — the pairing gate checks their consistency. diff --git a/.agents/notes/README.zh.md b/.agents/notes/README.zh.md index 4b3a1ee57e..d9989dd1d7 100644 --- a/.agents/notes/README.zh.md +++ b/.agents/notes/README.zh.md @@ -2,7 +2,7 @@ [English](README.md) | 中文 -这里存放一类设计文档。**Agent Note** 记录塑造本代码库的决策或提案:代码和文档无法承载的*为什么*以及*放弃了什么*。本文件是入口和约定:Agent Note 存放在哪里、何时需要写一份,以及[文件内格式](#the-file-format)。 +这里存放一类设计文档。**Agent Note** 记录影响本代码库的决策或提案:代码和文档无法承载的*为什么*以及*放弃了什么*。本文件规定 Agent Note 存放在哪里、何时需要写一份,以及[文件内格式](#the-file-format)。 ## 布局与命名 @@ -49,9 +49,9 @@ 更新已经拥有该决策的 Agent Note 即可满足规则;不要创建重复记录。只有不涉及行为、约定、结构、流程或理由变化的纯机械性或局部编辑才可豁免。Agent Note 永远不会被编辑为一个*不同的决策*:用新 Agent Note 取代旧记录,并让两个记录保持互相链接,除非后续依据下方规则完全合并旧记录。编辑 `implemented/` Agent Note 以跟踪其现有决策的所在位置是必需的,而非禁止的;见 [implemented/AGENTS.md](implemented/AGENTS.md)。 -被完全取代的 implemented Agent Note 可以合并到当前持有该决策的记录中,并删除原文件。删除前,当前记录必须保存所有独有的决策依据、备选方案、影响、验证约定和明确指出的覆盖缺口;修复所有入站链接;并在同一变更中删除中文对侧文件和一致性记录。仅部分被取代的记录不符合此条件:保留两个记录并让它们互相链接,同时更新所有仍然适用的事实。合并不得将旧文件改写成与其相反的决策,也不得让 git 历史成为决策依据的唯一副本。 +被完全取代的 implemented Agent Note 可以合并到当前持有该决策的记录中,并删除原文件。删除前,当前记录必须保存所有独有的决策依据、备选方案、影响、必需的验证和明确指出的覆盖缺口;修复所有入站链接;并在同一变更中删除中文对侧文件和一致性记录。仅部分被取代的记录不符合此条件:保留两个记录并让它们互相链接,同时更新所有仍然适用的事实。合并不得将旧文件改写成与其相反的决策,也不得让 git 历史成为决策依据的唯一副本。 -只有当一项功能已从生产代码、配置、schema、持久化格式或协议格式、迁移和兼容行为中完全消失,当前文档不再将其描述为可用,且没有测试把它作为受支持行为来执行时,新增该功能的 Agent Note 才可合并进后续的移除记录。移除决策的依据和验证该功能已不存在的测试可以保留。移除决策的持有记录必须保留最初动机、为什么该动机已不足以证明保留该功能的合理性、完全移除之外的备选方案、放弃的能力、重新引入的条件,以及证明已彻底移除的验证。过时的实现清单和只验证已删除行为的测试不属于当前验证约定。仅移除一种传输、默认值、实现或展示属于部分取代;仍有任何持久数据或兼容处理也同样如此。 +只有当一项功能已从生产代码、配置、schema、持久化格式或协议格式、迁移和兼容行为中完全消失,当前文档不再将其描述为可用,且没有测试把它作为受支持行为来执行时,新增该功能的 Agent Note 才可合并进后续的移除记录。移除决策的依据和验证该功能已不存在的测试可以保留。移除决策的持有记录必须保留最初动机、为什么该动机已不足以证明保留该功能的合理性、完全移除之外的备选方案、放弃的能力、重新引入的条件,以及证明已彻底移除的验证。过时的实现清单和只验证已删除行为的测试不属于当前验证证据。仅移除一种传输、默认值、实现或展示属于部分取代;仍有任何持久数据或兼容处理也同样如此。 @@ -126,4 +126,4 @@ Status: ### 中文对侧文件 -`.zh.md` 对侧文件按 [i18n 约定](../../docs/i18n/README.md)逐章节镜像其英文对侧文件的结构;机器检查的头部标记(`# Agent Note: ` 和 `Status:` 行)保持英文原样不翻译。格式门禁跳过 `.zh.md` 文件——配对门禁负责它们的一致性。 +`.zh.md` 对侧文件按 [i18n 约定](../../docs/i18n/README.md)逐章节与其英文对侧文件保持相同结构;机器检查的头部标记(`# Agent Note: ` 和 `Status:` 行)保持英文原样不翻译。格式门禁跳过 `.zh.md` 文件;配对门禁检查它们的一致性。 diff --git a/.agents/notes/implemented/AGENTS.md b/.agents/notes/implemented/AGENTS.md index b8da5dc8ef..5a22674a96 100644 --- a/.agents/notes/implemented/AGENTS.md +++ b/.agents/notes/implemented/AGENTS.md @@ -10,4 +10,4 @@ When a shipped note is unlikely to guide future work, archive its complete tripl ### This is not a license to rewrite the *decision* -Update factual realization in place. A reversal of the decision or its rationale requires a new Agent Note and cross-link; a fully superseded old note may be deleted only through the consolidation rule in the [Agent Note contract](../README.md). +Update factual realization in place. A reversal of the decision or its rationale requires a new Agent Note and cross-link; a fully superseded old note may be deleted only through the consolidation rule in the [Agent Note rules](../README.md). diff --git a/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.i18n.yaml index d47c50cdb7..c7be4bbdf1 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md -2026-07-19-gui-layering-and-rpc-protocol.md: 705b0df5feb5fedaae4d198aed71758b54586e93 -2026-07-19-gui-layering-and-rpc-protocol.zh.md: b28b08b4b9da058e01af62e610d4e226d794151f +2026-07-19-gui-layering-and-rpc-protocol.md: f9c95176321496e965a95b6358d6feaa8466fe89 +2026-07-19-gui-layering-and-rpc-protocol.zh.md: 7d20c5a2662c9036382b30a96bc9973c8f0349bd diff --git a/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md b/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md index 705b0df5fe..f9c9517632 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md +++ b/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md @@ -8,12 +8,12 @@ English | [中文](2026-07-19-gui-layering-and-rpc-protocol.zh.md) ## Problem -We need a UI integration layer. Beyond the existing ACP/stdio baseline, more product UI shapes are coming — Web (server), Electron, and others. We call these shapes Clients, uniformly, and want the following capabilities: +We need a UI integration layer. Beyond the existing ACP/stdio baseline, more product clients are coming — Web (server), Electron, and others. We call them Clients and want the following capabilities: - One `dsh` process supporting both `dsh web` (serve) and `dsh run` (headless) — one process, two modes (a design reservation) -- Launching inside Electron with the same Web technology shape as `dsh web` +- Launching inside Electron with the same Web technologies as `dsh web` -That demands a stable layered responsibility model in the engineering codebase, so future client shapes plug in cleanly. +That demands a stable layered responsibility model in the engineering codebase, so future clients plug in cleanly. At the same time the physical channels differ per consumer (browser HTTP/WebSocket, in-process fetch/SSE, IPC later), so we also need a channel-independent message model and a single contract source of truth — "adding a method" and "swapping a carrier" must not entangle each other, and every message on the wire must be type-validatable, observable, and reconcilable. @@ -29,19 +29,19 @@ Directories layer as follows: - **Pure libraries** (`ui-slots`, `web-react`, `ui-primitives`, plus the `loader` kernel package): ordinary root-index packages, statically bundled into the shell; the first three are seeded into the module table. - **Static-arrival entry packages** (`connection`, `runtime`, `ui-theme`, `i18n`, `hmr`): no `dshClient` key and no browser bundle — the shell bundles their `src/client/` half and registers it with `ctx.modules`; they are governed as entries of the host-authored graph like everything else. - **Fetch-arrival plugin packages** (`ui-layout`, `ui-sidebar`, `ui-conversation`, `ui-trajectory`): dual-entry — the root index is the node half (an empty `apply`, existing so the host Loader governs lifecycle and the web plugin registry discovers the package.json `dshClient` declaration); the implementation lives under `src/client/`, shipped as the `./client` subpath (a tsdown closure-factory bundle). Cross-plugin consumption of `/client` is type-only; value cooperation goes through cordis services. -- `apps/` holds the externally exported application shapes, assembled from Client / Host mixtures. +- `apps/` holds the externally exported applications, assembled from Client / Host mixtures. - `apps/web` (`dsh-frontend`) is the vite application: a thin `main.ts` over the shell surface exported by `dsh-client-web`. - - `apps/cli` (`@deepseek-ai/dsh`) dispatches shapes: `dsh web` = Host + webserver + the built `dsh-frontend` dist; `dsh run` = [a direct core Agent/Session entry point](2026-08-09-headless-direct-core-entry-point.md), with zero Host, HTTP, or browser layer. - - A future Electron shape reuses the same web client packages over an IPC fetch carrier. + - `apps/cli` (`@deepseek-ai/dsh`) dispatches commands: `dsh web` = Host + webserver + the built `dsh-frontend` dist; `dsh run` = [a direct core Agent/Session entry point](2026-08-09-headless-direct-core-entry-point.md), with zero Host, HTTP, or browser layer. + - A future Electron application reuses the same web client packages over an IPC fetch carrier. ``` -apps/* (application shapes: apps/web = vite app, apps/cli = bin dispatch) +apps/* (applications: apps/web = vite app, apps/cli = bin dispatch) │ consume ▼ packages/host/* packages/client/* apiproxy front layer: protocol pure libs: ui-slots / web-react / ui-primitives runtime assembly / host entity dshClient plugins ×8 (node half = empty apply, - webserver web-shape HTTP carriage client half = src/client/) + webserver Web HTTP carriage client half = src/client/) │ ctx.plugin(...) ▲ import only apiproxy's /api /client subpaths ▼ │ (type-only + the client base class) harness core packages ──────────────────┘ (types reach the browser via import type) @@ -51,12 +51,12 @@ Direction discipline (every rule auditable from package deps): - `runtime → apiproxy` is one-way; apiproxy depends only on type definitions. - Client-side packages **never import** host-side package runtime (they consume only the two browser-safe subpaths `/api` and `/client`). -- `webserver` does not depend on `runtime`: it provides a `{ fetch }`-shaped implementation — "webserver ← runtime" is a runtime injection relationship, not a package dependency. +- `webserver` does not depend on `runtime`: it provides an implementation of the `{ fetch }` interface — "webserver ← runtime" is a runtime injection relationship, not a package dependency. - Cross-package client imports use the `/client` subpath for plugin packages, and between plugin packages they are type-only — a cross-plugin value import is a build error at the tsdown purity gate (value cooperation goes through cordis services; the [client plugin loading note](2026-07-23-client-plugin-loading-model.md) owns the edge rules). TypeScript checks in **two aggregate programs** referenced by a solution root (`tsconfig.json` = solution; `tsconfig.host.json` = host side + tests, excluding `packages/client`; `tsconfig.client.json` = client packages and their tests): both sides merge the cordis `Context` interface under the same keys (`sessions`, `loader`) with different services, so one program would see both declaration merges and report a collision. Shared leaves (session/llm/tools/apiproxy…) build once and are referenced by both programs ([topology](../process/2026-07-22-tsconfig-solution-root-two-aggregates.md)). -On the protocol side: TS interfaces (`packages/host/apiproxy/src/api/`, zero Node dependencies, browser-importable); wire messages unify under a **bidirectional model** — each logical message is shaped by "who initiates × request/response" (two axes, four cells, called the four quadrants below), decoupled from the physical channel; clients all inherit `AbstractApiClient` (protocol invariants live entirely in the base class, platform differences are just the `doFetch` transport aspect). +On the protocol side: TS interfaces (`packages/host/apiproxy/src/api/`, zero Node dependencies, browser-importable); wire messages unify under a **bidirectional model** — each logical message is classified by "who initiates × request/response" (two axes, four cells, called the four quadrants below), decoupled from the physical channel; clients all inherit `AbstractApiClient` (protocol invariants live entirely in the base class, platform differences are just the `doFetch` transport aspect). #### Layer roles @@ -64,22 +64,22 @@ On the protocol side: TS interfaces (`packages/host/apiproxy/src/api/`, zero Nod |---|---|---|---| | Front layer | `dsh-host-apiproxy` | TS/zod definitions (api/) + the fetch abstraction (fetch/: handler + client base class) | Keep it simple — every consumer needs it; importable from Node and browser alike; protocol content in the "Message protocol" sections below; clients must not bypass api through ctx | | Assembly layer | `dsh-host-runtime` | Plugin composition + ApiProxy integration + the web UI plugin mount (in-memory Loader tree over the eight dshClient packages); home of host-level configuration (defaults/persistenceRoot, future user profile) | Which plugins mount and with what defaults is decided only here; shells must not alter the assembly | -| Carrier layer | `dsh-host-webserver` | Web-shape HTTP and upgrade: static serving + `/api/*`→handler forwarding + WebSocket upgrade route + close semantics; plugin bundle endpoint + `__DSH_BOOT__` manifest injection (fed by the web plugin registry) | Web (browser access) only; zero workspace dependencies (the registry arrives by structural injection); Electron does not reuse it | +| Carrier layer | `dsh-host-webserver` | Web HTTP and upgrade: static serving + `/api/*`→handler forwarding + WebSocket upgrade route + close semantics; plugin bundle endpoint + `__DSH_BOOT__` manifest injection (fed by the web plugin registry) | Web (browser access) only; zero workspace dependencies (the registry arrives by structural injection); Electron does not reuse it | | Client libraries | `dsh-client-ui-slots` / `dsh-client-web-react` / `dsh-client-ui-primitives` | Slot registry core / ctx↔React glue / pure React atoms | Zero cordis runtime dependency in components; seeded into the loader module table by the shell | | Client plugins | `dsh-client-connection` / `dsh-client-runtime` / `dsh-client-ui-theme` / `dsh-client-i18n` / `dsh-client-ui-layout` / `dsh-client-ui-sidebar` / `dsh-client-ui-conversation` / `dsh-client-ui-trajectory` | Browser-side cordis plugin tree (wire consumer, core services, theme, i18n, layout, sidebar, conversation, trajectory) — see the web client architecture note | Dual entry (node half = empty apply; implementation in `src/client/`); the consumption face goes exclusively through ApiProxy | -| Application shape | `@deepseek-ai/dsh` (apps/cli) + `dsh-frontend` (apps/web, the vite application) | Coarse bin dispatch + one assembly module per shape (web.ts / headless.ts); the vite app is a thin main over the `dsh-client-web` shell surface | Shapes dynamic-import so they never load each other; workspace knowledge like dist location stays in the app | +| Application | `@deepseek-ai/dsh` (apps/cli) + `dsh-frontend` (apps/web, the vite application) | Coarse bin dispatch + one assembly module per application (web.ts / headless.ts); the vite app is a thin main over the `dsh-client-web` shell surface | Applications use dynamic imports so they never load each other; workspace knowledge like dist location stays in the app | #### Naming rule Packages under `packages/host/*` and `packages/client/*` **must carry the directory-group prefix in the package name**: host/runtime → `dsh-host-runtime`, client/runtime → `dsh-client-runtime`. The directory name does not repeat the group prefix (host/ already expresses it). The package-name tail therefore ≠ the directory name, so the `dsh-*` wildcard in tsconfig.base.json (which resolves by directory name) misses them — **each package in these two groups needs an explicit paths entry**, including separate entries for the client packages' `/client` subpaths so source-level resolution matches the exports map. -#### How to integrate a new shape (operational checklist) +#### How to integrate a new application (operational checklist) 1. **Pick a fetch impersonation**: browser same-origin HTTP / in-process `host.handler.fetch` injection / your own transport-aspect subclass (e.g. future Electron IPC, see the "Subclass table" below). -2. **Write an assembly module under `apps/`**: `startHost()` + a client subclass + the shape's private signal/print/exit semantics; a mixture never becomes a package — assembly is written in the app. +2. **Write an assembly module under `apps/`**: `startHost()` + a client subclass + the application's private signal/print/exit semantics; a mixture never becomes a package — assembly is written in the app. 3. **Import `dsh-host-webserver` only if you need HTTP carriage**, otherwise zero ports. -The two existing shapes preserve the boundary: the Web shape mounts Host, carrier, and browser composition, while `dsh run` mounts a direct core runner with zero Host, HTTP, or ports. ACP-class protocol bridges do not follow the client-carrier checklist: they expose core to the external ecosystem, mount via `ctx.plugin(entry-point plugin)` directly, and wear no fetch. +The two existing applications preserve the division: the Web application mounts Host, carrier, and browser composition, while `dsh run` mounts a direct core runner with zero Host, HTTP, or ports. ACP-class protocol bridges do not follow the client-carrier checklist: they expose core to the external ecosystem and mount directly via `ctx.plugin(entry-point plugin)` without fetch. ## Message protocol @@ -169,7 +169,7 @@ The remaining methods (`session.create`/`session.history`/`session.rename`/`sess ### Frames (server→client, named unions) -Two logical streams: the mux stream (`/api/events.mux`, all-session aggregate) and the host stream (`/api/events.host`, host-level events). The browser consumes one downlink WebSocket per stream, while the in-process fetch carrier retains SSE to preserve the same shape; see the [WebSocket downlink carrier](2026-08-04-websocket-downlink-carrier.md) for the physical boundary. One example frame row: +Two logical streams: the mux stream (`/api/events.mux`, all-session aggregate) and the host stream (`/api/events.host`, host-level events). The browser consumes one downlink WebSocket per stream, while the in-process fetch carrier retains SSE with the same event framing; see the [WebSocket downlink carrier](2026-08-04-websocket-downlink-carrier.md) for the physical boundary. One example frame row: | frame type | payload | when | |---|---|---| @@ -216,7 +216,7 @@ All four quadrant full forms pass through `onEnvelope`; the base implementation | Subclass | Package | doFetch | Purpose | |---|---|---|---| | `InProcessApiClient` | apiproxy itself | the injected `{ fetch }` handler | **The isomorphic point**: `new InProcessApiClient(toFetchHandler(api))` never touches the network yet runs the real wire serialization/zod/SSE framing; carrier tests and callers can exercise the protocol without opening a port, while product `dsh run` drives core directly | -| `WebApiClient` | dsh-client-connection | `globalThis.fetch` uplink + one same-origin WebSocket downlink per logical stream | the browser shape; physical boundary in the [WebSocket downlink carrier](2026-08-04-websocket-downlink-carrier.md) | +| `WebApiClient` | dsh-client-connection | `globalThis.fetch` uplink + one same-origin WebSocket downlink per logical stream | the browser client; physical boundary in the [WebSocket downlink carrier](2026-08-04-websocket-downlink-carrier.md) | | `FixtureApiClient` | dsh-client-connection | unused (protocol-layer override) | serverless UI development (`?fixture`): overrides the `callUnary`/`openMux`/`openHost`/`respond` virtuals and is itself the fake server (frame rpcIds minted by it, semantics self-consistent) | | IPC bridge subclass (hypothetical example — no such shell exists) | an Electron shell | IPC serialization round trip | would swap only doFetch; contract and base class unchanged | @@ -234,15 +234,15 @@ All four quadrant full forms pass through `onEnvelope`; the base implementation ## Consequences -Every client shape consumes one contract: adding a unary method is a five-step mechanical change radiating from a single signature, swapping a carrier touches only a `doFetch` subclass, and every wire message is zod-validated, observable through the envelope tap, and reconcilable by rpcId. Ordinary unary calls remain bounded, while `host.pickDirectory` and `command.execute` may stay pending until the operation finishes or caller/connection cancellation arrives; this accepts that a non-cooperative user-paced operation can hang its request rather than treating valid operation duration as transport failure. The other accepted costs: two groups of packages need explicit tsconfig paths entries, and the reserved methods (fork/inject/task.list/listModels/hostInstanceId) stay dormant until a real consumer arrives. +Every client consumes one contract: adding a unary method is a five-step mechanical change from a single signature, swapping a carrier touches only a `doFetch` subclass, and every wire message is zod-validated, observable through the envelope tap, and reconcilable by rpcId. Ordinary unary calls remain bounded, while `host.pickDirectory` and `command.execute` may stay pending until the operation finishes or caller/connection cancellation arrives; this accepts that a non-cooperative user-paced operation can hang its request rather than treating valid operation duration as transport failure. The other accepted costs: two groups of packages need explicit tsconfig paths entries, and the reserved methods (fork/inject/task.list/listModels/hostInstanceId) stay dormant until a real consumer arrives. ## Alternatives considered | Rejected | One-line reason | |---|---| -| Packaging by "product shape" (a web family, an electron family) | What shapes share is host/client capability, not the shape itself; capability-provider layering means a new shape needs zero new packages | +| Packaging by product (a web family, an electron family) | Products share host/client capabilities rather than an application implementation; capability-provider layering means a new application needs zero new packages | | A package per mixture (e.g. a standalone headless package) | A mixture has exactly one consumer (its own app); packaging it is ownerless abstraction, while assembly in the app is readable and disposable | -| Consuming clients connecting to ctx directly (skipping the apiproxy layer) | Client shapes require wire validation, observability, and multi-client consistency. Direct headless is a local entry point with no client boundary and uses the public Agent/Session seams rather than a client command plane | +| Consuming clients connecting to ctx directly (skipping the apiproxy layer) | Clients require wire validation, observability, and multi-client consistency. Direct headless is a local entry point with no client boundary and uses the public Agent/Session seams rather than a client command plane | | webserver depending on runtime (saving the handler injection) | Structural-typing injection keeps webserver reusable by sidecars/tests with zero workspace deps; a package dependency would drag assembly knowledge into the carrier layer | | Package names without the group prefix (continuing dsh-) | `dsh-runtime`/`dsh-web-ui` lose their belonging in the flat npm namespace; the cost is one explicit paths entry per package | | Reusing the in-repo JSON-RPC 2.0 (dsh-jsonrpc) | Numeric error codes degrade to a single fallback code, contracts get aligned by hand in two copies, and naming drifts without a convention | diff --git a/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.zh.md b/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.zh.md index b28b08b4b9..7d20c5a266 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.zh.md @@ -8,11 +8,11 @@ Status: implemented ## Problem -需要提供 UI 对接层,除已有 ACP(Agent Client Protocol)/stdio 基线外,还需要 Web(server)、Electron 等其他产品 UI 形态。我们把这些形态统一称为 Client。希望具备以下能力: +需要提供 UI 对接层,除已有 ACP(Agent Client Protocol)/stdio 基线外,还需要 Web(server)、Electron 等其他产品客户端。我们把它们统一称为 Client。希望具备以下能力: - 一个 `dsh` 进程同时支持 `dsh web`(启动)和 `dsh run`(headless),一个进程两种模式(设计预留) -- 以与 `dsh web` 同构的 Web 技术形态,在 Electron 中启动 +- 在 Electron 中使用与 `dsh web` 相同的 Web 技术启动 -那么当前的工程代码需要稳定的分层职责模型,便于以后接入各类 client 形态。 +那么当前的工程代码需要稳定的分层职责模型,便于以后接入各类 client。 同时各消费端的物理通道不同(浏览器 HTTP/WebSocket、进程内 fetch/SSE、将来 IPC),还需要一个通道无关的消息模型和单一约定事实源,让「加一个方法」「换一种载体」互不牵连,且 wire 上的每条消息可类型校验、可观测、可对账。 @@ -27,19 +27,19 @@ Status: implemented - **纯库**(`ui-slots`、`web-react`、`ui-primitives`,外加内核包 `loader`):普通根入口包,静态打包进壳;前三者播种进模块表。 - **静态到达 entry 包**(`connection`、`runtime`、`ui-theme`、`i18n`、`hmr`):无 `dshClient` 键、无浏览器 bundle——壳把它们的 `src/client/` 半边打进自己的 bundle 并向 `ctx.modules` 登记;它们与其余单元一样,作为 host 独家撰写的图里的 entry 受治理。 - **fetch 到达插件包**(`ui-layout`、`ui-sidebar`、`ui-conversation`、`ui-trajectory`):双入口——根入口是 node 半边(空 `apply`,其存在是为了让 host Loader 管辖生命周期、让 web 插件注册表发现 package.json 的 `dshClient` 声明);实现住在 `src/client/` 下,经 `./client` 子路径发布(tsdown 闭包工厂 bundle)。跨插件消费 `/client` 只限类型;值层面的协作走 cordis 服务。 -- `apps/` 作为对外导出的应用形态入口,可以由 Client / Host 混合组装。 +- `apps/` 作为对外导出的应用入口,可以由 Client / Host 混合组装。 - `apps/web`(`dsh-frontend`)是 vite 应用:`dsh-client-web` 导出的壳表面之上的一层薄 `main.ts`。 - - `apps/cli`(`@deepseek-ai/dsh`)做形态分发:`dsh web` = Host + webserver + 构建出的 `dsh-frontend` dist;`dsh run` = [直接使用核心 Agent/Session 的入口](2026-08-09-headless-direct-core-entry-point.md),不含 Host、HTTP 或浏览器层。 - - 将来的 Electron 形态经由 IPC fetch 载体复用同一套 web client 包。 + - `apps/cli`(`@deepseek-ai/dsh`)分发命令:`dsh web` = Host + webserver + 构建出的 `dsh-frontend` dist;`dsh run` = [直接使用核心 Agent/Session 的入口](2026-08-09-headless-direct-core-entry-point.md),不含 Host、HTTP 或浏览器层。 + - 将来的 Electron 应用经由 IPC fetch 载体复用同一套 web client 包。 ``` -apps/* (application shapes: apps/web = vite app, apps/cli = bin dispatch) +apps/* (applications: apps/web = vite app, apps/cli = bin dispatch) │ consume ▼ packages/host/* packages/client/* apiproxy front layer: protocol pure libs: ui-slots / web-react / ui-primitives runtime assembly / host entity dshClient plugins ×8 (node half = empty apply, - webserver web-shape HTTP carriage client half = src/client/) + webserver Web HTTP carriage client half = src/client/) │ ctx.plugin(...) ▲ import only apiproxy's /api /client subpaths ▼ │ (type-only + the client base class) harness core packages ──────────────────┘ (types reach the browser via import type) @@ -54,7 +54,7 @@ harness core packages ──────────────────┘ TypeScript 以 solution 根引用的**两个聚合 program** 检查(`tsconfig.json` = solution;`tsconfig.host.json` = host 侧 + 测试,排除 `packages/client`;`tsconfig.client.json` = client 各包及其测试):两侧在相同键(`sessions`、`loader`)下以不同服务合并 cordis `Context` 接口,单一 program 会同时看到两份声明合并而报冲突。共享叶子包(session/llm/tools/apiproxy 等)只构建一次,由两个 program 共同引用([拓扑](../process/2026-07-22-tsconfig-solution-root-two-aggregates.md))。 -协议侧:TS interface(`packages/host/apiproxy/src/api/`,零 Node 依赖,浏览器可 import);wire 消息统一为**双向模型**——每条逻辑消息由「谁发起 × request/response」定形(两轴四格,后文称四象限),与物理通道解耦;客户端统一继承 `AbstractApiClient`(协议不变量全在基类,平台差异只是 `doFetch` 传输切面)。 +协议侧:TS interface(`packages/host/apiproxy/src/api/`,零 Node 依赖,浏览器可 import);wire 消息统一为**双向模型**——每条逻辑消息按「谁发起 × request/response」分类(两轴四格,后文称四象限),与物理通道解耦;客户端统一继承 `AbstractApiClient`(协议不变量全在基类,平台差异只是 `doFetch` 传输切面)。 #### 分层角色 @@ -62,22 +62,22 @@ TypeScript 以 solution 根引用的**两个聚合 program** 检查(`tsconfig. |---|---|---|---| | 前置层 | `dsh-host-apiproxy` | TS/zod 定义 (api/)+ fetch 抽象 (fetch/:handler + 客户端基类) | 做简单、所有接入方都要;Node/浏览器皆可 import;协议内容见下文「消息协议」起各节;client 不得经 ctx 绕开 api | | 装配层 | `dsh-host-runtime` | 插件组合 + ApiProxy 集成 + web UI 插件挂载(覆盖八个 dshClient 包的内存 Loader 树);host 级配置归属地(defaults/persistenceRoot,将来用户 profile) | 装什么插件、给什么默认值只在这里定;壳不得改装配 | -| 承载层 | `dsh-host-webserver` | Web 形态 HTTP 与 upgrade:静态服务 + `/api/*`→handler 转发 + WebSocket upgrade route + close 语义;插件 bundle 端点 + `__DSH_BOOT__` manifest(元数据清单)注入(由 web 插件注册表供给) | Web(浏览器访问)专用;零 workspace 依赖(注册表经结构注入到达);Electron 不复用它 | +| 承载层 | `dsh-host-webserver` | Web HTTP 与 upgrade:静态服务 + `/api/*`→handler 转发 + WebSocket upgrade route + close 语义;插件 bundle 端点 + `__DSH_BOOT__` manifest(元数据清单)注入(由 web 插件注册表供给) | Web(浏览器访问)专用;零 workspace 依赖(注册表经结构注入到达);Electron 不复用它 | | client 库 | `dsh-client-ui-slots` / `dsh-client-web-react` / `dsh-client-ui-primitives` | slot 注册表核心 / ctx↔React 胶合 / 纯 React 原子组件 | 组件零 cordis 运行时依赖;由壳播种进 loader 模块表 | | client 插件 | `dsh-client-connection` / `dsh-client-runtime` / `dsh-client-ui-theme` / `dsh-client-i18n` / `dsh-client-ui-layout` / `dsh-client-ui-sidebar` / `dsh-client-ui-conversation` / `dsh-client-ui-trajectory` | 浏览器侧 cordis 插件树(wire 消费者、核心服务、主题、i18n、布局、侧栏、对话、轨迹)——见 Web 客户端架构笔记 | 双入口(node 半边=空 apply;实现在 `src/client/`);消费面唯一经 ApiProxy | -| 应用态 | `@deepseek-ai/dsh`(apps/cli)+ `dsh-frontend`(apps/web,vite 应用) | bin 粗分发 + 每形态一个拼装模块(web.ts / headless.ts);vite 应用是 `dsh-client-web` 壳表面之上的薄 main | 形态间动态 import 互不加载;dist 定位等 workspace 知识留在 app | +| 应用 | `@deepseek-ai/dsh`(apps/cli)+ `dsh-frontend`(apps/web,vite 应用) | bin 粗分发 + 每个应用一个拼装模块(web.ts / headless.ts);vite 应用是 `dsh-client-web` 壳表面之上的薄 main | 各应用使用动态 import,因此不会互相加载;dist 定位等 workspace 知识留在 app | #### 命名规则 `packages/host/*` 与 `packages/client/*` 下的包名**必须含目录组前缀**:host/runtime → `dsh-host-runtime`、client/runtime → `dsh-client-runtime`。目录名不重复组前缀(host/ 已表达)。因此包名尾段 ≠ 目录名,tsconfig.base.json 的 `dsh-*` 通配(按目录名解析)命不中——**这两组的每包需显式 paths 条目**,且 client 各包的 `/client` 子路径要单列条目,使源码级解析与 exports map 一致。 -#### 怎么接入一个新形态(操作清单) +#### 怎么接入一个新应用(操作清单) 1. **选 fetch 伪造方式**:浏览器同源 HTTP / 进程内 `host.handler.fetch` 注入 / 自写传输切面子类(如将来 Electron IPC,见下文「子类表」)。 -2. **在 `apps/` 下写拼装模块**:`startHost()` + 客户端子类 + 该形态私有的信号/打印/退出语义;混合体不建包,拼装写在 app 里。 +2. **在 `apps/` 下写拼装模块**:`startHost()` + 客户端子类 + 该应用私有的信号/打印/退出语义;混合体不建包,拼装写在 app 里。 3. **需要 HTTP 承载才 import `dsh-host-webserver`**,否则零端口。 -现有两种形态保持这一边界:Web 形态挂载 Host、载体与浏览器组合,而 `dsh run` 挂载直接使用核心服务的 runner,不包含 Host、HTTP 或端口。ACP 类协议桥不遵循 client 载体清单:它把 core 暴露给外部生态,直接通过 `ctx.plugin(入口插件)` 挂载,不套 fetch。 +现有两个应用保持这一区分:Web 应用挂载 Host、载体与浏览器组合,而 `dsh run` 挂载直接使用核心服务的 runner,不包含 Host、HTTP 或端口。ACP 类协议桥不遵循 client 载体清单:它把 core 暴露给外部生态,直接通过 `ctx.plugin(入口插件)` 挂载,不使用 fetch。 ## 消息协议 @@ -214,7 +214,7 @@ export type ResponseValue = | 子类 | 所在包 | doFetch | 用途 | |---|---|---|---| | `InProcessApiClient` | apiproxy 本包 | 注入的 `{ fetch }` handler | **同构点**:`new InProcessApiClient(toFetchHandler(api))` 全程不过网络但真跑 wire 序列化/zod/SSE 帧;载体测试与调用方可以在不打开端口的情况下运行这套协议,而产品 `dsh run` 直接驱动 core | -| `WebApiClient` | dsh-client-connection | `globalThis.fetch` 上行 + 每逻辑流一条同源 WebSocket 下行 | 浏览器形态;物理边界见 [WebSocket 下行载体](2026-08-04-websocket-downlink-carrier.md) | +| `WebApiClient` | dsh-client-connection | `globalThis.fetch` 上行 + 每逻辑流一条同源 WebSocket 下行 | 浏览器客户端;物理边界见 [WebSocket 下行载体](2026-08-04-websocket-downlink-carrier.md) | | `FixtureApiClient` | dsh-client-connection | 不用(协议层覆写) | 无 server 的 UI 开发(`?fixture`):覆写 `callUnary`/`openMux`/`openHost`/`respond` 虚方法,自己就是假 server(帧 rpcId 由它 mint,语义自洽) | | IPC 桥子类(假想示例——尚无此形态) | Electron 壳 | IPC 序列化往返 | 只需换 doFetch,约定/基类零改 | @@ -232,15 +232,15 @@ export type ResponseValue = ## Consequences -所有 client 形态消费同一约定:加一个 unary 方法是从单一签名辐射的五步机械改动,换载体只动一个 `doFetch` 子类,wire 上每条消息可 zod 校验、可经 envelope tap 观测、可按 rpcId 对账。普通 unary 调用仍受时限约束,而 `host.pickDirectory` 与 `command.execute` 可保持挂起,直到操作完成或调用方/连接取消到来;若由用户掌控节奏的操作不自行结束,请求可能一直挂起,这是为避免把合理的操作时长视为传输失败而接受的代价。其余接受的代价:两组包需要显式 tsconfig paths 条目;预留方法(fork/inject/task.list/listModels/hostInstanceId)在真实消费者出现前保持休眠。 +所有 client 使用同一约定:加一个 unary 方法是从单一签名出发的五步机械改动,换载体只动一个 `doFetch` 子类,wire 上每条消息可 zod 校验、可经 envelope tap 观测、可按 rpcId 对账。普通 unary 调用仍受时限约束,而 `host.pickDirectory` 与 `command.execute` 可保持挂起,直到操作完成或调用方/连接取消到来;若由用户掌控节奏的操作不自行结束,请求可能一直挂起,这是为避免把合理的操作时长视为传输失败而接受的代价。其余接受的代价:两组包需要显式 tsconfig paths 条目;预留方法(fork/inject/task.list/listModels/hostInstanceId)在真实消费者出现前保持休眠。 ## Alternatives considered | 放弃项 | 一句话理由 | |---|---| -| 按「产品形态」分包(web 一族、electron 一族) | 形态间共享的是 host/client 两侧能力而非形态本身;能力支持方分层让新形态零新包 | +| 按产品分包(web 一族、electron 一族) | 产品共享的是 host/client 两侧能力,而不是某个应用实现;能力支持方分层让新应用零新包 | | 混合体建包(如 headless 独立包) | 混合体只有一个消费者(它自己的 app),建包是无主抽象;拼装写在 app 里可读可弃 | -| 消费型 client 直连 ctx(省 apiproxy 一层) | client 形态需要 wire 校验、观测与多 client 一致性。直接 headless 是没有 client 边界的本地入口,使用公开的 Agent/Session seam,而不是 client 命令面 | +| 消费型 client 直连 ctx(省 apiproxy 一层) | client 需要 wire 校验、观测与多 client 一致性。直接 headless 是没有 client 边界的本地入口,使用公开的 Agent/Session seam,而不是 client 命令面 | | webserver 依赖 runtime(省 handler 注入) | 结构 typing 注入让 webserver 可被 sidecar/测试复用且零 workspace 依赖;包依赖会把装配知识拖进承载层 | | 包名不带组前缀(沿用 dsh-<尾段>) | `dsh-runtime`/`dsh-web-ui` 在扁平 npm 命名空间里失去归属信息;代价只是每包一条显式 paths | | 复用仓内 JSON-RPC 2.0(dsh-jsonrpc) | 数字错误码退化成单码兜底、约定双份人肉对齐、命名无 convention 自然漂移 | diff --git a/.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.i18n.yaml index e7fd7799f0..1c73978183 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.md -2026-07-24-web-config-tree-boot-and-transport-layering.md: 92ec665acc745e61f656bd0e57454ad266b722f9 -2026-07-24-web-config-tree-boot-and-transport-layering.zh.md: 7cd96ad9e52c19a005e6bff356dc5159ad3a31bc +2026-07-24-web-config-tree-boot-and-transport-layering.md: 9bf44e398da66ee286fc9bbc1496c002606d1606 +2026-07-24-web-config-tree-boot-and-transport-layering.zh.md: 23d9bc790c792438cb699952ee802e1ffa89de88 diff --git a/.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.md b/.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.md index 92ec665acc..9bf44e398d 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.md +++ b/.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.md @@ -18,7 +18,7 @@ English | [中文](2026-07-24-web-config-tree-boot-and-transport-layering.zh.md) **Config sources have one declaration place each.** Bundle yml values are engineering defaults, Settings sections are writable user preferences, CLI flags address their owning launcher rows, and env values enter through yml `!!js` expressions. Patches replace a row's config wholesale. The resolved frontend `distIndex` uses that patch channel as an assembly fact. The transport-independent provider/model default belongs to `ctx.agentDefaultModel`; the [direct headless entry point](2026-08-09-headless-direct-core-entry-point.md) and the Web gateway consume the same state. -**The transport splits five ways.** `dsh-host-apiproxy` is the gateway plugin (`api-gateway` row): it default-exports `ApiProxyService`, configures only `{workspaceRoot?}`, consumes the base layer's entry-point-neutral `ctx.agentDefaultModel`, provides `ctx.apiProxy`, remains transport-agnostic, and registers no routes. `dsh-host-webserver` is a plain route-registration plugin: `HttpServerService` provides `ctx.httpServer` (`register(route) → disposer` with duplicate-pattern throw, `tapIndex` transforms applied in registration order, `port`), listens on activation, answers per-request failures with 400 and logging, and knows no harness concepts. The connection node half owns the `/api` binding from `ctx.apiProxy` through `toFetchHandler`. The modules node half (`ClientModuleHostService`, providing `ctx.clientModuleHost`) owns incremental package scanning, the bundle route, the index tap, and `onRebuilt`/`onGraphChanged` notification. The hmr node half owns dev reload through `fs.watchFile` membership and the `/plugins/events` SSE route. +**The transport splits five ways.** `dsh-host-apiproxy` is the gateway plugin (`api-gateway` row): it default-exports `ApiProxyService`, configures only `{nativeOpen?}`, consumes the base layer's entry-point-neutral `ctx.agentDefaultModel`, provides `ctx.apiProxy`, remains transport-agnostic, and registers no routes. `dsh-host-webserver` is a plain route-registration plugin: `HttpServerService` provides `ctx.httpServer` (`register(route) → disposer` with duplicate-pattern throw, `tapIndex` transforms applied in registration order, `port`), listens on activation, answers per-request failures with 400 and logging, and knows no harness concepts. The connection node half owns the `/api` binding from `ctx.apiProxy` through `toFetchHandler`. The modules node half (`ClientModuleHostService`, providing `ctx.clientModuleHost`) owns incremental package scanning, the bundle route, the index tap, and `onRebuilt`/`onGraphChanged` notification. The hmr node half owns dev reload through `fs.watchFile` membership and the `/plugins/events` SSE route. **Package export discipline.** The modules package exposes exactly `.` (node half) and `./client` (the complete browser half: `ClientModuleSystem`, `parseBootManifest`, the adoption plugin face) — no bespoke subpaths; wire types re-export through the root for host-side consumers. The adoption handshake: the kernel writes the constructed instance to `window.__DSH_MODULES__` before cordis exists; the `./client` apply reads the slot (missing = loud throw) and provides `ctx.modules`. diff --git a/.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.zh.md b/.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.zh.md index 7cd96ad9e5..23d9bc790c 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.zh.md @@ -18,7 +18,7 @@ Status: implemented **每个配置源有唯一声明位置。** 组合包 yml 值是工程默认,Settings 分节是可写的用户偏好,CLI(命令行界面)flags 面向其归属的启动器配置行,env 值则通过 yml `!!js` 表达式进入。patch 会整体替换一行的 config。解析后的前端 `distIndex` 通过同一条 patch 通道作为组装事实传递。与传输无关的提供方/模型默认值归 `ctx.agentDefaultModel` 所有;[直接 headless 入口](2026-08-09-headless-direct-core-entry-point.md)与 Web 网关消费同一份状态。 -**传输五分。** `dsh-host-apiproxy` 是网关插件(`api-gateway` 行):默认导出 `ApiProxyService`,只配置 `{workspaceRoot?}`,消费 base 层不偏向特定入口的 `ctx.agentDefaultModel`,provide `ctx.apiProxy`,保持传输无关且不注册路由。`dsh-host-webserver` 是朴素的路由注册插件:`HttpServerService` provide `ctx.httpServer`(`register(route) → disposer`、重复 pattern 即抛、`tapIndex` 按注册序应用、`port`),激活即 listen,单请求失败时答 400 并记日志,且不认识任何 harness 概念。connection node 半拥有从 `ctx.apiProxy` 经 `toFetchHandler` 绑定到 `/api` 的逻辑。modules node 半(`ClientModuleHostService`,provide `ctx.clientModuleHost`)拥有单包增量扫描、bundle 路由、index tap 与 `onRebuilt`/`onGraphChanged` 通知。HMR node 半通过 `fs.watchFile` membership 与 `/plugins/events` SSE 路由拥有开发期重载。 +**传输五分。** `dsh-host-apiproxy` 是网关插件(`api-gateway` 行):默认导出 `ApiProxyService`,只配置 `{nativeOpen?}`,消费 base 层不偏向特定入口的 `ctx.agentDefaultModel`,provide `ctx.apiProxy`,保持传输无关且不注册路由。`dsh-host-webserver` 是朴素的路由注册插件:`HttpServerService` provide `ctx.httpServer`(`register(route) → disposer`、重复 pattern 即抛、`tapIndex` 按注册序应用、`port`),激活即 listen,单请求失败时答 400 并记日志,且不认识任何 harness 概念。connection node 半拥有从 `ctx.apiProxy` 经 `toFetchHandler` 绑定到 `/api` 的逻辑。modules node 半(`ClientModuleHostService`,provide `ctx.clientModuleHost`)拥有单包增量扫描、bundle 路由、index tap 与 `onRebuilt`/`onGraphChanged` 通知。HMR node 半通过 `fs.watchFile` membership 与 `/plugins/events` SSE 路由拥有开发期重载。 **包出口纪律。** modules 包只暴露 `.`(node 半)与 `./client`(完整浏览器半:`ClientModuleSystem`、`parseBootManifest`、收编插件面)——不设专用子路径;wire 类型经根出口 re-export 给 host 侧消费方。收编握手:内核在 cordis 之前把建好的实例写入 `window.__DSH_MODULES__`;`./client` 的 apply 读取该槽位(缺少时显式抛错)并 provide `ctx.modules`。 diff --git a/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.i18n.yaml index fc8cf85334..5236b342a2 100644 --- a/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md -2026-07-26-task-registry-seam.md: 45801505f1729ec6094900acf94b4c17ed92c3b1 -2026-07-26-task-registry-seam.zh.md: 8dd90b34d2da1d22caba13fe8c388dab4a29be0d +2026-07-26-task-registry-seam.md: 4487bd9c53595fa8b4eed588b294ceafe3ab58dc +2026-07-26-task-registry-seam.zh.md: 6195dc809e84852c7e0f63ac101ba0ed6a46853e diff --git a/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md b/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md index 45801505f1..4487bd9c53 100644 --- a/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md +++ b/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md @@ -12,7 +12,7 @@ The [background-task runtime](2026-06-20-generic-long-running-tool-runtime.md) s `tasks/` is now a three-package capability family in the bash-trio shape: -- **`@deepseek-ai/dsh-tasks` (Service Definition)** — the abstract `TaskService extends Service` owning `ctx.tasks`, the eight-method contract (`start`, `list`, `get`, `read`, `kill`, `wait`, `onTaskDone`, `attachSurface`), all vocabulary types (`TaskId`, `TaskKindMap`, `TaskStart`, `TaskHooks`, `TaskOutcome`, `TaskSnapshot`, `TaskRead`, `TaskDoneListener`), and the snapshot invariant companion. The class-level JSDoc states the semantics every Service provider owes: registrations outlive producer and surface fibers, owned access is session-fenced, settlement is first-wins with contained listeners, and `start` refuses work while no control surface is attached. +- **`@deepseek-ai/dsh-tasks` (Service Definition)** — the abstract `TaskService extends Service` owning `ctx.tasks`, the eight-method contract (`start`, `list`, `get`, `read`, `kill`, `wait`, `onTaskDone`, `attachSurface`), all vocabulary types (`TaskId`, `TaskKindMap`, `TaskStart`, `TaskHooks`, `TaskOutcome`, `TaskSnapshot`, `TaskRead`, `TaskDoneListener`), and the snapshot invariant companion. The class-level JSDoc states the semantics every Service provider owes: registrations outlive producer and surface fibers, owned access is session-fenced, settlement is first-wins with contained listeners, and `start` refuses work while no attached control surface serves the spec's owner (surfaces and listeners are scope-layered, so one process-wide registry answers both questions per owner). - **`@deepseek-ai/dsh-tasks-local` (Service provider)** — `LocalTaskService`, the process-local registry moved verbatim: the in-memory store, per-kind counters, waiter bookkeeping, `TASK_WAIT_TIMEOUT` deadline code, owner-cleanup effects, and force-fail teardown. The `dsh-timeout` dependency moves here with it; the Service Definition package has no provider dependencies. - **`@deepseek-ai/dsh-tool-tasks` (Consumer)** — unchanged; it injects `'tasks'` and never imports provider types. diff --git a/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.zh.md b/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.zh.md index 8dd90b34d2..6195dc809e 100644 --- a/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.zh.md @@ -12,7 +12,7 @@ Status: implemented `tasks/` 如今是一个 bash 三件套形态的三包能力家族: -- **`@deepseek-ai/dsh-tasks`(Service Definition)**——抽象的 `TaskService extends Service`,拥有 `ctx.tasks`、八个方法的约定(`start`、`list`、`get`、`read`、`kill`、`wait`、`onTaskDone`、`attachSurface`)、全部词汇类型(`TaskId`、`TaskKindMap`、`TaskStart`、`TaskHooks`、`TaskOutcome`、`TaskSnapshot`、`TaskRead`、`TaskDoneListener`),以及快照不变式配套插件。类级 JSDoc 陈述了每个 Service provider 都必须兑现的语义:注册的存续期长于生产方与控制接口的 fiber,有所有者的访问以会话为界,结算遵循首次结果优先且监听器错误被隔离,并且在没有附加任何控制接口时 `start` 拒绝启动工作。 +- **`@deepseek-ai/dsh-tasks`(Service Definition)**——抽象的 `TaskService extends Service`,拥有 `ctx.tasks`、八个方法的约定(`start`、`list`、`get`、`read`、`kill`、`wait`、`onTaskDone`、`attachSurface`)、全部词汇类型(`TaskId`、`TaskKindMap`、`TaskStart`、`TaskHooks`、`TaskOutcome`、`TaskSnapshot`、`TaskRead`、`TaskDoneListener`),以及快照不变式配套插件。类级 JSDoc 陈述了每个 Service provider 都必须兑现的语义:注册的存续期长于生产方与控制接口的 fiber,有所有者的访问以会话为界,结算遵循首次结果优先且监听器错误被隔离,并且当没有任何已附加的控制接口服务于 spec 的所有者时 `start` 拒绝启动工作(控制接口与监听器按 scope 分层,因此一个进程级注册表能逐所有者地回答这两个问题)。 - **`@deepseek-ai/dsh-tasks-local`(Service provider)**——`LocalTaskService`,即原样迁移的进程内注册表:内存存储、按 kind 划分的计数器、等待方簿记、`TASK_WAIT_TIMEOUT` deadline 代码、所有者清理 effect,以及强制失败的拆除。`dsh-timeout` 依赖随之迁入此包;Service Definition 包不含任何提供方依赖。 - **`@deepseek-ai/dsh-tool-tasks`(Consumer)**——保持不变;它注入 `'tasks'`,从不导入提供方类型。 diff --git a/.agents/notes/implemented/architecture/2026-08-04-configuration-source-ownership.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-04-configuration-source-ownership.i18n.yaml index 8979270e36..0b2df8cc51 100644 --- a/.agents/notes/implemented/architecture/2026-08-04-configuration-source-ownership.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-04-configuration-source-ownership.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-04-configuration-source-ownership.md -2026-08-04-configuration-source-ownership.md: d656d329cbc5b3dfbae2561775ed1afc878cebd0 -2026-08-04-configuration-source-ownership.zh.md: 29ef3b83d18d8836b28e4c151ad44c52542b0a11 +2026-08-04-configuration-source-ownership.md: 8ec750de2efaf148fe44a58b415d7da44b87bdc6 +2026-08-04-configuration-source-ownership.zh.md: 64daa54843d55c6f4bde5a8fdece66dbbe835479 diff --git a/.agents/notes/implemented/architecture/2026-08-04-configuration-source-ownership.md b/.agents/notes/implemented/architecture/2026-08-04-configuration-source-ownership.md index d656d329cb..8ec750de2e 100644 --- a/.agents/notes/implemented/architecture/2026-08-04-configuration-source-ownership.md +++ b/.agents/notes/implemented/architecture/2026-08-04-configuration-source-ownership.md @@ -60,7 +60,7 @@ The line is that these take effect with no user action, before any turn, outside ## Alternatives considered -**Unify credentials into the non-secret ordering, by who authored each source.** Attempted and abandoned: it reads well, but the settings seam already fixes composition *below* the user section, so "authored by deployment" is not a tier the seam can express — and moving `.credentials.yaml` above the launching environment would take away the one override CI, containers, and a per-run `DEEPSEEK_API_KEY=…` depend on. Two orderings that each say why they are shaped that way beat one that describes neither accurately. +**Unify credentials into the non-secret ordering, by who authored each source.** Attempted and abandoned: it reads well, but the settings seam already fixes composition *below* the user section, so "authored by deployment" is not a tier the seam can express — and moving `.credentials.yaml` above the launching environment would take away the one override CI, containers, and a per-run `DEEPSEEK_API_KEY=…` depend on. Two orderings that each explain their precedence beat one that describes neither accurately. **Withhold routing and credentials from the invoking project until it is explicitly trusted.** Rejected as the product's stance: a checkout is trusted by default, with no prompt and no stored trust record. The residual is real and worth naming — cloning a repository that carries a `.env` naming another endpoint or key routes that session through it — and a later project-trust gate is where that gets addressed, not a rule that makes the common case require ceremony. diff --git a/.agents/notes/implemented/architecture/2026-08-04-configuration-source-ownership.zh.md b/.agents/notes/implemented/architecture/2026-08-04-configuration-source-ownership.zh.md index 29ef3b83d1..64daa54843 100644 --- a/.agents/notes/implemented/architecture/2026-08-04-configuration-source-ownership.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-04-configuration-source-ownership.zh.md @@ -62,7 +62,7 @@ inherited process environment (read-only, wins) ## Alternatives considered -**按「来源由谁书写」把凭据并入非机密顺序。** 尝试过并放弃:它读起来很顺,但 settings seam 已经把 composition 固定在用户 section *之下*,因此「由部署方写入」根本不是该 seam 能表达的一层;而把 `.credentials.yaml` 抬到启动环境之上,会夺走 CI、容器和一次性 `DEEPSEEK_API_KEY=…` 所依赖的那唯一一种覆盖。两条各自说清自身形状成因的顺序,好过一条两边都描述不准的顺序。 +**按「来源由谁书写」把凭据并入非机密顺序。** 尝试过并放弃:它读起来很顺,但 settings seam 已经把 composition 固定在用户 section *之下*,因此「由部署方写入」根本不是该 seam 能表达的一层;而把 `.credentials.yaml` 抬到启动环境之上,会夺走 CI、容器和一次性 `DEEPSEEK_API_KEY=…` 所依赖的那唯一一种覆盖。两条各自说明优先顺序的规则,好过一条两边都描述不准的规则。 **在项目被显式信任之前,不给它路由与凭据能力。** 作为产品立场被否决:checkout 默认可信,不询问,也不存储信任记录。残留风险是真实的、值得写明——克隆一个携带 `.env`、其中指定了另一个 endpoint 或密钥的仓库,会让该会话经由它——处理它的地方是日后的 project trust 门禁,而不是一条让常见情形都要走仪式的规则。 diff --git a/.agents/notes/implemented/architecture/2026-08-07-shared-feedback-telemetry-user-id.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-07-shared-feedback-telemetry-user-id.i18n.yaml new file mode 100644 index 0000000000..226b62f100 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-08-07-shared-feedback-telemetry-user-id.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-07-shared-feedback-telemetry-user-id.md +2026-08-07-shared-feedback-telemetry-user-id.md: 6d4020828cb1f2ab3de0328c8959a18a0fcfe6c4 +2026-08-07-shared-feedback-telemetry-user-id.zh.md: 892fa0f848d656609885d008ab36e3ebbe09b992 diff --git a/.agents/notes/implemented/architecture/2026-08-07-shared-feedback-telemetry-user-id.md b/.agents/notes/implemented/architecture/2026-08-07-shared-feedback-telemetry-user-id.md new file mode 100644 index 0000000000..6d4020828c --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-08-07-shared-feedback-telemetry-user-id.md @@ -0,0 +1,33 @@ +# Agent Note: Shared feedback and telemetry anonymous user id + +Status: implemented + +English | [中文](2026-08-07-shared-feedback-telemetry-user-id.zh.md) + +## Problem + +The OpenTelemetry backend already persisted one anonymous UUID in `$DSH_HOME/.userid`. `/feedback` now needs to report both the receiving session id and a user id so an operator can correlate the acknowledgement with exported records. Duplicating or independently generating that identity would make the reported user meaningless, while importing it from `session-telemetry-otel` would make a direct command depend on an exporter backend and create a dependency cycle when feedback export is mounted by telemetry. + +The earlier [anonymous-user-id decision](../feature/2026-07-31-telemetry-anonymous-user-id.md) deliberately kept the helper inside the OTel backend until a second real consumer existed. Feedback is that consumer. + +## Decision + +`@deepseek-ai/dsh-user-id` owns `getOrCreateAnonymousUserId()` and the `$DSH_HOME/.userid` storage contract. `session-telemetry-otel` uses the returned id as OpenTelemetry Resource `user.id`; the `/feedback` success acknowledgement reports `Feedback recorded for session {sessionId}` followed by `User: {userId}` on a second line, which keeps both identifiers available through the generic command row's expandable body. Invalid feedback is rejected before resolving the id, so an empty command does not create `.userid`. + +The extraction preserves the existing random UUID, home resolution, process memo, exclusive-create concurrency, corruption replacement, and best-effort write semantics. It does not unify the dsh-sdk launcher's separate `telemetry.json` identity. + +## Alternatives considered + +| Rejected | Reason | +|---|---| +| Import the helper from `session-telemetry-otel` | Couples feedback to an optional exporter backend and forms a reverse dependency cycle once telemetry exports feedback | +| Duplicate the persistence helper in feedback | Two implementations of one file contract can drift and race with different validation or failure semantics | +| Generate a separate feedback user id | The acknowledgement could not correlate with the OTel Resource and would not satisfy the reporting purpose | +| Move the launcher telemetry id too | The launcher feed is not a consumer of `.userid`; unifying unrelated stores remains out of scope | + +## Consequences + +- One harness home now has one anonymous id shared by feedback acknowledgements and session telemetry exports. +- The feedback package depends only on the identity capability, not the telemetry seam or OTel SDK. +- The new package is a justified shared seam with two consumers; its empty invariant companion explains why reading the private file is not a useful runtime relationship check. +- The original anonymous-user-id Note remains authoritative for storage and privacy semantics, while this Note supersedes only its OTel-local ownership decision. diff --git a/.agents/notes/implemented/architecture/2026-08-07-shared-feedback-telemetry-user-id.zh.md b/.agents/notes/implemented/architecture/2026-08-07-shared-feedback-telemetry-user-id.zh.md new file mode 100644 index 0000000000..892fa0f848 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-08-07-shared-feedback-telemetry-user-id.zh.md @@ -0,0 +1,33 @@ +# Agent Note: 反馈与遥测共享匿名用户 id + +Status: implemented + +[English](2026-08-07-shared-feedback-telemetry-user-id.md) | 中文 + +## 问题 + +OpenTelemetry 后端已在 `$DSH_HOME/.userid` 中持久化一个匿名 UUID。`/feedback` 需要同时报告接收反馈的会话 id 与用户 id,以便运维人员将确认文本与导出的记录相关联。复制该身份或单独生成身份会使报告的用户失去意义;从 `session-telemetry-otel` 导入身份则会让直接命令依赖导出后端,并在遥测侧挂载反馈导出时形成依赖环。 + +早先的[匿名用户 id 决策](../feature/2026-07-31-telemetry-anonymous-user-id.md)刻意将辅助函数留在 OTel 后端内,直至出现第二个真实消费方。反馈就是这个消费方。 + +## 决策 + +`@deepseek-ai/dsh-user-id` 负责 `getOrCreateAnonymousUserId()` 和 `$DSH_HOME/.userid` 存储契约。`session-telemetry-otel` 将返回的 id 用作 OpenTelemetry Resource 的 `user.id`;`/feedback` 的成功确认先报告 `Feedback recorded for session {sessionId}`,再在第二行显示 `User: {userId}`,使两个标识符都可通过通用命令行的可展开正文查看。系统在获取 id 前拒绝无效反馈,因此空命令不会创建 `.userid`。 + +此次抽取保留既有的随机 UUID、home 解析、进程内缓存、独占创建并发、损坏文件替换与 best-effort 写入语义。它不会统一 dsh-sdk launcher 独立的 `telemetry.json` 身份。 + +## 考虑过的替代方案 + +| 已否决 | 原因 | +|---|---| +| 从 `session-telemetry-otel` 导入辅助函数 | 使反馈耦合到可选的导出后端,并在遥测导出反馈后形成反向依赖环 | +| 在反馈中复制持久化辅助函数 | 同一文件契约的两份实现可能发生偏差,并因校验或失败语义不同而产生竞态 | +| 生成独立的反馈用户 id | 确认文本无法与 OTel Resource 相关联,因而不能达到报告目的 | +| 同时移动 launcher telemetry id | launcher 回流不是 `.userid` 的消费方;统一无关存储仍不在范围内 | + +## 后果 + +- 一个 harness home 只有一个匿名 id,由反馈确认与会话遥测导出共享。 +- 反馈包只依赖身份能力,不依赖遥测 seam 或 OTel SDK。 +- 新包由两个消费方使用,成为有充分依据的共享 seam;其空不变式伴生插件解释了为何读取私有文件并非有用的运行时关系检查。 +- 原始匿名用户 id Note 仍是存储与隐私语义的权威记录;本 Note 仅取代其中由 OTel 本地拥有身份的决策。 diff --git a/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.i18n.yaml index d8c55c9f0a..410e209e28 100644 --- a/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.md -2026-08-08-per-preset-standing-mounts.md: 834d645f5f293a2e137b8faf662e301f1e8bb971 -2026-08-08-per-preset-standing-mounts.zh.md: 45ce0f4e7dec28e5bf807898dc9cdbf32b8e4eb5 +2026-08-08-per-preset-standing-mounts.md: c2792454f90a88cd6fba36eed8e36104e5fffea4 +2026-08-08-per-preset-standing-mounts.zh.md: 47668c8c2c424eb188aa14bf55986d27bcfb8ee0 diff --git a/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.md b/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.md index 834d645f5f..c2792454f9 100644 --- a/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.md +++ b/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.md @@ -16,7 +16,7 @@ A preset is one composition per PROCESS, not one per session. The roster mounts Standing mounts fix the class, not the instances: the registrations a reader needs exist for the process lifetime, keyed by preset id, no agent required. What made it cheap -- The stateful preset plugins (`plan-mode`, `token-meter`, `compact-basic`, `tasks-local`) already key state by `Session`/`Agent` — they predate presets. Sharing one instance is a return to their design, not a rewrite. +- The stateful preset plugins (`plan-mode`, `token-meter`, `compact-basic`) already key state by `Session`/`Agent` — they predate presets. Sharing one instance is a return to their design, not a rewrite. `tasks-local` shared that property and has since left the preset plane entirely: producers outside its realm (`tool-bash`, `tool-pty`, a non-continuable `tool-subagent`) resolve the registry with `ctx.get`, which an entry-local realm hides from them, so it is composed on the host plane and only the model-facing `tool-tasks` row stays per preset. - Preset ymls are unchanged: one mount per preset = one Entry per preset, whose entry-local realms (`isolate: : true`) keep two presets' same-named services apart exactly as they kept two sessions' apart. - A shared realm label was NOT an option: `provide()` throws on a second registration under the same realm symbol, so labels pool the REALM, never the instance — a per-session world sharing a label crashes the second mount. diff --git a/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.zh.md b/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.zh.md index 45ce0f4e7d..47668c8c2c 100644 --- a/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.zh.md @@ -16,7 +16,7 @@ Status: implemented 常驻挂载修的是这一类问题而非其中的个例:读取方需要的注册在进程生命周期内始终存在,按 preset id 索引,不需要任何 agent。让它便宜的原因: -- 有状态的 preset 插件(`plan-mode`、`token-meter`、`compact-basic`、`tasks-local`)本就按 `Session`/`Agent` 分键存状态——它们早于 preset 存在。共享一份实例是回归其设计,不是改写。 +- 有状态的 preset 插件(`plan-mode`、`token-meter`、`compact-basic`)本就按 `Session`/`Agent` 分键存状态——它们早于 preset 存在。共享一份实例是回归其设计,不是改写。`tasks-local` 同样具备该性质,且此后已完全离开 preset 平面:realm 之外的生产方(`tool-bash`、`tool-pty`、非 continuable 的 `tool-subagent`)以 `ctx.get` 解析该注册表,而 entry-local realm 对它们不可见,因此它组合在宿主平面,只有面向模型的 `tool-tasks` 行仍留在各 preset 中。 - preset 的 yml 不变:每 preset 挂一次 = 每 preset 一个 Entry,其 entry 本地 realm(`isolate: : true`)让两个 preset 的同名服务互不相干,正如它从前隔开两个会话。 - 共享 realm label **不是**选项:`provide()` 对同一 realm 符号下的第二次注册直接抛错,label 池化的是 REALM 而非实例——按会话挂载的世界里共享 label 会让第二次挂载崩溃。 diff --git a/.agents/notes/implemented/bug-fix/2026-07-31-same-basename-workspace-adoption.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-07-31-same-basename-workspace-adoption.i18n.yaml index 990e7e39bb..e7bdf260be 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-31-same-basename-workspace-adoption.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-07-31-same-basename-workspace-adoption.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-07-31-same-basename-workspace-adoption.md -2026-07-31-same-basename-workspace-adoption.md: ed53804ea64df0d61db16e579c3d65af803dbb97 -2026-07-31-same-basename-workspace-adoption.zh.md: 82cfb7d90afca28f8e666742a758fda0202909f3 +2026-07-31-same-basename-workspace-adoption.md: 1192558632fdc8bd732ea59f0eca5051f49f5d2c +2026-07-31-same-basename-workspace-adoption.zh.md: 9c1f1ffd221936e24300b223ad395e1f44552810 diff --git a/.agents/notes/implemented/bug-fix/2026-07-31-same-basename-workspace-adoption.md b/.agents/notes/implemented/bug-fix/2026-07-31-same-basename-workspace-adoption.md index ed53804ea6..1192558632 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-31-same-basename-workspace-adoption.md +++ b/.agents/notes/implemented/bug-fix/2026-07-31-same-basename-workspace-adoption.md @@ -14,7 +14,7 @@ A Workspace is identified by its stable id and canonical directory path, while i The Host's `workspace.create({ path })` adoption route inherits that rule. The Workspace manager, picker, grouping tree, selection, rename, deletion, and Session creation continue to use `WorkspaceId`, so equal labels neither merge records nor redirect an operation. The sidebar hover card exposes each canonical path when the labels need disambiguation. -Explicit naming remains stricter. `workspace.create({ name })` and `workspace.rename` continue to reject a title already registered, as described by [manual Workspace naming](../feature/2026-07-25-session-list-browsing-and-manual-order.md). This prevents a user from deliberately introducing another ambiguous label while accepting collisions imposed by existing directory names. The path-adoption rule supersedes only the title-conflict clauses in the [Workspace product flow](../feature/2026-07-25-workspace-ui-product-flow.md) and [native directory picker](../feature/2026-07-27-native-workspace-directory-picker.md). +Explicit naming remains stricter. `workspace.rename` continues to reject a title already registered, as described by [manual Workspace naming](../feature/2026-07-25-session-list-browsing-and-manual-order.md). This prevents a user from deliberately introducing another ambiguous label while accepting collisions imposed by existing directory names. The path-adoption rule supersedes only the title-conflict clauses in the [Workspace product flow](../feature/2026-07-25-workspace-ui-product-flow.md) and [native directory picker](../feature/2026-07-27-native-workspace-directory-picker.md). The durable schema does not change: Workspace records already store id, path, and title independently, bootstrap can derive equal basenames, and startup validates duplicate paths rather than titles. @@ -30,7 +30,7 @@ Workspace registry and Host API tests create two real directories under differen **Use the full path as every Workspace title.** This removes the collision but makes the primary navigation label unnecessarily long. The full path remains available in the hover detail while the concise basename stays useful. -**Permit collisions from explicit rename and create-by-name operations too.** The registry supports that state, but those operations intentionally ask the user to choose a display name. Retaining their conflict response preserves the existing naming guard without blocking filesystem-selected paths. +**Permit collisions from the explicit rename operation too.** The registry supports that state, but rename intentionally asks the user to choose a display name. Retaining its conflict response preserves the existing naming guard without blocking filesystem-selected paths. ## Consequences diff --git a/.agents/notes/implemented/bug-fix/2026-07-31-same-basename-workspace-adoption.zh.md b/.agents/notes/implemented/bug-fix/2026-07-31-same-basename-workspace-adoption.zh.md index 82cfb7d90a..9c1f1ffd22 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-31-same-basename-workspace-adoption.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-07-31-same-basename-workspace-adoption.zh.md @@ -14,7 +14,7 @@ Workspace 的身份由其稳定 id 和规范目录路径确定,标题则是可 Host 的 `workspace.create({ path })` 接纳入口沿用该规则。Workspace 管理器、选择器、分组树、选择、重命名、删除和 Session 创建仍使用 `WorkspaceId`,因此相同标签既不会合并记录,也不会把操作指向其他记录。需要区分相同标签时,侧边栏悬停详情卡会显示各自的规范路径。 -显式命名仍采用更严格的规则。`workspace.create({ name })` 和 `workspace.rename` 仍会拒绝已注册的标题,具体见[手动 Workspace 命名](../feature/2026-07-25-session-list-browsing-and-manual-order.md)。这既防止用户主动引入另一个难以区分的标签,又允许既有目录名称造成的重名。路径接纳规则仅取代 [Workspace 产品流](../feature/2026-07-25-workspace-ui-product-flow.md)和[原生目录选择器](../feature/2026-07-27-native-workspace-directory-picker.md)中的标题冲突条款。 +显式命名仍采用更严格的规则。`workspace.rename` 仍会拒绝已注册的标题,具体见[手动 Workspace 命名](../feature/2026-07-25-session-list-browsing-and-manual-order.md)。这既防止用户主动引入另一个难以区分的标签,又允许既有目录名称造成的重名。路径接纳规则仅取代 [Workspace 产品流](../feature/2026-07-25-workspace-ui-product-flow.md)和[原生目录选择器](../feature/2026-07-27-native-workspace-directory-picker.md)中的标题冲突条款。 持久化 schema 未变:Workspace 记录本就分别存储 id、path 和 title,引导初始化可以派生出相同的 basename,启动校验检查的是重复路径而非重复标题。 @@ -30,7 +30,7 @@ Workspace 注册表与 Host API 测试会在不同父目录下创建两个末级 **将完整路径用作每个 Workspace 的标题。** 这会消除冲突,却使主导航标签不必要地过长。完整路径仍可在悬停详情中查看,而简洁的 basename 仍有价值。 -**也允许显式重命名和按名称创建操作产生重名。** 注册表支持这种状态,但这些操作本就是明确要求用户选择显示名称。保留冲突响应可维持现有命名防护,同时不阻止从文件系统选取的路径。 +**也允许显式重命名操作产生重名。** 注册表支持这种状态,但该操作本就是明确要求用户选择显示名称。保留冲突响应可维持现有命名防护,同时不阻止从文件系统选取的路径。 ## 后果 diff --git a/.agents/notes/implemented/feature/2026-06-15-code-mode.i18n.yaml b/.agents/notes/implemented/feature/2026-06-15-code-mode.i18n.yaml index a334bdc7d5..f8cf184ea9 100644 --- a/.agents/notes/implemented/feature/2026-06-15-code-mode.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-06-15-code-mode.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-06-15-code-mode.md -2026-06-15-code-mode.md: 5ad7be9506c2356f90050b86d85aa77f602946ce -2026-06-15-code-mode.zh.md: 1902c6ba86b6b8d3fecd06c417347a9c082f67b4 +2026-06-15-code-mode.md: 51c53c56f5755d56f49d8e5166a1ceeef0ffc202 +2026-06-15-code-mode.zh.md: dbf8d409dea152b39d215f5dd636989c3f4fa0bb diff --git a/.agents/notes/implemented/feature/2026-06-15-code-mode.md b/.agents/notes/implemented/feature/2026-06-15-code-mode.md index 5ad7be9506..51c53c56f5 100644 --- a/.agents/notes/implemented/feature/2026-06-15-code-mode.md +++ b/.agents/notes/implemented/feature/2026-06-15-code-mode.md @@ -18,7 +18,7 @@ Tool presentation belongs to the registry that owns tool visibility: implementin Three decisions, each elaborated in its own section below: -1. **Code Mode is a first-class presentation mode of `ToolRegistry`** (`dsh-tools`), selected by a validated `mode` config: `'native'` (the default, contributing the visible capability schemas), `'code'` (the registry contributes only its reserved `run_code` transport plus a generated SDK `.d.ts` in the system prompt), or `'both'` (native schemas and the transport + SDK). The registry shapes its canonical contribution at the source; the cooperative prompt-assembly result remains authoritative, and the logged request header records exactly that returned presentation. +1. **Code Mode is a first-class presentation mode of `ToolRegistry`** (`dsh-tools`), selected by a validated `mode` config: `'native'` (the default, contributing the visible capability schemas), `'code'` (the registry contributes only its reserved `run_code` transport plus a generated SDK `.d.ts` in the system prompt), or `'both'` (native schemas and the transport + SDK). The registry constructs its canonical contribution at the source; the cooperative prompt-assembly result remains authoritative, and the logged request header records exactly that returned presentation. 2. **Code execution is a capability seam** — `packages/code-runtime/` contains the Service Definition package `@deepseek-ai/dsh-code-runtime`, which owns `ctx.codeRuntime` ([capability seams](../architecture/2026-06-13-capability-seams.md); Consumer = `dsh-tools`, with core-consumes-a-seam precedent in `agent-loop` → `dsh-llm`). The runtime knows nothing about tools: it is handed a program and named async bindings, runs the program, and reports `{ value, logs, error? }`. Language and substrate are backend properties, so a future Python or container backend is another Service provider package, not a redesign. 3. **The shipped implementation is `@deepseek-ai/dsh-code-runtime-worker`**: one fresh Node worker thread per run, executing the model's TypeScript after type-strip, with bindings bridged over the message port, an empty environment, configurable heap/output/time caps, and hard termination. Its trust posture is bash-equivalent by design — no unsafe-acknowledgement flags — because the harness already ships `dsh-bash-local`, which executes arbitrary model-written shell commands with strictly *more* ambient authority. @@ -120,11 +120,11 @@ Deployments switching to `'code'` must update any native-only `toolOrder`. Assem **The worker is not a hard security boundary.** Deliberate and documented (§Trust posture): posture equals the existing bash tool, containment exceeds it, gating uses the same approval and sandbox policies. Deployments needing more need a future `isolation: 'container'` backend — tracked as the seam's designed extension, not a TODO on this design. -**`stripTypeScriptTypes` is marked experimental.** It is the same engine (amaro/swc) behind Node's own native `.ts` execution, exposed as an API across this repo's whole engines range. Mitigations: the runtime's unit suite pins the behaviors relied on (position preservation, erasable-only rejection message shape loosely), the call sits behind one private function, and `amaro`/`sucrase` are drop-in replacements if the API shifts. The erasable-only subset is a model-facing contract line, and the error path is a working feedback loop, not a dead end. +**`stripTypeScriptTypes` is marked experimental.** It is the same engine (amaro/swc) behind Node's own native `.ts` execution, exposed as an API across this repo's whole engines range. Mitigations: the runtime's unit suite checks position preservation and the required parts of the erasable-only rejection message, the call sits behind one private function, and `amaro`/`sucrase` are direct replacements if the API shifts. The erasable-only subset is a model-facing input restriction, and the error tells the model how to correct the program. **Prompt cost of the SDK, especially under `'both'`.** The `.d.ts` can rival the native schemas it complements; `'both'` carries two representations. Prefix stability + provider caching amortize per-session cost; the mode is per-deployment; the Agent Note makes no unconditional-savings claim. Measured guidance (when to prefer which mode) is explicitly post-ship learning. -**Registry scope growth.** `dsh-tools` absorbs codegen, a tool, a bridge, and an event. Contained by module boundaries inside the package (`ts-types.ts`, `code-mode.ts` beside `schema.ts`/`json-schema.ts`/`presentation.ts`) and by the seam: everything substrate-shaped lives behind `ctx.codeRuntime`. +**Registry scope growth.** `dsh-tools` absorbs codegen, a tool, a bridge, and an event. Package modules separate these responsibilities (`ts-types.ts` and `code-mode.ts` beside `schema.ts`, `json-schema.ts`, and `presentation.ts`), while `ctx.codeRuntime` owns all code-runtime-specific implementation. **Large lossless JSON values can exhaust memory.** Tool bindings snapshot lossless JSON before dispatch and return canonical JSON resolutions whole. The runtime validates both sides of the worker port and applies no per-binding byte cap; structured-clone cost and process or worker memory are the practical bounds. The combined outer-output ledger for logs, the completion value, and a failure diagnostic is the only byte-capped boundary. diff --git a/.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md b/.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md index 1902c6ba86..dbf8d409de 100644 --- a/.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md +++ b/.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md @@ -18,7 +18,7 @@ Cloudflare 的 [Code Mode](https://blog.cloudflare.com/code-mode/) 提出了一 三项决策,各自在下方独立小节中展开: -1. **Code Mode 是 `ToolRegistry`(`dsh-tools`)的一等呈现模式**,通过经校验的 `mode` 配置选择:`'native'`(默认,贡献可见能力 schema)、`'code'`(注册表仅贡献其保留的 `run_code` 传输通道加一份生成的 SDK `.d.ts` 到系统提示词中)或 `'both'`(原生 schema 加传输通道 + SDK)。注册表在源头塑造其权威贡献;协作式提示词组装的结果仍具权威性,记录在日志中的请求头精确反映该返回的呈现。 +1. **Code Mode 是 `ToolRegistry`(`dsh-tools`)的一等呈现模式**,通过经校验的 `mode` 配置选择:`'native'`(默认,贡献可见能力 schema)、`'code'`(注册表仅贡献其保留的 `run_code` 传输通道加一份生成的 SDK `.d.ts` 到系统提示词中)或 `'both'`(原生 schema 加传输通道 + SDK)。注册表在源头构建其权威贡献;协作式提示词组装的结果仍具权威性,记录在日志中的请求头精确反映该返回的呈现。 2. **代码执行是一个能力 seam**——`packages/code-runtime/` 包含 Service Definition 包 `@deepseek-ai/dsh-code-runtime`,拥有 `ctx.codeRuntime`([能力 seam](../architecture/2026-06-13-capability-seams.md);Consumer = `dsh-tools`,core 消费 seam 的先例见 `agent-loop` → `dsh-llm`)。运行时对工具一无所知:它接收一段程序和命名的异步绑定,执行程序,报告 `{ value, logs, error? }`。语言和基底是后端属性,因此未来的 Python 或容器后端只是另一个 Service provider 包,而非重新设计。 3. **交付的实现是 `@deepseek-ai/dsh-code-runtime-worker`**:每次运行 spawn 一个全新的 Node worker 线程,对模型的 TypeScript 进行 type-strip 后执行,绑定通过消息端口桥接,环境为空,堆/输出/时间上限可配置,并支持硬终止。其信任姿态在设计上等同于 bash——无需 unsafe-acknowledgement flag——因为 harness 已经交付了 `dsh-bash-local`,后者以严格*更高*的环境权限执行模型编写的任意 shell 命令。 @@ -120,11 +120,11 @@ SDK 指示模型编写一个所加载运行时语言的异步函数体(默认 **Worker 不是硬安全边界。** 有意为之且已文档化(§信任姿态):姿态等同于既有的 bash 工具,约束能力强于它,门禁使用相同的审批与沙箱策略。需要更强隔离的部署需要未来的 `isolation: 'container'` 后端——作为 seam 设计的扩展点跟踪,而非本设计的 TODO。 -**`stripTypeScriptTypes` 标记为 experimental。** 它与 Node 自身原生 `.ts` 执行背后的引擎(amaro/swc)相同,在本仓库的整个引擎范围内作为 API 暴露。缓解措施:运行时的单元测试套件固定了所依赖的行为(位置保持、可擦除限制的拒绝消息形状宽松匹配),调用位于一个私有函数之后,且 `amaro`/`sucrase` 是 API 变化时的直接替代品。仅可擦除子集是面向模型的约定线,错误路径是一个可工作的反馈循环,而非死胡同。 +**`stripTypeScriptTypes` 标记为 experimental。** 它与 Node 自身原生 `.ts` 执行背后的引擎(amaro/swc)相同,在本仓库的整个引擎范围内作为 API 暴露。缓解措施:运行时的单元测试套件会检查位置保持和可擦除限制拒绝消息中的必需部分;调用位于一个私有函数之后,且 `amaro`/`sucrase` 可在 API 变化时直接替换它。仅可擦除子集是面向模型的输入限制,错误消息会告诉模型如何修正程序。 **SDK 的提示词成本,尤其在 `'both'` 下。** `.d.ts` 可能与它补充的原生 schema 体量相当;`'both'` 携带两种表示。前缀稳定性 + 提供方缓存摊销了每会话成本;mode 是每部署的;本 Agent Note 不做无条件节省的声明。何时优先使用哪种模式的量化指导明确属于上线后学习。 -**注册表 scope 增长。** `dsh-tools` 吸收了代码生成、一个工具、一个桥和一个事件。包内的模块边界(`ts-types.ts`、`code-mode.ts` 与 `schema.ts`/`json-schema.ts`/`presentation.ts` 并列)和 seam 共同约束了这一增长:所有基底相关的内容都在 `ctx.codeRuntime` 之后。 +**注册表 scope 增长。** `dsh-tools` 吸收了代码生成、一个工具、一个桥和一个事件。包内模块把这些职责分开(`ts-types.ts`、`code-mode.ts` 与 `schema.ts`、`json-schema.ts`、`presentation.ts` 并列),所有 code-runtime 专用实现都由 `ctx.codeRuntime` 提供。 **大型无损 JSON 值可能耗尽内存。** 工具绑定会在分发前对无损 JSON 创建快照,并完整返回规范 JSON 返回值。运行时会校验 worker 端口两侧,但不对单次绑定设置字节数上限;结构化克隆成本以及进程或 worker 内存构成实际边界。只有包含日志、完成值和失败诊断的组合外层输出账本受字节数上限约束。 diff --git a/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.i18n.yaml b/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.i18n.yaml index c68db1d6fd..57e84b91ff 100644 --- a/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md -2026-06-21-subagent-capability-seam.md: 28ecf985db1289ce4f80570583eac6a051cd2c0b -2026-06-21-subagent-capability-seam.zh.md: 5f349ddcacebe66c346976b822c9dcc65bda8d18 +2026-06-21-subagent-capability-seam.md: 2b9ea93c40bd4b3547cfda83c3ad0bd52c047792 +2026-06-21-subagent-capability-seam.zh.md: 8b647fa51511e1c4cac55c3a9ea39b7e059bc381 diff --git a/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md b/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md index 28ecf985db..2b9ea93c40 100644 --- a/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md +++ b/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md @@ -10,7 +10,7 @@ English | [中文](2026-06-21-subagent-capability-seam.zh.md) The harness has a long-deferred seam for **subagents** — an agent delegating work to another agent. The intent was sketched in the `Agent`/`AgentLoop` interfaces ([packages/core/agent/src/types.ts](../../../../packages/core/agent/src/types.ts), [packages/core/agent-loop/src/index.ts](../../../../packages/core/agent-loop/src/index.ts)): a creation option referencing a parent agent (fork = seed the child session with the parent's event log; spawn = fresh session), with the child returned as an `Agent` handle so steering and event subscription work uniformly. -The distinctive requirement — the one that shapes the whole design — is that **multiple subagent implementations must coexist at runtime**. A parent may want a cheap in-process child for a scoped subtask AND an isolated out-of-process child (over ACP) in the same session. The transports: +**Multiple subagent implementations must coexist at runtime.** A parent may want a cheap in-process child for a scoped subtask AND an isolated out-of-process child (over ACP) in the same session. The transports: - **in-process** — a child concrete `Agent` on the same `Context` (the cheapest, and nearly free given the existing agent factory); - **ACP** — act as an ACP *client* driving another agent process (which can be another instance of ourselves); @@ -66,7 +66,7 @@ Each in-process subagent runs in its **own `Session`** (own id, `parentSession` ## Testing -Registry and tool tests replace only the nondeterministic child boundary with a package-local scripted provider while exercising the real `SubagentService`, lifecycle, task integration, and model-facing tool. Provider and consumer export shapes retain their Loader regression coverage for the failure described in [postmortem 0001](../../../../docs/postmortem/0001-acp-default-export-drops-inject.md). Registry tests cover reload safety, duplicate names, and start-time capability rejection; nested-agent scenarios replay keylessly through [per-session snapshot replay](../testing/2026-06-22-subagent-snapshot-replay.md); in-process backends also have real-loop unit tests and a with-key e2e. +Registry and tool tests replace only the nondeterministic child with a package-local scripted provider while exercising the real `SubagentService`, lifecycle, task integration, and model-facing tool. Loader regression tests still cover the provider and consumer exports for the failure described in [postmortem 0001](../../../../docs/postmortem/0001-acp-default-export-drops-inject.md). Registry tests cover reload safety, duplicate names, and start-time capability rejection; nested-agent scenarios replay keylessly through [per-session snapshot replay](../testing/2026-06-22-subagent-snapshot-replay.md); in-process backends also have real-loop unit tests and a with-key e2e. ## Consequences diff --git a/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.zh.md b/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.zh.md index 5f349ddcac..8b647fa515 100644 --- a/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.zh.md +++ b/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.zh.md @@ -10,7 +10,7 @@ Status: implemented harness 有一个长期搁置的 seam 用于 **subagent**:一个 agent 将工作委派给另一个 agent。这一意图在 `Agent`/`AgentLoop` 接口中已有草案([packages/core/agent/src/types.ts](../../../../packages/core/agent/src/types.ts)、[packages/core/agent-loop/src/index.ts](../../../../packages/core/agent-loop/src/index.ts)):一个创建选项引用父 agent(fork = 用父会话的事件日志初始化子会话;spawn = 全新会话),子 agent 以 `Agent` 句柄返回,使 steering(中途引导)和事件订阅可以统一工作。 -决定整体设计走向的核心需求是:**多种 subagent 实现必须在运行时共存**。一个父 agent 可能在同一个会话中既需要一个廉价的进程内子 agent 处理有限范围的子任务,又需要一个隔离的进程外子 agent(通过 ACP)。传输方式: +**多种 subagent 实现必须在运行时共存。**一个父 agent 可能在同一个会话中既需要一个廉价的进程内子 agent 处理有限范围的子任务,又需要一个隔离的进程外子 agent(通过 ACP)。传输方式: - **进程内**:在同一个 `Context` 上创建一个具体的子 `Agent`(最廉价,且鉴于现有 agent 工厂几乎零成本); - **ACP**:作为 ACP *客户端*驱动另一个 agent 进程(可以是自身的另一个实例); @@ -66,7 +66,7 @@ bash seam([能力 seam](../architecture/2026-06-13-capability-seams.md))在 ## 测试 -注册表与工具测试仅用包内脚本化提供方替换非确定性的子进程边界,同时运行真实的 `SubagentService`、生命周期、任务集成和面向模型的工具。提供方与消费方的 export 形状仍保留 Loader 回归覆盖,以防止[事故复盘(postmortem)0001](../../../../docs/postmortem/0001-acp-default-export-drops-inject.md) 中描述的失败。注册表测试覆盖重载安全性、重名和启动时能力拒绝;嵌套 agent 场景通过[逐会话快照回放](../testing/2026-06-22-subagent-snapshot-replay.md)进行无密钥回放;进程内后端还有真实循环的单元测试和带密钥的 e2e 测试。 +注册表与工具测试仅用包内脚本化提供方替换非确定性的子 agent,同时运行真实的 `SubagentService`、生命周期、任务集成和面向模型的工具。Loader 回归测试仍覆盖提供方与消费方的 export,以防止[事故复盘(postmortem)0001](../../../../docs/postmortem/0001-acp-default-export-drops-inject.md)中描述的失败。注册表测试覆盖重载安全性、重名和启动时能力拒绝;嵌套 agent 场景通过[逐会话快照回放](../testing/2026-06-22-subagent-snapshot-replay.md)进行无密钥回放;进程内后端还有真实循环的单元测试和带密钥的 e2e 测试。 ## 后果 diff --git a/.agents/notes/implemented/feature/2026-06-30-hook-bridges.i18n.yaml b/.agents/notes/implemented/feature/2026-06-30-hook-bridges.i18n.yaml index 96e76345cc..c9ace8d3d4 100644 --- a/.agents/notes/implemented/feature/2026-06-30-hook-bridges.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-06-30-hook-bridges.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-06-30-hook-bridges.md -2026-06-30-hook-bridges.md: eecc1fc75c10618481231083f890e0e4b122c1ee -2026-06-30-hook-bridges.zh.md: dcc8f496cb3bc6ea7857ce804a9159cbfcbc3f08 +2026-06-30-hook-bridges.md: 0dce552820186755e9243bc5ce1dd3361e2e739d +2026-06-30-hook-bridges.zh.md: 2df3a47fd6219e4007e2cad8f2ffa9bef9600a87 diff --git a/.agents/notes/implemented/feature/2026-06-30-hook-bridges.md b/.agents/notes/implemented/feature/2026-06-30-hook-bridges.md index eecc1fc75c..0dce552820 100644 --- a/.agents/notes/implemented/feature/2026-06-30-hook-bridges.md +++ b/.agents/notes/implemented/feature/2026-06-30-hook-bridges.md @@ -8,7 +8,7 @@ English | [中文](2026-06-30-hook-bridges.zh.md) The harness's extension surface is its typed interception points ([the interception extension-points Agent Note](2026-06-30-interception-extension-points.md)): a "native hook" is just an ordinary cordis plugin subscribing to `agent/session-start`, `agent/pre-step`, `tools/pre-execute`, `tools/post-execute`, `agent/turn-stopping`, `subagent/start`, or `subagent/end`. But users arrive with **existing** Claude Code (CC) and Codex hook configs — a `hooks.json` (or a settings file's `hooks` key) full of shell-command hooks — and want those to run unmodified. This Agent Note introduces the two **bridge plugins** that translate that external shell-hook protocol onto the typed extension points, built on the shared wire-protocol library ([the hook-protocol-lib Agent Note](2026-06-30-hook-protocol-lib.md)). -The framing that shapes the whole design: **a bridge is a compatibility adapter, not a power tool.** Anything a bridge does (block a tool, inject context, force continuation, observe a subagent) a native cordis plugin does more powerfully — typed returns, full `ctx`, no serialization boundary. The bridge's reason to exist is to run the explicitly supported subset of external CC/Codex command hooks. That keeps each bridge thin: parse the config, pick a matcher mode, build the per-event payload, call `runHook` + `mergeHookOutputs` from the shared lib, and map the neutral outcome to a typed Decision. The package READMEs own the exact current unsupported-event and partial-field inventory against the official protocols. +The core rule is: **a bridge is a compatibility adapter, not a power tool.** Anything a bridge does (block a tool, inject context, force continuation, observe a subagent) a native cordis plugin does more powerfully — typed returns, full `ctx`, no serialization boundary. The bridge's reason to exist is to run the explicitly supported subset of external CC/Codex command hooks. That keeps each bridge thin: parse the config, pick a matcher mode, build the per-event payload, call `runHook` + `mergeHookOutputs` from the shared lib, and map the neutral outcome to a typed Decision. The package READMEs own the exact current unsupported-event and partial-field inventory against the official protocols. ## Decision diff --git a/.agents/notes/implemented/feature/2026-06-30-hook-bridges.zh.md b/.agents/notes/implemented/feature/2026-06-30-hook-bridges.zh.md index dcc8f496cb..2df3a47fd6 100644 --- a/.agents/notes/implemented/feature/2026-06-30-hook-bridges.zh.md +++ b/.agents/notes/implemented/feature/2026-06-30-hook-bridges.zh.md @@ -8,7 +8,7 @@ Status: implemented harness 的扩展面是其类型化拦截点(见[拦截扩展点 Agent Note](2026-06-30-interception-extension-points.md)):所谓「原生钩子」不过是一个普通的 Cordis 插件,订阅 `agent/session-start`、`agent/pre-step`、`tools/pre-execute`、`tools/post-execute`、`agent/turn-stopping`、`subagent/start` 或 `subagent/end`。但用户带着**既有的** Claude Code(CC)和 Codex 钩子配置到来,一个 `hooks.json`(或 settings 文件中的 `hooks` 键)里满是 shell 命令钩子,并希望它们原样运行。本 Agent Note 引入两个**桥接插件**,将外部 shell 钩子协议翻译到类型化扩展点上,构建于共享的协议格式(wire format)库之上(见 [hook-protocol-lib Agent Note](2026-06-30-hook-protocol-lib.md))。 -贯穿整个设计的定位:**桥接是兼容性适配器,不是高级工具。** 桥接能做的事(阻止工具、注入上下文、强制继续、观察 subagent),原生 Cordis 插件都能做得更强——类型化返回值、完整 `ctx`、无序列化边界。桥接存在的理由是运行外部 CC/Codex 命令钩子中被明确支持的子集。这使每个桥接保持精简:解析配置、选择匹配模式、构建每事件的 payload、调用共享库的 `runHook` + `mergeHookOutputs`,再将中性结果映射为类型化 Decision。各包的 README 维护着当前不支持的事件和部分字段的完整清单,以官方协议为参照。 +核心规则是:**桥接是兼容性适配器,不是高级工具。** 桥接能做的事(阻止工具、注入上下文、强制继续、观察 subagent),原生 Cordis 插件都能做得更强——类型化返回值、完整 `ctx`、无序列化边界。桥接存在的理由是运行外部 CC/Codex 命令钩子中被明确支持的子集。这使每个桥接保持精简:解析配置、选择匹配模式、构建每事件的 payload、调用共享库的 `runHook` + `mergeHookOutputs`,再将中性结果映射为类型化 Decision。各包的 README 维护着当前不支持的事件和部分字段的完整清单,以官方协议为参照。 ## 决策 diff --git a/.agents/notes/implemented/feature/2026-07-16-durable-per-step-time-context.i18n.yaml b/.agents/notes/implemented/feature/2026-07-16-durable-per-step-time-context.i18n.yaml index d01c01c55a..a67e52a0b4 100644 --- a/.agents/notes/implemented/feature/2026-07-16-durable-per-step-time-context.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-16-durable-per-step-time-context.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-16-durable-per-step-time-context.md -2026-07-16-durable-per-step-time-context.md: e1a5c65894913ad93f46db8ae45e5ef5ead215f3 -2026-07-16-durable-per-step-time-context.zh.md: 129920a02dc21a91ddc92de3d920ddad24968656 +2026-07-16-durable-per-step-time-context.md: 3305d3644fa3baf7e1522311b98b4eb29d08f631 +2026-07-16-durable-per-step-time-context.zh.md: dd7e63710ae99d1a04bc0e87d49976e28af1dae5 diff --git a/.agents/notes/implemented/feature/2026-07-16-durable-per-step-time-context.md b/.agents/notes/implemented/feature/2026-07-16-durable-per-step-time-context.md index e1a5c65894..3305d3644f 100644 --- a/.agents/notes/implemented/feature/2026-07-16-durable-per-step-time-context.md +++ b/.agents/notes/implemented/feature/2026-07-16-durable-per-step-time-context.md @@ -6,7 +6,7 @@ English | [中文](2026-07-16-durable-per-step-time-context.zh.md) ## Problem -A request-only clock can tell the model the current time, but replacing that value in the system prompt removes the evidence behind earlier time-sensitive reasoning. Multi-step turns need requests to retain the readings that shaped preceding steps. The request must remain reconstructable after restart, and automatic compaction must account for the same timing context the model receives. +A request-only clock can tell the model the current time, but replacing that value in the system prompt removes the evidence behind earlier time-sensitive reasoning. Multi-step turns need requests to retain the readings used by preceding steps. The request must remain reconstructable after restart, and automatic compaction must account for the same timing context the model receives. A process-local refresh cache makes displayed time depend on state that cannot survive resume or be reconstructed from the durable session. Durable interval scheduling can reduce append frequency without introducing that hidden state. diff --git a/.agents/notes/implemented/feature/2026-07-16-durable-per-step-time-context.zh.md b/.agents/notes/implemented/feature/2026-07-16-durable-per-step-time-context.zh.md index 129920a02d..dd7e63710a 100644 --- a/.agents/notes/implemented/feature/2026-07-16-durable-per-step-time-context.zh.md +++ b/.agents/notes/implemented/feature/2026-07-16-durable-per-step-time-context.zh.md @@ -6,7 +6,7 @@ Status: implemented ## 问题 -仅存在于请求中的时钟可以告诉模型当前时间,但在系统提示词中替换这个值会移除先前对时间敏感的推理所依据的证据。在包含多个步骤的轮次中,请求需要保留影响先前步骤的读数。系统必须能在重启后重建请求,自动压缩(compaction)也必须将模型实际收到的同一份时间上下文纳入考量。 +仅存在于请求中的时钟可以告诉模型当前时间,但在系统提示词中替换这个值会移除先前对时间敏感的推理所依据的证据。在包含多个步骤的轮次中,请求需要保留先前步骤使用的读数。系统必须能在重启后重建请求,自动压缩(compaction)也必须将模型实际收到的同一份时间上下文纳入考量。 进程本地刷新缓存会使显示时间依赖于一种既无法在恢复后保留、也无法从持久会话重建的状态。持久的间隔调度可以减少追加频率,而不引入这种隐藏状态。 diff --git a/.agents/notes/implemented/feature/2026-07-25-session-list-browsing-and-manual-order.i18n.yaml b/.agents/notes/implemented/feature/2026-07-25-session-list-browsing-and-manual-order.i18n.yaml index a5e0f31b2d..097c0c9f2d 100644 --- a/.agents/notes/implemented/feature/2026-07-25-session-list-browsing-and-manual-order.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-25-session-list-browsing-and-manual-order.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-25-session-list-browsing-and-manual-order.md -2026-07-25-session-list-browsing-and-manual-order.md: 323c0db16cc6b9e83155a3fb873ab94e4facd3a2 -2026-07-25-session-list-browsing-and-manual-order.zh.md: 85b2b319f03113480b3953c21940c123ba594ca8 +2026-07-25-session-list-browsing-and-manual-order.md: 3d2125bdf67a70a1a5bca43c5d5acb09fda178b7 +2026-07-25-session-list-browsing-and-manual-order.zh.md: 161ebd2857073d4dd9cfc2883880cd3e2d91c040 diff --git a/.agents/notes/implemented/feature/2026-07-25-session-list-browsing-and-manual-order.md b/.agents/notes/implemented/feature/2026-07-25-session-list-browsing-and-manual-order.md index 323c0db16c..3d2125bdf6 100644 --- a/.agents/notes/implemented/feature/2026-07-25-session-list-browsing-and-manual-order.md +++ b/.agents/notes/implemented/feature/2026-07-25-session-list-browsing-and-manual-order.md @@ -24,7 +24,7 @@ The group-by menu offers two modes, WorkSpace / In one list. WorkSpace mode rend ### workspace.rename -`workspace.rename({ workspaceId, title })`: the title is trimmed and must be non-blank; both the same-title no-op and the duplicate check evaluate inside the Host's serialized workspace-operation chain (shared with create-by-name, so concurrent explicit naming operations cannot interleave a duplicate or an out-of-order fake success), and a conflict returns `workspace-name-conflict`. Path adoption may derive a title already present because canonical path, not title, owns identity ([decision](../bug-fix/2026-07-31-same-basename-workspace-adoption.md)). Durability goes through `setTitle`'s mutate path, and the `domain/changed` listener broadcasts the `host/workspace-changed` frame automatically. The UI is a standard modal with a client-side duplicate pre-check. +`workspace.rename({ workspaceId, title })`: the title is trimmed and must be non-blank; both the same-title no-op and the duplicate check evaluate inside the Host's serialized workspace-operation chain (shared with path adoption and deletion, so concurrent workspace operations cannot interleave a duplicate or an out-of-order fake success), and a conflict returns `workspace-name-conflict`. Path adoption may derive a title already present because canonical path, not title, owns identity ([decision](../bug-fix/2026-07-31-same-basename-workspace-adoption.md)). Durability goes through `setTitle`'s mutate path, and the `domain/changed` listener broadcasts the `host/workspace-changed` frame automatically. The UI is a standard modal with a client-side duplicate pre-check. ### Manual order: insertSessionBefore replaces activity pinning diff --git a/.agents/notes/implemented/feature/2026-07-25-session-list-browsing-and-manual-order.zh.md b/.agents/notes/implemented/feature/2026-07-25-session-list-browsing-and-manual-order.zh.md index 85b2b319f0..161ebd2857 100644 --- a/.agents/notes/implemented/feature/2026-07-25-session-list-browsing-and-manual-order.zh.md +++ b/.agents/notes/implemented/feature/2026-07-25-session-list-browsing-and-manual-order.zh.md @@ -24,7 +24,7 @@ group-by 菜单提供 WorkSpace / In one list 两种模式。WorkSpace 模式按 ### workspace.rename -`workspace.rename({ workspaceId, title })`:title trim 后非空;同名 no-op 与重名查重都在 Host 的 Workspace 操作串行链内求值(与按名称创建共链,并发的显式命名操作不能穿插出重名或乱序假成功),冲突返回 `workspace-name-conflict`。按路径收编可以派生出已有 title,因为拥有身份的是 canonical path,而不是 title(见[身份决策](../bug-fix/2026-07-31-same-basename-workspace-adoption.md))。落盘经 `setTitle` 的 mutate 通道,`domain/changed` 监听自动广播 `host/workspace-changed` 帧。UI 为标准 Modal,client 侧另做重名预检。 +`workspace.rename({ workspaceId, title })`:title trim 后非空;同名 no-op 与重名查重都在 Host 的 Workspace 操作串行链内求值(与按路径收编和删除共链,并发的 Workspace 操作不能穿插出重名或乱序假成功),冲突返回 `workspace-name-conflict`。按路径收编可以派生出已有 title,因为拥有身份的是 canonical path,而不是 title(见[身份决策](../bug-fix/2026-07-31-same-basename-workspace-adoption.md))。落盘经 `setTitle` 的 mutate 通道,`domain/changed` 监听自动广播 `host/workspace-changed` 帧。UI 为标准 Modal,client 侧另做重名预检。 ### 手动排序:insertSessionBefore 取代活动置顶 diff --git a/.agents/notes/implemented/feature/2026-07-25-workspace-ui-product-flow.i18n.yaml b/.agents/notes/implemented/feature/2026-07-25-workspace-ui-product-flow.i18n.yaml index 535d2a86a4..c0813607d3 100644 --- a/.agents/notes/implemented/feature/2026-07-25-workspace-ui-product-flow.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-25-workspace-ui-product-flow.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-25-workspace-ui-product-flow.md -2026-07-25-workspace-ui-product-flow.md: bfdde9797433eca67e47da73468cba855f2a02b9 -2026-07-25-workspace-ui-product-flow.zh.md: 74a9dd6f70396201a78f3204bfdc7d8b5d4f5d98 +2026-07-25-workspace-ui-product-flow.md: 98e963195126df2ec8291a11b3d9fc7a2baeb0df +2026-07-25-workspace-ui-product-flow.zh.md: 486093be0b8d10c2ae0b8083b305ecad5386351c diff --git a/.agents/notes/implemented/feature/2026-07-25-workspace-ui-product-flow.md b/.agents/notes/implemented/feature/2026-07-25-workspace-ui-product-flow.md index bfdde97974..98e9631951 100644 --- a/.agents/notes/implemented/feature/2026-07-25-workspace-ui-product-flow.md +++ b/.agents/notes/implemented/feature/2026-07-25-workspace-ui-product-flow.md @@ -19,13 +19,12 @@ The Host provides the following GUI wiring on the Workspace entity: | RPC | Behavior | | --- | --- | | `workspace.list` | Returns persistent Workspaces in order and filters out Session ids that fail header validation | -| `workspace.create({ name })` | Creates a directory and Workspace at `workspaceRoot/name`; fails on a display-name conflict | | `workspace.create({ path })` | Adopts an existing directory by canonical path; basename-derived display titles may repeat | | `workspace.delete({ workspaceId })` | Removes the Workspace registration while retaining its directory and session logs; its Sessions become Ungrouped | | `session.create({ workspaceId, sessionId? })` | Resolves cwd from the Workspace, idempotently creates a Session with an optional preallocated id, and attaches it | | `session.create({ cwd })` | Remains available to non-Workspace callers and creates an Ungrouped Session | -`workspaceRoot` is an independent Host setting that falls back to the Host cwd when unset; it is unrelated to `storageRoot`, which stores Workspace domain data. The Host stream pushes Workspace and Session deltas, including `host/workspace-removed`, and the Client refreshes the `workspace.list` and `session.list` baselines separately after reconnecting. Registration-deletion ownership and safety are defined in the [Workspace registration deletion Agent Note](2026-07-27-workspace-registration-deletion.md). +The Host stream pushes Workspace and Session deltas, including `host/workspace-removed`, and the Client refreshes the `workspace.list` and `session.list` baselines separately after reconnecting. Registration-deletion ownership and safety are defined in the [Workspace registration deletion Agent Note](2026-07-27-workspace-registration-deletion.md). A Workspace's `sessionIds` is an ordered candidate index. A membership projection requires both that an id appear in the index and that the corresponding canonicalized `SessionHeader.cwd` equal the Workspace path; SessionHeader does not gain a `workspaceId`. A Session whose cwd matches but whose id is absent from the index remains Ungrouped, while an indexed id is filtered out if its header is missing, its cwd is invalid, or its cwd does not match. Two Workspace indexes claiming the same Session is corrupt state and fails loudly. @@ -52,7 +51,7 @@ When no Workspace exists, the page creates a frontend Workspace object named `wo Top-level New Session, the plus button on a Workspace row, and the Workspace picker all invoke the same New Session action. An explicit Workspace id becomes the target directly; when none is specified, the action uses the most recent Workspace, or the Workspace Intent if no real Workspace exists. The Workspace picker's one Add workspace action ([one-route Note](../simplification/2026-07-31-one-route-to-add-a-workspace.md); it was a pair of Use-an-existing-folder and create-by-name actions when this was decided) immediately creates a real Workspace when the user confirms a directory, then retargets the frontend Session to it; an explicitly created empty Workspace remains even if the user sends no message. -A new Workspace takes its display name from the directory it was created in. Distinct canonical paths may share the same basename-derived title ([identity decision](../bug-fix/2026-07-31-same-basename-workspace-adoption.md)); explicit create-by-name and rename operations retain their duplicate-title checks. Moving Sessions across Workspaces, manual adoption from Ungrouped, and separate display-name and directory-name inputs remain outside this flow. +A new Workspace takes its display name from the directory it was created in. Distinct canonical paths may share the same basename-derived title ([identity decision](../bug-fix/2026-07-31-same-basename-workspace-adoption.md)); the explicit rename operation retains its duplicate-title check. Moving Sessions across Workspaces, manual adoption from Ungrouped, and separate display-name and directory-name inputs remain outside this flow. ### First send and recovery @@ -108,7 +107,7 @@ The Sidebar and conversation empty hero receive standardized actions through slo - Workspace list performs one reentrant bootstrap using only headers; an initialized empty registry does not initialize again after restart, and membership reads validate both the index and canonical cwd. - The initial default target is determined exactly once after both baselines are ready; Workspace groups are not reordered as a whole by hydration or Session activity, and an active Session moves only itself to the front. - A frontend Session under a real Workspace temporarily counts toward the sidebar total, while a Workspace Intent remains hidden; neither publication nor refresh leaves duplicate rows or counts. -- The UI and Host admit distinct same-basename directories as separate Workspaces, while explicit create-by-name and rename operations reject duplicate titles; cwd-only Sessions, Sessions with invalid historical cwd values, and unattached Sessions remain Ungrouped. +- The UI and Host admit distinct same-basename directories as separate Workspaces, while the explicit rename operation rejects duplicate titles; cwd-only Sessions, Sessions with invalid historical cwd values, and unattached Sessions remain Ungrouped. - Confirmed Workspace deletion removes only the registration, retains the current Session, directory, files, and session log, and survives reload; package tests pin unary/frame/baseline races and failure rollback. - Keyless runnable snapshots cover the zero state, explicit creation, and the first send; package-level tests cover bootstrap, membership validation, ordering, idempotency, failure recovery, and arbitrary frame order. diff --git a/.agents/notes/implemented/feature/2026-07-25-workspace-ui-product-flow.zh.md b/.agents/notes/implemented/feature/2026-07-25-workspace-ui-product-flow.zh.md index 74a9dd6f70..486093be0b 100644 --- a/.agents/notes/implemented/feature/2026-07-25-workspace-ui-product-flow.zh.md +++ b/.agents/notes/implemented/feature/2026-07-25-workspace-ui-product-flow.zh.md @@ -19,13 +19,12 @@ Host 在 Workspace entity 上提供以下 GUI 接线: | RPC | 行为 | | --- | --- | | `workspace.list` | 返回持久有序的 Workspace,并过滤未通过 header 校验的 Session id | -| `workspace.create({ name })` | 在 `workspaceRoot/name` 创建目录和 Workspace;显示名冲突时失败 | | `workspace.create({ path })` | 按 canonical path 收编已有目录;由 basename 派生的显示名可以重复 | | `workspace.delete({ workspaceId })` | 移除 Workspace 注册记录,同时保留目录和会话日志;相关 Session 进入 Ungrouped | | `session.create({ workspaceId, sessionId? })` | 从 Workspace 解析 cwd,以可选预分配 id 幂等创建 Session 并 attach | | `session.create({ cwd })` | 保留给非 Workspace 调用方,创建 Ungrouped Session | -`workspaceRoot` 是独立 Host 配置,未配置时回退到 Host cwd;它与保存 Workspace domain 数据的 `storageRoot` 无关。Host 流推送 Workspace 与 Session 增量,包括 `host/workspace-removed`;Client 重连后分别刷新 `workspace.list` 与 `session.list` 基线。删除注册记录的所有权与安全边界由 [Workspace 注册记录删除 Agent Note](2026-07-27-workspace-registration-deletion.md)定义。 +Host 流推送 Workspace 与 Session 增量,包括 `host/workspace-removed`;Client 重连后分别刷新 `workspace.list` 与 `session.list` 基线。删除注册记录的所有权与安全边界由 [Workspace 注册记录删除 Agent Note](2026-07-27-workspace-registration-deletion.md)定义。 Workspace 的 `sessionIds` 是有序候选索引。成员投影同时要求 id 位于索引且对应 `SessionHeader.cwd` canonical 后等于 Workspace path;SessionHeader 不增加 `workspaceId`。cwd 匹配但未入索引的 Session 保持 Ungrouped,索引命中但 header 缺失、cwd 无效或 cwd 不匹配的 id 被过滤。同一 Session 被两个 Workspace 索引占用属于损坏状态并明确报错。 @@ -52,7 +51,7 @@ Session 自己持有首条输入并驱动一条内部流水线:必要时以预 顶部 New Session、Workspace 行内加号和 Workspace picker 最终都调用同一 New Session 动作:显式 Workspace id 直接成为目标,未指定时使用最近 Workspace,没有真实 Workspace 时使用 Workspace Intent。Workspace picker 的单一 Add workspace 动作(见[单一路径 Note](../simplification/2026-07-31-one-route-to-add-a-workspace.md);本决策做出时是 Use an existing folder 与按名称创建两个动作)会在用户确认目录时立即创建真实 Workspace,再把前端 Session 定位到该 Workspace;即使用户不发送消息,显式创建的空 Workspace 也保留。 -新建 Workspace 的显示名取自其所在目录。不同 canonical path 可以拥有相同的 basename 派生显示名(见[身份决策](../bug-fix/2026-07-31-same-basename-workspace-adoption.md));显式的按名称创建和重命名操作仍保留显示名重名检查。跨 Workspace 移动 Session、从 Ungrouped 手动收编以及分别输入显示名和目录名仍不在此动线范围内。 +新建 Workspace 的显示名取自其所在目录。不同 canonical path 可以拥有相同的 basename 派生显示名(见[身份决策](../bug-fix/2026-07-31-same-basename-workspace-adoption.md));显式的重命名操作仍保留显示名重名检查。跨 Workspace 移动 Session、从 Ungrouped 手动收编以及分别输入显示名和目录名仍不在此动线范围内。 ### 首次发送与恢复 @@ -108,7 +107,7 @@ Sidebar 与 conversation empty hero 通过 slot 获得标准化动作:`startSe - Workspace list 只读取 header 完成一次可重入 bootstrap;initialized 的空 registry 重启不重复初始化,成员读取同时校验索引与 canonical cwd。 - 初始默认目标只在两份基线 ready 后确定一次;Workspace 组不因 hydration 或 Session 活跃整体重排,单个活跃 Session 只前移自身。 - 真实 Workspace 下的前端 Session 临时计入 sidebar 数量,Workspace Intent 保持隐藏,发布与刷新都不会留下重复行或重复计数。 -- UI 与 Host 会将 canonical path 不同但 basename 相同的目录接纳为独立 Workspace,而显式的按名称创建和重命名操作会拒绝重复显示名;cwd-only Session、无效历史 cwd 和未 attach Session 保持 Ungrouped。 +- UI 与 Host 会将 canonical path 不同但 basename 相同的目录接纳为独立 Workspace,而显式的重命名操作会拒绝重复显示名;cwd-only Session、无效历史 cwd 和未 attach Session 保持 Ungrouped。 - 经确认的 Workspace 删除只移除注册记录,保留当前 Session、目录、文件和会话日志,并在刷新后保持该状态;包级测试固定一元响应/帧/基线竞态和失败回滚行为。 - keyless runnable snapshot 覆盖零态、显式创建和首次发送;包级测试覆盖 bootstrap、成员校验、排序、幂等、失败恢复及任意 frame 顺序。 diff --git a/.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.i18n.yaml b/.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.i18n.yaml index 6bb0f7e156..f597828cec 100644 --- a/.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.md -2026-07-26-code-dispatch-log-spill.md: 19d37c7ec2dbe5192e4c7e7ecd26e5b7f1467606 -2026-07-26-code-dispatch-log-spill.zh.md: 8dd270278932011db9658f9e4d5c1a1b6fe707ec +2026-07-26-code-dispatch-log-spill.md: a4b8deee86b1f6102e48e34079a93a74dfcfa288 +2026-07-26-code-dispatch-log-spill.zh.md: 0d89329dd91d74491f3f9af0812bb9b4db9c8793 diff --git a/.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.md b/.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.md index 19d37c7ec2..a4b8deee86 100644 --- a/.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.md +++ b/.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.md @@ -4,28 +4,28 @@ Status: implemented English | [中文](2026-07-26-code-dispatch-log-spill.zh.md) -> Scope: bounding the `tool/code-dispatch` event's content with the existing spill machinery. The [host foundation note](2026-07-26-code-dispatch-ui-foundation.md) accepted the unbounded log deliberately with this spill integration as the payoff point; the [live-parallel note](2026-07-26-code-mode-live-parallel-dispatch.md) settled the event pair this shaping hooks into. +> Scope: limiting the `tool/code-dispatch` event's content with the existing spill implementation. The [host foundation note](2026-07-26-code-dispatch-ui-foundation.md) deliberately accepted the unlimited log and deferred spill support to this change; the [live-parallel note](2026-07-26-code-mode-live-parallel-dispatch.md) defines the event pair that this listener processes. ## Problem -Since the full-content dispatch logging landed, a `run_code` program that reads a large file wrote the complete rendered text into the session log — uncapped and outside spill policy, while native results were bounded to `maxInlineBytes` before logging. The asymmetry was backwards: sub-calls (built for bulk data work) were precisely the calls most likely to carry huge results, and the JSONL grew by megabytes per such turn. +After full-content dispatch logging was added, a `run_code` program that reads a large file wrote the complete rendered text into the session log without a limit or spill policy, while native results were limited to `maxInlineBytes` before logging. This treated the most likely large results differently: sub-calls are intended for bulk data work, and each affected turn added megabytes to the JSONL. ## Decision -**A log-shaping waterfall on the registry, and the spill policy as its first listener.** +**A `tools/code-dispatch-log` waterfall on the registry, with spill policy as its first listener.** -- **Extension point**: `tools/code-dispatch-log` — a scope-filtered waterfall the bridge runs (via the registry's PRIVATE `shapeDispatchLog` invoker, handed to the bridge as a capability closure in `RunCodeBridgeOptions` — the waterfall is the public contract, the invoker never widens the service surface; contained: a throwing listener falls back to the unshaped content, with total error formatting so a hostile thrown value cannot escape the containment) over each settled sub-dispatch before appending `tool/code-dispatch`. The payload (`CodeDispatchLog`) carries the outer execution, the hoisted `agent` routing key, the sub-call identity, and the default content — the RENDERED result projection a native `tool/result` would carry (the program itself received the structured `value`). Only the durable copy is shapeable; the model sees neither. Shaping runs OFF the program path as tracked side work, but bounded: past `maxParallelSubCalls` pending log tasks the ordered commit lane holds, so a slow spill backend backpressures the run instead of accumulating unbounded pending I/O; run settlement still drains every task inside the open turn. -- **Policy**: `dsh-spill-policy` registers a second arm on the new extension point sharing the exact replacement pipeline of its model-facing arm (same `maxInlineBytes` cap, same preview + locator + within-cap invariant, same best-effort fallbacks), with the artifact labeled `dispatch` under the sub-call id. UIs and replay read the full text through the spill artifact exactly as they do for spilled native results, so the native-parity rendering story survives bounding. -- **One deliberate asymmetry**: the model-facing arm skips `read` (the `read → spill → read again` loop); the dispatch-log arm bounds `read` sub-calls too — a log copy is not model context, so the loop cannot happen, and `read` is precisely the tool that produces huge logs. +- **Extension point**: `tools/code-dispatch-log` is a scope-filtered waterfall that the bridge runs over each settled sub-dispatch before appending `tool/code-dispatch`. The bridge receives the registry's private `shapeDispatchLog` invoker as a capability closure in `RunCodeBridgeOptions`; the waterfall is the public contract, and the invoker does not add a service method. If a listener throws, the invoker reports any thrown value safely and uses the original settled content. The `CodeDispatchLog` payload carries the outer execution, the `agent` routing key, the sub-call identity, and the default content: the rendered result projection that a native `tool/result` would carry, while the program receives the structured `value`. A listener can replace only the durable copy, which the model never sees. The listener runs as tracked work outside the program's result path. When more than `maxParallelSubCalls` log tasks are pending, the ordered commit loop waits, so a slow spill backend limits later sub-call starts instead of accumulating unlimited pending I/O. Run settlement still waits for every task inside the open turn. +- **Policy**: `dsh-spill-policy` registers a listener for this event and uses the same replacement code as its model-result listener: the same `maxInlineBytes` limit, preview and locator, within-limit invariant, and best-effort fallback. The spill artifact is labeled `dispatch` under the sub-call id. UIs and replay read its full text through the same path used for spilled native results, so both result kinds render with the same information. +- **One deliberate difference**: the model-result listener skips `read` to prevent a `read → spill → read again` loop. The dispatch-log listener also replaces oversized `read` sub-call content because a log copy is not model context, so that loop cannot occur, and `read` is the tool most likely to produce a large log entry. ## Alternatives considered -**Bound inside the bridge with a plain cap (no spill).** Rejected: truncation without a locator loses data replay/UIs may need, and re-introduces the "truncated summary" degraded render path the stack removed. +**Apply a plain byte limit inside the bridge without spill storage.** Rejected: truncation without a locator loses data that replay or UIs may need and restores the less informative "truncated summary" rendering that earlier changes removed. -**Spill inside the bridge directly (call `ctx.spillStore` from code-mode.ts).** Rejected: the registry would grow a hard dependency on the spill capability; the waterfall keeps the policy where every other spill decision lives, composable and disable-able (omitted `maxInlineBytes` still means a true no-op). +**Spill inside the bridge directly by calling `ctx.spillStore` from `code-mode.ts`.** Rejected: the registry would require the spill capability. The waterfall keeps this policy with the other spill decisions and allows compositions to omit it; omitting `maxInlineBytes` still makes the listener a no-op. -**Reuse `tools/post-execute` for nested calls instead of a new event.** Rejected: post-execute shapes the PROGRAM-facing result (nested calls deliberately skip it so programs get complete data); the durable copy needs its own decision point after the program has its value. +**Reuse `tools/post-execute` for nested calls instead of a new event.** Rejected: post-execute can change the program-facing result, so nested calls deliberately skip it and programs receive complete data. The durable copy needs a separate listener that runs after the program has its value. ## Consequences -The session log is bounded again for Code Mode turns — the README's Known Limitations entry about uncapped dispatch logging is resolved and now points here. Old logs with oversized dispatch content still replay (the event shape is unchanged; only future appends shrink). The web UI renders spilled sub-call output as the preview + locator text through the identical native path, no special casing. +Code Mode dispatch entries in the session log now have the configured byte limit, and the README's Known Limitations entry about unlimited dispatch logging now points here. Old logs with oversized dispatch content still replay because the event fields are unchanged; only future appends contain less text. The web UI renders spilled sub-call output as preview and locator text through the same path as native results, with no special case. diff --git a/.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.zh.md b/.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.zh.md index 8dd2702789..0d89329dd9 100644 --- a/.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.zh.md +++ b/.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.zh.md @@ -4,28 +4,28 @@ Status: implemented [English](2026-07-26-code-dispatch-log-spill.md) | 中文 -> 范围:用既有的 spill 机制为 `tool/code-dispatch` 事件的内容施加边界。[宿主侧基础 Agent Note](2026-07-26-code-dispatch-ui-foundation.md)当初有意接受了不设上限的日志,并以这次 spill 集成为兑现点;[实时并行 Agent Note](2026-07-26-code-mode-live-parallel-dispatch.md)敲定了本次整形所挂接的事件对。 +> 范围:用既有的 spill 实现限制 `tool/code-dispatch` 事件的内容。[宿主侧基础 Agent Note](2026-07-26-code-dispatch-ui-foundation.md)有意接受了不设上限的日志,并把 spill 支持留到本次更改;[实时并行 Agent Note](2026-07-26-code-mode-live-parallel-dispatch.md)定义了该监听器处理的事件对。 ## 问题 -自携带完整内容的分发日志落地以来,读取大文件的 `run_code` 程序过去会把完整的渲染文本写进会话日志,不设上限、位于 spill 策略之外;而原生结果在记录之前就已被限制在 `maxInlineBytes` 以内。这种不对称的方向完全反了:子调用(本就为批量数据工作而设计)恰恰是最可能携带巨大结果的调用,而每个这样的轮次都会让 JSONL 增长数 MB。 +加入完整内容的分发日志后,读取大文件的 `run_code` 程序会把完整的渲染文本写进会话日志,既没有上限,也不经过 spill 策略;原生结果则会在记录之前限制在 `maxInlineBytes` 以内。两类结果受到不同处理,而为批量数据工作设计的子调用最可能产生巨大结果;每个受影响的轮次都会让 JSONL 增长数 MB。 ## 决策 -**在注册表上增设一个日志整形 waterfall(瀑布式事件),spill 策略作为其第一个监听器。** +**在注册表上增设 `tools/code-dispatch-log` waterfall(瀑布式事件),spill 策略作为其第一个监听器。** -- **扩展点**:`tools/code-dispatch-log`,一个按作用域过滤的 waterfall,由桥接层在追加 `tool/code-dispatch` 之前对每个已结算的子分发运行(经由注册表的私有 `shapeDispatchLog` 调用器——作为能力闭包经 `RunCodeBridgeOptions` 交给桥接层;waterfall 才是公开约定,调用器绝不扩大服务接口。故障被兜住:监听器抛出异常时回退到未整形的内容,并用可处理任意抛出值的错误格式化,确保恶意抛出值无法逃出兜底)。载荷(`CodeDispatchLog`)携带外层执行、提升出来的 `agent` 路由键、子调用标识与默认内容——即原生 `tool/result` 所载的渲染后结果投影(程序本身收到的是结构化 `value`)。可整形的只有持久副本;模型两者都看不到。整形作为被跟踪的旁路工作在程序路径之外运行,但有界:待处理日志任务超过 `maxParallelSubCalls` 时有序提交通道会暂停,因此慢速 spill 后端会对整个 run 施加背压,而不是无限累积待完成 I/O;run 结算仍会在开放轮次内排空全部任务。 -- **策略**:`dsh-spill-policy` 在新扩展点上注册第二个分支,与其面向模型的分支共用一模一样的替换流水线(同样的 `maxInlineBytes` 上限、同样的预览 + 定位符 + 不超上限不变式、同样的尽力而为回退),产物以 `dispatch` 为标签,记在子调用 id 名下。UI 与回放通过 spill 产物读取全文,方式与读取被 spill 的原生结果完全相同,因此与原生同等保真的渲染在施加边界之后依然成立。 -- **一处有意的不对称**:面向模型的分支跳过 `read`(避免 `read → spill → read again` 循环);分发日志分支则连 `read` 子调用也施加边界:日志副本不是模型上下文,该循环因此不可能发生,而 `read` 恰恰是会产生巨大日志的那个工具。 +- **扩展点**:`tools/code-dispatch-log` 是一个按作用域过滤的 waterfall,桥接层会在追加 `tool/code-dispatch` 之前,对每个已结算的子分发运行它。桥接层通过 `RunCodeBridgeOptions` 接收注册表私有的 `shapeDispatchLog` 调用器;waterfall 是公开约定,该调用器不会增加服务方法。监听器抛出异常时,调用器会安全地报告任意抛出值,并使用原始的已结算内容。`CodeDispatchLog` 载荷包含外层执行、`agent` 路由键、子调用标识和默认内容;默认内容是原生 `tool/result` 会携带的渲染后结果投影,而程序收到结构化 `value`。监听器只能替换持久化副本,模型不会看到这份副本。监听器作为受跟踪任务在程序的返回路径之外运行。待处理日志任务超过 `maxParallelSubCalls` 时,有序提交循环会等待,因此慢速 spill 后端会限制后续子调用启动,而不会无限累积待完成 I/O。run 结算仍会在开放轮次内等待全部任务完成。 +- **策略**:`dsh-spill-policy` 为该事件注册监听器,并复用面向模型结果的监听器所用的替换代码:相同的 `maxInlineBytes` 上限、预览和定位符、不超上限不变式,以及尽力而为回退。spill 产物以 `dispatch` 为标签,记录在子调用 id 名下。UI 与回放通过读取被 spill 原生结果的同一路径读取全文,因此两类结果会渲染出相同的信息。 +- **一处有意差异**:面向模型结果的监听器跳过 `read`,以防出现 `read → spill → read again` 循环。分发日志监听器也会替换过大的 `read` 子调用内容,因为日志副本不是模型上下文,该循环不会发生,而 `read` 最可能产生巨大的日志条目。 ## 曾考虑的替代方案 -**在桥接层内部用普通上限施加边界(不做 spill)。** 否决:没有定位符的截断会丢失回放与 UI 可能需要的数据,还会重新引入本堆叠 PR(Pull Request)链已经移除的「截断摘要」降级渲染路径。 +**在桥接层内部使用普通字节数上限,不存入 spill。** 否决:没有定位符的截断会丢失回放或 UI 可能需要的数据,还会恢复之前更改已经移除的、信息较少的「截断摘要」渲染。 -**直接在桥接层内做 spill(从 code-mode.ts 调用 `ctx.spillStore`)。** 否决:注册表会因此对 spill 能力产生硬依赖;waterfall 则把策略留在所有其他 spill 决策所在的地方,既可组合也可禁用(省略 `maxInlineBytes` 依然意味着真正的 no-op)。 +**直接在桥接层内做 spill,即从 `code-mode.ts` 调用 `ctx.spillStore`。** 否决:注册表会要求提供 spill 能力。waterfall 把该策略与其他 spill 决策放在一起,并允许组合不加载它;省略 `maxInlineBytes` 时,该监听器仍不执行任何操作。 -**让嵌套调用复用 `tools/post-execute`,而不是新增一个事件。** 否决:post-execute 整形的是面向程序的那份结果(嵌套调用有意跳过它,好让程序拿到完整数据);持久副本需要一个属于自己的决策点,位于程序取得其值之后。 +**让嵌套调用复用 `tools/post-execute`,而不是新增一个事件。** 否决:post-execute 可以修改面向程序的结果,因此嵌套调用有意跳过它,让程序取得完整数据。持久化副本需要一个单独的监听器,在程序取得其值之后运行。 ## 后果 -对 Code Mode 轮次而言,会话日志重新有了边界:README 中关于分发日志不设上限的 「已知限制」条目已经解决,现在指向本篇。携带超大分发内容的旧日志仍可回放(事件形状未变;只有今后的追加才会变小)。Web UI 经由与原生完全相同的路径,把被 spill 的子调用输出渲染为预览 + 定位符文本,没有任何特殊处理。 +会话日志中的 Code Mode 分发条目现在遵守已配置的字节数上限,README 中关于分发日志不设上限的「已知限制」条目现在指向本篇。携带超大分发内容的旧日志仍可回放,因为事件字段没有变化;只有今后的追加包含更少文本。Web UI 经由与原生结果相同的路径,把被 spill 的子调用输出渲染为预览和定位符文本,不需要特殊处理。 diff --git a/.agents/notes/implemented/feature/2026-07-28-feedback-command.i18n.yaml b/.agents/notes/implemented/feature/2026-07-28-feedback-command.i18n.yaml index e0133df46f..809e37044f 100644 --- a/.agents/notes/implemented/feature/2026-07-28-feedback-command.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-28-feedback-command.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-28-feedback-command.md -2026-07-28-feedback-command.md: 78cc9e89a5811b3f1520bae2bd971cbcf2522ede -2026-07-28-feedback-command.zh.md: b0bba25ec3331123cb86066fc6c89186f2297cc9 +2026-07-28-feedback-command.md: 3edb29283c289d6d006891a4c19087b01fa8166f +2026-07-28-feedback-command.zh.md: c2513d2570474cbbaf8d94f87603d8ce10d40c14 diff --git a/.agents/notes/implemented/feature/2026-07-28-feedback-command.md b/.agents/notes/implemented/feature/2026-07-28-feedback-command.md index 78cc9e89a5..3edb29283c 100644 --- a/.agents/notes/implemented/feature/2026-07-28-feedback-command.md +++ b/.agents/notes/implemented/feature/2026-07-28-feedback-command.md @@ -12,7 +12,7 @@ The capture surface has to be usable at the moment of annoyance, which rules out ## Decision -`@deepseek-ai/dsh-command-feedback` in `packages/feedback/command-feedback/` registers one global `feedback` command over `ctx.commands`. `/feedback ` acknowledges; bare or whitespace-only input returns a direct usage error. The handler is synchronous, injects only `commands`, and has no configuration. +`@deepseek-ai/dsh-command-feedback` in `packages/feedback/command-feedback/` registers one global `feedback` command over `ctx.commands`. `/feedback ` acknowledges with the receiving session id and the shared harness-home anonymous user id; bare or whitespace-only input returns a direct usage error. The handler is synchronous, injects only `commands`, and has no configuration. [The shared-id decision](../architecture/2026-08-07-shared-feedback-telemetry-user-id.md) records why feedback and OpenTelemetry use the same `$DSH_HOME/.userid` value. The package declares the log-only `feedback/record { text }` session event and exports `recordFeedback(session, text)` as its command-independent producer. The producer discards surrounding whitespace, rejects an empty result, and appends exactly one event. `/feedback` delegates to it, so another UI, hook, or host integration can record the same domain fact without constructing a slash command. @@ -54,7 +54,7 @@ Surrounding whitespace is discarded, but nothing else is parsed. `/feedback /pla ## Consequences -The shipped `dsh` base mounts the command unconditionally — no configuration, no dependency on the goal stack. The Web client exposes it through its command adapter. Headless mode, ACP, and JSON-RPC do not provide a command adapter, so `/feedback` is unavailable there. +The shipped `dsh` base mounts the command unconditionally — no configuration, no dependency on the goal stack. The Web client exposes it through its command adapter. Headless mode, ACP, and JSON-RPC do not provide a command adapter, so `/feedback` is unavailable there. The first accepted feedback for a harness home can create `$DSH_HOME/.userid`; rejected empty input does not resolve or create an id. The package owns one independent append-only event with no cross-event or mutable-data relation for an invariant companion to check. The event follows the session log's existing replay, fork, persistence, and crash-tail behavior. diff --git a/.agents/notes/implemented/feature/2026-07-28-feedback-command.zh.md b/.agents/notes/implemented/feature/2026-07-28-feedback-command.zh.md index b0bba25ec3..c2513d2570 100644 --- a/.agents/notes/implemented/feature/2026-07-28-feedback-command.zh.md +++ b/.agents/notes/implemented/feature/2026-07-28-feedback-command.zh.md @@ -12,7 +12,7 @@ Status: implemented ## 决策 -位于 `packages/feedback/command-feedback/` 的 `@deepseek-ai/dsh-command-feedback` 通过 `ctx.commands` 注册一个全局 `feedback` 命令。`/feedback ` 给出确认;空输入或仅含空白的输入返回直接用法错误。处理器是同步的,只注入 `commands`,且没有任何配置。 +位于 `packages/feedback/command-feedback/` 的 `@deepseek-ai/dsh-command-feedback` 通过 `ctx.commands` 注册一个全局 `feedback` 命令。`/feedback ` 在确认文本中包含接收反馈的会话 id 与 harness home 的共享匿名用户 id;空输入或仅含空白的输入返回直接用法错误。处理器是同步的,只注入 `commands`,且没有任何配置。[共享 id 决策](../architecture/2026-08-07-shared-feedback-telemetry-user-id.md)说明了反馈与 OpenTelemetry 为何使用同一个 `$DSH_HOME/.userid` 值。 本包声明仅写入日志的 `feedback/record { text }` 会话事件,并导出 `recordFeedback(session, text)`,作为不依赖命令的生产方。该生产方丢弃前后空白,拒绝空结果,并且恰好追加一个事件。`/feedback` 委托给它,因此其他 UI、钩子或 host 集成无需构造斜杠命令也能记录同一个领域事实。 @@ -54,7 +54,7 @@ Status: implemented ## 后果 -随附的 `dsh` 基础组合无条件挂载该命令:没有配置,也不依赖 goal 栈。Web 客户端通过命令适配器暴露该命令。无头模式、ACP 和 JSON-RPC 不提供命令适配器,因此 `/feedback` 在那里不可用。 +随附的 `dsh` 基础组合无条件挂载该命令:没有配置,也不依赖 goal 栈。Web 客户端通过命令适配器暴露该命令。无头模式、ACP 和 JSON-RPC 不提供命令适配器,因此 `/feedback` 在那里不可用。对于某个 harness home,首次接受反馈时可能创建 `$DSH_HOME/.userid`;被拒绝的空输入不会获取或创建 id。 本包拥有一个独立的仅追加事件,不存在跨事件关系或可变数据关系可供不变式伴生插件检查。该事件遵循会话日志现有的回放、fork、持久化和崩溃尾部行为。 diff --git a/.agents/notes/implemented/feature/2026-07-30-plan-review-presentation-intent.i18n.yaml b/.agents/notes/implemented/feature/2026-07-30-plan-review-presentation-intent.i18n.yaml index 0e4f334485..30111dab4b 100644 --- a/.agents/notes/implemented/feature/2026-07-30-plan-review-presentation-intent.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-30-plan-review-presentation-intent.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-30-plan-review-presentation-intent.md -2026-07-30-plan-review-presentation-intent.md: f524f7ec67dff3e6d7a6bf59ac204f5c8aae13c6 -2026-07-30-plan-review-presentation-intent.zh.md: 69e0ae00fbc2a98ed45a6d2b711a17f7c947c7e6 +2026-07-30-plan-review-presentation-intent.md: 62e1c24aba7a2901a46da4e6ace9707814472974 +2026-07-30-plan-review-presentation-intent.zh.md: 8415b94cab697e0fa9c4094b938d8ceb99107c7f diff --git a/.agents/notes/implemented/feature/2026-07-30-plan-review-presentation-intent.md b/.agents/notes/implemented/feature/2026-07-30-plan-review-presentation-intent.md index f524f7ec67..62e1c24aba 100644 --- a/.agents/notes/implemented/feature/2026-07-30-plan-review-presentation-intent.md +++ b/.agents/notes/implemented/feature/2026-07-30-plan-review-presentation-intent.md @@ -12,15 +12,15 @@ Every one of those affordances is wrong for the surface. Reviewing a plan is one ## Decision -A question may declare a **presentation intent**, and the Web composer renders a declared intent as its own surface. `AskUserQuestionItem` gains `intent?: AskUserQuestionIntent`, a tagged shape whose one member is `{ kind: 'plan-review', approve: string }`; `plan-mode` sets it on the review question, naming `Approve` as the label that approves. +A question may declare a **presentation intent**, and the Web composer renders a declared intent as its own surface. `AskUserQuestionItem` gains `intent?: AskUserQuestionIntent`, a tagged union whose one member is `{ kind: 'plan-review', approve: string }`; `plan-mode` sets it on the review question, naming `Approve` as the label that approves. -An intent shapes presentation only. The answer protocol is untouched: a UI honouring the intent answers with the same option labels a generic UI would send, so `exit_plan_mode` reads one answer shape regardless of which surface collected it, and a UI that does not know a tag renders the generic flow with nothing lost but the layout. +An intent changes presentation only. The answer protocol is untouched: a UI honouring the intent answers with the same option labels a generic UI would send, so `exit_plan_mode` reads the same answer fields regardless of which surface collected them, and a UI that does not know a tag renders the generic flow with nothing lost but the layout. `approve` names the affirmative option instead of relying on option order, so no UI infers a verdict from a position. Two assertions an intent makes are beyond the types, and `UserInteractionService.ask()` rejects both as `BAD_INTENT` at the asker: an `approve` naming none of that question's own options — before any UI can answer a choice never offered — and an intent on a question with no `detail`, the thing it declares itself a review of, which would ask the user to approve something invisible. On the wire the intent is a discriminated union, so an unrecognised tag is a rejected frame rather than a silently generic render. `ui-question` renders the intent as `PlanReviewPanel`, in the waiting-approval card language: the amber strip carries `Plan review`, the plan is the scrolling markdown body, and the decision row holds three actions — `Chat about it`, `Refuse`, `Approve`. The question text becomes the card's accessible name rather than a headline, because the buttons already say what the decision is. Approve and Refuse answer with the asker's own option labels and keep the asker's descriptions as tooltips; `Chat about it` cancels the request, which returns the composer so the user can simply say what they want. All copy is bilingual under the existing `question` namespace. -Routing lives inside the single composer entry (`QuestionComposer` chooses the shape) rather than in a second chain registration, and `planReviewOf` claims a request only when the card can send every answer that request allows: one question declaring the intent, the plan as its `detail`, the named approve label offered, and a binary single choice — at most one option besides approve, and not multi-select. A third option or a multi-select batch has answers two buttons cannot express, so the generic flow keeps it, and keeps anything else the card cannot render. "Presentation only" is therefore literal: an intent never costs the user a reachable answer, and the client — downstream of a wire boundary — leaves every request answerable. +Routing lives inside the single composer entry (`QuestionComposer` chooses the presentation) rather than in a second chain registration, and `planReviewOf` claims a request only when the card can send every answer that request allows: one question declaring the intent, the plan as its `detail`, the named approve label offered, and a binary single choice — at most one option besides approve, and not multi-select. A third option or a multi-select batch has answers two buttons cannot express, so the generic flow keeps it, and keeps anything else the card cannot render. "Presentation only" is therefore literal: an intent never costs the user a reachable answer, and the client — downstream of a wire boundary — leaves every request answerable. Dismissal became its own model-facing outcome. `ASK_CANCELLED` previously reached the model as "the user cancelled ask_user_question", naming a tool it never called; `exit_plan_mode` now reports that the user dismissed the review to speak instead and to stay in plan mode and wait. Every other ask failure — an abort from turn cancel or provider teardown, where no user is coming — keeps its own message. diff --git a/.agents/notes/implemented/feature/2026-07-30-plan-review-presentation-intent.zh.md b/.agents/notes/implemented/feature/2026-07-30-plan-review-presentation-intent.zh.md index 69e0ae00fb..8415b94cab 100644 --- a/.agents/notes/implemented/feature/2026-07-30-plan-review-presentation-intent.zh.md +++ b/.agents/notes/implemented/feature/2026-07-30-plan-review-presentation-intent.zh.md @@ -12,15 +12,15 @@ Status: implemented ## 决定 -一个问题可以声明**呈现意图(presentation intent)**,Web 输入区把已声明的意图渲染为它自己的界面。`AskUserQuestionItem` 新增 `intent?: AskUserQuestionIntent`,一个带标签的形状,目前唯一成员是 `{ kind: 'plan-review', approve: string }`;`plan-mode` 在审阅问题上设置它,并指明 `Approve` 是表示批准的标签。 +一个问题可以声明**呈现意图(presentation intent)**,Web 输入区把已声明的意图渲染为它自己的界面。`AskUserQuestionItem` 新增 `intent?: AskUserQuestionIntent`,这是一个带标签的联合,目前唯一成员是 `{ kind: 'plan-review', approve: string }`;`plan-mode` 在审阅问题上设置它,并指明 `Approve` 是表示批准的标签。 -意图只塑造呈现。回答协议不变:遵循意图的 UI 回答的仍是通用 UI 会发送的那些选项标签,因此无论由哪个界面收集,`exit_plan_mode` 读到的都是同一种回答形状;而不认识某个标签的 UI 渲染通用流程,除布局之外一无所失。 +意图只改变呈现。回答协议不变:遵循意图的 UI 回答的仍是通用 UI 会发送的那些选项标签,因此无论由哪个界面收集,`exit_plan_mode` 读到的都是同一组回答字段;而不认识某个标签的 UI 渲染通用流程,除布局之外一无所失。 `approve` 指名肯定选项,而不依赖选项顺序,因此没有任何 UI 会从位置推断裁决。意图作出的两项断言超出类型的表达能力,`UserInteractionService.ask()` 都以 `BAD_INTENT` 在提问方一侧拒绝:`approve` 未命中该问题自身的任一选项 —— 早于任何 UI 回答一个从未被提供过的选择;以及意图落在没有 `detail` 的问题上,而 `detail` 正是它自称在审阅的东西,那会让用户去批准一件看不见的事。在协议格式(wire format)上意图是可辨识联合,因此无法识别的标签是被拒绝的帧,而不是静默退回通用渲染。 `ui-question` 把该意图渲染为 `PlanReviewPanel`,沿用等待审批卡片的语言:琥珀色条带写着 `Plan review`,计划是可滚动的 markdown 主体,决定行放三个操作 —— `Chat about it`、`Refuse`、`Approve`。问题文本成为卡片的无障碍名称而非标题,因为按钮已经说明了这次决定是什么。Approve 与 Refuse 用提问方自己的选项标签回答,并把提问方的描述保留为 tooltip;`Chat about it` 取消该请求,从而让输入区归位,用户直接说他想说的话即可。所有文案在既有 `question` 命名空间下双语。 -路由住在单一输入区条目内部(由 `QuestionComposer` 选择形状),而不是第二个链式注册;`planReviewOf` 仅在卡片能够发出该请求允许的每一个答案时才接管:只有一个问题且声明了意图、以 `detail` 承载计划、提供了被指名的批准标签,且是二元单选 —— 除批准外最多一个选项,且非多选。出现第三个选项或多选批次时,其答案是两个按钮无法表达的,通用流程保留它,也保留其他任何卡片渲染不了的请求。因此"只塑造呈现"是字面意义上的:意图绝不让用户失去一个可达的答案,而位于协议边界下游的客户端让每个请求都保持可回答。 +路由住在单一输入区条目内部(由 `QuestionComposer` 选择呈现),而不是第二个链式注册;`planReviewOf` 仅在卡片能够发出该请求允许的每一个答案时才接管:只有一个问题且声明了意图、以 `detail` 承载计划、提供了被指名的批准标签,且是二元单选 —— 除批准外最多一个选项,且非多选。出现第三个选项或多选批次时,其答案是两个按钮无法表达的,通用流程保留它,也保留其他任何卡片渲染不了的请求。因此"只改变呈现"是字面意义上的:意图绝不让用户失去一个可达的答案,而位于协议边界下游的客户端让每个请求都保持可回答。 放弃审阅成为面向模型的独立结果。`ASK_CANCELLED` 以前传到模型的是"the user cancelled ask_user_question",指名了一个它从未调用的工具;现在 `exit_plan_mode` 报告用户放弃审阅是为了改用说话,并要求留在 plan mode 中等待。其余每一种 ask 失败 —— 轮次取消或提供方拆卸导致的中止,那里并没有用户会来 —— 保留它们自己的消息。 diff --git a/.agents/notes/implemented/feature/2026-07-31-telemetry-anonymous-user-id.i18n.yaml b/.agents/notes/implemented/feature/2026-07-31-telemetry-anonymous-user-id.i18n.yaml index 47e1e0144b..1bec2a758b 100644 --- a/.agents/notes/implemented/feature/2026-07-31-telemetry-anonymous-user-id.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-31-telemetry-anonymous-user-id.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-31-telemetry-anonymous-user-id.md -2026-07-31-telemetry-anonymous-user-id.md: f53fc9ce7eb3a5aefcb0be0a20c7403b5601e369 -2026-07-31-telemetry-anonymous-user-id.zh.md: 99b2dd88df94810ccfc85d099e74a6f0852153a7 +2026-07-31-telemetry-anonymous-user-id.md: 75b65e9fd477d19afb3a3a25e424a7f7620099a3 +2026-07-31-telemetry-anonymous-user-id.zh.md: 3db5b665f9cbbe6f884a9717afa758f22a419658 diff --git a/.agents/notes/implemented/feature/2026-07-31-telemetry-anonymous-user-id.md b/.agents/notes/implemented/feature/2026-07-31-telemetry-anonymous-user-id.md index f53fc9ce7e..75b65e9fd4 100644 --- a/.agents/notes/implemented/feature/2026-07-31-telemetry-anonymous-user-id.md +++ b/.agents/notes/implemented/feature/2026-07-31-telemetry-anonymous-user-id.md @@ -10,7 +10,7 @@ Session telemetry is mounted by default ([default-mount Note](2026-07-31-web-tel ## Decision -The `session-telemetry-otel` package's own module `src/user-id.ts` owns the OTel feed's user identity: `getOrCreateAnonymousUserId()` returns the bare UUID line in `$DSH_HOME/.userid` (resolved by `resolveDshHome`, `$DSH_HOME` > `~/.dsh`), minting and persisting a random UUID v4 on first use; the backend constructor carries it as the Resource's `user.id` (the OTel semconv user attribute), once per export batch. This identity belongs to the OTel feed alone; the dsh-sdk launcher telemetry keeps its own anonymous-id store (`telemetry.json`), and the two are not shared (the first cut unified both feeds through a shared util package — no shared package before a second real consumer exists, revisit when a feed-correlation need appears). +`getOrCreateAnonymousUserId()` returns the bare UUID line in `$DSH_HOME/.userid` (resolved by `resolveDshHome`, `$DSH_HOME` > `~/.dsh`), minting and persisting a random UUID v4 on first use; the backend constructor carries it as the Resource's `user.id` (the OTel semconv user attribute), once per export batch. The original implementation lived inside `session-telemetry-otel` because no second real consumer existed. `/feedback` later became that consumer, so [the shared-id decision](../architecture/2026-08-07-shared-feedback-telemetry-user-id.md) moves ownership to `@deepseek-ai/dsh-user-id` without changing the storage, anonymity, concurrency, or loss semantics recorded here. The dsh-sdk launcher telemetry keeps its own anonymous-id store (`telemetry.json`) and remains unrelated. | Ruling | Value | Rationale | |---|---|---| @@ -22,8 +22,8 @@ The `session-telemetry-otel` package's own module `src/user-id.ts` owns the OTel | Write failure | Best-effort: return the in-memory id | Telemetry is never blocked by a read-only home | | Report position | Resource attribute, not per-record attributes | Once per batch suffices for Resource-dimension aggregation; per-record injection would touch the seam contract and grow the wire | | semconv dependency | `@opentelemetry/semantic-conventions` is not imported | One string constant does not justify a dependency | -| Home | A module inside `session-telemetry-otel`, not a shared util package | Repo rule: split a package only for a second real consumer; the sdk launcher feed keeps its own store, and no real correlation need exists | -| Separate switch | None | Identity follows the telemetry master switch (`DSH_TELEMETRY_DISABLED`); telemetry off means nothing reports | +| Home | `@deepseek-ai/dsh-user-id`, shared by the OTel backend and `/feedback` | The second real consumer now exists; direct feedback must not depend on an exporter backend | +| Separate switch | None | Either consumer can create the identity; `DSH_TELEMETRY_DISABLED` stops telemetry reporting but does not disable feedback acknowledgement | ## Alternatives considered @@ -31,7 +31,7 @@ The `session-telemetry-otel` package's own module `src/user-id.ts` owns the OTel |---|---| | Hostname/IP-hash-derived id (the prior ruling) | Reversible means not anonymous; the random UUID is semantically clean — the user ruled to supersede | | user.id on every record's attributes (Claude Code's shape) | Touches the session-telemetry seam contract or injects per record, growing the wire; once per batch on the Resource already aggregates | -| A shared util package unifying both feeds (the first cut) | The only real consumer is the OTel backend; switching the sdk launcher onto it was unification for its own sake — the user reconsidered and pulled it back, to be re-extracted when a correlation need appears | +| A shared package before `/feedback` needed the id (the first cut) | At that time the only real consumer was the OTel backend; extraction became justified only when direct feedback needed the same correlation id | | Reusing telemetry.json instead of a new file | The file name/JSON format files the identity under the launcher feed's naming; the OTel feed's identity is a standalone fact | | AppCLIEntry reading the id and injecting via config patch | Every surface entry needs wiring; a runtime fact inside deployment config conflates the two | | Housing it in `@deepseek-ai/dsh-paths` | paths is pure path computation with zero IO; a persisting identity capability would pollute the package boundary | @@ -39,6 +39,6 @@ The `session-telemetry-otel` package's own module `src/user-id.ts` owns the OTel ## Consequences - One `$DSH_HOME` is one stable user in the OTel feed; separate homes are separate users by construction, with no cross-home linking mechanism. -- The OTel feed and the launcher feed each hold their own id (`.userid` vs `telemetry.json`) and cannot be correlated — the direct cost of not extracting a shared package, to be unified when a real correlation need appears. +- The OTel feed and `/feedback` share `.userid`; the launcher feed still uses `telemetry.json` and cannot be correlated with them. - Deleting `.userid` resets the identity (effective next launch); on an unwritable home each process holds its own in-memory id until the home becomes writable. - The [default-mount Note](2026-07-31-web-telemetry-default-mount.md)'s identity follow-up is closed for the anonymous-user-id part by this decision; hostname/surface dimensions, the redaction rule, and the usage-metrics track remain open. diff --git a/.agents/notes/implemented/feature/2026-07-31-telemetry-anonymous-user-id.zh.md b/.agents/notes/implemented/feature/2026-07-31-telemetry-anonymous-user-id.zh.md index 99b2dd88df..3db5b665f9 100644 --- a/.agents/notes/implemented/feature/2026-07-31-telemetry-anonymous-user-id.zh.md +++ b/.agents/notes/implemented/feature/2026-07-31-telemetry-anonymous-user-id.zh.md @@ -10,7 +10,7 @@ session telemetry 已默认挂载([默认挂载 Note](2026-07-31-web-telemetry ## Decision -`session-telemetry-otel` 包内模块 `src/user-id.ts` 是 OTel 回流用户身份的属主:`getOrCreateAnonymousUserId()` 返回 `$DSH_HOME/.userid`(`resolveDshHome` 解析,`$DSH_HOME` > `~/.dsh`)中的裸 UUID 行,首用生成随机 UUID v4 并落盘;backend 构造时把它作为 Resource 的 `user.id`(OTel semconv 标准用户属性)随每批导出携带一次。该身份只属于 OTel 回流;dsh-sdk launcher telemetry 保留自己的匿名 id 存储(`telemetry.json`),两者不共享(初版曾做公用 util 包统一两条回流——在有第二个真实消费者之前不抽公共包,回流关联需求出现时再议)。 +`getOrCreateAnonymousUserId()` 返回 `$DSH_HOME/.userid`(`resolveDshHome` 解析,`$DSH_HOME` > `~/.dsh`)中的裸 UUID 行,首用生成随机 UUID v4 并落盘;后端构造时把它作为 Resource 的 `user.id`(OTel semconv 标准用户属性)随每批导出携带一次。原始实现位于 `session-telemetry-otel`,因为当时不存在第二个真实消费方。`/feedback` 后来成为该消费方,因此[共享 id 决策](../architecture/2026-08-07-shared-feedback-telemetry-user-id.md)将所有权移交给 `@deepseek-ai/dsh-user-id`,但不改变本 Note 记录的存储、匿名、并发与丢失语义。dsh-sdk launcher telemetry 继续使用自己独立的匿名 id 存储(`telemetry.json`),与此身份无关。 | 裁定 | 取值 | 理由 | |---|---|---| @@ -22,8 +22,8 @@ session telemetry 已默认挂载([默认挂载 Note](2026-07-31-web-telemetry | 写失败 | best-effort 返回内存 id | telemetry 永不因 home 只读被阻塞 | | 上报位置 | Resource 属性,非逐条 attributes | 每批一次即够接收端按 Resource 维度聚合;逐条注入要动 seam 约定且涨 wire 体积 | | semconv 依赖 | 不引 `@opentelemetry/semantic-conventions` 包 | 一个字符串常量不值一个依赖 | -| 落点 | `session-telemetry-otel` 包内模块,非公共 util 包 | 仓规「有第二个真实消费者才拆包」;sdk launcher 回流保留自有存储,无现实关联需求 | -| 单独开关 | 无 | 身份跟随 telemetry 整体开关(`DSH_TELEMETRY_DISABLED`);关 telemetry 即整体不报 | +| 落点 | `@deepseek-ai/dsh-user-id`,由 OTel 后端与 `/feedback` 共享 | 第二个真实消费方已经出现;直接反馈不能依赖导出后端 | +| 单独开关 | 无 | 任一消费方都可创建该身份;`DSH_TELEMETRY_DISABLED` 会停止遥测上报,但不会禁用反馈确认 | ## Alternatives considered @@ -31,7 +31,7 @@ session telemetry 已默认挂载([默认挂载 Note](2026-07-31-web-telemetry |---|---| | hostname/IP 哈希派生 id(此前口径) | 可反查即非匿名;随机 UUID 语义干净,用户裁决取代 | | user.id 放每条 record 的 attributes(Claude Code 形态) | 要动 session-telemetry seam 约定或逐条注入,wire 体积涨;Resource 每批一次已满足聚合 | -| 公用 util 包统一两条回流(初版实现) | 唯一现实消费者是 OTel backend;sdk launcher 换用它只是为统一而统一——用户复议收回,回流关联需求出现时再抽包 | +| 在 `/feedback` 需要该 id 之前抽取共享包(初版实现) | 当时唯一的真实消费方是 OTel 后端;只有直接反馈需要同一个关联 id 后,抽取才具备依据 | | 复用 telemetry.json 不新建文件 | 文件名/JSON 格式把身份挂在 launcher 链路命名下;OTel 回流身份是独立事实 | | AppCLIEntry 读好 id 经 config patch 注入 | 每个 surface 入口都要接线;config 里传运行时事实与部署配置混淆 | | 挂进 `@deepseek-ai/dsh-paths` | paths 是纯路径计算零 IO;带持久化的身份能力会污染包边界 | @@ -39,6 +39,6 @@ session telemetry 已默认挂载([默认挂载 Note](2026-07-31-web-telemetry ## Consequences - 一个 `$DSH_HOME` 在 OTel 回流中是一个稳定用户;不同 home 在构造上就是不同用户,无跨 home 关联机制。 -- OTel 回流与 launcher 回流各有各的 id(`.userid` 与 `telemetry.json`),无法互相关联——这是「不抽公共包」的直接代价,等真实关联需求出现再统一。 +- OTel 回流与 `/feedback` 共享 `.userid`;launcher 回流仍使用 `telemetry.json`,无法与前两者关联。 - 删除 `.userid` 即重置身份(下次启动生效);home 不可写时每进程各自持有一个内存 id 直至恢复可写。 - [默认挂载 Note](2026-07-31-web-telemetry-default-mount.md) 的身份 follow-up 中「匿名用户 id」项由本决定关闭;hostname/surface 维度与脱敏规则、usage-metrics track 仍是待办。 diff --git a/.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.i18n.yaml b/.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.i18n.yaml index cdcbcd8d4e..86b48310e7 100644 --- a/.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.md -2026-08-04-web-context-source-and-steer-marks.md: d74badedaa9623014dbba10ae68ca680daf75de1 -2026-08-04-web-context-source-and-steer-marks.zh.md: b2fd990f6f868fdf422ba97ddd25d19705041e3a +2026-08-04-web-context-source-and-steer-marks.md: 01bdca873a847f70b4b8632b961e01e099ae4f04 +2026-08-04-web-context-source-and-steer-marks.zh.md: b6a9cc5692826b402b5a08ec65a5c8fc3c547b6b diff --git a/.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.md b/.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.md index d74badedaa..01bdca873a 100644 --- a/.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.md +++ b/.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.md @@ -37,11 +37,12 @@ The transcript names all three roles a non-prompt message can play — injected ## Testing - `packages/client/runtime` unit coverage pins each source kind, the label fallbacks when a name field is missing, empty, or wrongly typed, the unnamed degradation for a source with no readable kind, and steering reconstruction on reset and live append paths. -- `packages/client/ui-conversation` jsdom coverage pins the role title, the producer label beside it, the label's survival while expanded, the roleless header, and the steering caption on both durable and pending bubbles. -- The keyless assembled-Web goldens carry the named header and the steering caption, so the assembled transcript — not only component tests — proves the marks. +- `packages/client/ui-conversation` jsdom coverage pins the role title, the producer label beside it, the label's survival while expanded, and the roleless header. +- The keyless assembled-Web goldens carry the named header, so the assembled transcript — not only component tests — proves the marks. ## Consequences +- **Superseded in part.** The steering-caption clause of the Decision no longer describes master: the [caption removal](../simplification/2026-08-10-web-remove-steering-interjection-caption.md) deleted the `插话` / `Interjection` caption, leaving a mid-turn steer recognizable only by its position in the flow. The context-source and recall naming in the Decision stays current, and the `SteeringMessageNode` projection is unchanged. - A reader can attribute every non-prompt message in the transcript at a glance, and the header stays honest for logs this client version has never seen a producer for. - Producer names in the UI are package-shaped (`dsh-tool-skill`, `@deepseek-ai/dsh-system-prompt`) wherever the source carries only a plugin id. That is the cost of refusing a client-side name table; a producer that wants a better label must record one in its source fields. - `ContextMessageNode` gains a required field, so every constructed node — including test fixtures — must supply it. diff --git a/.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.zh.md b/.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.zh.md index b2fd990f6f..b6a9cc5692 100644 --- a/.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.zh.md +++ b/.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.zh.md @@ -37,11 +37,12 @@ transcript 为非提示消息可能承担的三种角色分别命名:注入上 ## 测试 - `packages/client/runtime` 单元覆盖钉住每个来源分支、名称字段缺失/为空/类型不符时的回退、来源没有可读 kind 时的无名降级,以及 reset 和实时 append 路径上的 steering 重建。 -- `packages/client/ui-conversation` 的 jsdom 覆盖钉住角色标题、标题旁的生产者名称、展开后该名称的留存、无名时的标题形态,以及持久与待处理气泡上的 steering 标注。 -- 无密钥的组装 Web 黄金基线携带带名称的标题栏与 steering 标注,因此证明这些标识的是组装后的 transcript,而不只是组件测试。 +- `packages/client/ui-conversation` 的 jsdom 覆盖钉住角色标题、标题旁的生产者名称、展开后该名称的留存,以及无名时的标题形态。 +- 无密钥的组装 Web 黄金基线携带带名称的标题栏,因此证明这些标识的是组装后的 transcript,而不只是组件测试。 ## 后果 +- **部分被取代。** 决策中的 steering 标注条款已不再描述 master:[标注移除决策](../simplification/2026-08-10-web-remove-steering-interjection-caption.md)删除了 `插话` / `Interjection` 标注,轮次中途的 steer 只能靠它在消息流中的位置辨认。决策中的上下文来源与召回命名仍然有效,`SteeringMessageNode` 投影未变。 - 读者一眼即可归因 transcript 中每一条非提示消息;即便面对本客户端版本从未见过其生产者的日志,标题栏依然如实。 - 只要来源仅携带插件 id,UI 中的生产者名称就呈现为包名形态(`dsh-tool-skill`、`@deepseek-ai/dsh-system-prompt`)。这是拒绝客户端名称表的代价;想要更好标签的生产者必须在来源字段中记录该标签。 - `ContextMessageNode` 增加了一个必填字段,因此每一处构造该节点的代码——包括测试 fixture——都必须提供它。 diff --git a/.agents/notes/implemented/feature/2026-08-07-default-model-follows-the-picker.i18n.yaml b/.agents/notes/implemented/feature/2026-08-07-default-model-follows-the-picker.i18n.yaml index de5133b527..21b832d99a 100644 --- a/.agents/notes/implemented/feature/2026-08-07-default-model-follows-the-picker.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-07-default-model-follows-the-picker.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-07-default-model-follows-the-picker.md -2026-08-07-default-model-follows-the-picker.md: 2a3ada55486345c0f58f0767bed5a93ecba04b88 -2026-08-07-default-model-follows-the-picker.zh.md: 08fecc6ec9b177f6424ada3172c67018ac72baea +2026-08-07-default-model-follows-the-picker.md: 10f2d12a017a63d4651add7ce5c4d30aa3c8090c +2026-08-07-default-model-follows-the-picker.zh.md: b232ee22cc6580d85545d26c1a50e27780e00855 diff --git a/.agents/notes/implemented/feature/2026-08-07-default-model-follows-the-picker.md b/.agents/notes/implemented/feature/2026-08-07-default-model-follows-the-picker.md index 2a3ada5548..10f2d12a01 100644 --- a/.agents/notes/implemented/feature/2026-08-07-default-model-follows-the-picker.md +++ b/.agents/notes/implemented/feature/2026-08-07-default-model-follows-the-picker.md @@ -12,7 +12,7 @@ Reasoning effort makes the persistence shape significant: a model selection with ## Decision -`AgentDefaultModelService` provides `ctx.agentDefaultModel` and registers `{provider, model, reasoningEffort?}` as the `agent-default-model` Settings section. Its `{provider, model}` composition entry is the base layer and `settings.yaml` supplies the user layer. The service is entry-point-neutral, so direct creation and ApiProxy-backed creation share one default ([headless direct core entry point](../architecture/2026-08-09-headless-direct-core-entry-point.md)). `workspaceRoot` remains ApiProxy config because it is a Host launcher fact rather than model state. +`AgentDefaultModelService` provides `ctx.agentDefaultModel` and registers `{provider, model, reasoningEffort?}` as the `agent-default-model` Settings section. Its `{provider, model}` composition entry is the base layer and `settings.yaml` supplies the user layer. The service is entry-point-neutral, so direct creation and ApiProxy-backed creation share one default ([headless direct core entry point](../architecture/2026-08-09-headless-direct-core-entry-point.md)). `reasoningEffort` belongs to the Settings section but not to the plugin config. Settings layers merge by field, so a configured effort would survive a user selection that omits it. `saveSelection()` instead writes the complete user section; absence therefore clears a stored effort. A deployment-wide effort default belongs to the adapter profile, which resolves it per model. diff --git a/.agents/notes/implemented/feature/2026-08-07-default-model-follows-the-picker.zh.md b/.agents/notes/implemented/feature/2026-08-07-default-model-follows-the-picker.zh.md index 08fecc6ec9..b232ee22cc 100644 --- a/.agents/notes/implemented/feature/2026-08-07-default-model-follows-the-picker.zh.md +++ b/.agents/notes/implemented/feature/2026-08-07-default-model-follows-the-picker.zh.md @@ -12,7 +12,7 @@ Status: implemented ## 决定 -`AgentDefaultModelService` 提供 `ctx.agentDefaultModel`,并把 `{provider, model, reasoningEffort?}` 注册为 `agent-default-model` Settings 分节。其 `{provider, model}` 组合条目是 base 层,`settings.yaml` 提供用户层。该服务不偏向特定入口,因此直接创建与 ApiProxy 支撑的创建共享同一个默认值([headless 直接 core 入口](../architecture/2026-08-09-headless-direct-core-entry-point.md))。`workspaceRoot` 仍是 ApiProxy 配置,因为它是 Host 启动器事实,而不是模型状态。 +`AgentDefaultModelService` 提供 `ctx.agentDefaultModel`,并把 `{provider, model, reasoningEffort?}` 注册为 `agent-default-model` Settings 分节。其 `{provider, model}` 组合条目是 base 层,`settings.yaml` 提供用户层。该服务不偏向特定入口,因此直接创建与 ApiProxy 支撑的创建共享同一个默认值([headless 直接 core 入口](../architecture/2026-08-09-headless-direct-core-entry-point.md))。 `reasoningEffort` 属于 Settings 分节,但不属于插件配置。Settings 层按字段合并,因此已配置的强度会在用户选择省略它时继续存在。`saveSelection()` 写入完整的用户分节;缺席值由此清除已存强度。部署级强度默认值属于适配器 profile,并由它按模型解析。 diff --git a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml b/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml index a869a98b51..a993fdec64 100644 --- a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md -2026-07-02-bilingual-docs-and-pairing-gate.md: 9e6611aa8391e1478603bedb7d2b96fdd9a8bad2 -2026-07-02-bilingual-docs-and-pairing-gate.zh.md: 654d265f3e8b396e23a49fd53e522826a7adb2ff +2026-07-02-bilingual-docs-and-pairing-gate.md: d516c422d09a51cc47440d3ca73d914e96db2320 +2026-07-02-bilingual-docs-and-pairing-gate.zh.md: 8d478d293b4d6a07e68da5036301816bdeb1bdfd diff --git a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md b/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md index 9e6611aa83..d516c422d0 100644 --- a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md +++ b/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md @@ -15,7 +15,7 @@ This repo's documentation corpus is read by people and agents inside and outside - **`verify-translation-pairing` joins `doc-sync`.** The gate ([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts)) enforces: every discovered, non-excluded source has a complete pair; every existing pair is complete (all three files) and consistent (both hashes match, the Chinese side and every authored English source carry their switchers while listed generated English sources are exempt, structural signatures identical); and excluded generated, instruction, or bilingual-by-construction files stay unpaired. [scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) contains only explicit exclusions, so no requirement can bypass discovery and receive a weaker check. Source-oriented code gates consume a `.zh.md` fence sequence as a derivative only when its unsuffixed sibling has the same tracked fences in the same order with byte-identical bodies; an incomplete, reordered, reclassified, or changed sequence stays independent, so the owning code gate or pairing gate reports the mismatch. - **One corpus-wide requirement.** Every document in scope requires a complete pair from creation; the policy has no per-file rollout state, date cutoff, or README-specific class. README discovery covers every case-insensitive README basename outside vendored, dependency, and ignored build-output trees, including future top-level directories. A site-published pair uses `pairedPages()` so the root locale projects `.zh.md` and `/en/` projects `.md`; creating a counterpart alone does not publish it. - **Pairing records are metadata, not Cordis Loader configuration.** Cordis configuration discovery accepts actual `.cordis.yml` and `.cordis.yaml` files while excluding `*.i18n.yaml`, even when the document name contains `cordis`. This preserves validation of executable Loader entries without parsing translation hashes as configuration. -- **Translation is agent work with human review.** The committed workflow is [.agents/skills/dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md), following the same pattern as [dsh-code-review](../../../skills/dsh-code-review/SKILL.md): the skill carries the workflow and defers to the docs as sources of truth. The skill directs the orchestrating agent to delegate translation writing to a subagent. +- **Translation is agent work with human review.** Routine changes use the direct one-pass path owned by the [lightweight-translation decision](2026-08-08-lightweight-routine-documentation-translation.md). The [extended translation skill](../../../skills/dsh-translate-docs/SKILL.md) retains delegated translation and the other heavier mechanisms for explicit user invocation; both paths defer to the documentation contracts as their sources of truth. ## Verification @@ -32,7 +32,7 @@ The verification contract covers each boundary independently. `verify-translatio ## Industry precedent -Paired sibling files with locale suffixes are the dominant Chinese big-tech convention (ant-design `index.zh-CN.md`/`index.en-US.md`; arco-design `README.zh-CN.md` with a top-of-file switcher; Apache ShardingSphere's 387 `.cn.md`/`.en.md` pairs) — but none of those repos *enforce* pairing or consistency in CI; the convention holds by review alone. Consistency automation exists outside China: MDN's `l10n.sourceCommit` front-matter fingerprint, Vue's Ryu-Cho action (upstream-commit watcher that opens issues/PRs for stale translations), Kubernetes' localization drift scripts, and Microsoft's Azure co-op-translator (source-hash-driven LLM re-translation in CI). This design combines the two: the Chinese-ecosystem file layout with a hash-pair gate, plus a committed agent skill in place of a bot service. +Paired sibling files with locale suffixes are the dominant Chinese big-tech convention (ant-design `index.zh-CN.md`/`index.en-US.md`; arco-design `README.zh-CN.md` with a top-of-file switcher; Apache ShardingSphere's 387 `.cn.md`/`.en.md` pairs) — but none of those repos *enforce* pairing or consistency in CI; the convention holds by review alone. Consistency automation exists outside China: MDN's `l10n.sourceCommit` front-matter fingerprint, Vue's Ryu-Cho action (upstream-commit watcher that opens issues/PRs for stale translations), Kubernetes' localization drift scripts, and Microsoft's Azure co-op-translator (source-hash-driven LLM re-translation in CI). This design combines the two: the Chinese-ecosystem file layout with a hash-pair gate, plus an agent-run workflow in place of a bot service. ## Consequences diff --git a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md b/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md index 654d265f3e..8d478d293b 100644 --- a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md +++ b/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md @@ -15,7 +15,7 @@ Status: implemented - **`verify-translation-pairing` 加入 `doc-sync`。** 门禁([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts))强制执行以下规则:每个已发现且未排除的源文档都有完整配对;每个现有配对都完整(三个文件齐全)且一致(两个 hash 匹配、中文侧和所有普通撰写的英文源都带切换行而清单内的生成英文源除外、结构签名一致);被排除的生成文档、指令文档或本身即双语的文档不得配对。[scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) 只包含显式排除项,因此任何要求都无法绕过发现流程而接受较弱的检查。只有当 `.zh.md` 围栏序列与其无后缀兄弟文件拥有顺序相同、正文按字节一致的同一组受跟踪围栏时,面向源码的代码门禁才会将其作为派生内容消费;不完整、顺序变更、重分类或已改动的序列仍会独立受检,因此由其所属的代码门禁或配对门禁报告不匹配。 - **全语料统一要求。** 范围内的每篇文档从创建起就必须有完整配对;政策没有逐文件推进状态、日期分界或 README 专用类别。README 发现会覆盖 vendor 源码、依赖目录与被忽略的构建产物目录之外所有文件名不区分大小写匹配 README 的文件,包括今后新增的顶层目录。发布到文档站的配对使用 `pairedPages()`,由根 locale 投影 `.zh.md`,由 `/en/` 投影 `.md`;仅创建对侧文件并不会发布它。 - **配对记录是元数据,而不是 Cordis Loader 配置。** Cordis 配置发现会接受实际的 `.cordis.yml` 和 `.cordis.yaml` 文件,同时排除 `*.i18n.yaml`,即使文档名中包含 `cordis` 也不例外。这样既能继续校验可执行的 Loader 配置项,又不会把翻译 hash 当作配置来解析。 -- **翻译是 agent 的工作,由人评审。** 仓库内置的工作流是 [.agents/skills/dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md),与 [dsh-code-review](../../../skills/dsh-code-review/SKILL.md) 模式相同:skill(技能)承载工作流,并将文档作为真源。该 skill 要求编排 agent 把翻译写作委派给 subagent。 +- **翻译是 agent 的工作,由人评审。** 常规改动采用由[轻量翻译决策](2026-08-08-lightweight-routine-documentation-translation.md)确立的直接单遍路径。[扩展翻译 skill(技能)](../../../skills/dsh-translate-docs/SKILL.md)保留委派翻译和其他较重机制,供用户显式调用;两条路径均以文档契约为真源。 ## 验证 @@ -32,7 +32,7 @@ Status: implemented ## 业界先例 -带语言后缀的配对兄弟文件是中国大厂的主流约定(ant-design 的 `index.zh-CN.md`/`index.en-US.md`;arco-design 的 `README.zh-CN.md` 加顶部切换行;Apache ShardingSphere 的 387 对 `.cn.md`/`.en.md`),但这些仓库都没有在 CI 中**强制**配对或一致性检查;约定纯靠评审维系。一致性自动化存在于中国以外:MDN 的 `l10n.sourceCommit` front-matter 指纹、Vue 的 Ryu-Cho action(监视上游 commit,为陈旧译文自动开 issue/PR)、Kubernetes 的本地化漂移脚本、微软 Azure co-op-translator(CI 中由源 hash 驱动的 LLM 重译)。本设计将两者结合:中文生态的文件布局,加上 hash 配对门禁,再加一个仓库内置的 agent skill 替代 bot 服务。 +带语言后缀的配对兄弟文件是中国大厂的主流约定(ant-design 的 `index.zh-CN.md`/`index.en-US.md`;arco-design 的 `README.zh-CN.md` 加顶部切换行;Apache ShardingSphere 的 387 对 `.cn.md`/`.en.md`),但这些仓库都没有在 CI 中**强制**配对或一致性检查;约定纯靠评审维系。一致性自动化存在于中国以外:MDN 的 `l10n.sourceCommit` front-matter 指纹、Vue 的 Ryu-Cho action(监视上游 commit,为陈旧译文自动开 issue/PR)、Kubernetes 的本地化漂移脚本、微软 Azure co-op-translator(CI 中由源 hash 驱动的 LLM 重译)。本设计将两者结合:中文生态的文件布局,加上 hash 配对门禁,再加一个由 agent 运行的工作流替代 bot 服务。 ## 后果 diff --git a/.agents/notes/implemented/process/2026-07-23-translation-prompt-v4-contract.i18n.yaml b/.agents/notes/implemented/process/2026-07-23-translation-prompt-v4-contract.i18n.yaml index b768fb0abc..1e3db15084 100644 --- a/.agents/notes/implemented/process/2026-07-23-translation-prompt-v4-contract.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-23-translation-prompt-v4-contract.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-23-translation-prompt-v4-contract.md -2026-07-23-translation-prompt-v4-contract.md: adeb63e0f4bf03ec0b94a371f596bb84138d81ed -2026-07-23-translation-prompt-v4-contract.zh.md: bd9a18788cbc04f733f52270ec486923c552459e +2026-07-23-translation-prompt-v4-contract.md: 68d6837971a0bbb40fbc5ee093e5911515905ba3 +2026-07-23-translation-prompt-v4-contract.zh.md: d5370f5d6d62a6ca89187d9911fc4dd5c5758b07 diff --git a/.agents/notes/implemented/process/2026-07-23-translation-prompt-v4-contract.md b/.agents/notes/implemented/process/2026-07-23-translation-prompt-v4-contract.md index adeb63e0f4..68d6837971 100644 --- a/.agents/notes/implemented/process/2026-07-23-translation-prompt-v4-contract.md +++ b/.agents/notes/implemented/process/2026-07-23-translation-prompt-v4-contract.md @@ -10,7 +10,7 @@ Automated counterpart generation needs a stable prompt that reproduces the regis ## Decision -The committed [translation prompt](../../../../docs/i18n/translation-prompt.md) is the calibrated pipeline asset. Its renderer injects only the source language, target language, and current [terminology table](../../../../docs/i18n/terminology.md), and rejects unknown, missing, or malformed placeholder syntax before assembling a request. The request assembler retains the source basename outside the model-visible prompt and places each reviewed whole-document pair into one bare-text user/assistant example turn before the real source document. The template may carry model-specific calibration rules, but those rules remain subordinate to the repository's binding pairing, terminology, structure, and emphasis contracts. +The committed [translation prompt](../../../../docs/i18n/translation-prompt.md) is the calibrated pipeline asset. Its renderer injects only the source language, target language, and current [terminology table](../../../../docs/i18n/terminology.md), and rejects unknown, missing, or malformed placeholder syntax before assembling a request. The request assembler retains the source basename outside the model-visible prompt and places each reviewed whole-document pair into one bare-text user/assistant example turn before the real source document. The template may carry model-specific calibration rules, but those rules remain subordinate to the repository's pairing, terminology, structure, and emphasis contracts. The v7 calibration retains that v4 protocol and makes the instruction priority explicit: source meaning and protected structure, then the terminology table, then whole-document gold-pair voice, then general guidance and embedded examples. It directs the model to draft as a native technical author and then compare clause by clause, preserving actors, conditions, negation, modality, lifecycle conditions, direction, result channels, ownership, and quantities. Style guidance cannot invent an actor or vary a terminology-table form, defined concept, or contract verb merely for variety. Unresolved terminology stays unchanged in the translation and is reported only as pending review. @@ -26,7 +26,7 @@ The executable contract lives in [the renderer, request assembler, parser, and r **Inject `translation-rules.md` into every request.** That document governs humans and agents as well as the automated pipeline. Injecting it couples each editorial clarification to model behavior and displaces the manually calibrated prompt constraints; the pipeline instead injects the binding terminology table and verifies its own asset directly. -**Use a strict CDATA XML document.** CDATA provides general XML framing but adds a nested protocol, an additional `]]>` escape, and XML-parser behavior that the three-section contract does not otherwise need. Reserving and escaping six delimiter lines keeps the calibrated response shape while preserving arbitrary Markdown. +**Use a strict CDATA XML document.** CDATA provides general XML framing but adds a nested protocol, an additional `]]>` escape, and XML-parser behavior that the three-section contract does not otherwise need. Reserving and escaping six delimiter lines keeps the calibrated response sections while preserving arbitrary Markdown. **Return only the final translation.** A single body is simpler to parse but discards the explicit correction pass used to catch tone, structure, terminology, and punctuation defects before publication. @@ -36,4 +36,4 @@ The executable contract lives in [the renderer, request assembler, parser, and r ## Consequences -Prompt wording is executable behavior and receives code review, a translation-prompt verifier, and a runnable request/response snapshot. Focused tests pin the retained v4 examples and selected v7 safeguards. The `translation-prompt-v4` snapshot directory names the stable renderer/parser protocol lineage rather than the current calibration revision. The calibrated asset and the general translation rules can evolve for their different audiences, but review must reject contradictions with binding repository contracts. The line escape is visible only when source documentation contains a wrapper tag on its own line, and parser tests pin its lossless behavior. +Prompt wording is executable behavior and receives code review, a translation-prompt verifier, and a runnable request/response snapshot. Focused tests pin the embedded examples and selected v7 safeguards. The `translation-prompt-v4` snapshot directory names the stable renderer/parser protocol series rather than the current calibration revision. The calibrated asset and the general translation rules can evolve for their different audiences, but review must reject contradictions with repository contracts. The line escape is visible only when source documentation contains a wrapper tag on its own line, and parser tests pin its lossless behavior. diff --git a/.agents/notes/implemented/process/2026-07-23-translation-prompt-v4-contract.zh.md b/.agents/notes/implemented/process/2026-07-23-translation-prompt-v4-contract.zh.md index bd9a18788c..d5370f5d6d 100644 --- a/.agents/notes/implemented/process/2026-07-23-translation-prompt-v4-contract.zh.md +++ b/.agents/notes/implemented/process/2026-07-23-translation-prompt-v4-contract.zh.md @@ -10,7 +10,7 @@ Status: implemented ## 决策 -提交入库的[翻译提示词](../../../../docs/i18n/translation-prompt.md)是经过校准的流水线资源。其渲染器仅注入源语言、目标语言和当前[术语表](../../../../docs/i18n/terminology.md),并在组装请求前拒绝未知、缺失或语法格式错误的占位符。请求组装器在模型可见的提示词之外保留源文件基本名,并在真正的源文档之前,将每组经评审的整篇文档对编排为一个纯文本 user/assistant 示例轮次。模板可以包含针对特定模型的校准规则,但这些规则必须服从仓库中具约束力的配对、术语、结构与强调格式约定。 +提交入库的[翻译提示词](../../../../docs/i18n/translation-prompt.md)是经过校准的流水线资源。其渲染器仅注入源语言、目标语言和当前[术语表](../../../../docs/i18n/terminology.md),并在组装请求前拒绝未知、缺失或语法格式错误的占位符。请求组装器在模型可见的提示词之外保留源文件基本名,并在真正的源文档之前,将每组经评审的整篇文档对编排为一个纯文本 user/assistant 示例轮次。模板可以包含针对特定模型的校准规则,但这些规则必须服从仓库的配对、术语、结构与强调格式约定。 v7 校准保留这套 v4 协议,并明确指令优先级:先保持源文含义与受保护结构,再遵循术语表,然后以整篇金标校准语体,最后应用一般指导与内嵌示例。模型先以母语技术作者的方式起草,再逐项对照源文,保留执行主体、条件、否定、情态、生命周期条件、方向、结果通道、所有权和数量。文体指导不得虚构执行主体,也不得仅为丰富措辞而改换术语表词形、已定义概念或约定动词。无法裁定的术语在译文中保持不变,只在评审段标为待人工确认。 @@ -26,7 +26,7 @@ v7 校准保留这套 v4 协议,并明确指令优先级:先保持源文含 **在每个请求中注入 `translation-rules.md`。** 该文档既约束人类与 agent,也约束自动翻译流水线。注入它会让编辑规范的每次澄清都与模型行为耦合,并挤占经过人工校准的提示词约束;因此流水线仅注入具约束力的术语表,并直接校验自身资源。 -**使用严格的 CDATA XML 文档。** CDATA 提供通用的 XML 封装,但会引入一层嵌套协议、额外的 `]]>` 转义规则,以及三段式约定原本不需要的 XML 解析器行为。预留并转义六种定界行,既能维持经校准的响应形态,也能保留任意 Markdown 内容不变。 +**使用严格的 CDATA XML 文档。** CDATA 提供通用的 XML 封装,但会引入一层嵌套协议、额外的 `]]>` 转义规则,以及三段式约定原本不需要的 XML 解析器行为。预留并转义六种定界行,既能维持经校准的响应分段,也能保留任意 Markdown 内容不变。 **只返回最终译文。** 单一正文更易解析,却会丢弃显式修正步骤;这个步骤用于在发布前发现语气、结构、术语和标点缺陷。 @@ -36,4 +36,4 @@ v7 校准保留这套 v4 协议,并明确指令优先级:先保持源文含 ## 影响 -提示词措辞属于可执行行为,因此需要经过代码评审、翻译提示词校验器校验及可运行的请求/响应快照验证。聚焦测试固定保留的 v4 示例与选定的 v7 保护规则。`translation-prompt-v4` 快照目录命名的是稳定的渲染器/解析器协议谱系,而不是当前校准修订号。经校准的资源与通用翻译规则可以针对各自的受众分别演进,但评审必须拒绝任何与仓库约束性约定冲突的改动。只有当源文档中的封装标签独占一行时,行转义才会显现;解析器测试锁定这一无损行为。 +提示词措辞属于可执行行为,因此需要经过代码评审、翻译提示词校验器校验及可运行的请求/响应快照验证。聚焦测试固定内嵌示例与选定的 v7 保护规则。`translation-prompt-v4` 快照目录命名的是稳定的渲染器/解析器协议系列,而不是当前校准修订号。经校准的资源与通用翻译规则可以针对各自的受众分别演进,但评审必须拒绝任何与仓库约定冲突的改动。只有当源文档中的封装标签独占一行时,行转义才会显现;解析器测试锁定这一无损行为。 diff --git a/.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.i18n.yaml b/.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.i18n.yaml index 7f4bc40463..e98385c805 100644 --- a/.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.md -2026-07-26-briefed-minimal-translation-updates.md: afd990b7b63adfd0e66a4726975b678d044e7cad -2026-07-26-briefed-minimal-translation-updates.zh.md: a6559667f0f1140d8a26cd5ebc4bb64b7b95fe45 +2026-07-26-briefed-minimal-translation-updates.md: 1c032fa07167ec2407d0f46707f942ba0c830494 +2026-07-26-briefed-minimal-translation-updates.zh.md: 3e4b235ea6d65e5b8fec60d24208924537577951 diff --git a/.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.md b/.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.md index afd990b7b6..1c032fa071 100644 --- a/.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.md +++ b/.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.md @@ -10,10 +10,10 @@ The [bilingual pairing contract](2026-07-02-bilingual-docs-and-pairing-gate.md) ## Decision -Pair updates run on a generated briefing instead of the guidance corpus; only new pairs still run the whole-document workflow, which is unchanged. +The extended manual workflow runs pair updates on a generated briefing instead of the guidance corpus; new pairs in that workflow still use the unchanged whole-document path. Routine agent work uses the direct path defined by the [lightweight-translation decision](2026-08-08-lightweight-routine-documentation-translation.md). - **`pnpm run gen-translation-brief [--apply] [pair...]`** ([scripts/gen-translation-brief.ts](../../../../scripts/gen-translation-brief.ts), assembly in [scripts/translation-brief.ts](../../../../scripts/translation-brief.ts)) prints, per out-of-sync pair, the authored side's diff from its recorded last-confirmed blob to the working tree plus the change mapped at the narrowest safely aligned granularity, deterministically widening on mapping failure: a change confined to the pair's byte-identical code fences is computed outright (`--apply` splices it into the counterpart and validates the result against the pairing gate's structural signature before writing); otherwise changed Markdown units (headings, paragraphs, table rows, list items, code fences, block quotes, HTML blocks, thematic breaks, link definitions — matched by container-scoped kind sequences) each carry their last-confirmed source, current source, and current counterpart text with line numbers; units that do not align fall back to depth-matched heading sections; and when sections do not align either, or both sides drifted, the briefing says so and withholds the mapping instead of guessing. Terminology rows are matched against the changed spans only (word-boundary English matching with plural inflections), and for Chinese targets the briefing tracks each relevant term's document-wide first occurrence — when an edit moves it, the vacated and receiving spans join the briefing with an explanatory note, since the 首次出现 annotation must move with it. The unit mapping, code splice, and first-occurrence mechanics adopt the planner design from the incremental prompt-pipeline work; its provider-backed bake-off independently validated the same scope ladder for the automated pipeline. The briefing is the translator's whole working set; the full sources of truth remain the escalation path for decisions the briefing cannot answer. -- **The update path in [dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md)** consumes the briefing: mechanical (code-fence-only) changes are applied with `--apply`, no subagent; prose diffs go to a subagent whose prompt is the briefing, not the corpus; verification is clause-by-clause on the changed spans, not the whole document. +- **When explicitly invoked, the update path in [dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md)** consumes the briefing: mechanical (code-fence-only) changes are applied with `--apply`, no subagent; prose diffs go to a subagent whose prompt is the briefing, not the corpus; verification is clause-by-clause on the changed spans, not the whole document. - **The pairing gate takes pair arguments.** `verify-translation-pairing [pair...]` checks just the named pairs (any of a pair's three files, or the bare stem, names it); the corpus-wide sweep remains the no-argument form that `doc-sync` and CI run. `--write` now requires naming the confirmed pairs — bare `--write` refuses, and re-recording everything is an explicit `--write --all` — because the old bare form silently blessed every drifted pair in the tree, including ones the caller never looked at, and a prose-only drift would then stay green forever. Each record's comment names its own scoped command. Before recording, `--write` stores each side's exact bytes with `git hash-object -w --stdin` and pins the blob under a content-addressed local `refs/dsh/translation-pairing/snapshots/` ref; an uncommitted last-confirmed snapshot is therefore available to the briefing generator's later `git cat-file`, not merely named by a hash that Git cannot resolve or left vulnerable to garbage collection. ## Benchmark @@ -38,7 +38,7 @@ A second head-to-head replay on the same ten examples compared this note's shipp ## Consequences -- A small prose edit's counterpart update now costs a briefing generation plus one small focused task — no corpus reads, no archaeology, no corpus-wide scans inside the loop — and the same PR obligation holds; the cheap path and the correct path point the same way. +- In the explicitly invoked extended workflow, a small prose edit's counterpart update costs a briefing generation plus one small focused task — no corpus reads, no archaeology, no corpus-wide scans inside the loop — and the same PR obligation holds. - The briefing generator is a second consumer of the consistency records: recorded blob hashes now also drive diff recovery and section mapping, strengthening the incentive to keep records honest. - Each distinct confirmed snapshot retains a content-addressed local ref and object. An abandoned re-record may therefore leave an extra durable pin, but it changes no branch or commit history; this local retention is the tradeoff that prevents garbage collection from invalidating an accepted pairing record. - `--write` without arguments no longer works; muscle-memory callers must name pairs or pass `--all`. That is the point — the bulk bless is now a visible, deliberate act. diff --git a/.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.zh.md b/.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.zh.md index a6559667f0..3e4b235ea6 100644 --- a/.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.zh.md +++ b/.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.zh.md @@ -10,10 +10,10 @@ Status: implemented ## 决策 -配对更新基于生成的简报(briefing)运行,而非基于指导语料;只有新建配对仍走整篇文档工作流,后者保持不变。 +扩展的手动工作流使用生成的简报(briefing)而非指导语料来更新配对;该工作流中的新配对仍采用保持不变的整篇文档路径。常规 agent 工作采用由[轻量翻译决策](2026-08-08-lightweight-routine-documentation-translation.md)定义的直接路径。 - **`pnpm run gen-translation-brief [--apply] [pair...]`**([scripts/gen-translation-brief.ts](../../../../scripts/gen-translation-brief.ts),组装逻辑在 [scripts/translation-brief.ts](../../../../scripts/translation-brief.ts))针对每个失去同步的配对,打印被改一侧从其记录在案的上次确认 blob 到当前工作区的 diff,并附上以能安全对齐的最窄粒度映射的这次改动,映射失败时粒度确定性地逐级放宽:仅落在配对中逐字节一致的围栏代码块内的改动会直接算出(`--apply` 会把它拼接进对侧文件,并在写入前用配对门禁的结构签名校验所得结果);否则,每个有改动的 Markdown 单元(标题、段落、表格行、列表项、围栏代码块、块引用、HTML 块、分隔线、链接定义;匹配依据是以容器为作用域的种类序列)都带上各自的上次确认源文、当前源文与当前对侧文本及行号;无法对齐的单元回退到按深度匹配的标题章节;当章节也无法对齐或两侧同时漂移时,简报会明说这一点并省略映射,而不是靠猜。术语表行只与改动块匹配(英文术语按词边界匹配,含复数变形);当目标侧是中文时,简报还会跟踪每个相关术语在整篇文档中的首次出现:一旦某次编辑使其移位,腾出的与接收的两处区间就会附一条解释性说明加入简报,因为「首次出现」括注必须随之移动。单元映射、代码拼接与首次出现机制采纳了增量提示词流水线工作的规划器设计;该项工作中接入提供方的对比评测,已为自动流水线独立验证了同一套范围阶梯。简报就是译者的全部工作集;简报回答不了的决策,仍以完整的真源文档作为升级求证路径。 -- **[dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md) 中的更新路径**消费这份简报:机械类改动(只涉及围栏代码块)用 `--apply` 应用,不动用 subagent;行文类 diff 交给 subagent,其提示词就是简报本身,而非指导语料;核验只对改动块逐句进行,不覆盖整篇文档。 +- **显式调用时,[dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md) 中的更新路径**消费这份简报:机械类改动(只涉及围栏代码块)用 `--apply` 应用,不动用 subagent;行文类 diff 交给 subagent,其提示词就是简报本身,而非指导语料;核验只对改动块逐句进行,不覆盖整篇文档。 - **配对门禁接受配对参数。**`verify-translation-pairing [pair...]` 只检查被点名的配对(配对三个文件中的任意一个,或其裸词干,都能指代该配对);全语料扫描仍是 `doc-sync`(文档同步门禁)与 CI 运行的无参数形式。`--write` 现在要求点名已确认的配对:裸 `--write` 会拒绝执行,重新记录全部配对必须显式写 `--write --all`;原因是旧的裸形式会默默为树中每一个漂移的配对背书,包括调用者从未看过的那些,纯行文层面的漂移于是可以永远保持绿灯。每份记录的注释都写明针对该配对自身的按对命令。写下记录之前,`--write` 用 `git hash-object -w --stdin` 存入每一侧的精确字节,并在内容寻址的本地 `refs/dsh/translation-pairing/snapshots/` ref 下固定该 blob;未提交的上次确认快照因此能被简报生成器之后的 `git cat-file` 取回,而不只是留下一个 Git 无法解析的 hash 名称或暴露于垃圾回收。 ## 基准测试 @@ -38,7 +38,7 @@ Status: implemented ## 后果 -- 一次小的行文修改,其对侧更新如今只需生成一份简报,外加一个小而聚焦的任务(不读指导语料、不翻查历史、循环内不做全语料扫描),同一 PR 内完成更新的义务保持不变;低成本的路径与正确的路径指向同一个方向。 +- 在显式调用的扩展工作流中,一次小的行文修改,其对侧文件更新只需生成一份简报,外加一个小而聚焦的任务(不读指导语料、不翻查历史、循环内不做全语料扫描),同一 PR 内完成更新的义务保持不变。 - 简报生成器成为一致性记录的第二个消费方:记录的 blob hash 如今还驱动 diff 还原与章节映射,这进一步强化了如实维护记录的动机。 - 每个不同的已确认快照都会保留一个内容寻址的本地 ref 和对象。因此,中途放弃的重新记录可能留下额外的持久固定项,但它不会改变任何分支或提交历史;这种本地保留正是防止垃圾回收让已接受配对记录失效所付出的代价。 - 不带参数的 `--write` 不再可用;靠肌肉记忆的调用者必须点名配对或传 `--all`。这正是目的所在:批量背书如今是一个可见的、有意为之的动作。 diff --git a/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.i18n.yaml b/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.i18n.yaml index 1ae66cc1b7..df1a615d35 100644 --- a/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.md -2026-07-26-frozen-agent-note-archive.md: 52b43088b276c0c8e263fc8a81a2df1408cc8059 -2026-07-26-frozen-agent-note-archive.zh.md: a37e06e7cbc19b5000d4849cc3ee2ddc9b451ee3 +2026-07-26-frozen-agent-note-archive.md: 0c139c4a5d892de5bdace76b4935edf32586c4c1 +2026-07-26-frozen-agent-note-archive.zh.md: e67f981e5d7ae9d800b725e5ecbaf15dd7fef46b diff --git a/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.md b/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.md index 52b43088b2..0c139c4a5d 100644 --- a/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.md +++ b/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.md @@ -14,6 +14,8 @@ Only implemented Agent Notes can be archived. An implemented note moves when its The archive uses `.agents/notes/archived/{kind}/yyyy-mm-dd-topic.md`; the redundant `implemented` segment is absent. The archival change moves the complete English, Chinese, and consistency-sidecar triplet, leaves `Status: implemented` intact, and inserts `Archived: YYYY-MM-DD` immediately below it in both language files. Relocation, that metadata line, the corresponding sidecar re-record, and mechanical inbound-link repair are the only permitted archival edits. +The root `.rgignore` excludes the archive from searches that traverse a parent directory. Historical queries name the archive directory explicitly, so intentional access remains available without mixing frozen facts into active decision discovery. + After archival, the triplet is permanently frozen and is historical context rather than current authority. It is not updated for renamed packages, changed behavior, translation standards, formatting rules, broken outbound links, or later documentation contracts. Active prose may intentionally link into an archived note, redirect that link to current authority, or delete it. Repository gates therefore validate links into archived files but never treat archived files as link sources. [`verify-archived-agent-notes`](../../../../scripts/verify-archived-agent-notes.ts) owns the frozen boundary. It accepts only the closed set of Agent Note kinds, requires a complete triplet with implemented status and matching valid archive dates, verifies the sidecar against both current Git blob hashes, and seals every artifact by path and SHA-256 content hash in an append-only manifest. Its `--write` mode first proves every existing seal unchanged and then appends only newly archived artifacts. Pull-request CI supplies the trusted base SHA and checks out complete history before running the verifier, so a reused runner's shallow checkout cannot omit the baseline manifest. The ordinary Agent Note format, translation-pairing, wrapping, Markdown-link, package-path, Mermaid, documentation-TypeScript, and type-equivalence gates exclude archive sources; their evolving standards cannot create pressure to edit history. @@ -28,6 +30,8 @@ Supersession is checked while a new Agent Note is being written, not deferred to **Keep every implemented and rejected note active.** Rejected because maintenance effort and search noise grow with records that no longer help a future decision. Rejected notes in particular earn retention only by preventing a plausible fallacy. +**Leave archived notes in default repository search results.** Rejected because archived facts may be stale by design and can outrank current results by lexical match. Historical work can search the archive directory explicitly. + **Defer supersession cleanup to periodic corpus audits.** Rejected because the author of a replacement note has the freshest evidence about ownership and overlap. Postponement leaves redundant active authorities and makes later classification more expensive. **Archive rejected or proposed notes too.** Rejected because archive status means “implemented historical decision.” An obsolete proposal needs an explicit rejection, while a rejection with no guardrail value needs deletion rather than a second low-value holding area. @@ -38,4 +42,4 @@ Supersession is checked while a new Agent Note is being written, not deferred to ## Consequences -The active corpus becomes a set of decisions expected to influence future work, while low-value implemented history remains searchable and linkable without consuming maintenance attention. Writing a new note includes a scoped supersession check, so replacement decisions cannot silently leave redundant active records behind. Rejected clutter can disappear when it no longer protects a meaningful choice, and proposed work cannot quietly evade a verdict through archival. The archive adds a manifest, a dedicated verifier, and an explicit one-time metadata step. Archived facts and outbound links can become stale by design, so readers and agents must treat active code and documentation as authority and cite an archived note only as history. +The active corpus becomes a set of decisions expected to influence future work, while low-value implemented history remains explicitly searchable and linkable without consuming maintenance attention or appearing in parent-directory searches. Writing a new note includes a scoped supersession check, so replacement decisions cannot silently leave redundant active records behind. Rejected clutter can disappear when it no longer protects a meaningful choice, and proposed work cannot quietly evade a verdict through archival. The archive adds a manifest, a dedicated verifier, and an explicit one-time metadata step. Archived facts and outbound links can become stale by design, so readers and agents must treat active code and documentation as authority and cite an archived note only as history. diff --git a/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.zh.md b/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.zh.md index a37e06e7cb..e67f981e5d 100644 --- a/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.zh.md +++ b/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.zh.md @@ -14,6 +14,8 @@ implemented Agent Note 作为当前决策记录持续维护,因此活跃记录 归档路径为 `.agents/notes/archived/{kind}/yyyy-mm-dd-topic.md`,其中省略了冗余的 `implemented` 层级。归档变更会移动完整的英文、中文和一致性伴随记录三个文件,保留 `Status: implemented`,并在两种语言的文件中紧接该状态行插入 `Archived: YYYY-MM-DD`。归档时只允许做文件迁移、添加该元数据行、相应地重新记录伴随记录,以及机械修复入站链接。 +根目录的 `.rgignore` 会将归档目录排除在从上层目录开始的搜索之外。查找历史内容时会显式指定归档目录,因此仍可按需访问,同时不会把冻结事实混入对活跃决策的检索。 + 归档后,这三个文件永久冻结,只作为历史背景,不再是当前权威依据。不得因为包重命名、行为变化、翻译标准、格式规则、出站链接失效或后续文档约定而更新归档文件。活跃文档可以有意链接到归档 Agent Note,也可以把该链接重定向到当前权威依据,或直接删除。仓库门禁因此会校验指向归档文件的链接,但绝不把归档文件作为链接源来校验。 [`verify-archived-agent-notes`](../../../../scripts/verify-archived-agent-notes.ts) 负责维护冻结边界。它只接受封闭集合中的 Agent Note 类别,要求三个配对文件完整、状态为 implemented,且归档日期有效并互相匹配;它还会用双方当前的 Git blob hash 校验伴随记录,并在仅追加的 manifest(元数据清单) 中按路径和 SHA-256 内容 hash 封存每项产物。其 `--write` 模式会先证明每条现有封存记录对应的内容都未改变,再仅追加新归档的产物。拉取请求 CI 会提供可信的基准 SHA,并在运行校验器前检出完整历史,因此复用运行器上的浅克隆检出无法漏掉基线 manifest。普通的 Agent Note 格式、翻译配对、换行、Markdown 链接、包路径、Mermaid、文档 TypeScript 和类型等价门禁都排除归档源文件,因此这些门禁持续演进的标准不会产生修改历史记录的压力。 @@ -28,6 +30,8 @@ implemented Agent Note 作为当前决策记录持续维护,因此活跃记录 **继续将每一份 implemented 和 rejected Agent Note 作为活跃记录保留。** 不予采纳,因为不再帮助未来决策的记录会不断增加维护成本和搜索噪声。尤其是 rejected Agent Note,只有能避免一种可能发生的谬误时,才值得保留。 +**让归档 Agent Note 继续出现在默认的仓库搜索结果中。** 不予采纳,因为归档事实按设计可能已经陈旧,并可能仅凭字面匹配就排在当前结果之前。需要查找历史内容时,可以显式搜索归档目录。 + **把取代关系清理留到定期审计记录集合时再做。** 不予采纳,因为替代记录的作者掌握着关于归属和重叠的最新证据。推迟处理会留下冗余的活跃权威依据,并增加日后分类的成本。 **同时归档 rejected 或 proposed Agent Note。** 不予采纳,因为归档状态表达的是「已经实施的历史决策」。过时的提案需要明确转为 rejected;无法提供防错价值的 rejected Agent Note 则应删除,而不是再放入第二个低价值存放区。 @@ -38,4 +42,4 @@ implemented Agent Note 作为当前决策记录持续维护,因此活跃记录 ## 后果 -活跃记录集合由预计仍会影响未来工作的决策组成;未来指导价值较低的实施历史仍可搜索和链接,却不再消耗维护精力。编写新记录时会包含一项范围明确的取代关系检查,因此取代既有决策的新决策无法悄然留下冗余的活跃记录。当被否决的记录不再保护有意义的选择时,可以清除这类杂项;提案也无法通过归档悄悄逃避明确结论。归档机制增加一份 manifest、一个专用校验器和一个显式的一次性元数据步骤。归档中的事实和出站链接可以按设计逐渐陈旧,因此读者和 agent 必须以活跃代码与文档为权威依据,并且仅将归档 Agent Note 作为历史引用。 +活跃记录集合由预计仍会影响未来工作的决策组成;未来指导价值较低的实施历史仍可显式搜索和链接,却不再消耗维护精力,也不会出现在从上层目录开始的搜索中。编写新记录时会包含一项范围明确的取代关系检查,因此取代既有决策的新决策无法悄然留下冗余的活跃记录。当被否决的记录不再保护有意义的选择时,可以清除这类杂项;提案也无法通过归档悄悄逃避明确结论。归档机制增加一份 manifest、一个专用校验器和一个显式的一次性元数据步骤。归档中的事实和出站链接可以按设计逐渐陈旧,因此读者和 agent 必须以活跃代码与文档为权威依据,并且仅将归档 Agent Note 作为历史引用。 diff --git a/.agents/notes/implemented/process/2026-07-31-installer-adopts-existing-checkout.i18n.yaml b/.agents/notes/implemented/process/2026-07-31-installer-adopts-existing-checkout.i18n.yaml index 72574c7cf5..1c58887b2b 100644 --- a/.agents/notes/implemented/process/2026-07-31-installer-adopts-existing-checkout.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-31-installer-adopts-existing-checkout.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-31-installer-adopts-existing-checkout.md -2026-07-31-installer-adopts-existing-checkout.md: 531b780974e1490b27d8b96734ccfadae66c2fc2 -2026-07-31-installer-adopts-existing-checkout.zh.md: 405b58d8d53b3f0895cc7746c957582455667cda +2026-07-31-installer-adopts-existing-checkout.md: 1d45a14273095610e3ed8047da2ce9f4ca95cbb6 +2026-07-31-installer-adopts-existing-checkout.zh.md: 971bc3b389c341b314872b8e45ab20ebd2ed5b2c diff --git a/.agents/notes/implemented/process/2026-07-31-installer-adopts-existing-checkout.md b/.agents/notes/implemented/process/2026-07-31-installer-adopts-existing-checkout.md index 531b780974..1d45a14273 100644 --- a/.agents/notes/implemented/process/2026-07-31-installer-adopts-existing-checkout.md +++ b/.agents/notes/implemented/process/2026-07-31-installer-adopts-existing-checkout.md @@ -6,9 +6,9 @@ English | [中文](2026-07-31-installer-adopts-existing-checkout.zh.md) ## Problem -`scripts/install.sh` produced two incompatible install shapes. A `curl … | sh` install built the managed layout — a master clone at `~/.dsh/source/master`, a staging worktree on `dsh-staging/`, and the stable `current` symlink the PATH launcher resolves through. Running the same script from a checkout instead linked `dsh` straight at that checkout's `bin/dsh`, per the earlier [in-repo skip-clone decision](../../archived/process/2026-07-22-installer-in-repo-skip-clone.md). +`scripts/install.sh` produced two incompatible installation layouts. A `curl … | sh` install built the managed layout — a master clone at `~/.dsh/source/master`, a staging worktree on `dsh-staging/`, and the stable `current` symlink the PATH launcher resolves through. Running the same script from a checkout instead linked `dsh` straight at that checkout's `bin/dsh`, per the earlier [in-repo skip-clone decision](../../archived/process/2026-07-22-installer-in-repo-skip-clone.md). -The direct link is a terminal state. `current` is what an upgrade repoints, so an install without it is not upgradable by [`dsh-upgrade`](../../../../skills/dsh-upgrade/SKILL.md); the PATH symlink dangles if the checkout moves; and the launcher resolves to whatever branch the contributor happened to have checked out, which the upgrade contract forbids as a launcher target. The upgrade skill already described this shape as a legacy install needing a one-time migration, so the layouts diverged at install time and were reconciled only later, if ever. +The direct link cannot be upgraded. `current` is what an upgrade repoints, so an install without it is not upgradable by [`dsh-upgrade`](../../../../skills/dsh-upgrade/SKILL.md); the PATH symlink dangles if the checkout moves; and the launcher resolves to whatever branch the contributor happened to have checked out, which the upgrade contract forbids as a launcher target. The upgrade skill already described this layout as a legacy install needing a one-time migration, so the layouts diverged at install time and were reconciled only later, if ever. ## Decision @@ -28,9 +28,9 @@ Before `current` is repointed, the installer rejects a staging path that resolve **Make `~/.dsh/source/master` a symlink to the arbitrary clone.** Rejected. Git resolves the symlink and records the *real* path: a worktree created through it stores `gitdir: …//.git/worktrees/`, and `git worktree list` reports the clone. The symlink is therefore decorative — nothing reads it — while implying the container owns the repository. It also fails silently: moving the clone leaves `master` present but dangling and every staging worktree dead with `fatal: not a git repository`. Worst, it aliases two names onto one tree, so the "current must never be the master clone" check passes by string comparison while being false. `~/.dsh/source/master` is a location, not a name, and only the location is authoritative. -**Promote the checkout itself to the `current` target.** Rejected: the upgrade contract requires `current` to be a clean staging worktree on a staging branch, never a feature, review, or detached checkout. It would also make every upgrade rewrite the tree the contributor is editing. +**Promote the checkout itself to the `current` target.** Rejected: the upgrade contract requires `current` to point to a clean staging worktree on a staging branch, never a feature, review, or detached checkout. It would also make every upgrade rewrite the tree the contributor is editing. -**Keep link-in-place behind a prompt or a `DSH_ADOPT` flag.** Rejected, and an earlier revision of this change shipped exactly that before it was removed. The divergent shape was the defect itself, so retaining it as an option preserves the problem and doubles the states every later change must reason about — the prompt, the flag, the dirty-tree warning, and a second linking path all existed only to keep a shape nothing should produce. The original motivation for link-in-place, keeping the script testable against local source, survives adoption: a staging worktree branched from the checkout's `HEAD` runs the same code. `DSH_SOURCE` remains the escape hatch for installing a separate tree. +**Keep link-in-place behind a prompt or a `DSH_ADOPT` flag.** Rejected, and an earlier revision of this change shipped exactly that before it was removed. The second layout was the defect itself, so retaining it as an option preserves the problem and doubles the states every later change must handle — the prompt, the flag, the dirty-tree warning, and a second linking path all existed only to keep a layout nothing should produce. The original motivation for link-in-place, keeping the script testable against local source, survives adoption: a staging worktree branched from the checkout's `HEAD` runs the same code. `DSH_SOURCE` remains available for installing a separate tree. **Warn or prompt when the tree is dirty.** Rejected: `worktree add` from `HEAD` cannot carry uncommitted work, so the behavior is determined and a prompt only adds a decision the user cannot act on differently. The contract is documented instead. @@ -38,7 +38,7 @@ Before `current` is repointed, the installer rejects a staging path that resolve ## Consequences -One layout now serves every install, so an adopted clone is upgradable by `dsh-upgrade` without the one-time migration that skill described, and the installer has no branch that produces an unupgradable shape. In-repo runs still never mutate the working tree. +One layout now serves every install, so an adopted clone is upgradable by `dsh-upgrade` without the one-time migration that skill described, and the installer has no branch that produces an unupgradable layout. In-repo runs still never mutate the working tree. The cost is that a contributor can no longer point PATH at a checkout and have `dsh` follow that working tree as they switch branches: the launcher now resolves to a staging worktree pinned to the `HEAD` adopted at install time. Re-running the installer adopts the current `HEAD` again. diff --git a/.agents/notes/implemented/process/2026-07-31-installer-adopts-existing-checkout.zh.md b/.agents/notes/implemented/process/2026-07-31-installer-adopts-existing-checkout.zh.md index 405b58d8d5..971bc3b389 100644 --- a/.agents/notes/implemented/process/2026-07-31-installer-adopts-existing-checkout.zh.md +++ b/.agents/notes/implemented/process/2026-07-31-installer-adopts-existing-checkout.zh.md @@ -6,9 +6,9 @@ Status: implemented ## Problem -`scripts/install.sh`会产生两种互不兼容的安装形态。`curl … | sh`安装会构建受管布局——`~/.dsh/source/master`处的 master 克隆、位于`dsh-staging/<时间戳>`分支上的 staging worktree,以及 PATH 启动器据以解析的稳定`current`符号链接。而从检出中运行同一脚本时,则依据此前的[检出内跳过克隆决策](../../archived/process/2026-07-22-installer-in-repo-skip-clone.md),把`dsh`直接链接到该检出的`bin/dsh`。 +`scripts/install.sh`会产生两种互不兼容的安装布局。`curl … | sh`安装会构建受管布局——`~/.dsh/source/master`处的 master 克隆、位于`dsh-staging/<时间戳>`分支上的 staging worktree,以及 PATH 启动器据以解析的稳定`current`符号链接。而从检出中运行同一脚本时,则依据此前的[检出内跳过克隆决策](../../archived/process/2026-07-22-installer-in-repo-skip-clone.md),把`dsh`直接链接到该检出的`bin/dsh`。 -这种直接链接是一种终态。升级重指的正是`current`,因此缺少它的安装无法通过[`dsh-upgrade`](../../../../skills/dsh-upgrade/SKILL.md)升级;检出一旦移动,PATH 符号链接就会失效;而且启动器会解析到贡献者恰好检出的任意分支,这正是升级约定禁止作为启动器目标的情形。升级技能早已把这种形态描述为需要一次性迁移的旧式安装,于是两种布局在安装时就已分叉,并且要到很久以后才会被调和——甚至永远不会。 +这种直接链接无法升级。升级重指的正是`current`,因此缺少它的安装无法通过[`dsh-upgrade`](../../../../skills/dsh-upgrade/SKILL.md)升级;检出一旦移动,PATH 符号链接就会失效;而且启动器会解析到贡献者恰好检出的任意分支,这正是升级约定禁止作为启动器目标的情形。升级技能早已把这种布局描述为需要一次性迁移的旧式安装,因此两种布局从安装时起便不相同,只有以后执行迁移才会一致,而迁移也可能永远不执行。 ## Decision @@ -28,9 +28,9 @@ Status: implemented **把`~/.dsh/source/master`做成指向该任意克隆的符号链接。** 已否决。Git 会解析该符号链接并记录*真实*路径:经由它创建的 worktree 会存储`gitdir: …/<克隆>/.git/worktrees/<名称>`,而`git worktree list`报告的是该克隆。因此这个符号链接纯属装饰——没有任何代码读取它——却又暗示容器拥有该仓库。它还会静默失效:移动克隆后,`master`看似仍在却已悬空,而每个 staging worktree 都会以`fatal: not a git repository`失败。最糟的是,它把两个名称别名到同一棵树上,于是"current 绝不能是 master 克隆"这项检查会在字符串比较下通过,实则为假。`~/.dsh/source/master`是位置而非名称,且只有位置具有权威性。 -**把检出自身提升为`current`的目标。** 已否决:升级约定要求`current`必须是位于 staging 分支上的干净 staging worktree,绝不能是 feature、review 或 detached 检出。这还会使每次升级都改写贡献者正在编辑的那棵树。 +**把检出自身提升为`current`的目标。** 已否决:升级约定要求`current`指向 staging 分支上的干净 staging worktree,绝不能指向 feature、review 或 detached 检出。这还会使每次升级都改写贡献者正在编辑的那棵树。 -**把就地链接保留在提示或`DSH_ADOPT`开关之后。** 已否决;本次变更的早期修订版本正是如此实现,之后被移除。分叉的形态本身就是缺陷,因此把它保留为一个选项等于保留了问题,并使此后每次改动需要推敲的状态翻倍——提示、开关、工作树不干净的警告,以及第二条链接路径,全都只为维持一种本不该产生的形态而存在。就地链接最初的动机——让脚本能针对本地源码进行测试——在接管方案下依然成立:以检出的`HEAD`为起点创建的 staging worktree 运行的是同一份代码。`DSH_SOURCE`仍是安装另一棵树的退路。 +**把就地链接保留在提示或`DSH_ADOPT`开关之后。** 已否决;本次变更的早期修订版本正是如此实现,之后被移除。第二种布局本身就是缺陷,因此把它保留为一个选项等于保留了问题,并使此后每次改动必须处理的状态翻倍——提示、开关、工作树不干净的警告,以及第二条链接路径,全都只为维持一种本不该产生的布局而存在。就地链接最初的动机——让脚本能针对本地源码进行测试——在接管方案下依然成立:以检出的`HEAD`为起点创建的 staging worktree 运行的是同一份代码。`DSH_SOURCE`仍可用于安装另一棵树。 **在工作树不干净时发出警告或提示。** 已否决:以`HEAD`为起点的`worktree add`本就无法带上未提交的内容,因此该行为是确定的,提示只会增加一个用户无法做出不同选择的决策点。改为在文档中说明该约定。 @@ -38,7 +38,7 @@ Status: implemented ## Consequences -现在一套布局服务于所有安装,因此被接管的克隆无需该技能所述的一次性迁移,即可由`dsh-upgrade`升级,而且安装器不再有任何一条分支会产生无法升级的形态。检出内运行仍然绝不改动工作树。 +现在一套布局服务于所有安装,因此被接管的克隆无需该技能所述的一次性迁移,即可由`dsh-upgrade`升级,而且安装器不再有任何一条分支会产生无法升级的布局。检出内运行仍然绝不改动工作树。 代价是:贡献者不能再把 PATH 指向某个检出、并让`dsh`随其切换分支而跟随该工作树;启动器现在解析到的是一个固定在安装时所接管`HEAD`上的 staging worktree。重新运行安装器会再次接管当前的`HEAD`。 diff --git a/.agents/notes/implemented/process/2026-08-08-lightweight-routine-documentation-translation.i18n.yaml b/.agents/notes/implemented/process/2026-08-08-lightweight-routine-documentation-translation.i18n.yaml new file mode 100644 index 0000000000..9805a10d2c --- /dev/null +++ b/.agents/notes/implemented/process/2026-08-08-lightweight-routine-documentation-translation.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-08-lightweight-routine-documentation-translation.md +2026-08-08-lightweight-routine-documentation-translation.md: ff4d6005588b562018bf1ee40d6dabb5569766f0 +2026-08-08-lightweight-routine-documentation-translation.zh.md: fe809d328e739c2c869d5597cf6786198864bd56 diff --git a/.agents/notes/implemented/process/2026-08-08-lightweight-routine-documentation-translation.md b/.agents/notes/implemented/process/2026-08-08-lightweight-routine-documentation-translation.md new file mode 100644 index 0000000000..ff4d600558 --- /dev/null +++ b/.agents/notes/implemented/process/2026-08-08-lightweight-routine-documentation-translation.md @@ -0,0 +1,30 @@ +# Agent Note: Lightweight routine documentation translation + +Status: implemented + +English | [中文](2026-08-08-lightweight-routine-documentation-translation.zh.md) + +## Problem + +Routine bilingual edits automatically selected the full [translation skill](../../../skills/dsh-translate-docs/SKILL.md). Even after the [briefed-update optimization](2026-07-26-briefed-minimal-translation-updates.md), a small documentation change could still load a specialized workflow, generate a briefing, delegate prose to a subagent, and perform a separate verification pass. That orchestration consumed more time, context, and model tokens than translating the changed text itself, and automatic skill discovery exposed the workflow on ordinary documentation turns. + +## Decision + +- **Routine translation is one shot and one pass.** The active agent loads [terminology.md](../../../../docs/i18n/terminology.md), translates only the changed content directly, moves a terminology annotation when the true first occurrence crosses the edit boundary, otherwise preserves reviewed counterpart prose outside the change, and re-records the pair. It does not invoke a translation skill, generate a briefing, start a separate translation-review pass, or delegate translation to a subagent. +- **The extended workflow is manual-only.** [dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md) retains its briefing, delegated prose, whole-document, and scoped-verification paths. The [Claude Code skill contract](https://code.claude.com/docs/en/skills#control-who-invokes-a-skill) reads `disable-model-invocation: true` with `user-invocable: true` in `SKILL.md`; Codex reads `policy.allow_implicit_invocation: false` in `agents/openai.yaml`. The repository's `.claude/skills` symlink projects the same skill directory to Claude Code, so both products share one committed workflow while enforcing their own invocation metadata. The `doc-sync` skill-invocation-metadata gate keeps those independent policies aligned. +- **Automatic workflows do not chain into the manual skill.** Root and documentation instructions own the lightweight default. Documentation, website-sync, prose, and code-review skills link to those instructions or the i18n contracts instead of loading `dsh-translate-docs` from an inferred bilingual change. +- **The pairing and review contracts stay intact.** Both language files still update together, untouched counterpart wording remains stable, terminology stays binding, the consistency record is rewritten only after the active agent confirms the pair, and `doc-sync` retains the corpus-wide mechanical checks. Human review still owns semantic translation quality. + +## Alternatives considered + +- **Delete the extended skill and briefing tools** — rejected: explicit manual use remains valuable for whole-document translations, difficult reconciliation, and callers that deliberately choose the guarded workflow. +- **Replace the extended skill with an automatically invoked lightweight skill** — rejected: another automatic skill would still add discovery context and an invocation boundary around a task the active agent can complete directly from the terminology table and standing instructions. +- **Keep automatic invocation only for new pairs or large changes** — rejected: size-based inference is another hidden policy and can unexpectedly activate the expensive workflow. The user, not the agent, chooses when the extended path is worth its cost. +- **Drop the terminology load as well** — rejected: the glossary is the small, binding input that prevents repository-wide term drift; removing it would trade token savings for inconsistent product language. + +## Consequences + +- Ordinary development pays for the changed source text, its local counterpart context, and the terminology table rather than the extended workflow's briefing and subagent context. +- The active agent owns the final routine translation in the same turn. The lightweight path deliberately gives up the extended workflow's generated alignment, delegated isolation, and separate prose-verification pass. +- Explicit users can still invoke the full workflow through `/dsh-translate-docs` in Claude Code or `$dsh-translate-docs` in Codex. +- The Claude Code frontmatter and Codex policy file are separate product contracts; `doc-sync` rejects a skill that becomes manual-only on only one product or becomes unavailable to the Claude Code user as well as the model. diff --git a/.agents/notes/implemented/process/2026-08-08-lightweight-routine-documentation-translation.zh.md b/.agents/notes/implemented/process/2026-08-08-lightweight-routine-documentation-translation.zh.md new file mode 100644 index 0000000000..fe809d328e --- /dev/null +++ b/.agents/notes/implemented/process/2026-08-08-lightweight-routine-documentation-translation.zh.md @@ -0,0 +1,30 @@ +# Agent Note: 轻量化日常文档翻译 + +Status: implemented + +[English](2026-08-08-lightweight-routine-documentation-translation.md) | 中文 + +## 问题 + +日常双语编辑会自动选用完整的[翻译 skill(技能)](../../../skills/dsh-translate-docs/SKILL.md)。即使经过[基于简报的更新优化](2026-07-26-briefed-minimal-translation-updates.md),一次小的文档改动仍可能加载专用工作流、生成简报、把行文翻译委派给 subagent,并另行执行一轮核验。这种编排耗费的时间、上下文和模型 token 比直接翻译改动文本本身还多,而且 skill 的自动发现机制还会在普通文档处理轮次中暴露该工作流。 + +## 决策 + +- **日常翻译一次完成,只处理一遍。** 当前 agent(智能体)加载 [terminology.md](../../../../docs/i18n/terminology.md),直接翻译发生改动的内容;如果术语的实际首现位置跨过了编辑边界,则移动相应括注,否则保留改动之外已经评审的对侧文件行文;最后重新记录配对。它不会调用翻译 skill、生成简报、启动单独的翻译评审轮次,也不会把翻译委派给 subagent。 +- **扩展工作流仅限手动调用。** [dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md) 保留简报、行文翻译委派、整篇文档和按范围核验路径。[Claude Code skill 契约](https://code.claude.com/docs/en/skills#control-who-invokes-a-skill)读取 `SKILL.md` 中的 `disable-model-invocation: true` 和 `user-invocable: true`;Codex 读取 `agents/openai.yaml` 中的 `policy.allow_implicit_invocation: false`。仓库的 `.claude/skills` 符号链接把同一个 skill 目录映射给 Claude Code,因此两个产品共享同一份提交到仓库的工作流,同时分别执行各自的调用元数据契约。`doc-sync` 中的 skill 调用元数据门禁会让这两份独立策略保持一致。 +- **自动工作流不会串联调用这项仅限手动调用的 skill。** 轻量默认行为由根级指令和文档指令定义。文档、网站同步、行文和代码评审 skill 会链接这些指令或 i18n 契约,而不会因为推断到双语改动就加载 `dsh-translate-docs`。 +- **配对契约与评审契约保持不变。** 两种语言文件仍会一并更新;未触及的对侧文件措辞保持稳定;术语约束仍然有效;只有当前 agent 确认配对后,才会重写一致性记录;`doc-sync`(文档同步门禁)继续执行全语料机械检查。语义层面的翻译质量仍由人工评审负责。 + +## 曾考虑的替代方案 + +- **删除扩展 skill 和简报工具**:不予采纳。在整篇文档翻译或棘手的两侧内容协调中,以及对有意选择受控工作流的调用方而言,显式手动调用仍有价值。 +- **用自动调用的轻量 skill 取代扩展 skill**:不予采纳。另一项自动 skill 仍会给这项任务增加发现上下文和调用边界,而当前 agent 仅依据术语表与常驻指令即可直接完成该任务。 +- **仅对新配对或大规模改动保留自动调用**:不予采纳。基于规模的推断同样是一项隐藏政策,可能出乎意料地启用高开销工作流。何时值得为扩展路径付出成本,应由用户而非 agent 决定。 +- **同时取消加载术语表**:不予采纳。术语表是体量小但有约束力的输入,可以防止整个仓库发生术语漂移;移除它等于用产品语言不一致换取 token 节省。 + +## 后果 + +- 普通开发的成本来自发生改动的源文本、其局部对侧文件上下文和术语表,不再来自扩展工作流的简报与 subagent 上下文。 +- 当前 agent 在同一轮次内对日常翻译的最终结果负责。轻量路径有意放弃扩展工作流提供的自动生成对齐信息、委派所提供的隔离,以及单独的行文核验轮次。 +- 用户仍可在 Claude Code 中通过 `/dsh-translate-docs`,或在 Codex 中通过 `$dsh-translate-docs` 显式调用完整工作流。 +- Claude Code frontmatter 与 Codex 策略文件是彼此独立的产品契约;如果某项 skill 仅在一个产品中变为手动调用,或者在 Claude Code 中对模型和用户都不可用,`doc-sync` 会拒绝该状态。 diff --git a/.agents/notes/implemented/process/2026-08-08-unified-github-label-taxonomy.i18n.yaml b/.agents/notes/implemented/process/2026-08-08-unified-github-label-taxonomy.i18n.yaml index 295dcfd0bf..1c22b0d7f6 100644 --- a/.agents/notes/implemented/process/2026-08-08-unified-github-label-taxonomy.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-08-unified-github-label-taxonomy.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-08-unified-github-label-taxonomy.md -2026-08-08-unified-github-label-taxonomy.md: 44508b4f0e2dc4de97950b0738829c0b362cc506 -2026-08-08-unified-github-label-taxonomy.zh.md: b1e3629fd93dcbff6e253058b4c465dc9bb5fd63 +2026-08-08-unified-github-label-taxonomy.md: 8005629861f306d20293af6b348f19bf79cbe1c3 +2026-08-08-unified-github-label-taxonomy.zh.md: 855a2b98f44d517abe1f7718ae4e81262cb031b6 diff --git a/.agents/notes/implemented/process/2026-08-08-unified-github-label-taxonomy.md b/.agents/notes/implemented/process/2026-08-08-unified-github-label-taxonomy.md index 44508b4f0e..8005629861 100644 --- a/.agents/notes/implemented/process/2026-08-08-unified-github-label-taxonomy.md +++ b/.agents/notes/implemented/process/2026-08-08-unified-github-label-taxonomy.md @@ -27,18 +27,18 @@ The kind set is closed and mutually exclusive: | `kind/cleanup` | Preserves behavior while maintaining or simplifying implementation or repository process. | | `kind/dependency` | Updates dependencies without another dominant intent. | -The kind records the dominant intent. Accompanying tests, documentation, cleanup, or dependency movement do not override a feature or bug fix. A new kind changes this classification contract and requires an explicit taxonomy and policy change. +The kind records the dominant intent. Accompanying tests, documentation, cleanup, or dependency movement do not override a feature or bug fix. A new kind changes these classification rules and requires an explicit taxonomy and policy change. Repository policy rejects unsupported `kind/*` values and reserves every alias removed by the unification: `kind/bug`, `kind/documentation`, `feature`, `bug-fix`, `doc`, `cleanup`, `testing`, `dependencies`, `ci`, `cli`, `llm`, and `web-search`. Reserving the exact migrated set prevents an obsolete synonym from being recreated as an apparently unrelated operational label. ### Areas -Areas name durable semantic domains rather than temporary initiatives, ownership, or every path touched incidentally. A pull request carries multiple areas when it changes distinct contracts, but it does not combine an umbrella and a narrower label for the same contract. GitHub's live `area/*` names and descriptions own the current inventory; this record owns the selection rule and the non-obvious boundaries that cannot fit reliably in short label descriptions. +Areas name durable product or engineering subjects rather than temporary initiatives, ownership, or every path touched incidentally. A pull request carries multiple areas when it changes distinct behavior or APIs, but it does not combine an umbrella and a narrower label for the same change. GitHub's live `area/*` names and descriptions own the current inventory; this record defines selection cases that cannot fit reliably in short label descriptions. - `area/web` covers browser and Electron graphical interfaces, `area/vscode` covers the editor extension, and `area/api` covers cross-interface protocols and language SDKs. - `area/planning` covers goals, plans, todos, and scheduling, while `area/workflow` covers executable workflows and background task runtimes. - `area/artifact` deliberately combines artifacts, attachments, and multimodal delivery. Split labels become justified only when those concerns again need independent review or queries. -- `area/tools` applies to generic registry, schema, and execution contracts. A concrete capability uses its own area unless it also changes that generic contract. +- `area/tools` applies to generic registry, schema, and execution contracts. A concrete capability uses its own area unless it also changes one of those contracts. - `area/hooks` means the Claude Code and Codex bridges, `area/infra` covers build, release, CI, repository gates, generators, dependencies, and developer tooling, and `area/windows` covers native Windows product support rather than CI runner selection. The area set is intentionally extensible. When no existing description honestly covers a durable and reusable domain, an agent may create a concise `area/` label without separate approval. It must not create an area for one pull request, an incidental path, a temporary project, a status, or a person or team, and it reports the new label and rationale to the requester after applying it. Reusing an inaccurate area merely to avoid a justified addition is not acceptable. @@ -61,11 +61,11 @@ Label migrations preserve meaning before removing aliases: add the canonical rep **Separate labels for every delivery shell or media lifecycle.** Browser and Electron delivery share one graphical domain, and artifact, attachment, and multimodal delivery currently share one review/query domain. A split belongs in a later taxonomy change only when it restores useful independent classification. -**Broad implementation labels in place of semantic domains.** A concrete capability is not merely its tool, interface, filesystem, or process implementation. Generic implementation areas apply only when their own contracts change. +**Broad implementation labels in place of product or engineering subjects.** A concrete capability is not merely its tool, interface, filesystem, or process implementation. Generic implementation areas apply only when their own behavior or API changes. **Kinds on Issues.** Native Issue Type already owns that classification; duplicating it as a label creates drift. -**Exactly one area per pull request.** Coherent changes can materially affect several independent contracts, and dropping secondary areas hides affected scope. +**Exactly one area per pull request.** Coherent changes can materially affect several independent APIs or behaviors, and dropping secondary areas hides affected scope. ## Consequences diff --git a/.agents/notes/implemented/process/2026-08-08-unified-github-label-taxonomy.zh.md b/.agents/notes/implemented/process/2026-08-08-unified-github-label-taxonomy.zh.md index b1e3629fd9..855a2b98f4 100644 --- a/.agents/notes/implemented/process/2026-08-08-unified-github-label-taxonomy.zh.md +++ b/.agents/notes/implemented/process/2026-08-08-unified-github-label-taxonomy.zh.md @@ -27,18 +27,18 @@ Issue 已有原生 Issue Type 和独立的来源分类体系。在这两类对 | `kind/cleanup` | 在保持行为不变的前提下,维护或简化实现或仓库流程。 | | `kind/dependency` | 在没有其他主导意图时更新依赖。 | -类型记录主导意图。配套测试、文档、清理或依赖调整不会盖过功能变更或缺陷修复这一主导意图。新增类型会改变这项分类约定,因此必须明确修改分类体系和政策。 +类型记录主导意图。配套测试、文档、清理或依赖调整不会盖过功能变更或缺陷修复这一主导意图。新增类型会改变这些分类规则,因此必须明确修改分类体系和政策。 仓库政策会拒绝不支持的 `kind/*` 值,并将统一过程中移除的所有别名列为保留名称:`kind/bug`、`kind/documentation`、`feature`、`bug-fix`、`doc`、`cleanup`、`testing`、`dependencies`、`ci`、`cli`、`llm` 和 `web-search`。精确保留这组已迁移的名称,可以防止过时的同义名称被重新创建成看似无关的管理用途标签。 ### 领域 -领域表示持久的语义领域,而不是临时专项、归属关系或偶然触及的每条路径。一项 PR 修改不同约定时带有多个领域标签,但不会用一个总括标签和一个较窄标签重复描述同一项约定。GitHub 上现行的 `area/*` 名称和说明定义当前清单;本记录定义选择规则,以及简短标签说明无法可靠容纳的非显然边界。 +领域表示持久的产品或工程主题,而不是临时专项、归属关系或偶然触及的每条路径。一项 PR 修改不同的行为或 API 时带有多个领域标签,但不会用一个总括标签和一个较窄标签重复描述同一项变更。GitHub 上现行的 `area/*` 名称和说明定义当前清单;本记录定义简短标签说明无法可靠容纳的选择情形。 - `area/web` 覆盖浏览器与 Electron 图形界面,`area/vscode` 覆盖编辑器扩展,`area/api` 覆盖跨界面协议与各语言 SDK。 - `area/planning` 覆盖目标、计划、待办和调度,`area/workflow` 则覆盖可执行工作流与后台任务运行时。 - `area/artifact` 有意合并产物、附件与多模态交付。只有当这些关注点再次需要独立评审或查询时,才有理由拆分标签。 -- `area/tools` 适用于通用注册表、schema 与执行约定。具体能力使用自身的领域标签,除非它还修改了这项通用约定。 +- `area/tools` 适用于通用注册表、schema 与执行约定。具体能力使用自身的领域标签,除非它还修改了其中一项约定。 - `area/hooks` 表示 Claude Code 与 Codex 桥接,`area/infra` 覆盖构建、发布、CI、仓库门禁、生成器、依赖与开发者工具,`area/windows` 覆盖原生 Windows 产品支持,而不是 CI runner 的选型。 领域集合有意保持可扩展。当现有说明都无法如实涵盖一个持久且可复用的领域时,agent(智能体)无需另行批准,即可创建一个简洁的 `area/` 标签。agent 不得为单个 PR、偶然涉及的路径、临时项目、状态、个人或团队创建领域,并且必须在应用新标签后向请求者报告该标签及理由。仅为避免新增一个确有必要的领域标签而复用不准确的领域,不可接受。 @@ -61,11 +61,11 @@ Issue 使用原生 Issue Type,而不是 `kind/*`;其 `area/*` 标签仍然 **为每种交付载体或媒体生命周期单设标签。** 浏览器与 Electron 交付共用一个图形界面领域,产物、附件与多模态交付目前也共用一个评审/查询领域。只有当拆分能恢复有用的独立分类时,才应在后续分类体系变更中进行。 -**用宽泛的实现标签取代语义领域。** 一项具体能力并不只是其工具、接口、文件系统或进程实现。通用实现领域只在其自身约定变化时适用。 +**用宽泛的实现标签取代产品或工程主题。** 一项具体能力并不只是其工具、接口、文件系统或进程实现。通用实现领域只在其自身行为或 API 变化时适用。 **在 Issue 上使用类型标签。** 原生 Issue Type 已负责这项分类;再用标签复制会造成漂移。 -**每个 PR 恰好一个领域。** 内聚的变更可能对多个独立约定产生实质影响,丢弃次要领域会隐藏受影响范围。 +**每个 PR 恰好一个领域。** 内聚的变更可能对多个独立 API 或行为产生实质影响,丢弃次要领域会隐藏受影响范围。 ## 后果 diff --git a/.agents/notes/implemented/process/2026-08-09-chinese-contract-terminology.i18n.yaml b/.agents/notes/implemented/process/2026-08-09-chinese-contract-terminology.i18n.yaml index 408f164e06..9997e7252d 100644 --- a/.agents/notes/implemented/process/2026-08-09-chinese-contract-terminology.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-09-chinese-contract-terminology.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-09-chinese-contract-terminology.md -2026-08-09-chinese-contract-terminology.md: d2fea45c6864a19b89c78a52f614ccac341a0127 -2026-08-09-chinese-contract-terminology.zh.md: 772b78daba00c5d881fd0091951557dfc0951ee9 +2026-08-09-chinese-contract-terminology.md: fa9e3ab91133a995a63f817816c01b59d28f66e8 +2026-08-09-chinese-contract-terminology.zh.md: 299e9ac9cdb2b8166d2364723b92f878b15cfd66 diff --git a/.agents/notes/implemented/process/2026-08-09-chinese-contract-terminology.md b/.agents/notes/implemented/process/2026-08-09-chinese-contract-terminology.md index d2fea45c68..fa9e3ab911 100644 --- a/.agents/notes/implemented/process/2026-08-09-chinese-contract-terminology.md +++ b/.agents/notes/implemented/process/2026-08-09-chinese-contract-terminology.md @@ -14,7 +14,7 @@ English `convention` also commonly renders as `约定`. That overlap is intentio The terminology source of truth defines `contract` as `约定` and `adapter contract` as `适配器约定(adapter contract)` on first mention. Every active Chinese documentation pair follows that ruling; archived Agent Notes remain frozen. Unpaired bilingual calibration assets and the translation prompt's explanatory prose follow the same terms so they cannot teach the superseded rendering. -The migration is semantic prose maintenance, not a rename of identifiers. Inline code, file paths, links, API names, English filenames containing `contract`, and machine-readable values remain unchanged. `convention` does not receive a global terminology row or corpus-wide rewrite: translators preserve natural Chinese and explicitly disambiguate only where the source contrasts the two concepts. +The migration is semantic prose maintenance, not a rename of identifiers. Inline code, file paths, links, API names, English filenames containing `contract`, and machine-readable values remain unchanged. `convention` does not receive a global terminology row or corpus-wide rewrite: translators preserve natural Chinese and explicitly disambiguate only where the source contrasts the two concepts. The [concrete prose decision](2026-08-09-concrete-prose-names-actors-and-recorded-facts.md) separately decides when English prose should replace a vague `contract` use with the exact rule, API, or behavior before translation. ## Alternatives considered diff --git a/.agents/notes/implemented/process/2026-08-09-chinese-contract-terminology.zh.md b/.agents/notes/implemented/process/2026-08-09-chinese-contract-terminology.zh.md index 772b78daba..299e9ac9cd 100644 --- a/.agents/notes/implemented/process/2026-08-09-chinese-contract-terminology.zh.md +++ b/.agents/notes/implemented/process/2026-08-09-chinese-contract-terminology.zh.md @@ -14,7 +14,7 @@ Status: implemented 术语真源规定 `contract` 译为「约定」,`adapter contract` 首次出现时写作「适配器约定(adapter contract)」。所有活跃中文文档配对均遵循该裁决;归档 Agent Note 保持冻结。未参与配对的双语校准资产和翻译提示词说明文字也采用相同术语,避免继续教授已被取代的译法。 -这次迁移只维护语义正文,不重命名标识符。行内代码、文件路径、链接、API 名称、文件名中包含的英文 `contract` 以及机器可读值均保持不变。`convention` 不新增全局术语行,也不做全语料改写:翻译时保留自然中文,只在源文明确对比两个概念时消歧。 +这次迁移只维护语义正文,不重命名标识符。行内代码、文件路径、链接、API 名称、文件名中包含的英文 `contract` 以及机器可读值均保持不变。`convention` 不新增全局术语行,也不做全语料改写:翻译时保留自然中文,只在源文明确对比两个概念时消歧。[具体行文决策](2026-08-09-concrete-prose-names-actors-and-recorded-facts.md)另行规定:如果英文正文中的 `contract` 含糊不清,应在翻译前将其改为确切的规则、API 或行为。 ## 考虑过的替代方案 diff --git a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.i18n.yaml b/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.i18n.yaml index 0813498ae6..4c9b955615 100644 --- a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-09-committed-artifact-citations.md -2026-08-09-committed-artifact-citations.md: a578b1e32268af2669ee26c59d19c738bc9f707b -2026-08-09-committed-artifact-citations.zh.md: d525dc0f58884bdf808b597213a7e23c1d6a0301 +2026-08-09-committed-artifact-citations.md: 044f7683d51ebf2038f56d2b5a27755ecc9be6d5 +2026-08-09-committed-artifact-citations.zh.md: 7f194aef45710e2f24e462c99877ec2e112f9981 diff --git a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.md b/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.md index a578b1e322..044f7683d5 100644 --- a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.md +++ b/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.md @@ -18,12 +18,12 @@ Durable prose — comments, JSDoc, docs, notes, test comments and titles — cit - Implemented notes state shipped reality: a "deferred to a later PR" claim whose target shipped names the shipped note instead. - Recorded fixtures, snapshots, and archived notes are exempt: recorded model output and sealed history keep their original voice. Inside a note's change-story sections, a historical stage name ("the first cut shipped X") is current-state-safe; indexical stamps ("this cut") stay banned everywhere. -One repo-wide purge applied these rules across the prose surfaces, including the generator-owned templates (`scripts/gen-doc-graphs.ts`, `scripts/gen-tool-catalog.ts`, the typert generator's page notice) with regeneration, the type-equiv source JSDoc with page re-pastes, and the bilingual counterparts with pair re-records. The [dsh-trim-cot-leakage skill](../../../skills/dsh-trim-cot-leakage/SKILL.md) operationalizes these rules: the audit taxonomy, the committed recall batteries, and few-shot calibration for the keep/delete boundary. +One repo-wide purge applied these rules across the prose surfaces, including the generator-owned templates (`scripts/gen-doc-graphs.ts`, `scripts/gen-tool-catalog.ts`, the typert generator's page notice) with regeneration, the type-equiv source JSDoc with page re-pastes, and the bilingual counterparts with pair re-records. The [dsh-trim-cot-leakage skill](../../../skills/dsh-trim-cot-leakage/SKILL.md) operationalizes these rules: the audit taxonomy, the committed recall batteries, and few-shot examples for deciding what to keep or delete. ## Alternatives considered - **Commit the design ledgers and audit documents so the ordinals resolve.** Rejected: session transcripts are working artifacts, not maintained references; committing them would create a parallel, ungated decision corpus beside Agent Notes, and their internal numbering would still drift. -- **A mechanical gate for the banned vocabulary.** Deferred: the vocabulary is unbounded natural language, and the audit's recall batteries need judgment to separate leakage from legitimate prose ("wait" the noun, contrastive "actually", runtime old/new states). A narrow high-precision gate (for example `\(decision \d`, `\(audit [A-Z]\d`, `\bcut \d`, `this cut`, a bare `\bT\d\b`, `P-I`, `used to `, a bare `\bv1\b`, and `§\d` — the last excluding citations whose section numbering has a committed owner, such as web-styling.md's own §N) is the candidate if the pattern recurs; review of the purge itself caught residuals in exactly these post-battery shapes, so they lead the candidate list. +- **A mechanical gate for the banned vocabulary.** Deferred: the vocabulary is unbounded natural language, and the audit's recall batteries need judgment to separate leakage from legitimate prose ("wait" the noun, contrastive "actually", runtime old/new states). A narrow high-precision gate (for example `\(decision \d`, `\(audit [A-Z]\d`, `\bcut \d`, `this cut`, a bare `\bT\d\b`, `P-I`, `used to `, a bare `\bv1\b`, and `§\d` — the last excluding citations whose section numbering has a committed owner, such as web-styling.md's own §N) is the candidate if the pattern recurs; review of the purge itself caught residuals in exactly the cases those searches missed, so they lead the candidate list. - **Delete the rationale that cited dead artifacts.** Rejected: the factual clauses were preserved or restated; only citations, review choreography, and derivation transcripts were removed, per the prose standard's complete-proposition rule. ## Verification diff --git a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.zh.md b/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.zh.md index d525dc0f58..7f194aef45 100644 --- a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.zh.md +++ b/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.zh.md @@ -18,12 +18,12 @@ Status: implemented - 已实现的 Agent Note 陈述已交付的现实:「推迟到后续 PR」的说法若其目标已经交付,就改为点名那篇已交付的 note。 - 已录制的 fixture(测试前置数据)、快照与已归档的 Agent Note 不受此约束:已录制的模型输出与封存的历史保持原有行文。在 note 的变更故事段落内,历史阶段名称(「首版交付了 X」)属于安全的现状表述;指示性切次戳("this cut")在任何地方都仍被禁止。 -一次全仓库清理把这些规则应用到了各个行文表面,包括生成器持有的模板(`scripts/gen-doc-graphs.ts`、`scripts/gen-tool-catalog.ts`、typert 生成器的页面提示语,改后重新生成)、type-equiv 源码 JSDoc(改后把文档页重新粘贴)以及双语对侧文件(改后重新记录配对)。[dsh-trim-cot-leakage 技能](../../../skills/dsh-trim-cot-leakage/SKILL.md)把这些规则落地为可执行工作流:审计分类法、已提交的成批召回检索,以及校准保留/删除边界的少样本示例。 +一次全仓库清理把这些规则应用到了各个行文表面,包括生成器持有的模板(`scripts/gen-doc-graphs.ts`、`scripts/gen-tool-catalog.ts`、typert 生成器的页面提示语,改后重新生成)、type-equiv 源码 JSDoc(改后把文档页重新粘贴)以及双语对侧文件(改后重新记录配对)。[dsh-trim-cot-leakage 技能](../../../skills/dsh-trim-cot-leakage/SKILL.md)把这些规则落地为可执行工作流:审计分类法、已提交的成批召回检索,以及用于判断保留或删除内容的少样本示例。 ## 曾考虑的替代方案 - **把设计台账与审计文档提交入库,让序号得以解析。**不予采纳:会话 transcript 是工作产物,不是持续维护的参考资料;提交它们会在 Agent Note 之外形成一套平行且不受门禁约束的决策语料,其内部编号也仍会漂移。 -- **为被禁词汇建一道机械门禁。**暂缓:这类词汇是无界的自然语言,审计中以查全为目标的成批检索需要人工判断,才能把泄漏与正当行文区分开(作名词的「wait」、表转折的「actually」、运行时的新旧状态)。若该模式再次出现,候选方案是一道窄而高查准的门禁(例如 `\(decision \d`、`\(audit [A-Z]\d`、`\bcut \d`、`this cut`、裸 `\bT\d\b`、`P-I`、`used to `、裸 `\bv1\b` 与 `§\d`——最后一种需排除章节编号有已提交归属的引用,如 web-styling.md 自身的 §N);对本次清扫自身的评审恰好在这些电池之外的形态中发现残留,因此它们位居候选清单之首。 +- **为被禁词汇建一道机械门禁。**暂缓:这类词汇是无界的自然语言,审计中以查全为目标的成批检索需要人工判断,才能把泄漏与正当行文区分开(作名词的「wait」、表转折的「actually」、运行时的新旧状态)。若该模式再次出现,候选方案是一道窄而高查准的门禁(例如 `\(decision \d`、`\(audit [A-Z]\d`、`\bcut \d`、`this cut`、裸 `\bT\d\b`、`P-I`、`used to `、裸 `\bv1\b` 与 `§\d`——最后一种需排除章节编号有已提交归属的引用,如 web-styling.md 自身的 §N);对本次清扫自身的评审恰好在这些检索未覆盖的案例中发现残留,因此它们位居候选清单之首。 - **删除引用了失效产物的设计理由。**不予采纳:事实性语句都得到保留或改写;依行文标准的完整命题规则,删掉的只有引用、评审编排与推导过程记录。 ## 验证 diff --git a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.i18n.yaml b/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.i18n.yaml index b18d088e03..8f9e929996 100644 --- a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md -2026-08-09-concrete-prose-names-actors-and-recorded-facts.md: efb6e9bcc6f1c817bb0be07ccd31bafc4b2ac3fb -2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md: 41b0ce82631536c53ea45d60c1405a8387885d0f +2026-08-09-concrete-prose-names-actors-and-recorded-facts.md: b7df5403ea11ff8ba32be0f9bede5a32d5bcd6ee +2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md: 51a6c42dc75ad04f7d35ef90dd0d54046585dd6d diff --git a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md b/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md index efb6e9bcc6..b7df5403ea 100644 --- a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md +++ b/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md @@ -18,11 +18,13 @@ The rule applies to Markdown, READMEs, active Agent Notes, JSDoc and comments, p Exact code identifiers, public APIs, durable fields, protocol members, type names, headings with external references, and filenames stay unchanged unless a coordinated contract rename is independently required. Surrounding prose explains their fields or behavior directly. Generated documents and catalogs update from their owning source. +Before using `contract`, `boundary`, or `shape`, writers check whether the sentence means a more specific rule, operation, data structure, field set, validation point, timing point, API, type, or failure condition. `Contract` remains correct for preconditions, postconditions, invariants, compatibility promises, and other obligations that callers, callees, implementers, providers, producers, or consumers rely on. `Boundary` remains correct for a literal security, trust, wire, process, serialization, transaction, or lifecycle division. `Shape` remains correct when the structural form itself is the subject and no narrower term such as fields, schema, type, union variant, file layout, or export form states the fact. Code and API names containing these words remain unchanged unless a separate coordinated rename is required. + This decision complements the [documentation tiers and budgets](2026-07-04-doc-tiers-and-budgets.md) decision, which continues to own placement, document form, and word budgets. ## Alternatives considered -**Ban a fixed list of words.** Rejected because a word may be an exact identifier or the clearest term in another contract. Sentence-level review catches ambiguity without rejecting valid names. +**Ban a fixed list of words.** Rejected because a word may be an exact identifier or the clearest term in another contract. For example, caller/callee invariants are real contracts, and process or wire boundaries identify real divisions. Sentence-level review catches ambiguity without rejecting valid names. **Replace every abstract label with “source,” “origin,” or “metadata.”** Rejected because another broad label still leaves readers to infer whether the sentence means a file, caller, event seq, provider/model pair, commit, or build job. diff --git a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md b/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md index 41b0ce8263..51a6c42dc7 100644 --- a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md +++ b/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md @@ -18,11 +18,13 @@ Status: implemented 除非另一项独立需求明确要求协调重命名约定,否则确切的代码标识符、公开 API、持久字段、协议成员、类型名、带有外部引用的标题和文件名均保持不变。它们周围的行文直接说明其字段或行为。生成的文档和目录在维护它们的源文件修改后更新。 +使用 `contract`、`boundary` 或 `shape` 之前,写作者要确认句子是否实际指更具体的规则、操作、数据结构、字段集合、校验点、时间点、API、类型或失败条件。调用方、被调用方、实现方、提供方、生产方或消费方依赖的前置条件、后置条件、不变量、兼容性承诺及其他义务仍可准确称为 `contract`。真实的安全、信任、wire、进程、序列化、事务或生命周期分界仍可准确称为 `boundary`。当结构形式本身就是主题,且字段、schema、类型、联合变体、文件布局或导出形式等更窄的词无法说明事实时,仍可使用 `shape`。除非另一项独立需求要求协调重命名,否则包含这些词的代码和 API 名称保持不变。 + 该决策补充了[文档层级与字数预算](2026-07-04-doc-tiers-and-budgets.md)决策;后者继续规定内容位置、文档形式和字数预算。 ## 曾考虑的替代方案 -**禁止一份固定词表中的所有词。** 不予采纳:某个词可能是确切的标识符,也可能是另一项约定中最清楚的用词。逐句审查可以找出歧义,且不会拒绝有效名称。 +**禁止一份固定词表中的所有词。** 不予采纳:某个词可能是确切的标识符,也可能是另一项约定中最清楚的用词。例如,调用方与被调用方依赖的不变量属于真实 contract,进程或 wire boundary 也表示真实分界。逐句审查可以找出歧义,且不会拒绝有效名称。 **将每个抽象名称都替换为“来源”、“起源”或“元数据”。** 不予采纳:另一个宽泛名称仍会让读者自行推测句子指的是文件、调用方、事件 seq、提供方/模型组合、commit 还是构建任务。 diff --git a/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.i18n.yaml index 7c4e1fac3d..6ab79c8447 100644 --- a/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.md -2026-07-29-shared-base-config-overlays.md: b2ca5cc8d3a160dd09550827d5f8148c14048ea4 -2026-07-29-shared-base-config-overlays.zh.md: adec7c793839e84edb14173d71735d264cf2ec28 +2026-07-29-shared-base-config-overlays.md: bbffcda725e3e2c5c0d0ce56a4ee0f1f61559806 +2026-07-29-shared-base-config-overlays.zh.md: e883ee2c5fd5fc887690e85653a9fc07408b6c6f diff --git a/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.md b/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.md index b2ca5cc8d3..bbffcda725 100644 --- a/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.md +++ b/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.md @@ -22,7 +22,7 @@ Precedence is list order, last write winning per row: base, then the surface ove `--config ` now applies an overlay **instead of** the personal overlay, so a demo or test tree never inherits the user's provider and model. `--config-replace ` boots a file as the entire tree, bypassing base, surface overlay, and personal overlay alike; that is what the old `--config` did, so trees like `examples/web-cordis` moved to the new flag. Both flags survive the `/resume` execve handoff, or resuming would silently change the agent. -A patch replaces its target row's whole `config` rather than merging, which shapes the split: a row whose value differs per surface lives in the overlays, never in the base, so no row is patched by three layers at once. Session identity therefore cannot ride a config key at all — it moved to `dsh-agent-loop`'s `CONFIGURED_AGENT_IDENTITIES_KEY`, as the launcher-owned identity record documented. +A patch replaces its target row's whole `config` rather than merging. Therefore, a row whose value differs per surface lives in the overlays, never in the base, so no row is patched by three layers at once. Session identity cannot ride a config key at all — it moved to `dsh-agent-loop`'s `CONFIGURED_AGENT_IDENTITIES_KEY`, as the launcher-owned identity record documented. `examples/tui-agent`, `examples/cordis-agent`, `examples/code-mode`, and `packages/examples/tui-demo` are deleted. The TUI tests move to `apps/cli/tests/`, the cordis-toolset e2e to `packages/self-modification/tool-cordis/tests/`, and the supported Code Mode demo remains the ACP overlay at `examples/acp-agent/code-mode.cordis.yml`. diff --git a/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.zh.md b/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.zh.md index adec7c7938..e883ee2c5f 100644 --- a/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.zh.md +++ b/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.zh.md @@ -22,7 +22,7 @@ Status: implemented `--config ` 现在应用一个 overlay 来**取代**个人 overlay,因此 demo 或测试用的树绝不会继承用户的提供方与 model。`--config-replace ` 则把某个文件作为整棵树启动,同时绕过 base、surface overlay 与个人 overlay;这正是旧 `--config` 的行为,所以像 `examples/web-cordis` 这样的树改用了新 flag。两个 flag 都会在 `/resume` 的 execve 交接中保留,否则恢复时会静默更换 agent(智能体)。 -patch 会整体替换目标配置项的 `config` 而不合并,这决定了拆分方式:取值因 surface 而异的配置项住在 overlay 中,绝不住在 base 里,从而没有任何配置项会被三层同时 patch。因此会话身份根本不能经由配置键传递——它迁移到了 `dsh-agent-loop` 的 `CONFIGURED_AGENT_IDENTITIES_KEY`,正如启动器持有身份的记录所述。 +patch 会整体替换目标配置项的 `config` 而不合并。因此,取值因 surface 而异的配置项住在 overlay 中,绝不住在 base 里,从而没有任何配置项会被三层同时 patch。会话身份根本不能经由配置键传递——它迁移到了 `dsh-agent-loop` 的 `CONFIGURED_AGENT_IDENTITIES_KEY`,正如启动器持有身份的记录所述。 `examples/tui-agent`、`examples/cordis-agent`、`examples/code-mode` 与 `packages/examples/tui-demo` 均被删除。TUI 测试迁往 `apps/cli/tests/`,cordis 工具集的 e2e 迁入 `packages/self-modification/tool-cordis/tests/`,受支持的 Code Mode demo 则保留为 `examples/acp-agent/code-mode.cordis.yml` 中的 ACP(Agent Client Protocol)overlay。 diff --git a/.agents/notes/implemented/simplification/2026-07-31-one-route-to-add-a-workspace.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-31-one-route-to-add-a-workspace.i18n.yaml index 625bb457ea..05f3050df7 100644 --- a/.agents/notes/implemented/simplification/2026-07-31-one-route-to-add-a-workspace.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-07-31-one-route-to-add-a-workspace.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-07-31-one-route-to-add-a-workspace.md -2026-07-31-one-route-to-add-a-workspace.md: 46bd5d5ca5ecfbc340fcb46b6da5e9f237764ab1 -2026-07-31-one-route-to-add-a-workspace.zh.md: 6a6e8b3fc9f64a62d3264f4c7779d11c8515e447 +2026-07-31-one-route-to-add-a-workspace.md: 4de79b7db30853a13319aa016d4eb5fb5d9ec1f1 +2026-07-31-one-route-to-add-a-workspace.zh.md: 2b305494c5d61db11f9ecf247c323040e6a549bf diff --git a/.agents/notes/implemented/simplification/2026-07-31-one-route-to-add-a-workspace.md b/.agents/notes/implemented/simplification/2026-07-31-one-route-to-add-a-workspace.md index 46bd5d5ca5..4de79b7db3 100644 --- a/.agents/notes/implemented/simplification/2026-07-31-one-route-to-add-a-workspace.md +++ b/.agents/notes/implemented/simplification/2026-07-31-one-route-to-add-a-workspace.md @@ -25,9 +25,9 @@ The direct-open path carries the busy rule the menu entry states: while a pick i `WorkspaceCreateFlow` is now `WorkspacePickFlow` and its `createOnly` prop is `addOnly`; the injected `createWorkspace` narrows from `{ name } | { path }` to `{ path }`. -## Wire and CLI residue +## Wire and CLI surface -The host's `workspace.create` still accepts `{ name }`, and `dsh web --workspace-root` still feeds its target directory, but no product surface reaches either any more. The same is true of the client contract that carried the name to the wire: `WorkspaceCreateInput`, `WorkspacesService.create`'s `{ name }` arm, `intentName`'s name branch, and the manager's "name under workspaceRoot" contract. `apps/cli/README.md` and its Chinese counterpart still document `--workspace-root` as creating named Workspaces. The whole set is marked for deletion at the call site in `packages/host/apiproxy/src/api-proxy.ts` and left to a follow-up change: it is backend, client-contract, and CLI surface with its own test fallout (the api-proxy workspace suite, the runtime workspace suite, the config catalog), and the release-blocking part of this decision is the UI. +`workspace.create` accepts only `{ path }`; the wire schema and `WorkspaceApi` have no `name` member. The gateway has no `workspaceRoot` config, the client contract exposes only path adoption through `WorkspaceCreateInput`, `WorkspacesService.create`, and `intentName`, and `dsh web` has no `--workspace-root` flag. `workspace-name-conflict` remains on the wire as `workspace.rename`'s duplicate-title error. ## Testing @@ -45,13 +45,13 @@ The host's `workspace.create` still accepts `{ name }`, and `dsh web --workspace **Keep the menu shell for entries we might add later (clone a repo, remote directory).** Rejected under "require a current owner and need": no such entry exists, and restoring a menu when one arrives is a smaller change than shipping an empty frame now. -**Delete the wire's create-by-name branch in the same change.** Rejected here: it is backend/CLI surface with a wider test fallout, and the urgent decision is the UI. See the residue section — it is marked, not forgotten. +**Delete the wire's create-by-name branch in the same change.** Rejected because the UI decision did not depend on the backend and CLI deletion, whose separate contracts and tests formed an independently reviewable change. **Register the workspace through the host in the e2e scaffold instead of driving the dialog.** Rejected: it would have decoupled all 15 scenarios from the picker, so nothing in the lane would prove the surviving route reaches a live composer. Every scenario now walks the real dialog to adopt its directory; only the create-a-folder half is concentrated in one scenario, because repeating it everywhere makes the shared helper non-idempotent for no extra signal. ## Consequences -- Creating a Workspace outside the operator's chosen directory is no longer possible from the UI; the server-controlled `--workspace-root` target was the one way to constrain where new workspace folders land, and nothing replaces it. A deployment that needs that constraint has to re-introduce it deliberately. -- The one remaining route browses the host filesystem, so the picker's reach is now the whole host rather than one configured parent. That is already the browse occupant's contract; this change makes it the only contract. -- A composition that mounts `ui-workspace` without any directory-picker package can no longer add a Workspace at all, and now says so by omitting the button instead of offering a create-by-name fallback. +- The UI creates Workspace folders only under a directory the operator chooses. No server-controlled configuration constrains that location; a deployment that needs such a constraint must add it deliberately. +- The picker's configured reach defines the host filesystem available to the remaining route; there is no separate configured parent. +- A composition that mounts `ui-workspace` without a directory-picker package cannot add a Workspace and omits the button. - The hero chip still announces `aria-haspopup="menu"` while the direct-open path raises a dialog instead. Making that truthful means routing the flow's presentation choice up through the `conversation.hero.workspace` owner contract — the flow owns the decision, the chip owns the announcement, and they sit in different packages — so it is a named follow-up rather than a silent inconsistency. The sidebar button this change added makes no popup claim at all. diff --git a/.agents/notes/implemented/simplification/2026-07-31-one-route-to-add-a-workspace.zh.md b/.agents/notes/implemented/simplification/2026-07-31-one-route-to-add-a-workspace.zh.md index 6a6e8b3fc9..2b305494c5 100644 --- a/.agents/notes/implemented/simplification/2026-07-31-one-route-to-add-a-workspace.zh.md +++ b/.agents/notes/implemented/simplification/2026-07-31-one-route-to-add-a-workspace.zh.md @@ -25,9 +25,9 @@ Status: implemented `WorkspaceCreateFlow` 现更名为 `WorkspacePickFlow`,其 `createOnly` prop 更名为 `addOnly`;注入的 `createWorkspace` 从 `{ name } | { path }` 收窄为 `{ path }`。 -## Wire and CLI residue +## Wire 与 CLI 表层 -Host 侧的 `workspace.create` 仍接受 `{ name }`,`dsh web --workspace-root` 也仍在为它提供目标目录,但已没有任何产品表层会走到它们。把名称送到 wire 的客户端约定同样如此:`WorkspaceCreateInput`、`WorkspacesService.create` 的 `{ name }` 分支、`intentName` 的名称分支,以及 manager 中"workspaceRoot 下的 name"这一约定。`apps/cli/README.md` 及其中文对照本也仍把 `--workspace-root` 记为"创建具名 Workspace"。这一整套都在 `packages/host/apiproxy/src/api-proxy.ts` 的调用点标记为待删除,并留给后续改动:它横跨 backend、客户端约定与 CLI 面,有各自的测试波及面(api-proxy workspace 套件、runtime workspace 套件、配置目录),而本决定中阻塞发布的部分是 UI。 +`workspace.create` 只接受 `{ path }`;wire schema 与 `WorkspaceApi` 没有 `name` 成员。网关没有 `workspaceRoot` 配置;客户端约定只通过 `WorkspaceCreateInput`、`WorkspacesService.create` 与 `intentName` 提供按路径接纳,`dsh web` 没有 `--workspace-root` flag。`workspace-name-conflict` 仍保留在 wire 上,作为 `workspace.rename` 的标题重名错误。 ## Testing @@ -45,13 +45,13 @@ Host 侧的 `workspace.create` 仍接受 `{ name }`,`dsh web --workspace-root` **为将来可能新增的入口(克隆仓库、远程目录)保留菜单壳。** 否决,依据"require a current owner and need":这样的入口目前并不存在,而等它到来时再恢复菜单,比现在就发一个空壳的改动更小。 -**在同一改动中删除 wire 的按名称创建分支。** 在此否决:那是 backend/CLI 面,测试波及面更广,而紧急的决定是 UI。见 residue 一节——它是被标记了,不是被遗忘了。 +**在同一改动中删除 wire 的按名称创建分支。** 否决,因为 UI 决定不依赖后端与 CLI 删除,后两者各自的约定和测试构成一项可独立评审的改动。 **在 e2e scaffold 中经 host 注册 workspace,而不驱动对话框。** 否决:那会让全部 15 个场景与选择器解耦,整条 lane 将无法证明幸存的这条路径能走到可用的 composer。现在每个场景都会走真实对话框来接纳自己的目录;只有"新建文件夹"那一半集中在一个场景里,因为处处重复只会让共享辅助函数失去幂等性,却换不来额外信号。 ## Consequences -- 从 UI 已无法在操作者选定目录之外创建 Workspace;服务端控制的 `--workspace-root` 目标曾是约束新 workspace 文件夹落点的唯一手段,现在没有替代品。需要该约束的部署必须有意识地重新引入它。 -- 仅存的这条路径会浏览宿主机文件系统,因此选择器的可达范围现在是整台宿主机,而非一个配置好的父目录。这本就是浏览占用者的约定,本改动使它成为唯一的约定。 -- 挂载了 `ui-workspace` 但未挂任何 directory-picker 包的组合,已完全无法添加 Workspace;现在它通过不渲染按钮来说明这一点,而不是提供一个按名称创建的兜底。 +- UI 只在操作者选择的目录下创建 Workspace 文件夹。没有服务端控制的配置约束其位置;需要该约束的部署必须明确加入该约束。 +- picker 配置的可达范围决定剩余路径可用的 Host 文件系统;不存在另一个配置好的父目录。 +- 挂载 `ui-workspace` 但没有 directory-picker 包的组合无法添加 Workspace,也不渲染按钮。 - 主视觉区的 chip 仍声明 `aria-haspopup="menu"`,而直接拉起的路径实际弹出的是对话框。要让它如实,需要把流程的展示方式经 `conversation.hero.workspace` 的 owner 约定上报——决定权在流程,播报权在 chip,两者分属不同的包——因此这被列为一项具名的后续工作,而不是一处无声的不一致。本次新增的侧边栏按钮完全不作任何 popup 声明。 diff --git a/.agents/notes/implemented/simplification/2026-08-10-web-remove-steering-interjection-caption.i18n.yaml b/.agents/notes/implemented/simplification/2026-08-10-web-remove-steering-interjection-caption.i18n.yaml new file mode 100644 index 0000000000..2f63bbcfd7 --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-08-10-web-remove-steering-interjection-caption.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-08-10-web-remove-steering-interjection-caption.md +2026-08-10-web-remove-steering-interjection-caption.md: 2c396f54945fb1c3626f2e5fcc849e81893c23f0 +2026-08-10-web-remove-steering-interjection-caption.zh.md: 088b36449130f9e8f1be5bec7a3ee4a812b4b6f1 diff --git a/.agents/notes/implemented/simplification/2026-08-10-web-remove-steering-interjection-caption.md b/.agents/notes/implemented/simplification/2026-08-10-web-remove-steering-interjection-caption.md new file mode 100644 index 0000000000..2c396f5494 --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-08-10-web-remove-steering-interjection-caption.md @@ -0,0 +1,36 @@ +# Agent Note: Remove the steering interjection caption + +Status: implemented + +English | [中文](2026-08-10-web-remove-steering-interjection-caption.zh.md) + +## Problem + +The [context-source and steer marks decision](../feature/2026-08-04-web-context-source-and-steer-marks.md) captioned every durable and pending steering bubble with `插话` / `Interjection` so the transcript could say which right-aligned bubble interrupted a running turn. The caption repeats what the flow already shows: a steering bubble sits mid-turn, between the assistant content it interrupted, while a turn-opening prompt sits at a turn boundary. A permanent line of tertiary text above every steer bubble buys no reading a position-aware reader does not already have, and it is the only chrome any user-style bubble carries, so it also breaks the otherwise uniform right-aligned rhythm. + +## Decision + +Steering renders exactly as a user bubble. `UserStyleBubble` has no steering flag, the `message.steering` locale key and the `.steeringMark` style are deleted, and `PendingSteeringBubble` and `UserMessageNodeView` pass only content and actions. A mid-turn steer is recognizable by its position inside the running turn's flow, and by nothing else. + +The runtime distinction is untouched. `SteeringMessageNode` projection from durable `agent/inbox/spliced` history, the `data-pending-steering` attribute, and the pending-to-durable hand-off all remain: the pending lifecycle needs the node identity regardless of presentation, and tests still locate pending bubbles through the attribute. + +This partially supersedes the steering clause of the [context-source and steer marks decision](../feature/2026-08-04-web-context-source-and-steer-marks.md); its context-source and recall naming stays current. The caption has flipped before: the [archived no-steer decision](../../archived/simplification/2026-07-31-web-ui-no-steer-entry-or-interjection-chrome.md) removed it while the composer could not steer, and the 2026-08-04 decision reintroduced it after the composer gained a Steer gesture. This removal does not revisit the gesture — steering entry, the Queue dock's steer-send action, and the pending lifecycle keep their owners — it judges only that the transcript need not name the result. + +## Alternatives considered + +**Keep the caption.** It is the status quo and cheap to keep, but it decorates every steer bubble forever to encode a fact the bubble's position already states. Chrome that carries no information a reader lacks is removed, not maintained. + +**Remove the `SteeringMessageNode` distinction too.** The node kind is derived from durable inbox history and drives the pending-to-durable hand-off; it is a replay fact, not presentation. Folding it into `UserMessageNode` would change projection behavior for no UI gain. + +**Distinguish steering with quieter chrome (tint, indent, hover-only label).** Any replacement re-raises the same question with a weaker vocabulary. The distinction the transcript needs is positional and already visible; adding subtler decoration keeps the cost and loses the one virtue the text caption had, being explicit. + +## Testing + +- `packages/client/ui-conversation` jsdom coverage pins the plain bubble: the pending hand-off test locates pending bubbles by `data-pending-steering` and asserts the single-bubble hand-off without any caption, and the MessageItem steering arm asserts copy-without-branch on an uncaptioned bubble. +- The keyless assembled-Web goldens (`steering/mid-steer`, `steering/settled`, `plan-review/approved`) replay the unchanged session fixtures with no caption text. + +## Consequences + +- A replayed transcript no longer names steering: a reader infers a mid-turn interjection from its position inside the turn. That inference is weaker than an explicit label for a reader skimming turn boundaries; the decision accepts this. +- A pending steer bubble is visually identical to an ordinary sent bubble until admission; only its missing clock time differs. +- Reintroducing steering chrome of any form requires a new product decision superseding this note. diff --git a/.agents/notes/implemented/simplification/2026-08-10-web-remove-steering-interjection-caption.zh.md b/.agents/notes/implemented/simplification/2026-08-10-web-remove-steering-interjection-caption.zh.md new file mode 100644 index 0000000000..088b364491 --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-08-10-web-remove-steering-interjection-caption.zh.md @@ -0,0 +1,36 @@ +# Agent Note: 移除 steering 插话标注 + +Status: implemented + +[English](2026-08-10-web-remove-steering-interjection-caption.md) | 中文 + +## 问题 + +[上下文来源与 steer 标识决策](../feature/2026-08-04-web-context-source-and-steer-marks.md)给每个持久与待处理的 steering 气泡加上了 `插话` / `Interjection` 标注,让 transcript 能说明哪条右对齐气泡打断了正在运行的轮次。这个标注重复了消息流已经呈现的事实:steering 气泡位于轮次中途、夹在被它打断的助手内容之间,而开轮提示位于轮次边界。在每个 steer 气泡上方常驻一行三级文字,并没有让一个能看到位置的读者多读出任何信息,而且它是所有用户样式气泡中唯一带装饰的,还破坏了原本统一的右对齐节奏。 + +## 决策 + +steering 完全按用户气泡渲染。`UserStyleBubble` 不再有 steering 标志,`message.steering` locale 键与 `.steeringMark` 样式已删除,`PendingSteeringBubble` 与 `UserMessageNodeView` 只传内容与操作。轮次中途的 steer 只能靠它在运行轮次消息流中的位置辨认,除此之外没有任何标识。 + +运行时的区分保持不变。从持久 `agent/inbox/spliced` 历史投影 `SteeringMessageNode`、`data-pending-steering` 属性、待处理到持久的交接全部保留:待处理生命周期无论呈现如何都需要节点身份,测试也仍通过该属性定位待处理气泡。 + +本决策部分取代[上下文来源与 steer 标识决策](../feature/2026-08-04-web-context-source-and-steer-marks.md)中的 steering 条款;其上下文来源与召回命名仍然有效。这个标注此前已经翻转过一次:[已归档的取消 steer 装饰决策](../../archived/simplification/2026-07-31-web-ui-no-steer-entry-or-interjection-chrome.md)在 composer 无法 steer 时移除了它,2026-08-04 的决策在 composer 获得 Steer 手势后把它加了回来。本次移除不重议手势本身——steering 入口、Queue dock 的插话发送操作、待处理生命周期各归其主——只判定 transcript 不需要为其结果命名。 + +## 考虑过的替代方案 + +**保留标注。** 它是现状,维持成本低,但它永久装饰每个 steer 气泡,只为编码气泡位置已经陈述的事实。不承载读者缺少的信息的装饰应当删除,而不是维护。 + +**连 `SteeringMessageNode` 区分一起删。** 节点类型派生自持久 inbox 历史,驱动待处理到持久的交接;它是回放事实,不是呈现。把它并入 `UserMessageNode` 会改变投影行为,却没有任何 UI 收益。 + +**换更安静的装饰(底色、缩进、悬停标签)。** 任何替代装饰都会用更弱的表达重新提出同一个问题。transcript 需要的区分是位置性的、已经可见的;换成更含蓄的装饰保留了成本,却丢掉了文字标注唯一的优点,就是明确。 + +## 测试 + +- `packages/client/ui-conversation` 的 jsdom 覆盖固定了纯气泡行为:待处理交接测试通过 `data-pending-steering` 定位待处理气泡,在没有任何标注的前提下断言单气泡交接;MessageItem 的 steering 分支在无标注气泡上断言可复制且无分支操作。 +- 无密钥的组装 Web goldens(`steering/mid-steer`、`steering/settled`、`plan-review/approved`)用未变的会话 fixture 回放,不含标注文字。 + +## 后果 + +- 回放的 transcript 不再为 steering 命名:读者靠消息在轮次中的位置推断这是一次中途插话。对快速扫读轮次边界的读者,这个推断弱于显式标签;本决策接受这一代价。 +- 待处理的 steer 气泡在被准入前与普通已发送气泡在视觉上完全一致,仅缺少时钟时间。 +- 重新引入任何形式的 steering 装饰都需要一个取代本 note 的新产品决策。 diff --git a/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.i18n.yaml b/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.i18n.yaml index 8fd7d1dd91..b062440270 100644 --- a/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.i18n.yaml +++ b/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md -2026-07-28-storage-root-and-derived-medium-recovery.md: 45e8b3dfcad0590a26581e20c7bc6bb5692e5c36 -2026-07-28-storage-root-and-derived-medium-recovery.zh.md: 330473b2b17669e373a6dc433a74d8bb3ba37628 +2026-07-28-storage-root-and-derived-medium-recovery.md: 9463af00e2a2f5e2d3cee4cc7d386173007abf45 +2026-07-28-storage-root-and-derived-medium-recovery.zh.md: 215e43fac6bb52e1ecaf15d6d9d1ce1b5f913bc3 diff --git a/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md b/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md index 45e8b3dfca..9463af00e2 100644 --- a/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md +++ b/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md @@ -35,7 +35,7 @@ Two independent changes, one per gap. **Launcher patch + a `storageRoot` profile key** — not taken: one `!!js` yml expression reaches the global root with the same layering the session root already has; a launcher patch adds a second rewrite point, and the profile key is an empty seat until a real consumer exists (per-row overrides already have the personal config.yaml patch layer). -**Patch only the projection cache's route to a global root, leave `workspace.json` per-cwd** — rejected: the workspace registry has the identical global-vs-cwd mismatch, and the user decision that shaped the cache placed it deliberately beside `workspace.json` — one hub root keeps the media co-located and the mental model single. +**Patch only the projection cache's route to a global root, leave `workspace.json` per-cwd** — rejected: the workspace registry has the identical global-vs-cwd mismatch, and the user chose to place the cache beside `workspace.json` — one hub root keeps the media co-located and the mental model single. **Cache-plugin-local recovery (catch damage errors in `SessionProjectionCache[Service.init]`, delete the file, reopen)** — rejected: the plugin cannot name the medium path without reaching around the backend abstraction, and every future derived domain would re-implement the same catch; the facility is the one place that already classifies open failures. diff --git a/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.zh.md b/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.zh.md index 330473b2b1..215e43fac6 100644 --- a/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.zh.md +++ b/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.zh.md @@ -35,7 +35,7 @@ Status: proposed **launcher patch + `storageRoot` profile 键**——未采:一行 yml `!!js` 表达式即达全局根,与会话根的既有分层完全一致;launcher patch 多引入一个改写点,profile 键在有真实消费者前是空席(按行覆盖已有个人 config.yaml patch 层可用)。 -**只把投影缓存的 route 指到全局根,`workspace.json` 留在 per-cwd**——拒绝:工作区注册表有一模一样的全局 vs per-cwd 错位,而且塑造缓存的用户决策就是刻意把它放在 `workspace.json` 旁边——一个 hub 根让介质同址、心智模型单一。 +**只把投影缓存的 route 指到全局根,`workspace.json` 留在 per-cwd**——拒绝:工作区注册表有一模一样的全局 vs per-cwd 错位,而且用户选择把缓存放在 `workspace.json` 旁边——一个 hub 根让介质同址、心智模型单一。 **缓存插件本地恢复(在 `SessionProjectionCache[Service.init]` 捕获损坏错误、删文件、重开)**——拒绝:插件不越过后端抽象就叫不出介质路径,且未来每个派生域都要重抄同一段 catch;facility 是唯一已经在分类 open 失败的地方。 diff --git a/.agents/skills/dsh-archive-agent-notes/SKILL.md b/.agents/skills/dsh-archive-agent-notes/SKILL.md index e4df7e1fe7..ef20de8097 100644 --- a/.agents/skills/dsh-archive-agent-notes/SKILL.md +++ b/.agents/skills/dsh-archive-agent-notes/SKILL.md @@ -1,6 +1,6 @@ --- name: dsh-archive-agent-notes -description: Use when adding, auditing, pruning, archiving, restoring, or reviewing Agent Notes in deepseek-harness; checks every new note for superseded active records, classifies implemented notes by future decision value, deletes rejected notes that no longer prevent a tempting fallacy, and applies the frozen archived/{kind} triplet and manifest contract. +description: Use when adding, auditing, pruning, archiving, restoring, or reviewing Agent Notes in deepseek-harness; checks every new note for superseded active records, classifies implemented notes by future decision value, deletes rejected notes that no longer prevent a tempting fallacy, and applies the frozen archived/{kind} triplet and manifest rules. --- # Archive DeepSeek Harness Agent Notes @@ -9,18 +9,18 @@ Reduce the active decision corpus without erasing history that can still guide w ## Read the contracts -Read [the Agent Note contract](../../notes/README.md), [the archive instructions](../../notes/archived/AGENTS.md), and the applicable active lifecycle instructions before classifying. Use current code, configuration, package docs, generated catalogs, newer Agent Notes, and inbound links to establish whether a rationale still owns or constrains anything. +Read [the Agent Note rules](../../notes/README.md), [the archive instructions](../../notes/archived/AGENTS.md), and the applicable active lifecycle instructions before classifying. Use current code, configuration, package docs, generated catalogs, newer Agent Notes, and inbound links to establish whether a rationale still owns or constrains anything. ## Check supersession when adding a note -Every new Agent Note triggers a scoped audit of active notes covering the same decision, mechanism, or rejected alternative. Classify each full or partial supersession while writing the new note: archive qualifying implemented triplets in the same PR, retain and cross-link partial supersessions or independently useful rationale, reject obsolete proposals, and delete rejected notes that no longer prevent a plausible mistake. Apply the Agent Note contract's consolidation rule when the new owner absorbs every unique proposition; do not defer a known match to a later corpus audit. +Every new Agent Note triggers a scoped audit of active notes covering the same decision, mechanism, or rejected alternative. Classify each full or partial supersession while writing the new note: archive qualifying implemented triplets in the same PR, retain and cross-link partial supersessions or independently useful rationale, reject obsolete proposals, and delete rejected notes that no longer prevent a plausible mistake. Apply the Agent Note consolidation rule when the new owner absorbs every unique proposition; do not defer a known match to a later corpus audit. ## Classify by future value Apply these lifecycle-specific outcomes: - **Implemented — keep active:** retain a note when its rationale, alternatives, negative guarantees, durable/wire semantics, ownership boundary, security rule, or reintroduction condition is likely to guide a future change. Length does not matter. -- **Implemented — archive:** archive a note when the shipped decision is complete and its body is unlikely to guide future work, such as one-off UI chrome, a narrow adapter, a minor closed bug, superseded implementation detail, or process history whose current contract is obvious elsewhere. +- **Implemented — archive:** archive a note when the shipped decision is complete and its body is unlikely to guide future work, such as one-off UI chrome, a narrow adapter, a minor closed bug, superseded implementation detail, or process history whose current behavior is obvious elsewhere. - **Proposed — never archive:** keep a live proposal active; if it is no longer worth pursuing, reject it with an honest reason and satisfy the rejected lifecycle format. - **Rejected — keep only as a guardrail:** retain a rejection only when the losing proposal remains a tempting, meaningful mistake and the note explains why it loses. - **Rejected — delete:** delete the whole triplet when the rejected idea is obsolete, superseded, no longer plausible, or unlikely to prevent re-litigation. Repair or delete inbound links. @@ -47,7 +47,7 @@ Keep implemented notes such as: For rejected notes: -- keep folding the compaction package split — 426 words: the package-boundary temptation remains meaningful; +- keep folding the compaction package split — 426 words: the temptation to merge the packages remains meaningful; - delete streaming workflow progress through tool calls — 972 words: its ACP/UI premise is obsolete; - delete dropping ACP terminal metadata — 362 words: the later automation-only ACP decision resolved the question. @@ -65,4 +65,4 @@ After the triplet is sealed, never edit, move, translate, reformat, or delete it Run the archive verifier's focused test, `pnpm run verify-archived-agent-notes`, `pnpm run doc-sync`, `pnpm run lint`, and `git diff --check`; select any additional evidence through [dsh-pre-push-checks](../dsh-pre-push-checks/SKILL.md). -Report active implemented notes kept, implemented notes archived, rejected notes kept/deleted, proposed notes rejected if any, and every genuinely borderline case with its word count and chosen outcome. Do not claim archived outbound links are valid: the contract intentionally never checks them. +Report active implemented notes kept, implemented notes archived, rejected notes kept/deleted, proposed notes rejected if any, and every genuinely borderline case with its word count and chosen outcome. Do not claim archived outbound links are valid: the archive verifier intentionally never checks them. diff --git a/.agents/skills/dsh-code-review/SKILL.md b/.agents/skills/dsh-code-review/SKILL.md index c8a52f5d50..e79f11cdc7 100644 --- a/.agents/skills/dsh-code-review/SKILL.md +++ b/.agents/skills/dsh-code-review/SKILL.md @@ -5,39 +5,39 @@ description: Use when reviewing a pull request in the deepseek-harness repo — # Reviewing a DeepSeek-Harness PR -**This skill is guidance, not a complete checklist.** Verify and fetch the PR's live base and exact head, then run `pnpm --silent run change-scope --base --head ` before reading the diff and enough surrounding code to understand the design. The report identifies paths and dirty layers but does not replace semantic review. Re-establish the base and rerun it after a retarget or merge. Prioritize correctness, lifecycle, security, and contract failures over style; a short review with one substantiated blocker is better than a list of nits. +**This skill is guidance, not a complete checklist.** Verify and fetch the PR's live base and exact head, then run `pnpm --silent run change-scope --base --head ` before reading the diff and enough surrounding code to understand the design. The report identifies paths and dirty layers but does not replace semantic review. Re-establish the base and rerun it after a retarget or merge. Prioritize correctness, lifecycle, security, and broken required behavior over style; a short review with one substantiated blocker is better than a list of nits. ## Sources of truth -- [AGENTS.md](../../../AGENTS.md) and [packages/AGENTS.md](../../../packages/AGENTS.md): standing repository and package authoring contracts. +- [AGENTS.md](../../../AGENTS.md) and [packages/AGENTS.md](../../../packages/AGENTS.md): standing repository and package authoring rules. - [docs/defensive-patterns.md](../../../docs/defensive-patterns.md): subprocess, callback, async-state, and disposal bug classes. - [docs/AGENTS.md](../../../docs/AGENTS.md): documentation placement and prose discipline. - [dsh-prose-standard](../dsh-prose-standard/SKILL.md): required coverage and editorial judgment for comments, docs, prompts, and visible strings. - [docs/testing.md](../../../docs/testing.md) and the [quality-gates Agent Note](../../notes/implemented/process/2026-06-11-quality-gates.md): required test tiers and gates. - [Agent Notes](../../notes/README.md): design rationale. Treat disagreement with an Agent Note as a design discussion, not an automatic veto. -- For bilingual changes, read [translation-rules.md](../../../docs/i18n/translation-rules.md), [terminology.md](../../../docs/i18n/terminology.md), and [dsh-translate-docs](../dsh-translate-docs/SKILL.md). +- For bilingual changes, read [translation-rules.md](../../../docs/i18n/translation-rules.md) and [terminology.md](../../../docs/i18n/terminology.md); the extended translation skill is outside automatic review and runs only on explicit user invocation. ## Blocking requirements 1. **New prose receives semantic review.** Use [dsh-prose-standard](../dsh-prose-standard/SKILL.md) to critically review every added or changed Markdown passage, JSDoc, comment, prompt, description, diagnostic, and visible string. Verify required coverage, accuracy, placement, and editorial quality against the owning code or behavior; automated checks do not establish those properties. 2. **Docs match the code.** Config, defaults, errors, wire fields, events, and public behavior update the package README and JSDoc in the same diff. Comments state non-obvious contracts; flag implementation narration, test walkthroughs, review history, and duplicated rationale for deletion or a link to their one home. 3. **Core type docs match.** Changes to spine or seam vocabulary update the appropriate [subsystems](../../../docs/subsystems/README.md) page and any `type-equiv` entry. Internal types need no catalog entry. -4. **Registrations clean up.** Verify each new registry contribution satisfies the disposal-test contract in [packages/AGENTS.md](../../../packages/AGENTS.md). -5. **Invariant companions are semantic.** For every touched `./invariant`, require an owner event-stream or mutable-data relationship at its authoritative boundary; service or method presence, plugin metadata or effects, and fixed pure examples belong in type, load, or unit tests. Accept an empty installer when its package-specific reason establishes that no plausible runtime relationship exists; do not demand an invented check merely to eliminate emptiness ([repository rule](../../../AGENTS.md#conventions); [package contract](../../../packages/AGENTS.md)). +4. **Registrations clean up.** Verify each new registry contribution passes the disposal tests required by [packages/AGENTS.md](../../../packages/AGENTS.md). +5. **Invariant companions are semantic.** For every touched `./invariant`, require an owner event-stream or mutable-data relationship at the point where that package can observe it; service or method presence, plugin metadata or effects, and fixed pure examples belong in type, load, or unit tests. Accept an empty installer when its package-specific reason establishes that no plausible runtime relationship exists; do not demand an invented check merely to eliminate emptiness ([repository rule](../../../AGENTS.md#conventions); [package invariant rules](../../../packages/AGENTS.md)). 6. **Required evidence exists.** Verify the author ran the [relevant local checks](../../../AGENTS.md#run-relevant-checks-locally) for the diff and that CI covers the exhaustive matrix; review the semantic gaps neither can detect. ## Manual checks -- **Intent and seam contracts:** trace both sides of every changed interface. Confirm the implementation matches the PR and any Agent Note, including errors, cancellation, ownership, and disposal. +- **Intent and interface contracts:** trace both sides of every changed interface. Confirm the implementation matches the PR and any Agent Note, including errors, cancellation, ownership, and disposal. - **Lifecycle and concurrency:** for async setup, callbacks, processes, or teardown, apply [defensive-patterns.md](../../../docs/defensive-patterns.md). Check races before publication, cancellation during awaits, independent error reporting, callback containment, ownership before reentry, complete detach cleanup, and quiescent disposal. -- **Capability and consumer fit:** trace every current consumer, then flag consumer-specific behavior leaking into the interface under [the package contract](../../../packages/AGENTS.md). Flag the inverse too: a new public method on a generic service (registry, session, agent) whose only caller is one internal consumer is an ad-hoc surface widening — require a private capability closure handed to that consumer at construction instead. -- **Scope, ownership, and necessity:** map each abstraction, state machine, option, defensive copy, and compatibility path to its current contract, production consumer, and owning plugin or service. Challenge unrelated features and speculative generality, then test the PR's coherence against [the root contract](../../../AGENTS.md#conventions). +- **Capability and consumer fit:** trace every current consumer, then flag consumer-specific behavior leaking into the interface under [the package rules](../../../packages/AGENTS.md). Flag the inverse too: a new public method on a generic service (registry, session, agent) whose only caller is one internal consumer is an unnecessary API expansion — require a private capability closure handed to that consumer at construction instead. +- **Scope, ownership, and necessity:** map each abstraction, state machine, option, defensive copy, and compatibility path to its current contract, production consumer, and owning plugin or service. Challenge unrelated features and speculative generality, then test the PR against [the root rules](../../../AGENTS.md#conventions). - **Configuration and public choices:** ask what current-consumer evidence or prior art supports each default, public operation set, format, or imported external concept. Require an explicit choice or deferral when that evidence is absent. - **Model perspective:** inspect the exact prompts, tool schemas, results, and diagnostics the model receives across affected modes. Flag concepts outside the model's task, then verify stable text verbatim and dynamic behavior through snapshots or end-to-end coverage. -- **Enforcement boundaries:** follow every denial path to the operation that executes it; exercise direct and alternate callers that can bypass schemas, prompts, facades, wrappers, or listener ordering. -- **Borrowed and derived state:** classify each retained value under the package boundary contract, then trace notifications and every cache, prompt, UI echo, replay, and query view to the documented success point and authoritative source. +- **Enforcement:** follow every denial path to the operation that executes it; exercise direct and alternate callers that can bypass schemas, prompts, facades, wrappers, or listener ordering. +- **Borrowed and derived state:** determine whether each retained value is borrowed or owned under the package contract, then trace notifications and every cache, prompt, UI echo, replay, and query view to the documented success point and authoritative source. - **Bounds cover the final operation:** locate the owner of the complete emitted or retained result, including wrappers and metadata. Probe tiny and exact limits, oversized single chunks, and multibyte text for byte limits. -- **Real entry path:** tests exercise the shipped Loader, bin, worker, ACP bridge, or subprocess where relevant. A hand-mounted plugin does not catch Loader export-shape failures; a function plugin must named-export its namespace and have no default export. +- **Real entry path:** tests exercise the shipped Loader, bin, worker, ACP bridge, or subprocess where relevant. A hand-mounted plugin does not catch invalid Loader exports; a function plugin must named-export its namespace and have no default export. - **Test strength:** assertions fail on the intended regression and verify external state, logs, events, or disposal rather than restating the implementation or trusting an agent's report. Coverage is necessary but not evidence that the scenario is correct. - **Invariant lifecycle and negative controls:** verify candidate observations are rejected before publication where possible, session-backed checks reconstruct durable history after late loading or HMR, and a deliberately invalid case fails through the real runner for the intended rule. - **Implemented Agent Notes match shipped reality:** when a PR implements a proposed Agent Note, move and rewrite it as present-tense shipped state in the same diff, then verify paths, names, and mechanisms against the implementation. diff --git a/.agents/skills/dsh-doc-site-sync/SKILL.md b/.agents/skills/dsh-doc-site-sync/SKILL.md index 3f93a6560a..5d39470d72 100644 --- a/.agents/skills/dsh-doc-site-sync/SKILL.md +++ b/.agents/skills/dsh-doc-site-sync/SKILL.md @@ -12,7 +12,7 @@ Repository translations follow the sibling pairing contract: English `foo.md`, C ## Read the owning contracts - Read [docs/AGENTS.md](../../../docs/AGENTS.md) and use [dsh-doc-standards](../dsh-doc-standards/SKILL.md) when deciding where content belongs or changing product documentation prose. -- Use [dsh-translate-docs](../dsh-translate-docs/SKILL.md) whenever an edited source has a bilingual counterpart. +- For an edited bilingual source, follow the lightweight routine path in [docs/AGENTS.md](../../../docs/AGENTS.md#writing-rules) and the [pairing contract](../../../docs/i18n/README.md); never invoke the extended translation skill automatically. - Read the current `DocsPage` type and entries in [website/docs.ts](../../../website/docs.ts) before changing the manifest; do not rely on a remembered field set. - Read [website/.vitepress/config.ts](../../../website/.vitepress/config.ts) before adding a new section, sidebar collection, locale, or top-level navigation item. @@ -38,7 +38,7 @@ Set every `DocsPage` field deliberately: - `order`: stable order within the section. - `sourceAliases`: optional additional repository paths that should resolve to this page when links are projected. It does not create another public route. -Use `mirroredPages()` only for a source that intentionally falls back to the same available language in both route trees. Convert that entry to `pairedPages()` when its counterpart is added. Keep the manifest an explicit public allowlist. Do not publish RFCs, postmortems, testing guides, `AGENTS.md`, or maintainer workflows merely because they exist under `docs/`; add internal material only when the user explicitly changes the publication boundary. +Use `mirroredPages()` only for a source that intentionally falls back to the same available language in both route trees. Convert that entry to `pairedPages()` when its counterpart is added. Keep the manifest an explicit public allowlist. Do not publish RFCs, postmortems, testing guides, `AGENTS.md`, or maintainer workflows merely because they exist under `docs/`; add internal material only when the user explicitly expands what the site publishes. ## Preserve link behavior diff --git a/.agents/skills/dsh-doc-standards/SKILL.md b/.agents/skills/dsh-doc-standards/SKILL.md index dcbf2f2b21..1ea836b8ee 100644 --- a/.agents/skills/dsh-doc-standards/SKILL.md +++ b/.agents/skills/dsh-doc-standards/SKILL.md @@ -5,25 +5,25 @@ description: 'Use when writing, moving, reviewing, or auditing documentation in # Applying the DeepSeek Harness Documentation Standard -The contract lives in [docs/AGENTS.md](../../../docs/AGENTS.md). This workflow covers placement, corpus audits, budgets, and validation across Markdown, JSDoc, and code comments. It is guidance, not a script; use [dsh-prose-standard](../dsh-prose-standard/SKILL.md) for required coverage and editorial judgment, and never treat length alone as a defect. +The documentation rules live in [docs/AGENTS.md](../../../docs/AGENTS.md). This workflow covers placement, corpus audits, budgets, and validation across Markdown, JSDoc, and code comments. It is guidance, not a script; use [dsh-prose-standard](../dsh-prose-standard/SKILL.md) for required coverage and editorial judgment, and never treat length alone as a defect. ## Sources of truth (read, don't re-summarize) - [docs/AGENTS.md](../../../docs/AGENTS.md) — hierarchy, tutorial/reference forms, taxonomy, budgets, and slop checklist. - [.agents/notes/README.md](../../notes/README.md) — when a decision earns an Agent Note, how to file it, and what goes inside one (the header block, per-lifecycle skeleton, and Alternatives-considered mandate, gated by `verify-agent-note-format`); [docs/postmortem/README.md](../../../docs/postmortem/README.md) — when an incident earns a postmortem. -- [docs/i18n/README.md](../../../docs/i18n/README.md) — the bilingual pairing contract; editing either side of a pair obligates the counterpart in the same change. +- [docs/i18n/README.md](../../../docs/i18n/README.md) — the bilingual pairing rules; editing either side of a pair obligates the counterpart in the same change. - Root [AGENTS.md](../../../AGENTS.md) — the standing orders whose budget discipline this skill protects. - [Archived Agent Notes](../../notes/archived/AGENTS.md) — frozen historical snapshots excluded from editorial maintenance and evolving documentation gates. ## Review structure before prose -Apply the standard's authoring order to every human-facing document in scope. Do not apply this structural pass to Agent Notes. Classify a postmortem as a reference scoped to one incident; preserve the chronological evidence required by its contract without treating chronology as a teaching sequence. +Apply the standard's authoring order to every human-facing document in scope. Do not apply this structural pass to Agent Notes. Classify a postmortem as a reference scoped to one incident; preserve its required chronological evidence without treating chronology as a teaching sequence. 1. Locate the document in the repository and navigation trees. State its own subject and identify its direct children. -2. Set the detail boundary. Keep full detail about the document's subject, summarize direct children by purpose, responsibility, and high-level behavior, and move deeper explanations to their owning descendants with links. Treat test infrastructure as descendant-owned unless it is the document's subject. +2. Set the permitted level of detail. Keep full detail about the document's subject, summarize direct children by purpose, responsibility, and high-level behavior, and move deeper explanations to their owning descendants with links. Treat test infrastructure as descendant-owned unless it is the document's subject. 3. Classify the document from its intended use, not its path or title. A tutorial must lead through ordered work to an observable outcome; a reference must support lookup within an explicit scope without requiring sequential reading. 4. For a tutorial, privately classify the starting reader and concepts as beginner, intermediate, or advanced. Trace each concept to its prerequisites, reorder premature material, and move optional advanced detail to a later tutorial or reference. -5. Split substantial mixed forms. Keep a small secondary form only behind a clear structural boundary. +5. Split substantial mixed forms. Put a small secondary form in a clearly labeled section. Then check constraints that make placement expensive or wrong: @@ -37,7 +37,7 @@ Then check constraints that make placement expensive or wrong: After the structural pass, hunt the standard's slop checklist with the cheapest probes first. Verify and fetch the PR's live base, then run `pnpm --silent run change-scope --base ` to identify committed and dirty paths before applying semantic judgment. After a retarget or base merge, rerun the report and audit prose introduced by the new base. 1. Measure: `pnpm run verify-doc-budgets --list`, then `git ls-files '*.md' ':(exclude)vendor/**' | xargs wc -w | sort -rn | head -30` to spot unbudgeted outliers. -2. Hunt reasoning-transcript leakage — narrated history, dead design-session citations, review choreography, control-flow narration, test walkthroughs — with [dsh-trim-cot-leakage](../dsh-trim-cot-leakage/SKILL.md), which owns the taxonomy, recall batteries, and the keep/delete boundary. Preserve only a non-obvious contract or durable rationale; the same rationale repeated beside sibling methods keeps one home. +2. Hunt reasoning-transcript leakage — narrated history, dead design-session citations, review choreography, control-flow narration, test walkthroughs — with [dsh-trim-cot-leakage](../dsh-trim-cot-leakage/SKILL.md), which defines the taxonomy, recall batteries, and rules for what to keep or delete. Preserve only a non-obvious contract or durable rationale; the same rationale repeated beside sibling methods keeps one home. 3. Hunt duplication by grepping distinctive phrases. Keep one home and replace other copies with links. 4. Replace hand-written catalogs, test/status inventories, and JSDoc restatements with the authoritative tree, script, or generated reference. 5. In `implemented/` Agent Notes, remove migration plans, acceptance-task checklists, and future-tense spec language. Keep concise verification contracts that identify the behaviors and tiers pinning the shipped decision, plus named coverage gaps. @@ -53,4 +53,4 @@ Apply the ordered relocate-condense-raise policy in [docs/AGENTS.md](../../../do ## Validation and PR hygiene -Run at least `pnpm run doc-sync`, `pnpm run lint`, and `git diff --check`; JSDoc changes may regenerate catalogs. If a paired doc changed, follow [dsh-translate-docs](../dsh-translate-docs/SKILL.md) and run `pnpm run verify-translation-pairing --write `. The PR body should give word deltas, explain any deliberately long exception, and list checks. +Run at least `pnpm run doc-sync`, `pnpm run lint`, and `git diff --check`; JSDoc changes may regenerate catalogs. If a paired doc changed, follow the [lightweight routine path](../../../docs/AGENTS.md#writing-rules) and run `pnpm run verify-translation-pairing --write `. The PR body should give word deltas, explain any deliberately long exception, and list checks. diff --git a/.agents/skills/dsh-find-simplifications/SKILL.md b/.agents/skills/dsh-find-simplifications/SKILL.md index 33e5b4fd88..88b025e298 100644 --- a/.agents/skills/dsh-find-simplifications/SKILL.md +++ b/.agents/skills/dsh-find-simplifications/SKILL.md @@ -11,19 +11,19 @@ This skill helps turn a broad "find things to simplify" request into evidence-ba - Read `AGENTS.md`, especially the pre-release stance and the conventions (including the tests-are-not-golden-truth and Agent Notes-are-not-golden-truth doctrines), plus [docs/defensive-patterns.md](../../../docs/defensive-patterns.md) and [docs/testing.md](../../../docs/testing.md). - Skim [docs/architecture.md](../../../docs/architecture.md) before judging anything under `packages/`; simplifications that fight the service map or event taxonomy need extra evidence. -- Use the Agent Note tree and its [contract](../../notes/README.md) to understand intentional architecture. The most relevant implemented examples are [drop mutable session summary](../../notes/implemented/simplification/2026-06-19-drop-mutable-session-summary.md), [shared persistence write coordinator](../../notes/implemented/architecture/2026-06-18-shared-persistence-write-coordinator.md), [capability seams](../../notes/implemented/architecture/2026-06-13-capability-seams.md), and the twin adapter / dual persistence backend Agent Notes. +- Use the Agent Note tree and its [rules](../../notes/README.md) to understand intentional architecture. The most relevant implemented examples are [drop mutable session summary](../../notes/implemented/simplification/2026-06-19-drop-mutable-session-summary.md), [shared persistence write coordinator](../../notes/implemented/architecture/2026-06-18-shared-persistence-write-coordinator.md), [capability seams](../../notes/implemented/architecture/2026-06-13-capability-seams.md), and the twin adapter / dual persistence backend Agent Notes. - Treat dual LLM adapters and dual persistence backends as intentional by default. Do not propose deleting either twin/backend as "low effort" unless the user explicitly overrides that constraint. Removing an unused method or hook inside a protected seam can still be valid if it does not collapse the protected design. ## What Counts As A Strong Candidate -A strong simplification removes, folds, or demotes something real and has clear evidence that the current shape costs more than it buys: +A strong simplification removes, folds, or demotes something real and has clear evidence that the current design costs more than it buys: - A public method, event, config knob, registry notification, helper, package, durable event, or test artifact has no production consumer. - Tests or docs are the only consumers, and the behavior they pin is not load-bearing. - Two representations mirror the same fact, especially across durable session events and transient `agent/*` events. - A seam has methods every implementation must support but no consumer uses. -- A package boundary exists only for test/demo/support code and adds publish or dependency overhead. -- A feature implements speculative product generality: multi-session/session-load, background task rosters, live registry invalidation, mid-turn steering, tool-owned UI rendering, and similar shapes with no product owner. +- A separate package exists only for test/demo/support code and adds publish or dependency overhead. +- A feature implements speculative product generality: multi-session/session-load, background task rosters, live registry invalidation, mid-turn steering, tool-owned UI rendering, and similar designs with no product owner. - An invariant, rollback path, set of expected outputs, or special-case test exists only to protect an unused surface. - Hand-rolled code reimplements what a well-maintained external package or a Node builtin at the engine floor already provides, and the swap would delete the implementation plus its dedicated tests ([dependency policy](../../notes/implemented/process/2026-07-26-dependencies-over-hand-rolling.md)). - The simplified behavior may differ slightly, but the new behavior is still reasonable and easier to explain. @@ -38,7 +38,7 @@ Use parallel subagents when the user asks for breadth or many candidates. Give e - ACP automation and human UI surfaces: prompt settlement and teardown on the protocol side; transcript rendering and interaction state on the UI side. - LLM/tools/system prompt: stream/generate surfaces, assemblers, registries, tool schema defaults, presentation hooks. - Bash and tool execution: foreground/background split, task ownership, output spill files, executor methods. -- Packages/examples/scripts/tests: package boundaries, static inventories, redundant snapshot expected outputs, support packages. +- Packages/examples/scripts/tests: package splits, static inventories, redundant snapshot expected outputs, support packages. If subagents are unavailable, simulate the same breadth yourself. Do not let the first good candidate stop the survey. @@ -46,9 +46,9 @@ Start with the largest production-code deltas. A broad simplification audit that ## Audit Trust And Lifecycle Boundaries -Classify every defensive copy, freeze, validator, and callback capture by the boundary it crosses. Same-process typed service/plugin calls ordinarily borrow readonly values; parser/config, queue, model/tool JSON, durable/file, worker, process, and wire boundaries own or validate data. Tests built around hostile getters, fake typed objects, callback replacement, or mutation after a same-process handoff are evidence of a potentially speculative contract, not automatic justification for keeping it. +For every defensive copy, freeze, validator, and callback capture, name where the value came from and who owns it next. Same-process typed service/plugin calls ordinarily borrow readonly values; parsers, config loaders, queues, model/tool JSON, durable files, workers, processes, and wire decoders own or validate their data. Tests built around hostile getters, fake typed objects, callback replacement, or mutation after a same-process handoff are evidence of a potentially speculative contract, not automatic justification for keeping it. -For complex asynchronous code, draw the ownership graph and map each sentinel, readiness promise, cancellation path, disposer, and state flag to a distinct owner or transition. When several mechanisms mirror the same liveness or settlement fact, propose one transaction or lifecycle controller instead. Preserve separate machinery where it protects a real boundary: synchronous publication and rollback, callback containment, first-terminal-outcome arbitration, worker/process ownership, or dispose-to-quiescence. +For complex asynchronous code, draw the ownership graph and map each sentinel, readiness promise, cancellation path, disposer, and state flag to a distinct owner or transition. When several mechanisms mirror the same liveness or settlement fact, propose one transaction or lifecycle controller instead. Preserve separate machinery where it protects synchronous publication and rollback, callback containment, first-terminal-outcome arbitration, worker/process ownership, or dispose-to-quiescence. ## Hand-Rolled Code Versus A Dependency @@ -75,7 +75,7 @@ Reject or downgrade a candidate when: - A production caller exists and the simplification would be a feature decision rather than a cleanup. - The surface is explicitly justified by an implemented Agent Note or a hard-won defensive pattern, and the new evidence does not beat that reason. -- The removal would force unrelated churn without actually making the contract smaller. +- The removal would force unrelated churn without actually reducing the public API or required behavior. - The idea is correct but tiny. Add a targeted TODO/FIXME/XXX instead, using the urgency semantics in [docs/development.md](../../../docs/development.md). ## Coalesce Superseded Agent Notes @@ -84,23 +84,23 @@ Audit the Agent Note tree when the user asks to reduce or coalesce it, or when t Use [`dsh-archive-agent-notes`](../dsh-archive-agent-notes/SKILL.md) for retention judgment and archive mechanics. Low-future-value implemented notes move as frozen triplets to `archived/{kind}`; proposed notes are never archived; rejected notes that no longer prevent a tempting mistake are deleted. Do not edit an archived note while simplifying current prose or code. -Follow the deletion rule in the [Agent Note contract](../../notes/README.md#when-to-write-one); do not duplicate or weaken it here. For each candidate chain: +Follow the deletion rule in the [Agent Note rules](../../notes/README.md#when-to-write-one); do not duplicate or weaken it here. For each candidate chain: 1. Identify the current owner from shipped code, configuration, generated catalogs, package docs, newer Agent Notes, and inbound links; dates and titles are discovery hints, not proof. 2. Classify the old note as fully or partially superseded. Any surviving behavior, current contract, durable format, compatibility obligation, or independently current rejected alternative makes it partial. Rationale that can be transferred to the current owner does not by itself make supersession partial. -3. For full supersession, move every unique rationale, alternative, consequence, shipped verification contract, and named coverage gap into the current owner. An inventory that only describes deleted implementation mechanics is not one of those decision facts. +3. For full supersession, move every unique rationale, alternative, consequence, shipped verification evidence, and named coverage gap into the current owner. An inventory that only describes deleted implementation mechanics is not one of those decision facts. 4. Repair every inbound link, then delete the English note, Chinese counterpart, and consistency record together. 5. Search exact filenames, symbols, config keys, event names, and wire strings after the edit. Keep partial supersessions cross-linked and current. -An added-then-removed feature is a common full-supersession case. Let the removal note own the history only when the feature is absent from production code, configuration, schemas, durable or wire formats, migration, and compatibility behavior; no current documentation presents it as available; and no test exercises it as supported behavior. Removal rationale and tests that enforce absence may remain. Preserve why the feature originally existed, why that motivation no longer justified it, alternatives to full removal, the capability given up, conditions for reintroduction, and evidence that removal is complete. Old tests and implementation mechanics that verified only the deleted behavior are not current verification contracts. +An added-then-removed feature is a common full-supersession case. Let the removal note own the history only when the feature is absent from production code, configuration, schemas, durable or wire formats, migration, and compatibility behavior; no current documentation presents it as available; and no test exercises it as supported behavior. Removal rationale and tests that enforce absence may remain. Preserve why the feature originally existed, why that motivation no longer justified it, alternatives to full removal, the capability given up, conditions for reintroduction, and evidence that removal is complete. Old tests and implementation mechanics that verified only the deleted behavior are not current verification evidence. Reject consolidation when the removal is only one transport, default, implementation, or presentation of a feature; when persisted data or compatibility handling survives; or when the removal note does not yet carry enough rationale to prevent accidental reintroduction. A current negative design decision may legitimately need its own note even though the removed implementation is gone. ## Write The Agent Note -Create one file per durable proposal under `.agents/notes///yyyy-mm-dd-topic.md`, following the lifecycle/classification contract in `.agents/notes/README.md`. Keep prose paragraphs on one physical line and use relative Markdown links. +Create one file per durable proposal under `.agents/notes///yyyy-mm-dd-topic.md`, following the lifecycle and classification rules in `.agents/notes/README.md`. Keep prose paragraphs on one physical line and use relative Markdown links. -Prefer this shape, adjusting when the idea needs it: +Prefer this structure, adjusting when the idea needs it: - `# Agent Note: ` - `Status: proposed` diff --git a/.agents/skills/dsh-prose-standard/SKILL.md b/.agents/skills/dsh-prose-standard/SKILL.md index 2381d459c2..f100f78613 100644 --- a/.agents/skills/dsh-prose-standard/SKILL.md +++ b/.agents/skills/dsh-prose-standard/SKILL.md @@ -5,7 +5,9 @@ description: Use when writing, reviewing, restoring, trimming, or auditing prose # DeepSeek Harness Prose Standard -Write enough to preserve the contract, then remove reasoning transcripts, repetition, and decoration. This skill owns editorial judgment and required prose coverage; use [dsh-doc-standards](../dsh-doc-standards/SKILL.md) for placement, budgets, bilingual pairs, and documentation gates, and [dsh-trim-cot-leakage](../dsh-trim-cot-leakage/SKILL.md) for hunting and fixing reasoning-transcript leakage. It is guidance, not a script. +Write enough to preserve the contract, then remove reasoning transcripts, repetition, and decoration. A contract is an obligation, invariant, precondition, postcondition, or compatibility promise that a caller, callee, implementer, producer, or consumer relies on. This skill owns editorial judgment and required prose coverage; use [dsh-doc-standards](../dsh-doc-standards/SKILL.md) for placement, budgets, bilingual pairs, and documentation gates, and [dsh-trim-cot-leakage](../dsh-trim-cot-leakage/SKILL.md) for hunting and fixing reasoning-transcript leakage. It is guidance, not a script. + +Treat `contract`, `boundary`, `shape`, `surface`, `seam`, `gate`, and `vocabulary` as terms to check before use, not banned words. First ask whether the exact rule, API, field set, type, validation, timing point, component split, or failure states the fact better. Keep a term when it names the exact technical subject, including caller/callee contracts and security/process boundaries. Comments describe non-obvious contracts or rationale that code cannot express; they do not restate what code already implies. @@ -21,7 +23,7 @@ Always exclude `vendor/` from discovery, review, and edits, even when the reques Also exclude `.agents/notes/archived/` from prose review and edits. Archived Agent Notes are frozen snapshots; inspect an exact target only to understand a historical inbound citation, never to modernize its prose or outbound links. -Treat generated catalogs, snapshots, and fixtures as derivative. Edit the owning source or scenario first, then regenerate the artifact. When a generator extracts a summary from owner prose, make the extracted sentence complete for that surface. Bilingual pairs have no permanent owner: either language may be the authored side for an update. Update the counterpart minimally and re-record the pair. +Treat generated catalogs, snapshots, and fixtures as derivative. Edit the owning source or scenario first, then regenerate the artifact. When a generator extracts a summary from owner prose, make the extracted sentence complete for that surface. Bilingual pairs have no permanent owner: either language may be the authored side for an update. Follow the [lightweight routine path](../../../docs/AGENTS.md#writing-rules), update the counterpart minimally, and re-record the pair. ## Preserve the complete proposition @@ -45,14 +47,14 @@ This is not a one-way shortening pass. Add or restore prose when code, types, an - **Public JSDoc:** document caller-visible return distinctions, throws or rejections, side effects, ownership, timing, cancellation, and durability. - **Internal comments:** orient non-local structure and obviously complicated local structure, including invariants, race ordering, ownership, security boundaries, and surprising failure behavior. Delete control-flow narration and code restatement. -- **Module comments:** state the module's role, boundaries, and non-obvious architecture choices; link architecture choices to their owning explanation. +- **Module comments:** state the module's role, dependencies, responsibilities, and non-obvious architecture choices; link architecture choices to their owning explanation. - **Tests:** explain only non-obvious test design—why a fixture, assertion, platform accommodation, real entry path, or indirect observation is necessary. Delete walkthroughs and inventories. - **Cookbooks:** include prerequisites, required actions, the real entry path, observable verification, and concise warnings. -- **READMEs:** include the consumer contract: configuration, semantics, failures, limitations, extension points, and model-visible effects. Quote stable model-visible text owned by the package; link generated catalogs and cross-package owners. Keep durable gaps and maintainer traps, not ordinary cleanup inventories. Follow the [package README contract](../../../docs/cookbook/adding-a-package.md#4-write-the-package-readme). -- **Agent Notes:** retain unique rationale, mechanisms, alternatives, consequences, shipped verification contracts, and named coverage gaps. Implemented Agent Notes state shipped reality in the present tense; remove planning checklists, not evidence of what pins the decision. +- **READMEs:** include the consumer contract: configuration, semantics, failures, limitations, extension points, and model-visible effects. Quote stable model-visible text owned by the package; link generated catalogs and cross-package owners. Keep durable gaps and maintainer traps, not ordinary cleanup inventories. Follow the [package README requirements](../../../docs/cookbook/adding-a-package.md#4-write-the-package-readme). +- **Agent Notes:** retain unique rationale, mechanisms, alternatives, consequences, shipped verification evidence, and named coverage gaps. Implemented Agent Notes state shipped reality in the present tense; remove planning checklists, not evidence of what pins the decision. - **Postmortems:** retain the incident sequence, evidence, causal chain, impact, and prevention. Remove repeated persuasion or implementation detail that does not establish causality. - **Skills and agent instructions:** state behavioral guardrails and explicit scope limitations such as “guidance, not a script/checklist.” Keep the workflow concise and link its source of truth. -- **Examples and configuration comments:** explain boundaries, non-obvious wiring or load order, security stance, replay behavior, exceptions, and likely misuse. Do not narrate entries that the configuration already shows. +- **Examples and configuration comments:** explain access limits, non-obvious wiring or load order, security stance, replay behavior, exceptions, and likely misuse. Do not narrate entries that the configuration already shows. - **Prompts and visible strings:** treat wording as behavior. Inspect generated output and run behavior validation or state why no snapshot applies. - **Diagnostics:** name the failing subject or path, violated rule, and correction when it is non-obvious. Remove internal execution narration. @@ -70,7 +72,7 @@ Preserve searchable mechanism names and meaningful modal, temporal, or negative ## Borderline decisions -A case is borderline only when at least two versions satisfy the complete-proposition rule but trade accepted principles, and this skill does not already resolve the tradeoff. A new prose shape with one contract-preserving answer is not borderline. +A case is borderline only when at least two versions satisfy the complete-proposition rule but trade accepted principles, and this skill does not already resolve the tradeoff. A rewrite with one proposition-preserving answer is not borderline. In automatic mode, apply clear edits when authorized and report genuine borderline cases without asking questions. Do not weaken a proposition to make progress. diff --git a/.agents/skills/dsh-prose-standard/references/examples.md b/.agents/skills/dsh-prose-standard/references/examples.md index c4270ecc98..cb3f616b71 100644 --- a/.agents/skills/dsh-prose-standard/references/examples.md +++ b/.agents/skills/dsh-prose-standard/references/examples.md @@ -40,13 +40,13 @@ Keep the test tiers, required action, real entry path, and observable verificati **Over-detailed:** A chronological account of every promise and callback used to implement teardown. -The actor, ordering, ownership boundary, and completion guarantee are separate factual clauses. +The actor, ordering, point where ownership changes, and completion guarantee are separate factual clauses. ## Event JSDoc preserves boundary timing **Over-trimmed:** “Composes and caches the session prefix.” -**Balanced:** “Composes the session prefix once before the first pre-step and request boundary. Listener appends join the current request, and pre-step pressure accounting receives the composed prefix.” +**Balanced:** “Composes the session prefix once before the first pre-step and model request. Listener appends join the current request, and pre-step pressure accounting receives the composed prefix.” **Over-detailed:** A walkthrough of the loop helpers, cache fields, and promise callbacks that implement the ordering. @@ -60,7 +60,7 @@ Event order and its current-request consequence are caller-visible behavior, not **Over-detailed:** A paragraph-by-paragraph preview of the classes and helper functions below. -Keep role, boundaries, and non-obvious lifecycle behavior. Link architecture rationale and let the code show local control flow. +Keep the module's role, dependencies, responsibilities, and non-obvious lifecycle behavior. Link architecture rationale and let the code show local control flow. ## Public JSDoc includes failures @@ -76,11 +76,11 @@ Throws and state preconditions are caller-visible contract facts. **Over-trimmed:** “Search provider backed by an external API.” -**Balanced:** “Maps each provider result to the shared search-result shape, preserving the title, URL, and text while omitting provider-only ranking metadata.” +**Balanced:** “Maps each provider result to the shared search-result fields, preserving the title, URL, and text while omitting provider-only ranking metadata.” **Over-detailed:** A field-by-field restatement of the mapping code, including fields with identical names and obvious assignments. -Keep mapping details that explain an abstraction boundary or intentional information loss. +Keep mapping details that explain where an adapter drops or changes information. ## Link rationale while keeping the local contract @@ -110,7 +110,7 @@ Remove migration tasks and test narration. Keep the tiers, behaviors they pin, r **Over-detailed:** A list of every service a plugin could misuse and every hypothetical exploit. -Keep one example when it makes an otherwise abstract boundary operationally clear. +Keep one example when it makes an otherwise abstract security limit operationally clear. ## Delete reasoning transcripts entirely @@ -134,7 +134,7 @@ Keep the consequence of order, a surprising scope rule, or a security boundary. **Shorter but worse:** “The adapter normalizes provider errors.” -**Balanced decision:** Keep the current sentence unless a link or surrounding contract already carries the failure categories. The shorter version loses the consequence and distinctions without improving structure. +**Balanced decision:** Keep the current sentence unless a link or surrounding contract already lists the failure categories. The shorter version loses the consequence and distinctions without improving structure. ## Model-visible text follows ownership @@ -154,7 +154,7 @@ Wording that reaches a model is behavior, but duplication still drifts. Exactnes **Balanced:** “Approval service that applies session policy before answerers and logs every ask/outcome pair to the requesting session.” Keep non-catalog detail in later sentences. -Know what the generator extracts. That fragment must preserve the contract needed on its generated surface. +Know what the generator extracts. That fragment must preserve the contract needed on its generated output. ## Limitations are contracts, not debt inventories diff --git a/.agents/skills/dsh-translate-docs/SKILL.md b/.agents/skills/dsh-translate-docs/SKILL.md index e35096a5a2..332c55920b 100644 --- a/.agents/skills/dsh-translate-docs/SKILL.md +++ b/.agents/skills/dsh-translate-docs/SKILL.md @@ -1,10 +1,16 @@ --- name: dsh-translate-docs -description: Use when creating or updating the bilingual counterpart of a doc in this repo (English ↔ Chinese pairs) — tells the orchestrating agent when to delegate translation to a subagent, and orients the translator to the pairing contract, the terminology source of truth, the translation rules, and the consistency gate that verifies the result +description: Manually run the extended DeepSeek Harness bilingual-document workflow, including generated briefings, delegated prose translation, whole-document translation, and scoped pairing verification. +disable-model-invocation: true +user-invocable: true --- # Translating DeepSeek-Harness docs +## Invocation boundary + +Run this extended workflow only when the user explicitly invokes `dsh-translate-docs` by name. Never select or load it for ordinary documentation work, from another skill, or from an inferred translation need; routine translation follows the one-shot, one-pass rule in [docs/AGENTS.md](../../../docs/AGENTS.md). + ## What this skill is **This skill is guidance, not a translation memory.** It is the workflow map for keeping `foo.md ↔ foo.zh.md` pairs consistent and natural in both languages. Both languages carry equal authority — a change is authored in either one, and that side is the source for that update. You are the translator: the rules below say what must hold, not how to phrase any particular sentence — phrasing judgment is yours, terminology is not. @@ -43,6 +49,7 @@ When translations need to be written from scratch, the orchestrating agent does - **Pass 1 — write, don't transpose.** Read a semantic unit, then restate it as a native technical author in the nearest [style sample's](../../../docs/i18n/style-samples.md) register. Preserve the required frame without forcing sentence-by-sentence correspondence. - **Pass 2 — verify against the source, clause by clause.** Fidelity is checked here, not written in: confirm nothing was added or dropped, every term follows the table, and each code span survived verbatim. Fix by rewriting the sentence natively, not by patching words into it. +- **Read the completed counterpart alone.** After the source comparison, read the translated file without the source beside it and rewrite phrasing whose awkwardness only becomes visible in isolation. - Write only the final text to the file, never drafts or notes. - Every term in [terminology.md](../../../docs/i18n/terminology.md) renders exactly as specified. For a Chinese target, use the Chinese and first-occurrence columns; an unlisted term needs a citable Chinese OSS/vendor precedent or stays English under 「待定术语」. For an English target, use the English column and an established English technical term; preserve an ambiguous source term with a short gloss and list it as pending. Never invent a rendering inline. - Code blocks are byte-identical across the pair, comments included. Relative links keep their `.md` targets; only the switcher line links `.zh.md`. @@ -64,4 +71,4 @@ When translations need to be written from scratch, the orchestrating agent does ## How to respond to translation review -Follow the [code-review reporting guidance](../dsh-code-review/SKILL.md#reporting-findings): evaluate each comment on its merits, and for terminology comments, remember the table is the contract — a reviewer's rendering decision gets applied to [terminology.md](../../../docs/i18n/terminology.md) so it binds every future translation, not just patched into one file. +Follow the [code-review reporting guidance](../dsh-code-review/SKILL.md#reporting-findings): evaluate each comment on its merits, and for terminology comments, remember the terminology table is the contract — apply a reviewer's rendering decision to [terminology.md](../../../docs/i18n/terminology.md), not only to one file. diff --git a/.agents/skills/dsh-translate-docs/agents/openai.yaml b/.agents/skills/dsh-translate-docs/agents/openai.yaml new file mode 100644 index 0000000000..8f02948105 --- /dev/null +++ b/.agents/skills/dsh-translate-docs/agents/openai.yaml @@ -0,0 +1,7 @@ +interface: + display_name: "DSH Extended Doc Translation" + short_description: "Run the full bilingual documentation workflow manually" + default_prompt: "Use $dsh-translate-docs to run the extended bilingual-document workflow for the specified pair." + +policy: + allow_implicit_invocation: false diff --git a/.agents/skills/dsh-trim-cot-leakage/SKILL.md b/.agents/skills/dsh-trim-cot-leakage/SKILL.md index c80dfbd465..7fad4d50c2 100644 --- a/.agents/skills/dsh-trim-cot-leakage/SKILL.md +++ b/.agents/skills/dsh-trim-cot-leakage/SKILL.md @@ -24,7 +24,7 @@ For every suspect passage ask: **could a reader at HEAD, with no access to any s ## What is not leakage -The citation boundary is where unaided passes fail in both directions — deleting durable references and keeping dead ones. Apply these keeps as written; [examples](references/examples.md) calibrates each: +Unaided citation passes fail in both directions by deleting durable references and keeping dead ones. Apply these keep rules as written; [examples](references/examples.md) calibrates each: - **Issue references** — `#1470`, `TODO(name):`, "issue #N owns the follow-up" resolve at HEAD; keep them on any surface, including READMEs. Do not relocate them to Agent Notes. - **Merged-PR and issue citations inside Agent Notes and postmortems** — sanctioned evidence per the [documentation standard](../../../docs/AGENTS.md)'s change-story routing. @@ -39,7 +39,7 @@ The citation boundary is where unaided passes fail in both directions — deleti ## Workflow 1. Scope and exclusions per [dsh-prose-standard](../dsh-prose-standard/SKILL.md): require an explicit scope; never touch `vendor/`, `.agents/notes/archived/`, or recorded fixtures and snapshots — recorded model output and sealed history keep their original voice. -2. Audit read-only first: run the [recall batteries](references/recall-batteries.md) (with `--hidden` so `.agents/` is searched), then judge every hit semantically. The batteries are probes, not the definition — each review round of the original purge surfaced shapes the batteries missed, so also read the densest prose in scope (module JSDoc, READMEs, Agent Notes) without a pattern in hand. +2. Audit read-only first: run the [recall batteries](references/recall-batteries.md) (with `--hidden` so `.agents/` is searched), then judge every hit semantically. The batteries are probes, not the definition — each review round of the original purge found cases the batteries missed, so also read the densest prose in scope (module JSDoc, READMEs, Agent Notes) without a pattern in hand. 3. Fix owner-first per surface: generated catalogs → fix the source JSDoc or generator template, then regenerate; type-equivalence fences → fix the source JSDoc, then re-paste both bilingual pages (`verify-type-equiv` pins them); bilingual pairs → update the counterpart and re-record per [dsh-translate-docs](../dsh-translate-docs/SKILL.md); model-visible strings → wording is behavior, so flag for a snapshot-backed change instead of silently rewording. 4. Before deleting anything, enumerate the passage's propositions (prose-standard) and check the [overcorrection traps](references/examples.md#overcorrection-traps): trims that flip an obligation into an endorsement, promote a hypothetical to a shipped feature, delete a true fact, or drop provenance. 5. Verify: re-run the batteries expecting only sanctioned keeps, this skill's own directory, and the owning note's quoted evidence; confirm every remaining citation resolves at HEAD; run the gates for touched surfaces (`doc-sync` for docs, `verify-type-equiv`, `verify-translation-pairing`). diff --git a/.agents/skills/dsh-trim-cot-leakage/references/examples.md b/.agents/skills/dsh-trim-cot-leakage/references/examples.md index 22e73e10f4..9afc22c209 100644 --- a/.agents/skills/dsh-trim-cot-leakage/references/examples.md +++ b/.agents/skills/dsh-trim-cot-leakage/references/examples.md @@ -1,6 +1,6 @@ # Few-shot leakage examples -Distilled from the 2026-08 repo-wide purge and its review rounds. Use them to identify the governing principle, not as text templates. This file deliberately quotes leaked shapes as calibration material — the [recall batteries](recall-batteries.md) exclude the skill's directory, and its wording is not a license elsewhere. +Distilled from the 2026-08 repo-wide purge and its review rounds. Use them to identify the governing principle, not as text templates. This file deliberately quotes leaked wording as calibration material — the [recall batteries](recall-batteries.md) exclude the skill's directory, and its wording is not a license elsewhere. ## Dead citations diff --git a/.agents/skills/dsh-trim-cot-leakage/references/recall-batteries.md b/.agents/skills/dsh-trim-cot-leakage/references/recall-batteries.md index 8f9cce4aad..9b1c79054c 100644 --- a/.agents/skills/dsh-trim-cot-leakage/references/recall-batteries.md +++ b/.agents/skills/dsh-trim-cot-leakage/references/recall-batteries.md @@ -1,12 +1,12 @@ # Recall batteries -Probes for [the taxonomy](../SKILL.md#taxonomy), tuned during the 2026-08 purge. Every hit needs semantic judgment — the batteries over-match by design, and they under-match by nature: each review round of the purge found shapes no battery caught, so pair them with an unpatterned read of the densest prose in scope. +Probes for [the taxonomy](../SKILL.md#taxonomy), tuned during the 2026-08 purge. Every hit needs semantic judgment — the batteries over-match by design, and they under-match by nature: each review round of the purge found cases no battery caught, so pair them with an unpatterned read of the densest prose in scope. ## Invocation rules - Add `--hidden --glob '!.git/**'` so `.agents/` is searched; ripgrep skips dot-directories by default and the purge's biggest miss risk was Agent Notes. -- Exclusions go last so a later include cannot re-admit them: `--glob '!vendor/**' --glob '!node_modules/**' --glob '!.agents/notes/archived/**' --glob '!.agents/skills/dsh-trim-cot-leakage/**'` (the skill's own files quote leaked shapes as calibration), plus recorded fixture and snapshot directories in scope. The [owning note](../../../notes/implemented/process/2026-08-09-committed-artifact-citations.md) also self-hits through its quoted evidence; judge it as evidence, not usage. -- Natural-language lines carry `-i` so sentence-initial capitals hit ("This PR adds…", "Probably fine…"); the code-shaped first line stays case-sensitive — `-i` would turn `\bT\d\b` and `\bP-I\b` into noise. +- Exclusions go last so a later include cannot re-admit them: `--glob '!vendor/**' --glob '!node_modules/**' --glob '!.agents/notes/archived/**' --glob '!.agents/skills/dsh-trim-cot-leakage/**'` (the skill's own files quote leaked wording as calibration), plus recorded fixture and snapshot directories in scope. The [owning note](../../../notes/implemented/process/2026-08-09-committed-artifact-citations.md) also self-hits through its quoted evidence; judge it as evidence, not usage. +- Natural-language lines carry `-i` so sentence-initial capitals hit ("This PR adds…", "Probably fine…"); the first line, which matches code patterns, stays case-sensitive — `-i` would turn `\bT\d\b` and `\bP-I\b` into noise. - A zero-hit pattern proves nothing until you have seen it match: test it against a known-positive string before trusting the negative. ## English battery diff --git a/.agents/skills/record-browser-gif/SKILL.md b/.agents/skills/record-browser-gif/SKILL.md index d7df478074..3566dfb763 100644 --- a/.agents/skills/record-browser-gif/SKILL.md +++ b/.agents/skills/record-browser-gif/SKILL.md @@ -15,7 +15,7 @@ A pull request that changes product-user-visible GUI behavior MUST include a dem The recording itself is part of the evidence: use a real server booted from that pull request's branch tree, a real API key, and real model rounds. Never substitute fixture queries, mock transports, synthetic event injection, or test-only hooks unless the user explicitly asked for a fixture recording. Next to the embed, state the exact demonstrated commit SHA, the tree and origin that served it, any mode flags or browser-state exceptions, and whether a real model round ran, so reviewers know exactly what the recording proves. -## Keep the boundary explicit +## Keep recording separate from publication - Recording produces frame images and one local `.gif` artifact only; it never mutates remote state. - Publication — pushing the GIF to an assets branch and embedding it in a pull request body — is the separate final step, performed only when the task includes attaching the GIF to a pull request. It never touches the pull request's own branch. @@ -33,9 +33,9 @@ A GIF for a specific pull request demonstrates that pull request's tree, so stag ## Record the flow -1. Invoke the available browser-control skill and follow its setup, interaction, and cleanup instructions. Use the user's existing Chrome state only when requested or required; state that exception next to the GIF and do not claim fresh client state. If browser control is unavailable, use the repository-declared Playwright dependency in an isolated headless browser; do not install another driver or launch the user's browser. State that fallback next to the GIF. -2. Resolve the evidence boundary before recording: identify the exact origin, whether the app is built or in development, the transport, and any fixture or mock mode. Record only claims that the observed setup supports. -3. When a production default opens a native operating-system surface that headless automation cannot drive, select an official browser-operable production backend through the application's normal configuration. State that override next to the GIF; a fixture, mock transport, or test-only hook is not an acceptable substitute. +1. Invoke the available browser-control skill and follow its setup, interaction, and cleanup instructions. Use the user's existing Chrome state only when requested or required; state that exception in the provenance and do not claim fresh client state. If browser control is unavailable, use the repository-declared Playwright dependency in an isolated headless browser; do not install another driver or launch the user's browser. State that fallback in the provenance. +2. Before recording, identify the exact origin, whether the app is built or in development, the transport, and any fixture or mock mode. Record only claims that the observed setup supports. +3. When a production default opens a native operating-system surface that headless automation cannot drive, select an official browser-operable production backend through the application's normal configuration. State the override in the provenance; a fixture, mock transport, or test-only hook is not an acceptable substitute. 4. Choose three to six states that tell one story, such as typed, running, settled, and detail. Prefer semantic state changes over continuous capture; omit loading churn that does not help the viewer. 5. Keep one viewport and crop for every frame, and name frames lexically: `00-initial.png`, `01-typed.png`, and so on. 6. Store frames under the repository's gitignored `.playwright-mcp/` directory — browser-tool screenshots can only be written under the tool's allowed roots, and relative filenames resolve against the repository root. Create the frame subdirectory first (`mkdir -p .playwright-mcp/gif-frames-